From 2ca3fd29f9adfa9c7b953e5379922785b45fdacd Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 22 Feb 2026 23:51:39 +0330 Subject: [PATCH 01/44] =?UTF-8?q?feat:=20admin=20force=20activation=20?= =?UTF-8?q?=E2=80=94=20skip=20order/wallet=20checks=20for=20BackOffice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Proto: add force_activation field to ActivateClubMembershipRequest - Command: add ForceActivation property - Handler: wrap steps 2-5 in ForceActivation guard - Skip PackagePurchaseMethod check (set to DirectPurchase if None) - Skip wallet balance check - Skip Magic wallet mode check - Skip UserOrder/Transaction validation - Proto NuGet bumped to 0.0.184 --- .../ActivateClubMembershipCommand.cs | 5 + .../ActivateClubMembershipCommandHandler.cs | 187 ++++++++++-------- .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/clubmembership.proto | 1 + 4 files changed, 110 insertions(+), 85 deletions(-) diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommand.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommand.cs index 6d94a03..2706c91 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommand.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommand.cs @@ -12,4 +12,9 @@ public record ActivateClubMembershipCommand : IRequest /// شناسه کاربر /// public long UserId { get; init; } + + /// + /// فعال‌سازی اجباری توسط ادمین — بدون بررسی سفارش و کیف‌پول + /// + public bool ForceActivation { get; init; } } diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index 65b279d..20e1def 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -54,94 +54,113 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler w.UserId == user.Id, cancellationToken); + + if (wallet == null) + { + _logger.LogError("Wallet not found for UserId: {UserId}", request.UserId); + throw new NotFoundException("کیف پول کاربر یافت نشد"); + } + + if (wallet.Balance < SystemConstants.BasePackageAmount) + { + _logger.LogWarning( + "User {UserId} has insufficient balance: {Balance}", + request.UserId, + wallet.Balance + ); + throw new BadRequestException( + $"برای فعالسازی باشگاه مشتریان باید حداقل {SystemConstants.BasePackageAmount:N0} ریال موجودی اصلی داشته باشید" + ); + } + + // 3.5. بررسی وضعیت کیف‌پول جادویی — اگر در حالت Magic است، فعالسازی مجاز نیست + if (wallet.WalletMode == WalletMode.Magic) + { + _logger.LogWarning( + "User {UserId} cannot activate club while in Magic wallet mode", + request.UserId + ); + throw new BadRequestException( + "ابتدا باید چرخه کیف‌پول جادویی تکمیل شود" + ); + } + + // 4. پیدا کردن UserOrder با PackageId + var packageOrder = await _context.UserOrders + .Include(o => o.Transaction) + .Where(o => + o.UserId == user.Id && + o.PackageId != null && + o.PaymentStatus == PaymentStatus.Success) + .OrderByDescending(o => o.Created) + .FirstOrDefaultAsync(cancellationToken); + + if (packageOrder == null) + { + _logger.LogWarning( + "No successful package order found for UserId: {UserId}", + request.UserId + ); + throw new NotFoundException("سفارش پکیج طلایی یافت نشد"); + } + + // 5. بررسی Transaction + if (packageOrder.Transaction == null) + { + _logger.LogError( + "Transaction not found for OrderId: {OrderId}", + packageOrder.Id + ); + throw new NotFoundException("تراکنش مربوط به سفارش یافت نشد"); + } + + var transaction = packageOrder.Transaction; + + if (transaction.Type != TransactionType.DepositIpg && + transaction.Type != TransactionType.DepositExternal1) + { + _logger.LogWarning( + "Invalid transaction type for OrderId {OrderId}: {Type}", + packageOrder.Id, + transaction.Type + ); + throw new BadRequestException( + "تراکنش معتبر برای فعالسازی باشگاه یافت نشد" + ); + } + } + else + { + _logger.LogInformation( + "Force activation by admin for UserId: {UserId} — skipping order/wallet validation", request.UserId ); - throw new BadRequestException( - "برای فعالسازی باشگاه مشتریان ابتدا باید پکیج طلایی خریداری کنید" - ); - } - // 3. بررسی موجودی کیف پول - var wallet = await _context.UserWallets - .FirstOrDefaultAsync(w => w.UserId == user.Id, cancellationToken); - - if (wallet == null) - { - _logger.LogError("Wallet not found for UserId: {UserId}", request.UserId); - throw new NotFoundException("کیف پول کاربر یافت نشد"); - } - - if (wallet.Balance < SystemConstants.BasePackageAmount) - { - _logger.LogWarning( - "User {UserId} has insufficient balance: {Balance}", - request.UserId, - wallet.Balance - ); - throw new BadRequestException( - $"برای فعالسازی باشگاه مشتریان باید حداقل {SystemConstants.BasePackageAmount:N0} ریال موجودی اصلی داشته باشید" - ); - } - - // 3.5. بررسی وضعیت کیف‌پول جادویی — اگر در حالت Magic است، فعالسازی مجاز نیست - if (wallet.WalletMode == WalletMode.Magic) - { - _logger.LogWarning( - "User {UserId} cannot activate club while in Magic wallet mode", - request.UserId - ); - throw new BadRequestException( - "ابتدا باید چرخه کیف‌پول جادویی تکمیل شود" - ); - } - - // 4. پیدا کردن UserOrder با PackageId - var packageOrder = await _context.UserOrders - .Include(o => o.Transaction) - .Where(o => - o.UserId == user.Id && - o.PackageId != null && - o.PaymentStatus == PaymentStatus.Success) - .OrderByDescending(o => o.Created) - .FirstOrDefaultAsync(cancellationToken); - - if (packageOrder == null) - { - _logger.LogWarning( - "No successful package order found for UserId: {UserId}", - request.UserId - ); - throw new NotFoundException("سفارش پکیج طلایی یافت نشد"); - } - - // 5. بررسی Transaction - if (packageOrder.Transaction == null) - { - _logger.LogError( - "Transaction not found for OrderId: {OrderId}", - packageOrder.Id - ); - throw new NotFoundException("تراکنش مربوط به سفارش یافت نشد"); - } - - var transaction = packageOrder.Transaction; - - if (transaction.Type != TransactionType.DepositIpg && - transaction.Type != TransactionType.DepositExternal1) - { - _logger.LogWarning( - "Invalid transaction type for OrderId {OrderId}: {Type}", - packageOrder.Id, - transaction.Type - ); - throw new BadRequestException( - "تراکنش معتبر برای فعالسازی باشگاه یافت نشد" - ); + // در فعال‌سازی اجباری، اگر PackagePurchaseMethod تنظیم نشده، مقدار DirectPurchase بگذار + if (user.PackagePurchaseMethod == PackagePurchaseMethod.None) + { + user.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase; + _context.Users.Update(user); + await _context.SaveChangesAsync(cancellationToken); + } } // 6. بررسی عضویت فعلی diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index a06c1ac..c07bc07 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.183 + 0.0.184 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto b/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto index 9951376..9520d8d 100644 --- a/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto @@ -80,6 +80,7 @@ message ActivateClubMembershipRequest int64 package_id = 2; google.protobuf.StringValue activation_code = 3; int32 duration_months = 4; + bool force_activation = 5; } // Deactivate Command From 8cdb197ee7b4070f22e27671bb09a47617490eb7 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 00:01:56 +0330 Subject: [PATCH 02/44] =?UTF-8?q?chore:=20prevent=20appsettings.Production?= =?UTF-8?q?.json=20from=20merging=20=E2=80=94=20keep=20ours=20strategy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e926f51 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Keep production config from current branch during merges — never overwrite +src/CMSMicroservice.WebApi/appsettings.Production.json merge=ours From 3153fd8a746a0121cdabc3abfbc60568d8a7aea3 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 20:11:56 +0330 Subject: [PATCH 03/44] feat: add PersistentVolume for CMS uploads + apply manifests in CI/CD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add k8s/staging/ and k8s/production/ deployment manifests - PVC 20Gi for /app/Uploads (file uploads persist across pod restarts) - FileStorage__UploadPath env var set to /app/Uploads - Replicas reduced to 1 (ReadWriteOnce PVC) - Pipelines now: create namespace → apply manifests → restart/update --- .gitea/workflows/kub-deploy.yml | 12 ++- .gitea/workflows/prod-deploy.yml | 12 ++- k8s/production/cms-deployment.yaml | 155 +++++++++++++++++++++++++++++ k8s/staging/cms-deployment.yaml | 155 +++++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 k8s/production/cms-deployment.yaml create mode 100644 k8s/staging/cms-deployment.yaml diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index 8ba2a3f..0e3c999 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -86,8 +86,16 @@ jobs: - name: Deploy to Kubernetes run: | export SSHPASS="${{ secrets.SERVER_PASSWORD }}" + + # Copy K8s manifests to server + sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/cms-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-deployment.yaml + + # Apply manifests and restart sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " - kubectl rollout restart deployment/cms - kubectl rollout status deployment/cms --timeout=180s + kubectl create namespace foursat --dry-run=client -o yaml | kubectl apply -f - && + kubectl apply -f /tmp/cms-deployment.yaml && + kubectl rollout restart deployment/cms -n foursat && + kubectl rollout status deployment/cms -n foursat --timeout=180s && + rm -f /tmp/cms-deployment.yaml " echo "✅ Deployed!" diff --git a/.gitea/workflows/prod-deploy.yml b/.gitea/workflows/prod-deploy.yml index 9788b35..af59001 100644 --- a/.gitea/workflows/prod-deploy.yml +++ b/.gitea/workflows/prod-deploy.yml @@ -86,8 +86,16 @@ jobs: - name: Deploy to Production run: | export SSHPASS="${{ secrets.SERVER_PASSWORD }}" + + # Copy K8s manifests to server + sshpass -e scp -o StrictHostKeyChecking=no k8s/production/cms-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-deployment.yaml + + # Apply manifests and update image sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " - kubectl set image deployment/cms cms=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} - kubectl rollout status deployment/cms --timeout=300s + kubectl create namespace foursat --dry-run=client -o yaml | kubectl apply -f - && + kubectl apply -f /tmp/cms-deployment.yaml && + kubectl set image deployment/cms cms=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} -n foursat && + kubectl rollout status deployment/cms -n foursat --timeout=300s && + rm -f /tmp/cms-deployment.yaml " echo "✅ Deployed to Production!" diff --git a/k8s/production/cms-deployment.yaml b/k8s/production/cms-deployment.yaml new file mode 100644 index 0000000..374a0ce --- /dev/null +++ b/k8s/production/cms-deployment.yaml @@ -0,0 +1,155 @@ +--- +# CMS Uploads PersistentVolumeClaim (Production) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: cms-uploads-pvc + namespace: foursat + labels: + app: cms +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + +--- +# CMS Microservice Deployment (Production) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cms + namespace: foursat + labels: + app: cms +spec: + replicas: 1 + selector: + matchLabels: + app: cms + template: + metadata: + labels: + app: cms + spec: + containers: + - name: cms + image: git.se.kbs1.ir/admin/cms:prod + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: ASPNETCORE_URLS + value: "http://+:8080" + - name: Kestrel__EndpointDefaults__Protocols + value: "Http1AndHttp2" + - name: FileStorage__UploadPath + value: "/app/Uploads" + - name: ConnectionStrings__DefaultConnection + valueFrom: + secretKeyRef: + name: postgres-secret + key: connection-string + - name: Email__Host + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-host + - name: Email__Port + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-port + - name: Email__Username + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-username + - name: Email__Password + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-password + - name: Kavenegar__ApiKey + valueFrom: + secretKeyRef: + name: sms-secret + key: api-key + volumeMounts: + - name: cms-uploads + mountPath: /app/Uploads + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" + livenessProbe: + httpGet: + path: /health/live + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health/ready + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + volumes: + - name: cms-uploads + persistentVolumeClaim: + claimName: cms-uploads-pvc + +--- +# CMS Service (Production) +apiVersion: v1 +kind: Service +metadata: + name: cms-svc + namespace: foursat +spec: + selector: + app: cms + ports: + - name: http + port: 8080 + targetPort: 8080 + type: ClusterIP + +--- +# CMS Ingress (Production) +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cms-ingress + namespace: foursat + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + tls: + - hosts: + - cms.kbs1.ir + secretName: cms-tls + rules: + - host: cms.kbs1.ir + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: cms-svc + port: + number: 8080 diff --git a/k8s/staging/cms-deployment.yaml b/k8s/staging/cms-deployment.yaml new file mode 100644 index 0000000..7ac719f --- /dev/null +++ b/k8s/staging/cms-deployment.yaml @@ -0,0 +1,155 @@ +--- +# CMS Uploads PersistentVolumeClaim +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: cms-uploads-pvc + namespace: foursat + labels: + app: cms +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + +--- +# CMS Microservice Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cms + namespace: foursat + labels: + app: cms +spec: + replicas: 1 + selector: + matchLabels: + app: cms + template: + metadata: + labels: + app: cms + spec: + containers: + - name: cms + image: git.se.kbs1.ir/admin/cms:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: ASPNETCORE_URLS + value: "http://+:8080" + - name: Kestrel__EndpointDefaults__Protocols + value: "Http1AndHttp2" + - name: FileStorage__UploadPath + value: "/app/Uploads" + - name: ConnectionStrings__DefaultConnection + valueFrom: + secretKeyRef: + name: postgres-secret + key: connection-string + - name: Email__Host + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-host + - name: Email__Port + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-port + - name: Email__Username + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-username + - name: Email__Password + valueFrom: + secretKeyRef: + name: email-secret + key: smtp-password + - name: Kavenegar__ApiKey + valueFrom: + secretKeyRef: + name: sms-secret + key: api-key + volumeMounts: + - name: cms-uploads + mountPath: /app/Uploads + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" + livenessProbe: + httpGet: + path: /health/live + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health/ready + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + volumes: + - name: cms-uploads + persistentVolumeClaim: + claimName: cms-uploads-pvc + +--- +# CMS Service +apiVersion: v1 +kind: Service +metadata: + name: cms-svc + namespace: foursat +spec: + selector: + app: cms + ports: + - name: http + port: 8080 + targetPort: 8080 + type: ClusterIP + +--- +# CMS Ingress +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cms-ingress + namespace: foursat + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + tls: + - hosts: + - cms.se.kbs1.ir + secretName: cms-tls + rules: + - host: cms.se.kbs1.ir + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: cms-svc + port: + number: 8080 From 68da3f45a33350e2ac0f37f443fe1a9d50a76646 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 20:38:27 +0330 Subject: [PATCH 04/44] fix: staging uses namespace default, not foursat --- .gitea/workflows/kub-deploy.yml | 5 ++--- k8s/staging/cms-deployment.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index 0e3c999..5ec5fb4 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -92,10 +92,9 @@ jobs: # Apply manifests and restart sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " - kubectl create namespace foursat --dry-run=client -o yaml | kubectl apply -f - && kubectl apply -f /tmp/cms-deployment.yaml && - kubectl rollout restart deployment/cms -n foursat && - kubectl rollout status deployment/cms -n foursat --timeout=180s && + kubectl rollout restart deployment/cms && + kubectl rollout status deployment/cms --timeout=180s && rm -f /tmp/cms-deployment.yaml " echo "✅ Deployed!" diff --git a/k8s/staging/cms-deployment.yaml b/k8s/staging/cms-deployment.yaml index 7ac719f..0917fdf 100644 --- a/k8s/staging/cms-deployment.yaml +++ b/k8s/staging/cms-deployment.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: name: cms-uploads-pvc - namespace: foursat + namespace: default labels: app: cms spec: @@ -20,7 +20,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: cms - namespace: foursat + namespace: default labels: app: cms spec: @@ -116,7 +116,7 @@ apiVersion: v1 kind: Service metadata: name: cms-svc - namespace: foursat + namespace: default spec: selector: app: cms @@ -132,7 +132,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: cms-ingress - namespace: foursat + namespace: default annotations: kubernetes.io/ingress.class: "nginx" cert-manager.io/cluster-issuer: "letsencrypt-prod" From e41747afe018d6fb01d5d011b692a20801a908ec Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 20:41:12 +0330 Subject: [PATCH 05/44] =?UTF-8?q?fix:=20production=20ingress=20=E2=80=94?= =?UTF-8?q?=20add=20cms.kbs2.ir,=20use=20ingressClassName=20instead=20of?= =?UTF-8?q?=20annotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/production/cms-deployment.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/k8s/production/cms-deployment.yaml b/k8s/production/cms-deployment.yaml index 374a0ce..599d2d6 100644 --- a/k8s/production/cms-deployment.yaml +++ b/k8s/production/cms-deployment.yaml @@ -134,15 +134,26 @@ metadata: name: cms-ingress namespace: foursat annotations: - kubernetes.io/ingress.class: "nginx" cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: + ingressClassName: nginx tls: - hosts: - cms.kbs1.ir + - cms.kbs2.ir secretName: cms-tls rules: + - host: cms.kbs2.ir + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: cms-svc + port: + number: 8080 - host: cms.kbs1.ir http: paths: From 2d6c95e6ee9fe3ad8273f0a9ff2d4de3052306cf Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 20:58:16 +0330 Subject: [PATCH 06/44] fix: use local registry 194.5.195.53:30080 instead of git.se.kbs1.ir for image pull --- k8s/production/cms-deployment.yaml | 2 +- k8s/staging/cms-deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/k8s/production/cms-deployment.yaml b/k8s/production/cms-deployment.yaml index 599d2d6..62d8645 100644 --- a/k8s/production/cms-deployment.yaml +++ b/k8s/production/cms-deployment.yaml @@ -35,7 +35,7 @@ spec: spec: containers: - name: cms - image: git.se.kbs1.ir/admin/cms:prod + image: 194.5.195.53:30080/admin/cms:prod imagePullPolicy: Always ports: - containerPort: 8080 diff --git a/k8s/staging/cms-deployment.yaml b/k8s/staging/cms-deployment.yaml index 0917fdf..4b4c138 100644 --- a/k8s/staging/cms-deployment.yaml +++ b/k8s/staging/cms-deployment.yaml @@ -35,7 +35,7 @@ spec: spec: containers: - name: cms - image: git.se.kbs1.ir/admin/cms:latest + image: 194.5.195.53:30080/admin/cms:latest imagePullPolicy: Always ports: - containerPort: 8080 From f8dc4abd04acb6e37659b7f4d6c9627c568b8c23 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 21:17:23 +0330 Subject: [PATCH 07/44] fix: staging ASPNETCORE_ENVIRONMENT=Staging, remove secretKeyRef (config in appsettings) --- k8s/production/cms-deployment.yaml | 30 ---------------------------- k8s/staging/cms-deployment.yaml | 32 +----------------------------- 2 files changed, 1 insertion(+), 61 deletions(-) diff --git a/k8s/production/cms-deployment.yaml b/k8s/production/cms-deployment.yaml index 62d8645..859bf42 100644 --- a/k8s/production/cms-deployment.yaml +++ b/k8s/production/cms-deployment.yaml @@ -49,36 +49,6 @@ spec: value: "Http1AndHttp2" - name: FileStorage__UploadPath value: "/app/Uploads" - - name: ConnectionStrings__DefaultConnection - valueFrom: - secretKeyRef: - name: postgres-secret - key: connection-string - - name: Email__Host - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-host - - name: Email__Port - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-port - - name: Email__Username - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-username - - name: Email__Password - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-password - - name: Kavenegar__ApiKey - valueFrom: - secretKeyRef: - name: sms-secret - key: api-key volumeMounts: - name: cms-uploads mountPath: /app/Uploads diff --git a/k8s/staging/cms-deployment.yaml b/k8s/staging/cms-deployment.yaml index 4b4c138..59a1e50 100644 --- a/k8s/staging/cms-deployment.yaml +++ b/k8s/staging/cms-deployment.yaml @@ -42,43 +42,13 @@ spec: name: http env: - name: ASPNETCORE_ENVIRONMENT - value: "Production" + value: "Staging" - name: ASPNETCORE_URLS value: "http://+:8080" - name: Kestrel__EndpointDefaults__Protocols value: "Http1AndHttp2" - name: FileStorage__UploadPath value: "/app/Uploads" - - name: ConnectionStrings__DefaultConnection - valueFrom: - secretKeyRef: - name: postgres-secret - key: connection-string - - name: Email__Host - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-host - - name: Email__Port - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-port - - name: Email__Username - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-username - - name: Email__Password - valueFrom: - secretKeyRef: - name: email-secret - key: smtp-password - - name: Kavenegar__ApiKey - valueFrom: - secretKeyRef: - name: sms-secret - key: api-key volumeMounts: - name: cms-uploads mountPath: /app/Uploads From de83c313468c7d15b472c646a031bf4b5390b5c4 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 21:36:08 +0330 Subject: [PATCH 08/44] fix: production uses namespace default + remove foursat namespace references --- .gitea/workflows/prod-deploy.yml | 5 ++--- k8s/production/cms-deployment.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/prod-deploy.yml b/.gitea/workflows/prod-deploy.yml index af59001..decefb7 100644 --- a/.gitea/workflows/prod-deploy.yml +++ b/.gitea/workflows/prod-deploy.yml @@ -92,10 +92,9 @@ jobs: # Apply manifests and update image sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " - kubectl create namespace foursat --dry-run=client -o yaml | kubectl apply -f - && kubectl apply -f /tmp/cms-deployment.yaml && - kubectl set image deployment/cms cms=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} -n foursat && - kubectl rollout status deployment/cms -n foursat --timeout=300s && + kubectl set image deployment/cms cms=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} && + kubectl rollout status deployment/cms --timeout=300s && rm -f /tmp/cms-deployment.yaml " echo "✅ Deployed to Production!" diff --git a/k8s/production/cms-deployment.yaml b/k8s/production/cms-deployment.yaml index 859bf42..a5f3188 100644 --- a/k8s/production/cms-deployment.yaml +++ b/k8s/production/cms-deployment.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: name: cms-uploads-pvc - namespace: foursat + namespace: default labels: app: cms spec: @@ -20,7 +20,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: cms - namespace: foursat + namespace: default labels: app: cms spec: @@ -86,7 +86,7 @@ apiVersion: v1 kind: Service metadata: name: cms-svc - namespace: foursat + namespace: default spec: selector: app: cms @@ -102,7 +102,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: cms-ingress - namespace: foursat + namespace: default annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/ssl-redirect: "true" From 9288d0640b90774de464f7f67b6f3242dd3112a5 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 21:58:05 +0330 Subject: [PATCH 09/44] =?UTF-8?q?feat:=20externalize=20appsettings=20to=20?= =?UTF-8?q?K8s=20Secret=20=E2=80=94=20config=20persists=20independently=20?= =?UTF-8?q?of=20image=20rebuilds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add cms-config.yaml (K8s Secret) for staging and production - Mount appsettings.{Environment}.json from Secret into /app/ - Update CI/CD pipelines to apply Secret before Deployment - Remove redundant env vars (Kestrel, FileStorage) — now in config file --- .gitea/workflows/kub-deploy.yml | 6 +- .gitea/workflows/prod-deploy.yml | 6 +- k8s/production/cms-config.yaml | 54 ++++++++++++++ k8s/production/cms-deployment.yaml | 11 +-- k8s/staging/cms-config.yaml | 111 +++++++++++++++++++++++++++++ k8s/staging/cms-deployment.yaml | 11 +-- 6 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 k8s/production/cms-config.yaml create mode 100644 k8s/staging/cms-config.yaml diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index 5ec5fb4..afef447 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -88,13 +88,15 @@ jobs: export SSHPASS="${{ secrets.SERVER_PASSWORD }}" # Copy K8s manifests to server + sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/cms-config.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-config.yaml sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/cms-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-deployment.yaml - # Apply manifests and restart + # Apply config (Secret) first, then deployment sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl apply -f /tmp/cms-config.yaml && kubectl apply -f /tmp/cms-deployment.yaml && kubectl rollout restart deployment/cms && kubectl rollout status deployment/cms --timeout=180s && - rm -f /tmp/cms-deployment.yaml + rm -f /tmp/cms-config.yaml /tmp/cms-deployment.yaml " echo "✅ Deployed!" diff --git a/.gitea/workflows/prod-deploy.yml b/.gitea/workflows/prod-deploy.yml index decefb7..b090926 100644 --- a/.gitea/workflows/prod-deploy.yml +++ b/.gitea/workflows/prod-deploy.yml @@ -88,13 +88,15 @@ jobs: export SSHPASS="${{ secrets.SERVER_PASSWORD }}" # Copy K8s manifests to server + sshpass -e scp -o StrictHostKeyChecking=no k8s/production/cms-config.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-config.yaml sshpass -e scp -o StrictHostKeyChecking=no k8s/production/cms-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/cms-deployment.yaml - # Apply manifests and update image + # Apply config (Secret) first, then deployment and update image sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl apply -f /tmp/cms-config.yaml && kubectl apply -f /tmp/cms-deployment.yaml && kubectl set image deployment/cms cms=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} && kubectl rollout status deployment/cms --timeout=300s && - rm -f /tmp/cms-deployment.yaml + rm -f /tmp/cms-config.yaml /tmp/cms-deployment.yaml " echo "✅ Deployed to Production!" diff --git a/k8s/production/cms-config.yaml b/k8s/production/cms-config.yaml new file mode 100644 index 0000000..b96af17 --- /dev/null +++ b/k8s/production/cms-config.yaml @@ -0,0 +1,54 @@ +--- +# CMS Application Configuration Secret (Production) +# This Secret stores appsettings.Production.json externally in K8s +# so configuration persists independently of Docker image rebuilds. +# +# To edit config without redeploying: +# kubectl edit secret cms-appsettings +# kubectl rollout restart deployment/cms +# +apiVersion: v1 +kind: Secret +metadata: + name: cms-appsettings + namespace: default + labels: + app: cms + environment: production +type: Opaque +stringData: + appsettings.Production.json: | + { + "ZarinPal": { + "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", + "UseSandbox": false + }, + "CmsBaseUrl": "https://cms.kbs1.ir", + "FrontOfficeBaseUrl": "https://kbs1.ir", + "ConnectionStrings": { + "DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" + }, + "SeedWorkers": { + "MagicWalletCycleSeed": { + "Enabled": true + } + }, + "FileStorage": { + "UploadPath": "/app/Uploads" + }, + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, + "Seq": { + "ServerUrl": "http://seq-svc:5341", + "ApiKey": "oxpvpUzU1pZxMS4s3Fqq" + }, + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.AspNetCore": "Warning" + } + } + } diff --git a/k8s/production/cms-deployment.yaml b/k8s/production/cms-deployment.yaml index a5f3188..9fdec43 100644 --- a/k8s/production/cms-deployment.yaml +++ b/k8s/production/cms-deployment.yaml @@ -45,13 +45,13 @@ spec: value: "Production" - name: ASPNETCORE_URLS value: "http://+:8080" - - name: Kestrel__EndpointDefaults__Protocols - value: "Http1AndHttp2" - - name: FileStorage__UploadPath - value: "/app/Uploads" volumeMounts: - name: cms-uploads mountPath: /app/Uploads + - name: cms-config + mountPath: /app/appsettings.Production.json + subPath: appsettings.Production.json + readOnly: true resources: requests: memory: "512Mi" @@ -79,6 +79,9 @@ spec: - name: cms-uploads persistentVolumeClaim: claimName: cms-uploads-pvc + - name: cms-config + secret: + secretName: cms-appsettings --- # CMS Service (Production) diff --git a/k8s/staging/cms-config.yaml b/k8s/staging/cms-config.yaml new file mode 100644 index 0000000..138f927 --- /dev/null +++ b/k8s/staging/cms-config.yaml @@ -0,0 +1,111 @@ +--- +# CMS Application Configuration Secret (Staging) +# This Secret stores appsettings.Staging.json externally in K8s +# so configuration persists independently of Docker image rebuilds. +# +# To edit config without redeploying: +# kubectl edit secret cms-appsettings +# kubectl rollout restart deployment/cms +# +apiVersion: v1 +kind: Secret +metadata: + name: cms-appsettings + namespace: default + labels: + app: cms + environment: staging +type: Opaque +stringData: + appsettings.Staging.json: | + { + "PaymentProvider": "zarinpal", + "ZarinPal": { + "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", + "UseSandbox": true + }, + "FMS": { + "Address": "https://dl.afrino.co" + }, + "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", + "JwtIssuer": "https://localhost", + "JwtAudience": "https://localhost", + "JwtExpiryInDays": 5, + "ConnectionStrings": { + "DefaultConnection": "Data Source=194.5.195.53,31433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False", + "providerName": "System.Data.SqlClient" + }, + "Otp": { + "Secret": "K2w8k1h1mH2Qz1kqWk0c8kQ2Pq8q9H1eE2nqN1qQ8x7M=" + }, + "Monitoring": { + "SentryEnabled": false, + "SentryDsn": "", + "SlackEnabled": false, + "SlackWebhookUrl": "", + "EmailAlertsEnabled": false, + "AdminEmails": ["admin@example.com"], + "SmsNotificationsEnabled": false, + "SmsApiKey": "", + "SmsGatewayUrl": "" + }, + "Email": { + "Enabled": true, + "SmtpHost": "smtp.gmail.com", + "SmtpPort": 587, + "SmtpUsername": "your-email@gmail.com", + "SmtpPassword": "your-app-password", + "FromEmail": "noreply@foursat.com", + "FromName": "FourSat CMS", + "EnableSsl": true + }, + "Sms": { + "Enabled": true, + "Provider": "Kavenegar", + "KavenegarApiKey": "497263626F32626A48685A6137524C4F78575A766E4C74694A556B79317648424964655030682B554545413D", + "Sender": "1000001110100" + }, + "DayaPayment": { + "BaseUrl": "https://api.daya.ir", + "ApiKey": "YOUR_DAYA_API_KEY" + }, + "DayaApi": { + "UseMock": false, + "BaseAddress": "https://Dayadiamond.ir", + "MerchantPermissionKey": "56146364$04sXjethI5WxhItR1Q9xnmFdJzl2BB8Bclsq8dAy7YVSZp3vtt-wP7ivrcCvmKLq", + "CacheDurationMinutes": 20 + }, + "Chatika": { + "Enabled": true, + "BaseUrl": "https://api.chatika.ir", + "ApiKey": "tIukvL8dnV4cB3yVWcCD9Xyfbj8rBxm5wPt2mLyJCgTsBBoMTWjt6mFEqQwpw-er" + }, + "BackgroundJobs": { + "WeeklyCommissionCalculation": { + "Enabled": true, + "CronExpression": "5 0 * * 0" + } + }, + "SeedWorkers": { + "MagicWalletCycleSeed": { + "Enabled": true + } + }, + "FileStorage": { + "UploadPath": "/app/Uploads" + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http1AndHttp2" + } + }, + "Authentication": { + "Authority": "https://ids.domain.com/", + "Audience": "domain_api" + }, + "Seq": { + "ServerUrl": "https://seq.afrino.co", + "ApiKey": "oxpvpUzU1pZxMS4s3Fqq" + } + } diff --git a/k8s/staging/cms-deployment.yaml b/k8s/staging/cms-deployment.yaml index 59a1e50..b65aade 100644 --- a/k8s/staging/cms-deployment.yaml +++ b/k8s/staging/cms-deployment.yaml @@ -45,13 +45,13 @@ spec: value: "Staging" - name: ASPNETCORE_URLS value: "http://+:8080" - - name: Kestrel__EndpointDefaults__Protocols - value: "Http1AndHttp2" - - name: FileStorage__UploadPath - value: "/app/Uploads" volumeMounts: - name: cms-uploads mountPath: /app/Uploads + - name: cms-config + mountPath: /app/appsettings.Staging.json + subPath: appsettings.Staging.json + readOnly: true resources: requests: memory: "512Mi" @@ -79,6 +79,9 @@ spec: - name: cms-uploads persistentVolumeClaim: claimName: cms-uploads-pvc + - name: cms-config + secret: + secretName: cms-appsettings --- # CMS Service From e72673c184eb3cc8c6e73d620a7203e309952b6a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 22:07:58 +0330 Subject: [PATCH 10/44] =?UTF-8?q?chore(staging):=20remove=20appsettings.Pr?= =?UTF-8?q?oduction.json=20=E2=80=94=20config=20comes=20from=20K8s=20Secre?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DO NOT cherry-pick this commit to production branch --- .../appsettings.Production.json | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 src/CMSMicroservice.WebApi/appsettings.Production.json diff --git a/src/CMSMicroservice.WebApi/appsettings.Production.json b/src/CMSMicroservice.WebApi/appsettings.Production.json deleted file mode 100644 index 1c337a1..0000000 --- a/src/CMSMicroservice.WebApi/appsettings.Production.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "ZarinPal": { - "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", - "UseSandbox": false - }, - "CmsBaseUrl": "https://cms.kbs1.ir", - "FrontOfficeBaseUrl": "https://kbs1.ir", - "ConnectionStrings": { - "DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" - }, - "SeedWorkers": { - "MagicWalletCycleSeed": { - "Enabled": true - } - }, - "Kestrel": { - "EndpointDefaults": { - "Protocols": "Http2" - } - }, - "Seq": { - "ServerUrl": "http://seq-svc:5341", - "ApiKey": "oxpvpUzU1pZxMS4s3Fqq" - }, - "Logging": { - "LogLevel": { - "Default": "Warning", - "Microsoft.AspNetCore": "Warning" - } - } -} From f3ac5ad7dfc848694c2c80b65da383c92b3b0953 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 22:59:00 +0330 Subject: [PATCH 11/44] fix: add missing UserWalletChangeLog for discount shop purchases and discount wallet charge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: when buying from discount shop, DiscountBalance was deducted from DB but no UserWalletChangeLog was created — making it invisible in wallet history. Same issue existed for discount wallet top-up (charge). Fixed in 3 handlers: - PlaceOrderCommandHandler (fully paid by discount balance path) - CompleteOrderPaymentCommandHandler (gateway + discount balance path) - VerifyDiscountWalletChargeCommandHandler (discount wallet charge) --- .../CompleteOrderPaymentCommandHandler.cs | 16 +++++++++++++++- .../PlaceOrder/PlaceOrderCommandHandler.cs | 18 +++++++++++++++++- ...VerifyDiscountWalletChargeCommandHandler.cs | 15 +++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs index 0216a08..ffd5769 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs @@ -62,9 +62,23 @@ public class CompleteOrderPaymentCommandHandler : IRequestHandler w.UserId == order.UserId, cancellationToken); - if (userWallet != null) + if (userWallet != null && order.DiscountBalanceUsed > 0) { userWallet.DiscountBalance -= order.DiscountBalanceUsed; + + // ثبت لاگ تغییرات کیف پول + _context.UserWalletChangeLogs.Add(new Domain.Entities.UserWalletChangeLog + { + WalletId = userWallet.Id, + CurrentBalance = userWallet.Balance, + ChangeValue = 0, + CurrentNetworkBalance = userWallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = userWallet.DiscountBalance, + ChangeDiscountValue = -order.DiscountBalanceUsed, + IsIncrease = false, + RefrenceId = transaction.Id + }); } // تایید فروش و کسر موجودی از طریق InventoryService diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs index 58bb737..9a3d9a4 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs @@ -263,9 +263,25 @@ public class PlaceOrderCommandHandler : IRequestHandler w.UserId == request.UserId, cancellationToken); - if (walletForDeduct != null) + if (walletForDeduct != null && actualDiscountBalanceUsed > 0) + { walletForDeduct.DiscountBalance -= actualDiscountBalanceUsed; + // ثبت لاگ تغییرات کیف پول + _context.UserWalletChangeLogs.Add(new Domain.Entities.UserWalletChangeLog + { + WalletId = walletForDeduct.Id, + CurrentBalance = walletForDeduct.Balance, + ChangeValue = 0, + CurrentNetworkBalance = walletForDeduct.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = walletForDeduct.DiscountBalance, + ChangeDiscountValue = -actualDiscountBalanceUsed, + IsIncrease = false, + RefrenceId = transaction.Id + }); + } + foreach (var cartItem in cartItems) { await _inventoryService.ConfirmSaleAsync( diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs index c1989b7..3dac1d8 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs @@ -114,6 +114,21 @@ public class VerifyDiscountWalletChargeCommandHandler _context.Transactions.Add(transaction); await _context.SaveChangesAsync(cancellationToken); + // ثبت لاگ تغییرات کیف پول (بعد از ایجاد Transaction برای داشتن TransactionId) + _context.UserWalletChangeLogs.Add(new Domain.Entities.UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = 0, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = wallet.DiscountBalance, + ChangeDiscountValue = request.Amount, + IsIncrease = true, + RefrenceId = transaction.Id + }); + await _context.SaveChangesAsync(cancellationToken); + // لینک PaymentTransaction به Transaction داخلی if (paymentTx != null) { From 0457ef6c7c8b0dc05fd3afbf40b67c443d70441a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 23:06:37 +0330 Subject: [PATCH 12/44] fix: validate discount wallet balance before applying discount in PlaceOrderCommandHandler --- .../Commands/PlaceOrder/PlaceOrderCommandHandler.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs index 9a3d9a4..898aaba 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs @@ -111,7 +111,18 @@ public class PlaceOrderCommandHandler : IRequestHandler Date: Tue, 24 Feb 2026 00:24:02 +0330 Subject: [PATCH 13/44] fix: reduce discount order expiry from 30 to 15 minutes --- .../BackgroundServices/ExpirePendingOrdersService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs b/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs index 47bd396..3eb9df5 100644 --- a/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs +++ b/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs @@ -17,9 +17,9 @@ public class ExpirePendingOrdersService : BackgroundService private readonly ILogger _logger; /// - /// مدت زمان انقضا (۳۰ دقیقه) + /// مدت زمان انقضا (۱۵ دقیقه) /// - private static readonly TimeSpan ExpirationTime = TimeSpan.FromMinutes(30); + private static readonly TimeSpan ExpirationTime = TimeSpan.FromMinutes(15); /// /// هر ۵ دقیقه چک می‌شود From cbaa20f339aa9f93ebee35d1178971a9fe0354b1 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Tue, 24 Feb 2026 00:49:16 +0330 Subject: [PATCH 14/44] =?UTF-8?q?fix:=20remove=20double=20=C3=9710=20Rial?= =?UTF-8?q?=20conversion=20in=20ZarinPalPaymentService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: FrontOffice already converts Toman→Rial (×10) before sending to CMS. ZarinPalPaymentService was multiplying by 10 AGAIN, causing amounts to be 10x too large. Example: user enters 500K Toman → FO sends 5M Rial → CMS did ×10 → 50M Rial sent to ZarinPal → showed 5M Toman instead of 500K. Changes: - ZarinPalPaymentService.InitiatePaymentAsync: remove ×10 (amount already Rial) - ZarinPalPaymentService.VerifyPaymentWithAmountAsync: remove ×10 + accept Rial - ZarinPalPaymentService.VerifyResult.Amount: return Rial (no /10 conversion) - VerifyMagicWalletChargeCommandHandler: remove /10 before calling Verify - PaymentCallbackController: update comments (amount is Rial) - Also includes: improved HTTP error logging for non-200 responses - Also includes: production URL fix (kbs1→kbs2) --- k8s/production/cms-config.yaml | 4 +- .../VerifyMagicWalletChargeCommandHandler.cs | 5 +-- .../Payment/ZarinPalPaymentService.cs | 41 ++++++++++++------- .../Controllers/PaymentCallbackController.cs | 4 +- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/k8s/production/cms-config.yaml b/k8s/production/cms-config.yaml index b96af17..1dd737d 100644 --- a/k8s/production/cms-config.yaml +++ b/k8s/production/cms-config.yaml @@ -23,8 +23,8 @@ stringData: "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": false }, - "CmsBaseUrl": "https://cms.kbs1.ir", - "FrontOfficeBaseUrl": "https://kbs1.ir", + "CmsBaseUrl": "https://cms.kbs2.ir", + "FrontOfficeBaseUrl": "https://kbs2.ir", "ConnectionStrings": { "DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;" }, diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs index 855c657..9bf541c 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs @@ -74,12 +74,11 @@ public class VerifyMagicWalletChargeCommandHandler throw new BadRequestException("پرداخت توسط کاربر لغو شد"); } - // 3. Verify با درگاه (زرین‌پال نیاز به مبلغ دارد) - var amountInToman = depositAmount / 10m; + // 3. Verify با درگاه (مبلغ به ریال) var verifyResult = await _paymentGateway.VerifyPaymentAsync( request.Authority, request.Status, - amountInToman, + depositAmount, cancellationToken ); diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs index 46dab44..9b790ee 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs @@ -66,8 +66,8 @@ public class ZarinPalPaymentService : IPaymentGatewayService { try { - // زرین‌پال مبلغ را به ریال می‌خواهد — تبدیل تومان به ریال - var amountInRials = (long)(request.Amount * 10); + // مبلغ از caller به ریال می‌رسد — مستقیم ارسال به زرین‌پال + var amountInRials = (long)request.Amount; var zarinPalRequest = new ZarinPalPaymentRequest { @@ -85,12 +85,26 @@ public class ZarinPalPaymentService : IPaymentGatewayService var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); _logger.LogInformation( - "ZarinPal payment request: Amount={AmountToman} Toman ({AmountRial} Rial), User={UserId}, Sandbox={Sandbox}", - request.Amount, amountInRials, request.UserId, _useSandbox); + "ZarinPal payment request: Amount={AmountRial} Rial ({AmountToman} Toman), User={UserId}, Sandbox={Sandbox}", + amountInRials, amountInRials / 10m, request.UserId, _useSandbox); var response = await _httpClient.PostAsync(RequestEndpoint, content, cancellationToken); var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + // لاگ HTTP status — مهم برای دیباگ + if (!response.IsSuccessStatusCode) + { + _logger.LogError( + "ZarinPal HTTP error: StatusCode={StatusCode}, Body={Body}", + (int)response.StatusCode, responseBody); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطای ارتباط با زرین‌پال (HTTP {(int)response.StatusCode}). لطفاً MerchantId و IP سرور در پنل زرین‌پال بررسی شود." + }; + } + _logger.LogDebug("ZarinPal request response: {StatusCode} - {Body}", response.StatusCode, responseBody); @@ -153,21 +167,21 @@ public class ZarinPalPaymentService : IPaymentGatewayService /// /// تأیید پرداخت با مبلغ — نسخه اصلی برای زرین‌پال - /// refId = Authority، verificationToken = Status (OK/NOK)، amountInToman = مبلغ به تومان + /// refId = Authority، verificationToken = Status (OK/NOK)، amount = مبلغ به ریال /// public Task VerifyPaymentAsync( string refId, string verificationToken, - decimal amountInToman, + decimal amount, CancellationToken cancellationToken = default) { - return VerifyPaymentWithAmountAsync(refId, verificationToken, amountInToman, cancellationToken); + return VerifyPaymentWithAmountAsync(refId, verificationToken, amount, cancellationToken); } private async Task VerifyPaymentWithAmountAsync( string refId, string verificationToken, - decimal amountInToman, + decimal amountInRials, CancellationToken cancellationToken) { try @@ -184,21 +198,18 @@ public class ZarinPalPaymentService : IPaymentGatewayService }; } - // تبدیل تومان → ریال (×۱۰) - var amountInRials = (long)(amountInToman * 10); - var verifyRequest = new ZarinPalVerifyRequest { MerchantId = _merchantId, Authority = refId, - Amount = amountInRials + Amount = (long)amountInRials }; var jsonContent = JsonSerializer.Serialize(verifyRequest, JsonOptions); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); - _logger.LogInformation("ZarinPal verify request: Authority={Authority}, Amount={Amount} Rial", - refId, amountInRials); + _logger.LogInformation("ZarinPal verify request: Authority={Authority}, Amount={Amount} Rial ({AmountToman} Toman)", + refId, (long)amountInRials, amountInRials / 10m); var response = await _httpClient.PostAsync(VerifyEndpoint, content, cancellationToken); var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); @@ -220,7 +231,7 @@ public class ZarinPalPaymentService : IPaymentGatewayService IsSuccess = true, RefId = refId, TrackingCode = result.Data.RefId?.ToString(), - Amount = (result.Data.Amount ?? 0) / 10m, // ریال → تومان + Amount = result.Data.Amount ?? 0, // ریال — بدون تبدیل CardPan = result.Data.CardPan, CardHash = result.Data.CardHash, VerificationCode = result.Data.Code, diff --git a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs index 404ce91..d553488 100644 --- a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs +++ b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs @@ -82,11 +82,11 @@ public class PaymentCallbackController : ControllerBase if (string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(authority)) { - // Verify با مبلغ از دیتابیس (تومان) + // Verify با مبلغ از دیتابیس (ریال) var verifyResult = await _paymentGateway.VerifyPaymentAsync( authority, status!, - order.GatewayAmountPaid, // مبلغ به تومان + order.GatewayAmountPaid, // مبلغ به ریال cancellationToken); paymentSuccess = verifyResult.IsSuccess; From 8b9c317de6199305c5f106560bc2eb5173b2cef8 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Tue, 24 Feb 2026 21:52:36 +0330 Subject: [PATCH 15/44] =?UTF-8?q?fix:=20Phase=200=20=E2=80=94=20critical?= =?UTF-8?q?=20bugs=20in=20package=20purchase=20handlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B1: VerifyGoldenPackagePurchase — DiscountBalance was NOT charged (Amount × 2) Now charges DiscountBalance same as all other handlers. B2: VerifyGoldenPackagePurchase — UserPackagePurchase record was NOT created Now creates UPP with OrderId and TransactionId. B3: VerifyPackagePurchase — UserPackagePurchase record was NOT created Now creates UPP after wallet charge. B4: VerifyBasePackagePayment — UserPackagePurchase record was NOT created Now creates UPP after wallet change log. Also: VerifyGolden WalletChangeLog split into 2 logs (Balance + Discount) to match the pattern used by VerifyPackagePurchase. Transaction description changed from 'پکیج طلایی' to 'پکیج' (generic). Impact: Only Daya Loan handler was creating UserPackagePurchase before. Now all 4 payment paths create it consistently. --- .../VerifyBasePackagePaymentCommandHandler.cs | 24 +++++++- ...rifyGoldenPackagePurchaseCommandHandler.cs | 56 ++++++++++++++++--- .../VerifyPackagePurchaseCommandHandler.cs | 24 +++++++- 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs index b7d79c3..bda6448 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs @@ -164,13 +164,33 @@ public class VerifyBasePackagePaymentCommandHandler : IRequestHandler {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}", order.UserId, oldBalance, - wallet.Balance); + wallet.Balance, + oldDiscountBalance, + wallet.DiscountBalance); // 5. ثبت Transaction var transaction = new Transaction { Amount = order.Amount, - Description = $"خرید پکیج طلایی از درگاه - سفارش #{order.Id}", + Description = $"خرید پکیج از درگاه - سفارش #{order.Id}", PaymentStatus = PaymentStatus.Success, PaymentDate = DateTime.Now, RefId = verifyResult.RefId, @@ -133,23 +139,57 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler Date: Tue, 24 Feb 2026 22:53:36 +0330 Subject: [PATCH 16/44] refactor: archive 12 dead gRPC RPCs with [ARCHIVED] markers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Archived RPCs (code preserved, marked obsolete): - fms.proto: excluded from csproj (CreateNewFileInfo, DeleteFileInfo) - UserOrderService: GetOrdersByDateRange, CreateNewOrderForCustomer, SubmitOrderForCustomer - ConfigurationService: DeactivateConfiguration, GetConfigurationHistory - CityService: UpdateCity, DeleteCity - HealthService: GetServiceHealth - CategoryService: GetCategoryByIdForCustomer - inventory.proto: BulkAdjustStock (no implementation existed) All archived RPCs wrapped in #region [ARCHIVED] with [Obsolete] attributes. No code deleted — archive only. Build passes with 0 errors. --- .../CMSMicroservice.Protobuf.csproj | 4 ++-- .../Services/CategoryService.cs | 4 ++++ src/CMSMicroservice.WebApi/Services/CityService.cs | 4 ++++ .../Services/ConfigurationService.cs | 8 ++++++-- src/CMSMicroservice.WebApi/Services/HealthService.cs | 5 ++++- .../Services/UserOrderService.cs | 11 +++++++++-- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index c07bc07..fd03f97 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -64,8 +64,8 @@ - - + + diff --git a/src/CMSMicroservice.WebApi/Services/CategoryService.cs b/src/CMSMicroservice.WebApi/Services/CategoryService.cs index 33d9231..d3b1d1d 100644 --- a/src/CMSMicroservice.WebApi/Services/CategoryService.cs +++ b/src/CMSMicroservice.WebApi/Services/CategoryService.cs @@ -98,8 +98,11 @@ public class CategoryService : CategoryContract.CategoryContractBase return response; } + #region [ARCHIVED] GetCategoryByIdForCustomer — تکراری: GetCategory (admin) + GetAllCategoriesForCustomer کافی است + [Obsolete("ARCHIVED: Use GetCategory or GetAllCategoriesForCustomer instead")] public override async Task GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest request, ServerCallContext context) { + // [ARCHIVED] duplicate — GetCategory + GetAllCategoriesForCustomer cover this var query = new GetCategoryQuery { Id = request.Id }; var result = await _sender.Send(query, context.CancellationToken); @@ -118,4 +121,5 @@ public class CategoryService : CategoryContract.CategoryContractBase } }; } + #endregion } diff --git a/src/CMSMicroservice.WebApi/Services/CityService.cs b/src/CMSMicroservice.WebApi/Services/CityService.cs index 4156424..8649fe8 100644 --- a/src/CMSMicroservice.WebApi/Services/CityService.cs +++ b/src/CMSMicroservice.WebApi/Services/CityService.cs @@ -177,6 +177,8 @@ public class CityService : CityContract.CityContractBase }; } + #region [ARCHIVED] UpdateCity & DeleteCity — شهرها seed data هستند، ویرایش/حذف دستی باعث خرابی آدرس‌ها می‌شود + [Obsolete("ARCHIVED: Cities are seed data — manual edit/delete breaks user addresses")] public override async Task UpdateCity( UpdateCityRequest request, ServerCallContext context) { @@ -195,6 +197,7 @@ public class CityService : CityContract.CityContractBase return new Empty(); } + [Obsolete("ARCHIVED: Cities are seed data — manual delete breaks user addresses")] public override async Task DeleteCity( DeleteCityRequest request, ServerCallContext context) { @@ -206,6 +209,7 @@ public class CityService : CityContract.CityContractBase await _context.SaveChangesAsync(context.CancellationToken); return new Empty(); } + #endregion #endregion } diff --git a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs index 8a096aa..b2e4c7f 100644 --- a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs +++ b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs @@ -154,18 +154,22 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. تغییر از طریق کد انجام می‌شود")); } + #region [ARCHIVED] DeactivateConfiguration & GetConfigurationHistory — SystemConstants ثابت هستند، این RPCها بی‌معنی [RequiresPermission(PermissionNames.SettingsManageConfiguration)] + [Obsolete("ARCHIVED: SystemConstants are read-only — deactivation is meaningless")] public override Task DeactivateConfiguration(DeactivateConfigurationRequest request, ServerCallContext context) { - _logger.LogWarning("DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key); + _logger.LogWarning("[ARCHIVED] DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key); throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. غیرفعال‌سازی از طریق کد انجام می‌شود")); } + [Obsolete("ARCHIVED: SystemConstants have no history — always returns empty")] public override Task GetConfigurationHistory(GetConfigurationHistoryRequest request, ServerCallContext context) { - // چون constant هستند، تاریخچه‌ای وجود نداره + // [ARCHIVED] چون constant هستند، تاریخچه‌ای وجود نداره return Task.FromResult(new GetConfigurationHistoryResponse()); } + #endregion #region Private Helpers diff --git a/src/CMSMicroservice.WebApi/Services/HealthService.cs b/src/CMSMicroservice.WebApi/Services/HealthService.cs index 4f7f3e5..4b896c6 100644 --- a/src/CMSMicroservice.WebApi/Services/HealthService.cs +++ b/src/CMSMicroservice.WebApi/Services/HealthService.cs @@ -25,9 +25,11 @@ public class HealthService : HealthContract.HealthContractBase return result.Adapt(); } + #region [ARCHIVED] GetServiceHealth — تکراری: GetSystemHealth کل سیستم را برمی‌گرداند، فیلتر client-side کافی است + [Obsolete("ARCHIVED: Use GetSystemHealth + client-side filter by service name")] public override async Task GetServiceHealth(GetServiceHealthRequest request, ServerCallContext context) { - // For now, just return system health filtered by service name + // [ARCHIVED] duplicate — GetSystemHealth returns all, client can filter var systemHealthQuery = new GetSystemHealthQuery(); var systemHealth = await _mediator.Send(systemHealthQuery, context.CancellationToken); @@ -45,4 +47,5 @@ public class HealthService : HealthContract.HealthContractBase CheckedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow) }; } + #endregion } \ No newline at end of file diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index 0dfd15d..e8c9e53 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -497,7 +497,9 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase }; } + #region [ARCHIVED] GetOrdersByDateRange — تکراری: GetAllUserOrderByFilter همین قابلیت + فیلترهای بیشتر دارد [RequiresPermission(PermissionNames.ReportsView)] + [Obsolete("ARCHIVED: Use GetAllUserOrderByFilter instead — has date range + more filters")] public override async Task GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context) { var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1; @@ -551,6 +553,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase Orders = { orders } }; } + #endregion [RequiresPermission(PermissionNames.OrdersUpdate)] public override async Task ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context) @@ -624,17 +627,21 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase // ============= Customer-specific Methods ============= + #region [ARCHIVED] CreateNewOrderForCustomer & SubmitOrderForCustomer — Stub خالی: مسیر سفارش مشتری از SubmitShopBuyOrder می‌گذرد + [Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")] public override async Task CreateNewOrderForCustomer(CreateNewUserOrderRequest request, ServerCallContext context) { - // For now, return empty response - will be implemented properly later + // [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder می‌گذرد return new CreateNewUserOrderResponse(); } + [Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")] public override async Task SubmitOrderForCustomer(SubmitShopBuyOrderRequest request, ServerCallContext context) { - // For now, return empty response - will be implemented properly later + // [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder می‌گذرد return new SubmitShopBuyOrderResponse(); } + #endregion public override async Task GetCustomerOrders(GetAllUserOrderByFilterRequest request, ServerCallContext context) { From fe3edd178d36048eb0c885c1d1dbb5cab785e761 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 25 Feb 2026 21:59:30 +0330 Subject: [PATCH 17/44] =?UTF-8?q?fix(B6):=20EXIT=20Magic=20Mode=20?= =?UTF-8?q?=E2=80=94=20reset=20PackagePurchaseMethod=20+=20close=20current?= =?UTF-8?q?=20cycle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B6: PackagePurchaseMethod was NOT reset when exiting Magic Mode. This caused Guards G1-G3 to permanently block re-purchase. Changes: - user.PackagePurchaseMethod = None (allows re-purchase via G1-G3) - currentCycle.IsCurrentCycle = false (ready for new cycle) Note: membership.IsActive is intentionally preserved (Q19 — contract is one-time). Features are preserved — DIFF will be applied on next purchase (Q20). --- src/CMSMicroservice.WebApi/Services/UserOrderService.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index e8c9e53..c55abcc 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -358,6 +358,13 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase if (currentCycle != null) { currentCycle.MagicCompletedAt = DateTime.UtcNow; + currentCycle.IsCurrentCycle = false; + } + + // B6 fix: ریست PackagePurchaseMethod تا Guards G1-G3 خرید مجدد را مسدود نکنند + if (user != null) + { + user.PackagePurchaseMethod = PackagePurchaseMethod.None; } } else if (wallet.WalletMode == WalletMode.Normal From ae92ab86978121a597f123d47e1880ad402ef270 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 25 Feb 2026 22:32:42 +0330 Subject: [PATCH 18/44] Phase 1: Package-Based Domain Infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ENTITIES: - Package: +11 fields (SortOrder, IsActive, IsBasePackage, SupportsDaya/Direct, ActivationFee, DiscountMultiplier, MagicWalletMultiplier, MaxBalancesPerLeg, MaxNetworkLevel, MagicWalletMaxDeposit/Credit) + nav props - PackageFeature: NEW junction entity (Package × ClubFeature) for feature DIFF - ClubMembership: +4 fields (FirstActivationDate/PackageId, LastActivation/PackageId) ActivatedAt kept as [Obsolete] for backward compat - ClubMembershipCycle: +PackageId FK (tracks which package per cycle) - WeeklyCommissionPool: +PackageId FK (separate pool per package) - UserCommissionPayout: +PackageId FK (payout per package) - NetworkWeeklyBalance: +PackageId FK (balance per package) - UserWalletChangeLog: +PackageId? FK (optional package tracking) EF CONFIGS: - PackageConfiguration: 11 new field configs + indexes - PackageFeatureConfiguration: NEW (unique PackageId+ClubFeatureId) - ClubMembershipConfiguration: First/Last Package FKs + LastActivationDate index - ClubMembershipCycleConfiguration: Package FK - WeeklyCommissionPoolConfiguration: Package FK + unique(WeekDef,Package) - UserCommissionPayoutConfiguration: Package FK + unique(User,WeekDef,Package) - NetworkWeeklyBalanceConfiguration: Package FK + unique(User,WeekDef,Package) - UserWalletChangeLogConfiguration: Package FK (nullable) DBCONTEXT: - DbSet added to ApplicationDbContext + IApplicationDbContext SYSTEMCONSTANTS: - 9 constants marked [Obsolete] → moved to Package entity (ClubMembershipGiftValue, ClubActivationFee, BasePackageAmount, DayaLoanAmount, MagicWalletMultiplier, MagicWalletMaxDeposit, MagicWalletMaxCredit, CommissionMaxWeeklyBalancesPerLeg, CommissionMaxNetworkLevel) Build: 0 errors, 610 warnings (40 new = expected Obsolete CS0618) --- .../Interfaces/IApplicationDbContext.cs | 1 + .../Common/SystemConstants.cs | 19 ++++- .../Entities/Club/ClubMembership.cs | 38 +++++++++- .../Entities/Club/ClubMembershipCycle.cs | 10 +++ .../Commission/UserCommissionPayout.cs | 10 +++ .../Commission/WeeklyCommissionPool.cs | 10 +++ .../Entities/Network/NetworkWeeklyBalance.cs | 10 +++ .../Entities/Package.cs | 73 +++++++++++++++++-- .../Entities/PackageFeature.cs | 34 +++++++++ .../Entities/UserWalletChangeLog.cs | 10 +++ .../Persistence/ApplicationDbContext.cs | 1 + .../ClubMembershipConfiguration.cs | 24 ++++++ .../ClubMembershipCycleConfiguration.cs | 6 ++ .../NetworkWeeklyBalanceConfiguration.cs | 13 +++- .../Configurations/PackageConfiguration.cs | 22 ++++++ .../PackageFeatureConfiguration.cs | 41 +++++++++++ .../UserCommissionPayoutConfiguration.cs | 13 +++- .../UserWalletChangeLogConfiguration.cs | 8 ++ .../WeeklyCommissionPoolConfiguration.cs | 13 +++- 19 files changed, 339 insertions(+), 17 deletions(-) create mode 100644 src/CMSMicroservice.Domain/Entities/PackageFeature.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageFeatureConfiguration.cs diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs index 64dcc7d..17b0b88 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs @@ -39,6 +39,7 @@ public interface IApplicationDbContext DbSet ClubMemberships { get; } DbSet ClubMembershipHistories { get; } DbSet ClubMembershipCycles { get; } + DbSet PackageFeatures { get; } DbSet ClubFeatures { get; } DbSet UserClubFeatures { get; } DbSet NetworkWeeklyBalances { get; } diff --git a/src/CMSMicroservice.Domain/Common/SystemConstants.cs b/src/CMSMicroservice.Domain/Common/SystemConstants.cs index e0b4b3d..2d02837 100644 --- a/src/CMSMicroservice.Domain/Common/SystemConstants.cs +++ b/src/CMSMicroservice.Domain/Common/SystemConstants.cs @@ -24,12 +24,16 @@ public static class SystemConstants /// /// مبلغ هدیه حق عضویت باشگاه (ریال) - این مبلغ از کیف پول کم نمی‌شود + /// [DEPRECATED] از Package.Price استفاده کنید /// + [Obsolete("Moved to Package entity. Use package.Price or package.ActivationFee instead.")] public const long ClubMembershipGiftValue = 25_200_000; /// /// هزینه فعال‌سازی عضویت باشگاه (ریال) + /// [DEPRECATED] از Package.ActivationFee استفاده کنید /// + [Obsolete("Moved to Package.ActivationFee. Read from Package entity instead.")] public const long ClubActivationFee = 25_200_000; #endregion @@ -38,13 +42,16 @@ public static class SystemConstants /// /// مبلغ پکیج طلایی / پایه (ریال) - 56 میلیون تومان - /// شامل: هدیه باشگاه + هزینه فعال‌سازی + مزایای دیگر + /// [DEPRECATED] از Package.Price استفاده کنید /// + [Obsolete("Moved to Package.Price. Read from Package entity instead.")] public const long BasePackageAmount = 56_000_000; /// /// مبلغ وام دایا (ریال) - همان مبلغ پکیج طلایی + /// [DEPRECATED] از Package.Price استفاده کنید /// + [Obsolete("Moved to Package.Price. Read from Package entity with SupportsDayaPurchase=true.")] public const long DayaLoanAmount = 56_000_000; #endregion @@ -63,12 +70,16 @@ public static class SystemConstants /// /// سقف تعادل هفتگی برای هر دست (چپ یا راست) - حداکثر کل = 600 + /// [DEPRECATED] از Package.MaxBalancesPerLeg استفاده کنید /// + [Obsolete("Moved to Package.MaxBalancesPerLeg. Read from Package entity instead.")] public const int CommissionMaxWeeklyBalancesPerLeg = 300; /// /// حداکثر عمق شبکه برای محاسبه کمیسیون (تعداد لول زیرمجموعه) + /// [DEPRECATED] از Package.MaxNetworkLevel استفاده کنید /// + [Obsolete("Moved to Package.MaxNetworkLevel. Read from Package entity instead.")] public const int CommissionMaxNetworkLevel = 15; /// @@ -96,17 +107,23 @@ public static class SystemConstants /// /// ضریب شارژ کیف‌پول جادویی — واریز × 2.5 = اعتبار + /// [DEPRECATED] از Package.MagicWalletMultiplier استفاده کنید /// + [Obsolete("Moved to Package.MagicWalletMultiplier. Read from Package entity instead.")] public const decimal MagicWalletMultiplier = 2.5m; /// /// سقف واریز در هر دور جادویی (ریال) — 100M تومان + /// [DEPRECATED] از Package.MagicWalletMaxDeposit استفاده کنید /// + [Obsolete("Moved to Package.MagicWalletMaxDeposit. Read from Package entity instead.")] public const long MagicWalletMaxDeposit = 1_000_000_000; /// /// سقف اعتبار در هر دور جادویی (ریال) — 250M تومان + /// [DEPRECATED] از Package.MagicWalletMaxCredit استفاده کنید /// + [Obsolete("Moved to Package.MagicWalletMaxCredit. Read from Package entity instead.")] public const long MagicWalletMaxCredit = 2_500_000_000; #endregion diff --git a/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs b/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs index 089f6bf..e67754b 100644 --- a/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs +++ b/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs @@ -20,9 +20,45 @@ public class ClubMembership : BaseAuditableEntity /// public bool IsActive { get; set; } + // === v5: First/Last Activation Tracking (Q21) === + /// - /// تاریخ فعال‌سازی عضویت + /// اولین فعال‌سازی — فقط یک بار ست می‌شود، هیچ‌وقت overwrite نمی‌شود /// + public DateTime FirstActivationDate { get; set; } + + /// + /// اولین پکیج خریداری‌شده — فقط یک بار ست می‌شود + /// + public long FirstPackageId { get; set; } + + /// + /// FirstPackage Navigation Property + /// + public virtual Package FirstPackage { get; set; } + + /// + /// آخرین/جاری فعال‌سازی — هر خرید مجدد بروزرسانی می‌شود + /// مبنای تشخیص "فعال‌شدگان این هفته" (Q22) + /// + public DateTime LastActivationDate { get; set; } + + /// + /// آخرین/جاری پکیج — هر خرید مجدد بروزرسانی می‌شود + /// مبنای carryover per-package (Q23) + /// + public long LastPackageId { get; set; } + + /// + /// LastPackage Navigation Property + /// + public virtual Package LastPackage { get; set; } + + /// + /// [DEPRECATED] تاریخ فعال‌سازی — جایگزین با FirstActivationDate + LastActivationDate + /// حفظ موقت برای backward compatibility + /// + [Obsolete("Use FirstActivationDate / LastActivationDate instead")] public DateTime? ActivatedAt { get; set; } /// diff --git a/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs b/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs index 070f713..2b1f787 100644 --- a/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs +++ b/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs @@ -57,6 +57,16 @@ public class ClubMembershipCycle : BaseAuditableEntity /// public long PackageAmount { get; set; } + /// + /// شناسه پکیج خریداری‌شده در این دور + /// + public long PackageId { get; set; } + + /// + /// Package Navigation Property + /// + public virtual Package Package { get; set; } + /// /// آیا این دور فعلی است؟ فقط یک رکورد true می‌باشد /// diff --git a/src/CMSMicroservice.Domain/Entities/Commission/UserCommissionPayout.cs b/src/CMSMicroservice.Domain/Entities/Commission/UserCommissionPayout.cs index 12cd658..e8c8340 100644 --- a/src/CMSMicroservice.Domain/Entities/Commission/UserCommissionPayout.cs +++ b/src/CMSMicroservice.Domain/Entities/Commission/UserCommissionPayout.cs @@ -36,6 +36,16 @@ public class UserCommissionPayout : BaseAuditableEntity /// public virtual WeeklyCommissionPool WeeklyPool { get; set; } + /// + /// شناسه پکیج (کمیسیون هر پکیج جداگانه) + /// + public long PackageId { get; set; } + + /// + /// Package Navigation Property + /// + public virtual Package Package { get; set; } + /// /// تعداد امتیازی که کاربر داشت /// diff --git a/src/CMSMicroservice.Domain/Entities/Commission/WeeklyCommissionPool.cs b/src/CMSMicroservice.Domain/Entities/Commission/WeeklyCommissionPool.cs index a5ba946..a8ca1cc 100644 --- a/src/CMSMicroservice.Domain/Entities/Commission/WeeklyCommissionPool.cs +++ b/src/CMSMicroservice.Domain/Entities/Commission/WeeklyCommissionPool.cs @@ -16,6 +16,16 @@ public class WeeklyCommissionPool : BaseAuditableEntity /// public virtual WeekDefinition WeekDefinition { get; set; } + /// + /// شناسه پکیج (استخر هر پکیج جداگانه) + /// + public long PackageId { get; set; } + + /// + /// Package Navigation Property + /// + public virtual Package Package { get; set; } + /// /// مجموع مبلغ جمع‌شده در استخر (ریال) /// diff --git a/src/CMSMicroservice.Domain/Entities/Network/NetworkWeeklyBalance.cs b/src/CMSMicroservice.Domain/Entities/Network/NetworkWeeklyBalance.cs index c60b705..65134a9 100644 --- a/src/CMSMicroservice.Domain/Entities/Network/NetworkWeeklyBalance.cs +++ b/src/CMSMicroservice.Domain/Entities/Network/NetworkWeeklyBalance.cs @@ -25,6 +25,16 @@ public class NetworkWeeklyBalance : BaseAuditableEntity /// public virtual WeekDefinition WeekDefinition { get; set; } + /// + /// شناسه پکیج (تعادل هر پکیج جداگانه) + /// + public long PackageId { get; set; } + + /// + /// Package Navigation Property + /// + public virtual Package Package { get; set; } + /// /// تعداد اعضای جدید شاخه چپ در این هفته /// diff --git a/src/CMSMicroservice.Domain/Entities/Package.cs b/src/CMSMicroservice.Domain/Entities/Package.cs index 7429c23..394e899 100644 --- a/src/CMSMicroservice.Domain/Entities/Package.cs +++ b/src/CMSMicroservice.Domain/Entities/Package.cs @@ -1,15 +1,76 @@ namespace CMSMicroservice.Domain.Entities; -//پکیج + +/// +/// پکیج — هر پکیج قیمت، ویژگی‌ها و تنظیمات مستقل دارد +/// public class Package : BaseAuditableEntity { - //عنوان + // === فیلدهای فعلی (حفظ) === + + /// عنوان پکیج public string Title { get; set; } - //توضیحات + + /// توضیحات public string Description { get; set; } - //آدرس تصویر + + /// آدرس تصویر public string ImagePath { get; set; } - //قیمت + + /// قیمت پکیج (ریال) public long Price { get; set; } - //UserOrder Collection Navigation Reference + + // === فیلدهای جدید v3 === + + /// ترتیب نمایش + public int SortOrder { get; set; } + + /// فعال/غیرفعال + public bool IsActive { get; set; } = true; + + /// پکیج پایه؟ (فقط یکی true) + public bool IsBasePackage { get; set; } + + /// پشتیبانی از خرید دایا + public bool SupportsDayaPurchase { get; set; } + + /// پشتیبانی از پرداخت مستقیم + public bool SupportsDirectPurchase { get; set; } = true; + + // === محاسبات مالی === + + /// سهم Commission Pool (ریال) — معمولاً Price × 0.45 + public long ActivationFee { get; set; } + + /// ضریب شارژ DiscountBalance — فعلاً ×2 برای همه + public decimal DiscountMultiplier { get; set; } = 2.0m; + + /// ضریب کیف‌پول جادویی — واریز × این مقدار = اعتبار + public decimal MagicWalletMultiplier { get; set; } = 2.5m; + + // === تنظیمات پورسانت === + + /// سقف تعادل هر پا (نقره‌ای=۳۰، پایه=۳۰۰) + public int MaxBalancesPerLeg { get; set; } = 300; + + /// عمق شبکه برای محاسبه کمیسیون + public int MaxNetworkLevel { get; set; } = 15; + + // === تنظیمات کیف‌پول جادویی === + + /// سقف شارژ هر دور جادویی (ریال) + public long MagicWalletMaxDeposit { get; set; } = 1_000_000_000; + + /// سقف اعتبار هر دور جادویی (ریال) + public long MagicWalletMaxCredit { get; set; } = 2_500_000_000; + + // === Navigation Properties === + + /// فیچرهای این پکیج + public virtual ICollection? PackageFeatures { get; set; } + + /// سفارشات مرتبط public virtual ICollection UserOrders { get; set; } + + /// خریدهای پکیج + public virtual ICollection? Purchases { get; set; } } diff --git a/src/CMSMicroservice.Domain/Entities/PackageFeature.cs b/src/CMSMicroservice.Domain/Entities/PackageFeature.cs new file mode 100644 index 0000000..860707d --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/PackageFeature.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Domain.Entities.Club; + +namespace CMSMicroservice.Domain.Entities; + +/// +/// ارتباط پکیج–فیچر — هر پکیج مجموعه فیچرهای خودش را دارد +/// +public class PackageFeature : BaseAuditableEntity +{ + /// + /// شناسه پکیج + /// + public long PackageId { get; set; } + + /// + /// Package Navigation Property + /// + public virtual Package Package { get; set; } + + /// + /// شناسه فیچر باشگاه + /// + public long ClubFeatureId { get; set; } + + /// + /// ClubFeature Navigation Property + /// + public virtual ClubFeature ClubFeature { get; set; } + + /// + /// آیا این فیچر در پکیج فعال است + /// + public bool IsIncluded { get; set; } = true; +} diff --git a/src/CMSMicroservice.Domain/Entities/UserWalletChangeLog.cs b/src/CMSMicroservice.Domain/Entities/UserWalletChangeLog.cs index 5a568ac..a5b6471 100644 --- a/src/CMSMicroservice.Domain/Entities/UserWalletChangeLog.cs +++ b/src/CMSMicroservice.Domain/Entities/UserWalletChangeLog.cs @@ -22,4 +22,14 @@ public class UserWalletChangeLog : BaseAuditableEntity public bool IsIncrease { get; set; } //شناسه ارجاع public long? RefrenceId { get; set; } + + /// + /// شناسه پکیج مرتبط (nullable — برای تغییرات کیف پول که مرتبط با پکیج هستند) + /// + public long? PackageId { get; set; } + + /// + /// Package Navigation Property + /// + public virtual Package? Package { get; set; } } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs index d772ffa..5d8ca45 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs @@ -110,6 +110,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public DbSet UserClubFeatures => Set(); public DbSet ClubMembershipHistories => Set(); public DbSet ClubMembershipCycles => Set(); + public DbSet PackageFeatures => Set(); // Network public DbSet NetworkWeeklyBalances => Set(); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipConfiguration.cs index 5b21622..d4fe762 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipConfiguration.cs @@ -23,12 +23,32 @@ public class ClubMembershipConfiguration : IEntityTypeConfiguration entity.GiftValue).IsRequired(); builder.Property(entity => entity.TotalEarned).IsRequired(); + // فیلدهای جدید First/Last Activation + builder.Property(entity => entity.FirstActivationDate).IsRequired(false); + builder.Property(entity => entity.FirstPackageId).IsRequired(false); + builder.Property(entity => entity.LastActivationDate).IsRequired(false); + builder.Property(entity => entity.LastPackageId).IsRequired(false); + // رابطه یک‌به‌یک با User builder.HasOne(entity => entity.User) .WithOne(u => u.ClubMembership) .HasForeignKey(entity => entity.UserId) .OnDelete(DeleteBehavior.Restrict); + // رابطه با Package — اولین پکیج خریداری‌شده + builder.HasOne(entity => entity.FirstPackage) + .WithMany() + .HasForeignKey(entity => entity.FirstPackageId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Restrict); + + // رابطه با Package — آخرین پکیج خریداری‌شده + builder.HasOne(entity => entity.LastPackage) + .WithMany() + .HasForeignKey(entity => entity.LastPackageId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Restrict); + // Index برای UserId (یونیک برای یک‌به‌یک) builder.HasIndex(e => e.UserId) .IsUnique() @@ -37,5 +57,9 @@ public class ClubMembershipConfiguration : IEntityTypeConfiguration e.IsActive) .HasDatabaseName("IX_ClubMembership_IsActive"); + + // Index برای LastActivationDate (تشخیص فعال‌سازی هفتگی Q22) + builder.HasIndex(e => e.LastActivationDate) + .HasDatabaseName("IX_ClubMembership_LastActivationDate"); } } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleConfiguration.cs index 19fb522..33fd41d 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleConfiguration.cs @@ -21,6 +21,7 @@ public class ClubMembershipCycleConfiguration : IEntityTypeConfiguration entity.MagicCompletedAt).IsRequired(false); builder.Property(entity => entity.PurchaseMethod).IsRequired(); builder.Property(entity => entity.PackageAmount).IsRequired(); + builder.Property(entity => entity.PackageId).IsRequired(); builder.Property(entity => entity.IsCurrentCycle) .IsRequired() .HasDefaultValue(false); @@ -36,6 +37,11 @@ public class ClubMembershipCycleConfiguration : IEntityTypeConfiguration entity.ClubMembershipId) .OnDelete(DeleteBehavior.Restrict); + builder.HasOne(entity => entity.Package) + .WithMany() + .HasForeignKey(entity => entity.PackageId) + .OnDelete(DeleteBehavior.Restrict); + // Indexes builder.HasIndex(e => new { e.UserId, e.IsCurrentCycle }) .HasDatabaseName("IX_ClubMembershipCycle_UserId_IsCurrentCycle"); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/NetworkWeeklyBalanceConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/NetworkWeeklyBalanceConfiguration.cs index e176f3f..99f520b 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/NetworkWeeklyBalanceConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/NetworkWeeklyBalanceConfiguration.cs @@ -18,6 +18,7 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration entity.UserId).IsRequired(); builder.Property(entity => entity.WeekDefinitionId).IsRequired(); + builder.Property(entity => entity.PackageId).IsRequired(); builder.Property(entity => entity.LeftLegBalances).IsRequired(); builder.Property(entity => entity.RightLegBalances).IsRequired(); builder.Property(entity => entity.TotalBalances).IsRequired(); @@ -37,10 +38,16 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration entity.WeekDefinitionId) .OnDelete(DeleteBehavior.Restrict); - // Composite Index برای UserId و WeekDefinitionId - builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId }) + // رابطه با Package + builder.HasOne(entity => entity.Package) + .WithMany() + .HasForeignKey(entity => entity.PackageId) + .OnDelete(DeleteBehavior.Restrict); + + // Composite Index: هر کاربر × هر هفته × هر پکیج فقط یک رکورد + builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId }) .IsUnique() - .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + .HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package"); // Index برای WeekDefinitionId builder.HasIndex(e => e.WeekDefinitionId) diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageConfiguration.cs index 9cd5e28..6b6d591 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageConfiguration.cs @@ -15,6 +15,28 @@ public class PackageConfiguration : IEntityTypeConfiguration builder.Property(entity => entity.Description).IsRequired(true); builder.Property(entity => entity.ImagePath).IsRequired(true); builder.Property(entity => entity.Price).IsRequired(true); + + // فیلدهای جدید پکیج + builder.Property(entity => entity.SortOrder).IsRequired().HasDefaultValue(0); + builder.Property(entity => entity.IsActive).IsRequired().HasDefaultValue(true); + builder.Property(entity => entity.IsBasePackage).IsRequired().HasDefaultValue(false); + builder.Property(entity => entity.SupportsDayaPurchase).IsRequired().HasDefaultValue(false); + builder.Property(entity => entity.SupportsDirectPurchase).IsRequired().HasDefaultValue(true); + builder.Property(entity => entity.ActivationFee).IsRequired().HasDefaultValue(0L); + builder.Property(entity => entity.DiscountMultiplier).IsRequired().HasDefaultValue(2.0m) + .HasPrecision(18, 4); + builder.Property(entity => entity.MagicWalletMultiplier).IsRequired().HasDefaultValue(2.5m) + .HasPrecision(18, 4); + builder.Property(entity => entity.MaxBalancesPerLeg).IsRequired().HasDefaultValue(300); + builder.Property(entity => entity.MaxNetworkLevel).IsRequired().HasDefaultValue(15); + builder.Property(entity => entity.MagicWalletMaxDeposit).IsRequired().HasDefaultValue(1_000_000_000L); + builder.Property(entity => entity.MagicWalletMaxCredit).IsRequired().HasDefaultValue(2_500_000_000L); + + // Indexes + builder.HasIndex(e => e.IsActive) + .HasDatabaseName("IX_Package_IsActive"); + builder.HasIndex(e => e.SortOrder) + .HasDatabaseName("IX_Package_SortOrder"); } } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageFeatureConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageFeatureConfiguration.cs new file mode 100644 index 0000000..2e42e1c --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageFeatureConfiguration.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +/// +/// EF Configuration — ارتباط پکیج–فیچر +/// +public class PackageFeatureConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasQueryFilter(p => !p.IsDeleted); + builder.Ignore(entity => entity.DomainEvents); + + builder.HasKey(entity => entity.Id); + builder.Property(entity => entity.Id).UseIdentityColumn(); + + builder.Property(entity => entity.PackageId).IsRequired(); + builder.Property(entity => entity.ClubFeatureId).IsRequired(); + builder.Property(entity => entity.IsIncluded).IsRequired().HasDefaultValue(true); + + // رابطه با Package + builder.HasOne(entity => entity.Package) + .WithMany(p => p.PackageFeatures) + .HasForeignKey(entity => entity.PackageId) + .OnDelete(DeleteBehavior.Cascade); + + // رابطه با ClubFeature + builder.HasOne(entity => entity.ClubFeature) + .WithMany() + .HasForeignKey(entity => entity.ClubFeatureId) + .OnDelete(DeleteBehavior.Restrict); + + // Unique: هر فیچر فقط یک بار در هر پکیج + builder.HasIndex(e => new { e.PackageId, e.ClubFeatureId }) + .IsUnique() + .HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserCommissionPayoutConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserCommissionPayoutConfiguration.cs index f190faa..d9632f9 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserCommissionPayoutConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserCommissionPayoutConfiguration.cs @@ -19,6 +19,7 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration entity.UserId).IsRequired(); builder.Property(entity => entity.WeekDefinitionId).IsRequired(); builder.Property(entity => entity.WeeklyPoolId).IsRequired(); + builder.Property(entity => entity.PackageId).IsRequired(); builder.Property(entity => entity.BalancesEarned).IsRequired(); builder.Property(entity => entity.ValuePerBalance).IsRequired(); builder.Property(entity => entity.TotalAmount).IsRequired(); @@ -50,10 +51,16 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration new { e.UserId, e.WeekDefinitionId }) + // رابطه با Package + builder.HasOne(entity => entity.Package) + .WithMany() + .HasForeignKey(entity => entity.PackageId) + .OnDelete(DeleteBehavior.Restrict); + + // Composite Index برای UserId، WeekDefinitionId و PackageId + builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId }) .IsUnique() - .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + .HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package"); // Index برای WeeklyPoolId builder.HasIndex(e => e.WeeklyPoolId) diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletChangeLogConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletChangeLogConfiguration.cs index 897dbec..f06d48f 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletChangeLogConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletChangeLogConfiguration.cs @@ -22,6 +22,14 @@ public class UserWalletChangeLogConfiguration : IEntityTypeConfiguration entity.ChangeNerworkValue).IsRequired(true); builder.Property(entity => entity.IsIncrease).IsRequired(true); builder.Property(entity => entity.RefrenceId).IsRequired(false); + builder.Property(entity => entity.PackageId).IsRequired(false); + + // رابطه با Package (nullable) + builder.HasOne(entity => entity.Package) + .WithMany() + .HasForeignKey(entity => entity.PackageId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Restrict); } } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/WeeklyCommissionPoolConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/WeeklyCommissionPoolConfiguration.cs index 5a56c75..7789dae 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/WeeklyCommissionPoolConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/WeeklyCommissionPoolConfiguration.cs @@ -17,6 +17,7 @@ public class WeeklyCommissionPoolConfiguration : IEntityTypeConfiguration entity.Id).UseIdentityColumn(); builder.Property(entity => entity.WeekDefinitionId).IsRequired(); + builder.Property(entity => entity.PackageId).IsRequired(); builder.Property(entity => entity.TotalPoolAmount).IsRequired(); builder.Property(entity => entity.TotalBalances).IsRequired(); builder.Property(entity => entity.ValuePerBalance).IsRequired(); @@ -30,10 +31,16 @@ public class WeeklyCommissionPoolConfiguration : IEntityTypeConfiguration e.WeekDefinitionId) + // رابطه با Package + builder.HasOne(entity => entity.Package) + .WithMany() + .HasForeignKey(entity => entity.PackageId) + .OnDelete(DeleteBehavior.Restrict); + + // Index یونیک ترکیبی: هر هفته × هر پکیج فقط یک استخر + builder.HasIndex(e => new { e.WeekDefinitionId, e.PackageId }) .IsUnique() - .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package"); // Index برای IsCalculated builder.HasIndex(e => e.IsCalculated) From a9cd2fd67a2f27aa10c41a73cea3e65e5fd9d15e Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 25 Feb 2026 22:49:12 +0330 Subject: [PATCH 19/44] Phase 1.5: EF Migration + Data Seed + Backfill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MIGRATION (AddPackageBasedSystem): - Adds 11 new columns to Packages table - Creates PackageFeatures table (Package×ClubFeature junction) - Adds PackageId FK to: ClubMembershipCycles, WeeklyCommissionPools, UserCommissionPayouts, NetworkWeeklyBalances, UserWalletChangeLogs - Adds First/LastActivationDate, First/LastPackageId to ClubMemberships - Drops old unique indexes, creates new composite unique indexes: (WeekDef,Package), (User,WeekDef,Package) DATA MIGRATION (embedded in EF migration): 1. Seeds Golden Package (Id=1): Price=56M, ActivationFee=25.2M, DiscountMultiplier=2.0, MagicWalletMultiplier=2.5, MaxBalancesPerLeg=300, MaxNetworkLevel=15, MagicWalletMaxDeposit=1B, MagicWalletMaxCredit=2.5B 2. Backfills PackageId=1 on all existing commission/network records 3. Backfills ClubMembership First/Last fields from existing ActivatedAt FIXES: - ClubMembership First/Last fields now nullable (DateTime?, long?) for backward compat with existing records - UserPackagePurchaseConfiguration: .WithMany() → .WithMany(p => p.Purchases) to prevent shadow FK PackageId1 Build: 0 errors, 73 warnings --- .../Entities/Club/ClubMembership.cs | 12 +- .../UserPackagePurchaseConfiguration.cs | 2 +- ...25191322_AddPackageBasedSystem.Designer.cs | 5013 +++++++++++++++++ .../20260225191322_AddPackageBasedSystem.cs | 683 +++ .../ApplicationDbContextModelSnapshot.cs | 247 +- src/dbbkup/SeedGoldenPackageAndBackfill.sql | 90 + 6 files changed, 6033 insertions(+), 14 deletions(-) create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.Designer.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.cs create mode 100644 src/dbbkup/SeedGoldenPackageAndBackfill.sql diff --git a/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs b/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs index e67754b..7f22f28 100644 --- a/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs +++ b/src/CMSMicroservice.Domain/Entities/Club/ClubMembership.cs @@ -25,34 +25,34 @@ public class ClubMembership : BaseAuditableEntity /// /// اولین فعال‌سازی — فقط یک بار ست می‌شود، هیچ‌وقت overwrite نمی‌شود /// - public DateTime FirstActivationDate { get; set; } + public DateTime? FirstActivationDate { get; set; } /// /// اولین پکیج خریداری‌شده — فقط یک بار ست می‌شود /// - public long FirstPackageId { get; set; } + public long? FirstPackageId { get; set; } /// /// FirstPackage Navigation Property /// - public virtual Package FirstPackage { get; set; } + public virtual Package? FirstPackage { get; set; } /// /// آخرین/جاری فعال‌سازی — هر خرید مجدد بروزرسانی می‌شود /// مبنای تشخیص "فعال‌شدگان این هفته" (Q22) /// - public DateTime LastActivationDate { get; set; } + public DateTime? LastActivationDate { get; set; } /// /// آخرین/جاری پکیج — هر خرید مجدد بروزرسانی می‌شود /// مبنای carryover per-package (Q23) /// - public long LastPackageId { get; set; } + public long? LastPackageId { get; set; } /// /// LastPackage Navigation Property /// - public virtual Package LastPackage { get; set; } + public virtual Package? LastPackage { get; set; } /// /// [DEPRECATED] تاریخ فعال‌سازی — جایگزین با FirstActivationDate + LastActivationDate diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserPackagePurchaseConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserPackagePurchaseConfiguration.cs index b3bfb8d..736fe02 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserPackagePurchaseConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserPackagePurchaseConfiguration.cs @@ -33,7 +33,7 @@ public class UserPackagePurchaseConfiguration : IEntityTypeConfiguration entity.Package) - .WithMany() + .WithMany(p => p.Purchases) .HasForeignKey(entity => entity.PackageId) .OnDelete(DeleteBehavior.Restrict); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.Designer.cs new file mode 100644 index 0000000..375251c --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.Designer.cs @@ -0,0 +1,5013 @@ +// +using System; +using CMSMicroservice.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260225191322_AddPackageBasedSystem")] + partial class AddPackageBasedSystem + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("CMS") + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_BlogCategories_IsActive"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogCategories_Slug"); + + b.ToTable("BlogCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsFeatured") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("ScheduledPublishAt") + .HasColumnType("datetime2"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId") + .HasDatabaseName("IX_BlogPosts_AuthorUserId"); + + b.HasIndex("IsFeatured") + .HasDatabaseName("IX_BlogPosts_IsFeatured"); + + b.HasIndex("PublishedAt") + .HasDatabaseName("IX_BlogPosts_PublishedAt"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogPosts_Slug"); + + b.HasIndex("Status") + .HasDatabaseName("IX_BlogPosts_Status"); + + b.HasIndex("Status", "PublishedAt") + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + + b.ToTable("BlogPosts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogCategoryId") + .HasColumnType("bigint"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogCategoryId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("Caption") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("TagId"); + + b.ToTable("BlogPostTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive", "SortOrder") + .HasDatabaseName("IX_ClubFeature_IsActive_SortOrder"); + + b.ToTable("ClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstActivationDate") + .HasColumnType("datetime2"); + + b.Property("FirstPackageId") + .HasColumnType("bigint"); + + b.Property("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastActivationDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastPackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FirstPackageId"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("LastActivationDate") + .HasDatabaseName("IX_ClubMembership_LastActivationDate"); + + b.HasIndex("LastPackageId"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CycleNumber") + .HasColumnType("int"); + + b.Property("IsCurrentCycle") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("MagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("PackageAmount") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PackagePurchasedAt") + .HasColumnType("datetime2"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubMembershipId"); + + b.HasIndex("PackageId"); + + b.HasIndex("PackagePurchasedAt") + .HasDatabaseName("IX_ClubMembershipCycle_PackagePurchasedAt"); + + b.HasIndex("UserId", "IsCurrentCycle") + .HasDatabaseName("IX_ClubMembershipCycle_UserId_IsCurrentCycle"); + + b.ToTable("ClubMembershipCycles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_UserClubFeature_ClubMembershipId"); + + b.HasIndex("UserId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_UserClubFeature_UserId_ClubFeatureId"); + + b.ToTable("UserClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BalancesEarned") + .HasColumnType("int"); + + b.Property("BankReferenceId") + .HasColumnType("nvarchar(max)"); + + b.Property("BankTrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IbanNumber") + .HasMaxLength(26) + .HasColumnType("nvarchar(26)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("PaymentFailureReason") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedAt") + .HasColumnType("datetime2"); + + b.Property("ProcessedBy") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolId") + .HasColumnType("bigint"); + + b.Property("WithdrawalMethod") + .HasColumnType("int"); + + b.Property("WithdrawnAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package"); + + b.ToTable("UserCommissionPayouts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCalculated") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalPoolAmount") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsCalculated") + .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated"); + + b.HasIndex("PackageId"); + + b.HasIndex("WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package"); + + b.ToTable("WeeklyCommissionPools", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Details") + .HasColumnType("nvarchar(max)"); + + b.Property("DurationMs") + .HasColumnType("bigint"); + + b.Property("ErrorCount") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ErrorStackTrace") + .HasColumnType("nvarchar(max)"); + + b.Property("ExecutionId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedCount") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StartedAt"); + + b.HasIndex("Status"); + + b.HasIndex("WeekDefinitionId"); + + b.ToTable("WorkerExecutionLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.AppVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AppName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MinRequiredVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("RequiresFullCacheClear") + .HasColumnType("bit"); + + b.Property("UpdateMessage") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AppVersions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePages_PageKey"); + + b.ToTable("SitePages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImageGroup") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("SitePageSettingsId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageSettingsId", "ImageGroup") + .HasDatabaseName("IX_SitePageImages_SettingsId_Group"); + + b.ToTable("SitePageImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtraData") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SectionKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SitePageId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageId", "SectionKey") + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + + b.ToTable("SitePageSections", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SettingsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePageSettings_PageKey"); + + b.ToTable("SitePageSettings", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Contracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsProcessed") + .HasColumnType("bit"); + + b.Property("LastCheckDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NationalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("DayaLoanContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentCategoryId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("DiscountCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("DiscountBalanceUsed") + .HasColumnType("bigint"); + + b.Property("GatewayAmountPaid") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("TrackingCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VatAmount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("DiscountOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountAmount") + .HasColumnType("bigint"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("DiscountPercentUsed") + .HasColumnType("int"); + + b.Property("FinalPrice") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DiscountOrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("DiscountOrderDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxDiscountPercent") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DiscountProducts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId", "CategoryId") + .IsUnique(); + + b.ToTable("DiscountProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId"); + + b.HasIndex("DiscountProductId", "SortOrder"); + + b.ToTable("DiscountProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId", "ProductId") + .IsUnique(); + + b.ToTable("DiscountShoppingCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsChangePrice") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitDiscount") + .HasColumnType("int"); + + b.Property("UnitDiscountPrice") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("FactorDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StateId") + .HasDatabaseName("IX_Cities_StateId"); + + b.ToTable("Cities", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Capital") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("CurrencyName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CurrencySymbol") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Emoji") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("EmojiU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PhoneCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Subregion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Tld") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.HasKey("Id"); + + b.ToTable("Countries", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CountryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId") + .HasDatabaseName("IX_States_CountryId"); + + b.ToTable("States", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewInitialContribution") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("OldInitialContribution") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipHistory_Action"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_ClubMembershipHistory_ClubMembershipId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipHistory_UserId_Created"); + + b.ToTable("ClubMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("AmountAfter") + .HasColumnType("bigint"); + + b.Property("AmountBefore") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("OldStatus") + .HasColumnType("int"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserCommissionPayoutId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_CommissionPayoutHistory_Action"); + + b.HasIndex("UserCommissionPayoutId") + .HasDatabaseName("IX_CommissionPayoutHistory_PayoutId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_CommissionPayoutHistory_WeekDefinitionId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created"); + + b.ToTable("CommissionPayoutHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.NetworkMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewLegPosition") + .HasColumnType("int"); + + b.Property("NewParentId") + .HasColumnType("bigint"); + + b.Property("OldLegPosition") + .HasColumnType("int"); + + b.Property("OldParentId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_NetworkMembershipHistory_Action"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_NetworkMembershipHistory_UserId_Created"); + + b.ToTable("NetworkMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastRestockedAt") + .HasColumnType("datetime2"); + + b.Property("LastSoldAt") + .HasColumnType("datetime2"); + + b.Property("LowStockThreshold") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("MaxStockLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1000); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductType") + .HasColumnType("int"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ReorderPoint") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(5); + + b.Property("ReservedQuantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1L); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId") + .HasDatabaseName("IX_InventoryItems_DiscountProductId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_InventoryItems_ProductId"); + + b.HasIndex("WarehouseId") + .HasDatabaseName("IX_InventoryItems_WarehouseId"); + + b.HasIndex("ProductType", "Quantity") + .HasDatabaseName("IX_InventoryItems_ProductType_Quantity"); + + b.ToTable("InventoryItems", "CMS", t => + { + t.HasCheckConstraint("CK_InventoryItem_ProductReference", "(ProductId IS NOT NULL AND DiscountProductId IS NULL) OR (ProductId IS NULL AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_ProductType_Match", "(ProductType = 1 AND ProductId IS NOT NULL) OR (ProductType = 2 AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_Quantity_NonNegative", "Quantity >= 0"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_LessOrEqualQuantity", "ReservedQuantity <= Quantity"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_NonNegative", "ReservedQuantity >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FlushedPerSide") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsExpired") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LeftLegBalances") + .HasColumnType("int"); + + b.Property("LeftLegCarryover") + .HasColumnType("int"); + + b.Property("LeftLegNewMembers") + .HasColumnType("int"); + + b.Property("LeftLegRemainder") + .HasColumnType("int"); + + b.Property("LeftLegTotal") + .HasColumnType("int"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("RightLegBalances") + .HasColumnType("int"); + + b.Property("RightLegCarryover") + .HasColumnType("int"); + + b.Property("RightLegNewMembers") + .HasColumnType("int"); + + b.Property("RightLegRemainder") + .HasColumnType("int"); + + b.Property("RightLegTotal") + .HasColumnType("int"); + + b.Property("SubordinateBalances") + .HasColumnType("int"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalFlushed") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolContribution") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsExpired") + .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); + + b.HasIndex("PackageId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package"); + + b.ToTable("NetworkWeeklyBalances", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BaseAmount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsPaid") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("VATAmount") + .HasColumnType("bigint"); + + b.Property("VATRate") + .HasColumnType("decimal(5,4)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_OrderVATs_Created"); + + b.HasIndex("IsPaid") + .HasDatabaseName("IX_OrderVATs_IsPaid"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderVATs_OrderId"); + + b.ToTable("OrderVATs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.OtpToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Attempts") + .HasColumnType("int"); + + b.Property("CodeHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Purpose") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OtpTokens", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivationFee") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.0m); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsBasePackage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicWalletMaxCredit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(2500000000L); + + b.Property("MagicWalletMaxDeposit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1000000000L); + + b.Property("MagicWalletMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.5m); + + b.Property("MaxBalancesPerLeg") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(300); + + b.Property("MaxNetworkLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(15); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("SupportsDayaPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SupportsDirectPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Package_IsActive"); + + b.HasIndex("SortOrder") + .HasDatabaseName("IX_Package_SortOrder"); + + b.ToTable("Packages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncluded") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("PackageId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId"); + + b.ToTable("PackageFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedBy") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImageDocumentId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("Created"); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Status"); + + b.ToTable("ManualPayments", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.PaymentTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Authority") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CallbackUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("CardHash") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CardPan") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("GatewayProvider") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MerchantId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("OrderId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PaymentStatus") + .HasColumnType("bit"); + + b.Property("RefId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RequestStatusCode") + .HasColumnType("int"); + + b.Property("RequestStatusMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VerificationStatusCode") + .HasColumnType("int"); + + b.Property("VerificationStatusMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("Authority"); + + b.HasIndex("GatewayProvider"); + + b.HasIndex("RefId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("PaymentTransactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubDiscountPercent") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Discount") + .HasColumnType("int"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsClubExclusive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IsClubExclusive") + .HasDatabaseName("IX_Products_IsClubExclusive"); + + b.ToTable("Products", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductImageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("ProductImageId"); + + b.ToTable("ProductGalleries", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("TagId"); + + b.ToTable("ProductTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PublicMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArchivedAt") + .HasColumnType("datetime2"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedByUserId") + .HasColumnType("bigint"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsArchived") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkText") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId") + .HasDatabaseName("IX_PublicMessages_CreatedByUserId"); + + b.HasIndex("ExpiresAt") + .HasDatabaseName("IX_PublicMessages_ExpiresAt"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_PublicMessages_IsActive"); + + b.HasIndex("Priority") + .HasDatabaseName("IX_PublicMessages_Priority"); + + b.HasIndex("StartsAt") + .HasDatabaseName("IX_PublicMessages_StartsAt"); + + b.HasIndex("Type") + .HasDatabaseName("IX_PublicMessages_Type"); + + b.HasIndex("IsActive", "ExpiresAt") + .HasDatabaseName("IX_PublicMessages_IsActive_ExpiresAt"); + + b.ToTable("PublicMessages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("InventoryItemId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MovementType") + .HasColumnType("int"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PerformedByUserId") + .HasColumnType("bigint"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityAfter") + .HasColumnType("int"); + + b.Property("QuantityBefore") + .HasColumnType("int"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_StockMovements_Created"); + + b.HasIndex("DiscountOrderId") + .HasDatabaseName("IX_StockMovements_DiscountOrderId") + .HasFilter("[DiscountOrderId] IS NOT NULL"); + + b.HasIndex("InventoryItemId") + .HasDatabaseName("IX_StockMovements_InventoryItemId"); + + b.HasIndex("MovementType") + .HasDatabaseName("IX_StockMovements_MovementType"); + + b.HasIndex("OrderId") + .HasDatabaseName("IX_StockMovements_OrderId") + .HasFilter("[OrderId] IS NOT NULL"); + + b.HasIndex("ReferenceNumber") + .HasDatabaseName("IX_StockMovements_ReferenceNumber") + .HasFilter("[ReferenceNumber] IS NOT NULL"); + + b.HasIndex("InventoryItemId", "MovementType", "Created") + .HasDatabaseName("IX_StockMovements_Item_Type_Date"); + + b.ToTable("StockMovements", "CMS", t => + { + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_Calculation", "QuantityAfter = QuantityBefore + Quantity"); + + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_NonNegative", "QuantityAfter >= 0"); + + t.HasCheckConstraint("CK_StockMovement_QuantityBefore_NonNegative", "QuantityBefore >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("RefId") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Transactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AvatarPath") + .HasColumnType("nvarchar(max)"); + + b.Property("BirthDate") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DayaCreditReceivedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailNotifications") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("HasReceivedDayaCredit") + .HasColumnType("bit"); + + b.Property("HashPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsMobileVerified") + .HasColumnType("bit"); + + b.Property("IsRulesAccepted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LegPosition") + .HasColumnType("int"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("MobileVerifiedAt") + .HasColumnType("datetime2"); + + b.Property("NationalCode") + .HasColumnType("nvarchar(max)"); + + b.Property("NetworkParentId") + .HasColumnType("bigint"); + + b.Property("PackagePurchaseMethod") + .HasColumnType("int"); + + b.Property("PushNotifications") + .HasColumnType("bit"); + + b.Property("ReferralCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RulesAcceptedAt") + .HasColumnType("datetime2"); + + b.Property("SmsNotifications") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("LegPosition") + .HasDatabaseName("IX_User_LegPosition"); + + b.HasIndex("NetworkParentId") + .HasDatabaseName("IX_User_NetworkParentId"); + + b.ToTable("Users", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CityId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserAddresses", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("UserCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SignGuid") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SignedPdfFile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("UserId"); + + b.ToTable("UserContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("HasVAT") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("PurchasedAt") + .HasColumnType("datetime2"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("PackageId") + .HasDatabaseName("IX_UserPackagePurchase_PackageId"); + + b.HasIndex("PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_PurchasedAt"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_UserPackagePurchase_UserId"); + + b.HasIndex("UserId", "PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_UserId_PurchasedAt"); + + b.ToTable("UserPackagePurchases", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("UserRoles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicActivatedAt") + .HasColumnType("datetime2"); + + b.Property("MagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("MagicTotalCredited") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("MagicTotalDeposited") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WalletMode") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserWallets", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangeDiscountValue") + .HasColumnType("bigint"); + + b.Property("ChangeNerworkValue") + .HasColumnType("bigint"); + + b.Property("ChangeValue") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentBalance") + .HasColumnType("bigint"); + + b.Property("CurrentDiscountBalance") + .HasColumnType("bigint"); + + b.Property("CurrentNetworkBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncrease") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("WalletId"); + + b.ToTable("UserWalletChangeLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("IX_Warehouses_Code_Unique"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Warehouses_IsActive"); + + b.HasIndex("IsDefault") + .HasDatabaseName("IX_Warehouses_IsDefault") + .HasFilter("[IsDefault] = 1"); + + b.ToTable("Warehouses", "CMS"); + + b.HasData( + new + { + Id = 1L, + Address = "تهران - انبار مرکزی فروشگاه", + Code = "WH-001", + Created = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + CreatedBy = "System", + IsActive = true, + IsDefault = true, + IsDeleted = false, + Name = "انبار اصلی" + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("GregorianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("GregorianYear") + .HasColumnType("int"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PersianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("WeekOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GregorianWeekNumber") + .IsUnique() + .HasDatabaseName("IX_WeekDefinition_GregorianWeekNumber"); + + b.HasIndex("GregorianYear") + .HasDatabaseName("IX_WeekDefinition_GregorianYear"); + + b.HasIndex("PersianWeekNumber") + .HasDatabaseName("IX_WeekDefinition_PersianWeekNumber"); + + b.HasIndex("PersianYear") + .HasDatabaseName("IX_WeekDefinition_PersianYear"); + + b.HasIndex("StartDate") + .HasDatabaseName("IX_WeekDefinition_StartDate"); + + b.ToTable("WeekDefinitions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogCategory", "BlogCategory") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogCategory"); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostImages") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostTags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent") + .WithMany("Categories") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "FirstPackage") + .WithMany() + .HasForeignKey("FirstPackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "LastPackage") + .WithMany() + .HasForeignKey("LastPackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FirstPackage"); + + b.Navigation("LastPackage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("Cycles") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + + b.Navigation("Package"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserClubFeatures") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("ClubMembership"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("CommissionPayouts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", "WeeklyPool") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeeklyPoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WorkerExecutionLogs") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePageSettings", "SitePageSettings") + .WithMany("Images") + .HasForeignKey("SitePageSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePageSettings"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePage", "SitePage") + .WithMany("Sections") + .HasForeignKey("SitePageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DayaLoanContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany() + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", "DiscountOrder") + .WithMany("OrderDetails") + .HasForeignKey("DiscountOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("OrderDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountOrder"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany("Images") + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DiscountProduct"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ShoppingCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountShoppingCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany("FactorDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("FactorDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.State", "State") + .WithMany("Cities") + .HasForeignKey("StateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("State"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.Country", "Country") + .WithMany("States") + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("ClubMembershipHistories") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", "UserCommissionPayout") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("UserCommissionPayoutId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("UserCommissionPayout"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany() + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Warehouse", "Warehouse") + .WithMany("InventoryItems") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountProduct"); + + b.Navigation("Product"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne("OrderVAT") + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany() + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("PackageFeatures") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("Package"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductGalleries") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.ProductImage", "ProductImage") + .WithMany("ProductGalleries") + .HasForeignKey("ProductImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("ProductImage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductTags") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany("ProductTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.InventoryItem", "InventoryItem") + .WithMany("StockMovements") + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InventoryItem"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "NetworkParent") + .WithMany("NetworkChildren") + .HasForeignKey("NetworkParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("NetworkParent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserAddresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("UserCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Contract", "Contract") + .WithMany("UserContracts") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("UserOrders") + .HasForeignKey("PackageId"); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany("UserOrders") + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany("UserOrders") + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("Purchases") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Role", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserWallets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Navigation("BlogPostCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Navigation("BlogPostCategories"); + + b.Navigation("BlogPostImages"); + + b.Navigation("BlogPostTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Navigation("Categories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Navigation("ClubMembershipHistories"); + + b.Navigation("Cycles"); + + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Navigation("CommissionPayoutHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Navigation("UserCommissionPayouts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Navigation("Sections"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSettings", b => + { + b.Navigation("Images"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Navigation("UserContracts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Navigation("ChildCategories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Navigation("Images"); + + b.Navigation("OrderDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ShoppingCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Navigation("States"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Navigation("Cities"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Navigation("StockMovements"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Navigation("PackageFeatures"); + + b.Navigation("Purchases"); + + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ProductGalleries"); + + b.Navigation("ProductTags"); + + b.Navigation("UserCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Navigation("ProductGalleries"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Navigation("ProductTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Navigation("ClubMembership"); + + b.Navigation("CommissionPayouts"); + + b.Navigation("DayaLoanContracts"); + + b.Navigation("DiscountOrders"); + + b.Navigation("DiscountShoppingCarts"); + + b.Navigation("NetworkChildren"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserAddresses"); + + b.Navigation("UserCarts"); + + b.Navigation("UserClubFeatures"); + + b.Navigation("UserContracts"); + + b.Navigation("UserOrders"); + + b.Navigation("UserRoles"); + + b.Navigation("UserWallets"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("OrderVAT"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Navigation("UserWalletChangeLogs"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Navigation("CommissionPayoutHistories"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserCommissionPayouts"); + + b.Navigation("WeeklyCommissionPools"); + + b.Navigation("WorkerExecutionLogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.cs new file mode 100644 index 0000000..fbb6205 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260225191322_AddPackageBasedSystem.cs @@ -0,0 +1,683 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddPackageBasedSystem : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_WeeklyCommissionPool_WeekDefinitionId", + schema: "CMS", + table: "WeeklyCommissionPools"); + + migrationBuilder.DropIndex( + name: "IX_UserCommissionPayout_UserId_WeekDefinitionId", + schema: "CMS", + table: "UserCommissionPayouts"); + + migrationBuilder.DropIndex( + name: "IX_NetworkWeeklyBalance_UserId_WeekDefinitionId", + schema: "CMS", + table: "NetworkWeeklyBalances"); + + migrationBuilder.AddColumn( + name: "PackageId", + schema: "CMS", + table: "WeeklyCommissionPools", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "PackageId", + schema: "CMS", + table: "UserWalletChangeLogs", + type: "bigint", + nullable: true); + + migrationBuilder.AddColumn( + name: "PackageId", + schema: "CMS", + table: "UserCommissionPayouts", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "ActivationFee", + schema: "CMS", + table: "Packages", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "DiscountMultiplier", + schema: "CMS", + table: "Packages", + type: "decimal(18,4)", + precision: 18, + scale: 4, + nullable: false, + defaultValue: 2.0m); + + migrationBuilder.AddColumn( + name: "IsActive", + schema: "CMS", + table: "Packages", + type: "bit", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "IsBasePackage", + schema: "CMS", + table: "Packages", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "MagicWalletMaxCredit", + schema: "CMS", + table: "Packages", + type: "bigint", + nullable: false, + defaultValue: 2500000000L); + + migrationBuilder.AddColumn( + name: "MagicWalletMaxDeposit", + schema: "CMS", + table: "Packages", + type: "bigint", + nullable: false, + defaultValue: 1000000000L); + + migrationBuilder.AddColumn( + name: "MagicWalletMultiplier", + schema: "CMS", + table: "Packages", + type: "decimal(18,4)", + precision: 18, + scale: 4, + nullable: false, + defaultValue: 2.5m); + + migrationBuilder.AddColumn( + name: "MaxBalancesPerLeg", + schema: "CMS", + table: "Packages", + type: "int", + nullable: false, + defaultValue: 300); + + migrationBuilder.AddColumn( + name: "MaxNetworkLevel", + schema: "CMS", + table: "Packages", + type: "int", + nullable: false, + defaultValue: 15); + + migrationBuilder.AddColumn( + name: "SortOrder", + schema: "CMS", + table: "Packages", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "SupportsDayaPurchase", + schema: "CMS", + table: "Packages", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "SupportsDirectPurchase", + schema: "CMS", + table: "Packages", + type: "bit", + nullable: false, + defaultValue: true); + + migrationBuilder.AddColumn( + name: "PackageId", + schema: "CMS", + table: "NetworkWeeklyBalances", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "FirstActivationDate", + schema: "CMS", + table: "ClubMemberships", + type: "datetime2", + nullable: true); + + migrationBuilder.AddColumn( + name: "FirstPackageId", + schema: "CMS", + table: "ClubMemberships", + type: "bigint", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastActivationDate", + schema: "CMS", + table: "ClubMemberships", + type: "datetime2", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastPackageId", + schema: "CMS", + table: "ClubMemberships", + type: "bigint", + nullable: true); + + migrationBuilder.AddColumn( + name: "PackageId", + schema: "CMS", + table: "ClubMembershipCycles", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.CreateTable( + name: "PackageFeatures", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + PackageId = table.Column(type: "bigint", nullable: false), + ClubFeatureId = table.Column(type: "bigint", nullable: false), + IsIncluded = table.Column(type: "bit", nullable: false, defaultValue: true), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageFeatures", x => x.Id); + table.ForeignKey( + name: "FK_PackageFeatures_ClubFeatures_ClubFeatureId", + column: x => x.ClubFeatureId, + principalSchema: "CMS", + principalTable: "ClubFeatures", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_PackageFeatures_Packages_PackageId", + column: x => x.PackageId, + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_WeeklyCommissionPool_WeekDef_Package", + schema: "CMS", + table: "WeeklyCommissionPools", + columns: new[] { "WeekDefinitionId", "PackageId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_WeeklyCommissionPools_PackageId", + schema: "CMS", + table: "WeeklyCommissionPools", + column: "PackageId"); + + migrationBuilder.CreateIndex( + name: "IX_UserWalletChangeLogs_PackageId", + schema: "CMS", + table: "UserWalletChangeLogs", + column: "PackageId"); + + migrationBuilder.CreateIndex( + name: "IX_UserCommissionPayout_User_WeekDef_Package", + schema: "CMS", + table: "UserCommissionPayouts", + columns: new[] { "UserId", "WeekDefinitionId", "PackageId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserCommissionPayouts_PackageId", + schema: "CMS", + table: "UserCommissionPayouts", + column: "PackageId"); + + migrationBuilder.CreateIndex( + name: "IX_Package_IsActive", + schema: "CMS", + table: "Packages", + column: "IsActive"); + + migrationBuilder.CreateIndex( + name: "IX_Package_SortOrder", + schema: "CMS", + table: "Packages", + column: "SortOrder"); + + migrationBuilder.CreateIndex( + name: "IX_NetworkWeeklyBalance_User_WeekDef_Package", + schema: "CMS", + table: "NetworkWeeklyBalances", + columns: new[] { "UserId", "WeekDefinitionId", "PackageId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_NetworkWeeklyBalances_PackageId", + schema: "CMS", + table: "NetworkWeeklyBalances", + column: "PackageId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubMembership_LastActivationDate", + schema: "CMS", + table: "ClubMemberships", + column: "LastActivationDate"); + + migrationBuilder.CreateIndex( + name: "IX_ClubMemberships_FirstPackageId", + schema: "CMS", + table: "ClubMemberships", + column: "FirstPackageId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubMemberships_LastPackageId", + schema: "CMS", + table: "ClubMemberships", + column: "LastPackageId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubMembershipCycles_PackageId", + schema: "CMS", + table: "ClubMembershipCycles", + column: "PackageId"); + + migrationBuilder.CreateIndex( + name: "IX_PackageFeature_PackageId_ClubFeatureId", + schema: "CMS", + table: "PackageFeatures", + columns: new[] { "PackageId", "ClubFeatureId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PackageFeatures_ClubFeatureId", + schema: "CMS", + table: "PackageFeatures", + column: "ClubFeatureId"); + + // === DATA MIGRATION: Seed golden package and backfill existing records === + + // 1. Ensure golden package exists with known Id + // Use IDENTITY_INSERT to guarantee Id=1 (if Package table uses identity) + migrationBuilder.Sql(@" + SET IDENTITY_INSERT [CMS].[Packages] ON; + + IF NOT EXISTS (SELECT 1 FROM [CMS].[Packages] WHERE [Id] = 1) + BEGIN + INSERT INTO [CMS].[Packages] + ([Id], [Title], [Description], [ImagePath], [Price], + [SortOrder], [IsActive], [IsBasePackage], + [SupportsDayaPurchase], [SupportsDirectPurchase], + [ActivationFee], [DiscountMultiplier], [MagicWalletMultiplier], + [MaxBalancesPerLeg], [MaxNetworkLevel], + [MagicWalletMaxDeposit], [MagicWalletMaxCredit], + [Created], [IsDeleted]) + VALUES + (1, N'پکیج طلایی', N'پکیج اصلی باشگاه مشتریان کارا بازار سلامت', + N'/images/packages/golden.png', 56000000, + 1, 1, 1, + 1, 1, + 25200000, 2.0, 2.5, + 300, 15, + 1000000000, 2500000000, + GETUTCDATE(), 0); + END + + SET IDENTITY_INSERT [CMS].[Packages] OFF; + "); + + // 2. Backfill PackageId on existing records → point to golden package (Id=1) + migrationBuilder.Sql(@" + UPDATE [CMS].[WeeklyCommissionPools] SET [PackageId] = 1 WHERE [PackageId] = 0; + UPDATE [CMS].[UserCommissionPayouts] SET [PackageId] = 1 WHERE [PackageId] = 0; + UPDATE [CMS].[NetworkWeeklyBalances] SET [PackageId] = 1 WHERE [PackageId] = 0; + UPDATE [CMS].[ClubMembershipCycles] SET [PackageId] = 1 WHERE [PackageId] = 0; + "); + + // 3. Backfill ClubMembership First/Last fields from existing ActivatedAt + migrationBuilder.Sql(@" + UPDATE cm SET + cm.[FirstActivationDate] = cm.[ActivatedAt], + cm.[LastActivationDate] = cm.[ActivatedAt], + cm.[FirstPackageId] = 1, + cm.[LastPackageId] = 1 + FROM [CMS].[ClubMemberships] cm + WHERE cm.[ActivatedAt] IS NOT NULL + AND cm.[FirstActivationDate] IS NULL; + "); + + // === END DATA MIGRATION === + + migrationBuilder.AddForeignKey( + name: "FK_ClubMembershipCycles_Packages_PackageId", + schema: "CMS", + table: "ClubMembershipCycles", + column: "PackageId", + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_ClubMemberships_Packages_FirstPackageId", + schema: "CMS", + table: "ClubMemberships", + column: "FirstPackageId", + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_ClubMemberships_Packages_LastPackageId", + schema: "CMS", + table: "ClubMemberships", + column: "LastPackageId", + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_NetworkWeeklyBalances_Packages_PackageId", + schema: "CMS", + table: "NetworkWeeklyBalances", + column: "PackageId", + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_UserCommissionPayouts_Packages_PackageId", + schema: "CMS", + table: "UserCommissionPayouts", + column: "PackageId", + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_UserWalletChangeLogs_Packages_PackageId", + schema: "CMS", + table: "UserWalletChangeLogs", + column: "PackageId", + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_WeeklyCommissionPools_Packages_PackageId", + schema: "CMS", + table: "WeeklyCommissionPools", + column: "PackageId", + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ClubMembershipCycles_Packages_PackageId", + schema: "CMS", + table: "ClubMembershipCycles"); + + migrationBuilder.DropForeignKey( + name: "FK_ClubMemberships_Packages_FirstPackageId", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropForeignKey( + name: "FK_ClubMemberships_Packages_LastPackageId", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropForeignKey( + name: "FK_NetworkWeeklyBalances_Packages_PackageId", + schema: "CMS", + table: "NetworkWeeklyBalances"); + + migrationBuilder.DropForeignKey( + name: "FK_UserCommissionPayouts_Packages_PackageId", + schema: "CMS", + table: "UserCommissionPayouts"); + + migrationBuilder.DropForeignKey( + name: "FK_UserWalletChangeLogs_Packages_PackageId", + schema: "CMS", + table: "UserWalletChangeLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_WeeklyCommissionPools_Packages_PackageId", + schema: "CMS", + table: "WeeklyCommissionPools"); + + migrationBuilder.DropTable( + name: "PackageFeatures", + schema: "CMS"); + + migrationBuilder.DropIndex( + name: "IX_WeeklyCommissionPool_WeekDef_Package", + schema: "CMS", + table: "WeeklyCommissionPools"); + + migrationBuilder.DropIndex( + name: "IX_WeeklyCommissionPools_PackageId", + schema: "CMS", + table: "WeeklyCommissionPools"); + + migrationBuilder.DropIndex( + name: "IX_UserWalletChangeLogs_PackageId", + schema: "CMS", + table: "UserWalletChangeLogs"); + + migrationBuilder.DropIndex( + name: "IX_UserCommissionPayout_User_WeekDef_Package", + schema: "CMS", + table: "UserCommissionPayouts"); + + migrationBuilder.DropIndex( + name: "IX_UserCommissionPayouts_PackageId", + schema: "CMS", + table: "UserCommissionPayouts"); + + migrationBuilder.DropIndex( + name: "IX_Package_IsActive", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropIndex( + name: "IX_Package_SortOrder", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropIndex( + name: "IX_NetworkWeeklyBalance_User_WeekDef_Package", + schema: "CMS", + table: "NetworkWeeklyBalances"); + + migrationBuilder.DropIndex( + name: "IX_NetworkWeeklyBalances_PackageId", + schema: "CMS", + table: "NetworkWeeklyBalances"); + + migrationBuilder.DropIndex( + name: "IX_ClubMembership_LastActivationDate", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropIndex( + name: "IX_ClubMemberships_FirstPackageId", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropIndex( + name: "IX_ClubMemberships_LastPackageId", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropIndex( + name: "IX_ClubMembershipCycles_PackageId", + schema: "CMS", + table: "ClubMembershipCycles"); + + migrationBuilder.DropColumn( + name: "PackageId", + schema: "CMS", + table: "WeeklyCommissionPools"); + + migrationBuilder.DropColumn( + name: "PackageId", + schema: "CMS", + table: "UserWalletChangeLogs"); + + migrationBuilder.DropColumn( + name: "PackageId", + schema: "CMS", + table: "UserCommissionPayouts"); + + migrationBuilder.DropColumn( + name: "ActivationFee", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "DiscountMultiplier", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "IsActive", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "IsBasePackage", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "MagicWalletMaxCredit", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "MagicWalletMaxDeposit", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "MagicWalletMultiplier", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "MaxBalancesPerLeg", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "MaxNetworkLevel", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "SortOrder", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "SupportsDayaPurchase", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "SupportsDirectPurchase", + schema: "CMS", + table: "Packages"); + + migrationBuilder.DropColumn( + name: "PackageId", + schema: "CMS", + table: "NetworkWeeklyBalances"); + + migrationBuilder.DropColumn( + name: "FirstActivationDate", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropColumn( + name: "FirstPackageId", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropColumn( + name: "LastActivationDate", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropColumn( + name: "LastPackageId", + schema: "CMS", + table: "ClubMemberships"); + + migrationBuilder.DropColumn( + name: "PackageId", + schema: "CMS", + table: "ClubMembershipCycles"); + + migrationBuilder.CreateIndex( + name: "IX_WeeklyCommissionPool_WeekDefinitionId", + schema: "CMS", + table: "WeeklyCommissionPools", + column: "WeekDefinitionId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserCommissionPayout_UserId_WeekDefinitionId", + schema: "CMS", + table: "UserCommissionPayouts", + columns: new[] { "UserId", "WeekDefinitionId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_NetworkWeeklyBalance_UserId_WeekDefinitionId", + schema: "CMS", + table: "NetworkWeeklyBalances", + columns: new[] { "UserId", "WeekDefinitionId" }, + unique: true); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 2edceac..2c65473 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -400,6 +400,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("CreatedBy") .HasColumnType("nvarchar(max)"); + b.Property("FirstActivationDate") + .HasColumnType("datetime2"); + + b.Property("FirstPackageId") + .HasColumnType("bigint"); + b.Property("GiftValue") .HasColumnType("bigint"); @@ -412,12 +418,18 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("IsDeleted") .HasColumnType("bit"); + b.Property("LastActivationDate") + .HasColumnType("datetime2"); + b.Property("LastModified") .HasColumnType("datetime2"); b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); + b.Property("LastPackageId") + .HasColumnType("bigint"); + b.Property("PurchaseMethod") .HasColumnType("int"); @@ -429,9 +441,16 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasKey("Id"); + b.HasIndex("FirstPackageId"); + b.HasIndex("IsActive") .HasDatabaseName("IX_ClubMembership_IsActive"); + b.HasIndex("LastActivationDate") + .HasDatabaseName("IX_ClubMembership_LastActivationDate"); + + b.HasIndex("LastPackageId"); + b.HasIndex("UserId") .IsUnique() .HasDatabaseName("IX_ClubMembership_UserId"); @@ -482,6 +501,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("PackageAmount") .HasColumnType("bigint"); + b.Property("PackageId") + .HasColumnType("bigint"); + b.Property("PackagePurchasedAt") .HasColumnType("datetime2"); @@ -495,6 +517,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasIndex("ClubMembershipId"); + b.HasIndex("PackageId"); + b.HasIndex("PackagePurchasedAt") .HasDatabaseName("IX_ClubMembershipCycle_PackagePurchasedAt"); @@ -598,6 +622,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); + b.Property("PackageId") + .HasColumnType("bigint"); + b.Property("PaidAt") .HasColumnType("datetime2"); @@ -641,6 +668,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasKey("Id"); + b.HasIndex("PackageId"); + b.HasIndex("Status") .HasDatabaseName("IX_UserCommissionPayout_Status"); @@ -649,9 +678,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasIndex("WeeklyPoolId") .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); - b.HasIndex("UserId", "WeekDefinitionId") + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") .IsUnique() - .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + .HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package"); b.ToTable("UserCommissionPayouts", "CMS"); }); @@ -685,6 +714,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); + b.Property("PackageId") + .HasColumnType("bigint"); + b.Property("TotalBalances") .HasColumnType("int"); @@ -702,9 +734,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasIndex("IsCalculated") .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated"); - b.HasIndex("WeekDefinitionId") + b.HasIndex("PackageId"); + + b.HasIndex("WeekDefinitionId", "PackageId") .IsUnique() - .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package"); b.ToTable("WeeklyCommissionPools", "CMS"); }); @@ -2242,6 +2276,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("LeftLegTotal") .HasColumnType("int"); + b.Property("PackageId") + .HasColumnType("bigint"); + b.Property("RightLegBalances") .HasColumnType("int"); @@ -2280,12 +2317,14 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasIndex("IsExpired") .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); + b.HasIndex("PackageId"); + b.HasIndex("WeekDefinitionId") .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); - b.HasIndex("UserId", "WeekDefinitionId") + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") .IsUnique() - .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + .HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package"); b.ToTable("NetworkWeeklyBalances", "CMS"); }); @@ -2412,6 +2451,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + b.Property("ActivationFee") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + b.Property("Created") .HasColumnType("datetime2"); @@ -2422,10 +2466,26 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("DiscountMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.0m); + b.Property("ImagePath") .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsBasePackage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("IsDeleted") .HasColumnType("bit"); @@ -2435,18 +2495,110 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); + b.Property("MagicWalletMaxCredit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(2500000000L); + + b.Property("MagicWalletMaxDeposit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1000000000L); + + b.Property("MagicWalletMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.5m); + + b.Property("MaxBalancesPerLeg") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(300); + + b.Property("MaxNetworkLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(15); + b.Property("Price") .HasColumnType("bigint"); + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("SupportsDayaPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SupportsDirectPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + b.Property("Title") .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); + b.HasIndex("IsActive") + .HasDatabaseName("IX_Package_IsActive"); + + b.HasIndex("SortOrder") + .HasDatabaseName("IX_Package_SortOrder"); + b.ToTable("Packages", "CMS"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncluded") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("PackageId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId"); + + b.ToTable("PackageFeatures", "CMS"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => { b.Property("Id") @@ -3718,6 +3870,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); + b.Property("PackageId") + .HasColumnType("bigint"); + b.Property("RefrenceId") .HasColumnType("bigint"); @@ -3726,6 +3881,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasKey("Id"); + b.HasIndex("PackageId"); + b.HasIndex("WalletId"); b.ToTable("UserWalletChangeLogs", "CMS"); @@ -3947,12 +4104,26 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "FirstPackage") + .WithMany() + .HasForeignKey("FirstPackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "LastPackage") + .WithMany() + .HasForeignKey("LastPackageId") + .OnDelete(DeleteBehavior.Restrict); + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") .WithOne("ClubMembership") .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.Navigation("FirstPackage"); + + b.Navigation("LastPackage"); + b.Navigation("User"); }); @@ -3964,6 +4135,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") .WithMany() .HasForeignKey("UserId") @@ -3972,6 +4149,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("ClubMembership"); + b.Navigation("Package"); + b.Navigation("User"); }); @@ -4004,6 +4183,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") .WithMany("CommissionPayouts") .HasForeignKey("UserId") @@ -4022,6 +4207,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.Navigation("Package"); + b.Navigation("User"); b.Navigation("WeekDefinition"); @@ -4031,12 +4218,20 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") .WithMany("WeeklyCommissionPools") .HasForeignKey("WeekDefinitionId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.Navigation("Package"); + b.Navigation("WeekDefinition"); }); @@ -4292,6 +4487,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") .WithMany("NetworkWeeklyBalances") .HasForeignKey("UserId") @@ -4304,6 +4505,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.Navigation("Package"); + b.Navigation("User"); b.Navigation("WeekDefinition"); @@ -4320,6 +4523,25 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("Order"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany() + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("PackageFeatures") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("Package"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => { b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") @@ -4504,7 +4726,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Restrict); b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") - .WithMany() + .WithMany("Purchases") .HasForeignKey("PackageId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); @@ -4561,12 +4783,19 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict); + b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet") .WithMany("UserWalletChangeLogs") .HasForeignKey("WalletId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Package"); + b.Navigation("Wallet"); }); @@ -4670,6 +4899,10 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => { + b.Navigation("PackageFeatures"); + + b.Navigation("Purchases"); + b.Navigation("UserOrders"); }); diff --git a/src/dbbkup/SeedGoldenPackageAndBackfill.sql b/src/dbbkup/SeedGoldenPackageAndBackfill.sql new file mode 100644 index 0000000..443fd4a --- /dev/null +++ b/src/dbbkup/SeedGoldenPackageAndBackfill.sql @@ -0,0 +1,90 @@ +-- ============================================= +-- Data Migration: Package-Based System +-- Date: 2026-02-25 +-- Description: Seeds golden package and backfills +-- existing records with PackageId = 1 +-- NOTE: This is automatically executed in the EF Migration +-- (AddPackageBasedSystem). This file is for reference only. +-- ============================================= + +USE [YourDatabaseName]; -- Replace with actual DB name +GO + +-- 1. Seed Golden Package (Id=1) +SET IDENTITY_INSERT [CMS].[Packages] ON; + +IF NOT EXISTS (SELECT 1 FROM [CMS].[Packages] WHERE [Id] = 1) +BEGIN + INSERT INTO [CMS].[Packages] + ([Id], [Title], [Description], [ImagePath], [Price], + [SortOrder], [IsActive], [IsBasePackage], + [SupportsDayaPurchase], [SupportsDirectPurchase], + [ActivationFee], [DiscountMultiplier], [MagicWalletMultiplier], + [MaxBalancesPerLeg], [MaxNetworkLevel], + [MagicWalletMaxDeposit], [MagicWalletMaxCredit], + [Created], [IsDeleted]) + VALUES + (1, N'پکیج طلایی', N'پکیج اصلی باشگاه مشتریان کارا بازار سلامت', + N'/images/packages/golden.png', 56000000, + 1, 1, 1, + 1, 1, + 25200000, 2.0, 2.5, + 300, 15, + 1000000000, 2500000000, + GETUTCDATE(), 0); + + PRINT 'Golden Package (Id=1) seeded successfully.'; +END +ELSE +BEGIN + PRINT 'Golden Package (Id=1) already exists. Skipping seed.'; +END + +SET IDENTITY_INSERT [CMS].[Packages] OFF; +GO + +-- 2. Backfill PackageId on commission/network tables +UPDATE [CMS].[WeeklyCommissionPools] SET [PackageId] = 1 WHERE [PackageId] = 0; +PRINT CONCAT('WeeklyCommissionPools updated: ', @@ROWCOUNT, ' rows'); + +UPDATE [CMS].[UserCommissionPayouts] SET [PackageId] = 1 WHERE [PackageId] = 0; +PRINT CONCAT('UserCommissionPayouts updated: ', @@ROWCOUNT, ' rows'); + +UPDATE [CMS].[NetworkWeeklyBalances] SET [PackageId] = 1 WHERE [PackageId] = 0; +PRINT CONCAT('NetworkWeeklyBalances updated: ', @@ROWCOUNT, ' rows'); + +UPDATE [CMS].[ClubMembershipCycles] SET [PackageId] = 1 WHERE [PackageId] = 0; +PRINT CONCAT('ClubMembershipCycles updated: ', @@ROWCOUNT, ' rows'); +GO + +-- 3. Backfill ClubMembership First/Last Activation from existing ActivatedAt +UPDATE cm SET + cm.[FirstActivationDate] = cm.[ActivatedAt], + cm.[LastActivationDate] = cm.[ActivatedAt], + cm.[FirstPackageId] = 1, + cm.[LastPackageId] = 1 +FROM [CMS].[ClubMemberships] cm +WHERE cm.[ActivatedAt] IS NOT NULL + AND cm.[FirstActivationDate] IS NULL; + +PRINT CONCAT('ClubMemberships backfilled: ', @@ROWCOUNT, ' rows'); +GO + +-- 4. Verification Queries +SELECT 'Packages' AS [Table], COUNT(*) AS [Total], + SUM(CASE WHEN [IsBasePackage] = 1 THEN 1 ELSE 0 END) AS [BasePackages] +FROM [CMS].[Packages] WHERE [IsDeleted] = 0; + +SELECT 'WeeklyCommissionPools' AS [Table], + COUNT(*) AS [Total], + SUM(CASE WHEN [PackageId] = 0 THEN 1 ELSE 0 END) AS [MissingPackageId] +FROM [CMS].[WeeklyCommissionPools] WHERE [IsDeleted] = 0; + +SELECT 'ClubMemberships' AS [Table], + COUNT(*) AS [Total], + SUM(CASE WHEN [FirstPackageId] IS NOT NULL THEN 1 ELSE 0 END) AS [WithFirstPackage], + SUM(CASE WHEN [LastActivationDate] IS NOT NULL THEN 1 ELSE 0 END) AS [WithLastActivation] +FROM [CMS].[ClubMemberships] WHERE [IsDeleted] = 0; + +PRINT 'Data migration completed successfully.'; +GO From 8e5c7c520586d16d8f0774a8644ec2d47fa5ccf5 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 25 Feb 2026 23:25:18 +0330 Subject: [PATCH 20/44] Phase 2: Replace all deprecated SystemConstants with Package entity reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ActivateClubMembership: Load Package, set First/Last fields, PackageId on Cycle+Pool - AcceptClubMembershipContract: Load Package, set First/Last fields, PackageId on Pool - CalculateWeeklyBalances: Load base package, use package.MaxBalancesPerLeg/MaxNetworkLevel, set PackageId on NetworkWeeklyBalance - ProcessUserPayouts: Load base package, use package.MaxNetworkLevel, set PackageId on UserCommissionPayout - CheckAndProcessDayaLoans: Load base package (IsBasePackage), replace DayaLoanAmount→package.Price, fix hardcoded p.Id==4 - CreateManualPayment: Load base package, replace BasePackageAmount→package.Price, use DiscountMultiplier - InitiateBasePackagePayment: Remove hardcoded BasePackageId=4, load from IsBasePackage, use package.Price/Id - VerifyBasePackagePayment: Load Package from order.PackageId, use package.Price+DiscountMultiplier - ChargeMagicWallet: Load user's package from current cycle, use package.MagicWalletMaxDeposit - VerifyMagicWalletCharge: Load user's package, use package.MagicWalletMultiplier/MagicWalletMaxDeposit - OrmCommissionCalculationStrategy: Load base package in both methods, set PackageId on Balance+Payout - ConfigurationService: Inject IApplicationDbContext, read package values from DB instead of SystemConstants - UserOrderService: Load user's package for EXIT trigger MagicWalletMaxDeposit check - UserWalletService: Load user's package for GetMagicWalletStatus display All 9 deprecated SystemConstants now have ZERO usages outside SystemConstants.cs Build: 0 errors, 521 warnings (all pre-existing) --- ...eptClubMembershipContractCommandHandler.cs | 31 ++++++-- .../ActivateClubMembershipCommandHandler.cs | 66 +++++++++++------ .../CalculateWeeklyBalancesCommandHandler.cs | 14 ++-- .../ProcessUserPayoutsCommandHandler.cs | 8 ++- .../CheckAndProcessDayaLoansCommandHandler.cs | 44 ++++++------ .../CreateManualPaymentCommandHandler.cs | 10 ++- ...nitiateBasePackagePaymentCommandHandler.cs | 24 ++++--- .../VerifyBasePackagePaymentCommandHandler.cs | 18 +++-- .../ChargeMagicWalletCommandHandler.cs | 11 ++- .../VerifyMagicWalletChargeCommandHandler.cs | 19 +++-- .../OrmCommissionCalculationStrategy.cs | 16 ++++- .../Services/ConfigurationService.cs | 72 +++++++++++-------- .../Services/UserOrderService.cs | 14 +++- .../Services/UserWalletService.cs | 12 +++- 14 files changed, 239 insertions(+), 120 deletions(-) diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs index b135fa0..5e403c8 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs @@ -135,13 +135,25 @@ public class AcceptClubMembershipContractCommandHandler }; await _context.UserContracts.AddAsync(userContract, cancellationToken); - // 6. دریافت مقادیر از SystemConstants (استاتیک) - long giftValue = SystemConstants.ClubMembershipGiftValue; - long activationFeeValue = SystemConstants.ClubActivationFee; + // 6. بارگذاری پکیج از آخرین خرید کاربر یا پکیج پایه + var latestPurchase = await _context.UserPackagePurchases + .Where(p => p.UserId == userId) + .OrderByDescending(p => p.PurchasedAt) + .FirstOrDefaultAsync(cancellationToken); + + var package = latestPurchase != null + ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == latestPurchase.PackageId && !p.IsDeleted, cancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + + if (package == null) + throw new NotFoundException("پکیج یافت نشد"); + + long giftValue = package.ActivationFee; + long activationFeeValue = package.ActivationFee; _logger.LogInformation( - "Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}", - giftValue, activationFeeValue + "Using Package {PackageId} ({PackageName}): GiftValue={GiftValue}, ActivationFee={ActivationFee}", + package.Id, package.Title, giftValue, activationFeeValue ); // 7. فعال‌سازی باشگاه مشتریان @@ -156,6 +168,10 @@ public class AcceptClubMembershipContractCommandHandler UserId = user.Id, IsActive = true, ActivatedAt = activationDate, + FirstActivationDate = activationDate, + FirstPackageId = package.Id, + LastActivationDate = activationDate, + LastPackageId = package.Id, InitialContribution = activationFeeValue, GiftValue = giftValue, TotalEarned = 0, @@ -175,6 +191,8 @@ public class AcceptClubMembershipContractCommandHandler clubMembership = user.ClubMembership!; clubMembership.IsActive = true; clubMembership.ActivatedAt = activationDate; + clubMembership.LastActivationDate = activationDate; + clubMembership.LastPackageId = package.Id; clubMembership.PurchaseMethod = user.PackagePurchaseMethod; _context.ClubMemberships.Update(clubMembership); @@ -206,13 +224,14 @@ public class AcceptClubMembershipContractCommandHandler // 9. اضافه کردن مبلغ به Pool هفته جاری var currentWeekDefinitionId = GetCurrentWeekDefinitionId(); var weeklyPool = await _context.WeeklyCommissionPools - .FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken); + .FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken); if (weeklyPool == null) { weeklyPool = new WeeklyCommissionPool { WeekDefinitionId = currentWeekDefinitionId, + PackageId = package.Id, TotalPoolAmount = activationFeeValue, TotalBalances = 0, ValuePerBalance = 0, diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index 20e1def..b4e8e84 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -54,6 +54,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler p.Id == packageOrder.PackageId && !p.IsDeleted, cancellationToken) + ?? throw new NotFoundException("پکیج یافت نشد"); + + // بررسی موجودی با مبلغ پکیج واقعی + if (wallet.Balance < package.Price) + { + throw new BadRequestException( + $"برای فعالسازی باشگاه مشتریان باید حداقل {package.Price:N0} ریال موجودی اصلی داشته باشید" + ); + } } else { @@ -161,19 +168,24 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new NotFoundException("پکیج پایه یافت نشد"); } // 6. بررسی عضویت فعلی var existingMembership = await _context.ClubMemberships .FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken); - // 6.1. دریافت مبلغ هدیه و هزینه فعالسازی از SystemConstants - long giftValue = SystemConstants.ClubMembershipGiftValue; - long activationFeeValue = SystemConstants.ClubActivationFee; + // 6.1. دریافت مبلغ هدیه و هزینه فعالسازی از پکیج + long giftValue = package.ActivationFee; + long activationFeeValue = package.ActivationFee; _logger.LogInformation( - "Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}", - giftValue, activationFeeValue + "Using Package {PackageId} ({PackageName}): GiftValue={GiftValue}, ActivationFee={ActivationFee}, Price={Price}", + package.Id, package.Title, giftValue, activationFeeValue, package.Price ); ClubMembership entity; @@ -188,8 +200,12 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken); + .FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken); if (weeklyPool == null) { - // ایجاد Pool جدید برای این هفته + // ایجاد Pool جدید برای این هفته × این پکیج weeklyPool = new WeeklyCommissionPool { WeekDefinitionId = currentWeekDefinitionId, - TotalPoolAmount = activationFeeValue, // مبلغ هدیه به Pool اضافه میشه + PackageId = package.Id, + TotalPoolAmount = activationFeeValue, TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه IsCalculated = false, diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs index b7b2a0a..eeae512 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs @@ -82,11 +82,14 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler(); var calculatedAt = DateTime.Now; - // استفاده از SystemConstants (استاتیک - بدون کوئری به دیتابیس) - // سقف تعادل هفتگی برای هر دست (نه کل) - 300 برای چپ + 300 برای راست = حداکثر 600 تعادل - var maxBalancesPerLeg = SystemConstants.CommissionMaxWeeklyBalancesPerLeg; - // حداکثر عمق شبکه برای شمارش اعضا (15 لول) - var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel; + // بارگذاری پکیج پایه برای خواندن سقف‌ها + var package = await _context.Packages + .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); + + // خواندن سقف‌ها از پکیج (قبلاً از SystemConstants بود) + var maxBalancesPerLeg = package.MaxBalancesPerLeg; + var maxNetworkLevel = package.MaxNetworkLevel; foreach (var user in usersInNetwork.OrderBy(o=>o.Id)) { @@ -140,6 +143,7 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); + var maxNetworkLevel = package.MaxNetworkLevel; // دریافت همه تعادل‌های هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه) var allWeeklyBalances = await _context.NetworkWeeklyBalances @@ -113,6 +116,7 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); + // 1. ایجاد تراکنش var transaction = new Transaction { - Amount = SystemConstants.DayaLoanAmount, + Amount = package.Price, Description = $"دریافت اعتبار دایا - قرارداد {contractNumber}", PaymentStatus = PaymentStatus.Success, PaymentDate = DateTime.Now, @@ -216,13 +221,13 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler p.Id == 4, cancellationToken); - - if (goldenPackage != null) + // 6. ثبت UserPackagePurchase برای پکیج پایه + var packagePurchase = new UserPackagePurchase { - // 6. ثبت UserPackagePurchase برای پکیج پایه - var goldenPackageId =goldenPackage.Id; - var packagePurchase = new UserPackagePurchase - { - UserId = user.Id, - PackageId = goldenPackageId, - PurchaseMethod = PackagePurchaseMethod.DayaLoan, - PurchasedAt = DateTime.Now, - Amount = SystemConstants.DayaLoanAmount, - TransactionId = transaction.Id - }; + UserId = user.Id, + PackageId = package.Id, + PurchaseMethod = PackagePurchaseMethod.DayaLoan, + PurchasedAt = DateTime.Now, + Amount = package.Price, + TransactionId = transaction.Id + }; - await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken); - - } + await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken); // 7. Domain Event user.AddDomainEvent(new DayaLoanApprovedEvent(user, transaction, contractNumber)); diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs index 2b6a7ae..9402612 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs @@ -70,9 +70,13 @@ public class CreateManualPaymentCommandHandler : IRequestHandler p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new NotFoundException("پکیج پایه یافت نشد"); + + var balanceAmount = package.Price; + var discountBalanceAmount = (long)(package.Price * package.DiscountMultiplier); // 5. ثبت تراکنش diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs index b921ab1..0fde9b1 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs @@ -20,10 +20,7 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler _logger; - /// - /// شناسه پکیج پایه در دیتابیس - /// - private const long BasePackageId = 4; + // شناسه پکیج پایه از دیتابیس خوانده می‌شود (Package.IsBasePackage) public InitiateBasePackagePaymentCommandHandler( IApplicationDbContext context, @@ -68,12 +65,17 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new NotFoundException("پکیج پایه یافت نشد"); + // 3. بررسی عدم وجود سفارش Pending var pendingOrder = await _context.UserOrders .FirstOrDefaultAsync(o => o.UserId == request.UserId && o.PaymentStatus == PaymentStatus.Pending && - o.PackageId == BasePackageId, + o.PackageId == package.Id, cancellationToken); if (pendingOrder != null) @@ -93,7 +95,7 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler p.Id == order.PackageId.Value, cancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + + if (package == null) + throw new NotFoundException("پکیج یافت نشد"); + // 6. شارژ کیف پول (هم Balance و هم DiscountBalance) var oldBalance = userWallet.Balance; var oldDiscountBalance = userWallet.DiscountBalance; - userWallet.Balance += SystemConstants.BasePackageAmount; - userWallet.DiscountBalance += SystemConstants.BasePackageAmount * 2; + userWallet.Balance += package.Price; + userWallet.DiscountBalance += (long)(package.Price * package.DiscountMultiplier); _logger.LogInformation( "Charging wallet for user {UserId}. Balance: {OldBalance} -> {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}", @@ -153,11 +161,11 @@ public class VerifyBasePackagePaymentCommandHandler : IRequestHandler c.UserId == request.UserId && c.IsCurrentCycle, cancellationToken); + var package = currentCycle != null + ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, cancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + if (package == null) + throw new NotFoundException("پکیج یافت نشد"); + // 4. بررسی سقف واریزی (per-cycle) - var remainingDeposit = SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited; + var remainingDeposit = package.MagicWalletMaxDeposit - wallet.MagicTotalDeposited; if (remainingDeposit <= 0) { diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs index 9bf541c..ab78b4e 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs @@ -121,8 +121,17 @@ public class VerifyMagicWalletChargeCommandHandler throw new BadRequestException("کیف‌پول در حالت جادویی نیست"); } - // 5. محاسبه اعتبار ×2.5 - var creditAmount = (long)(depositAmount * SystemConstants.MagicWalletMultiplier); // مبلغ × 2.5 + // 4.5. بارگذاری پکیج کاربر برای ضریب جادویی + var currentCycle = await _context.ClubMembershipCycles + .FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, cancellationToken); + var package = currentCycle != null + ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, cancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + if (package == null) + throw new NotFoundException("پکیج یافت نشد"); + + // 5. محاسبه اعتبار + var creditAmount = (long)(depositAmount * package.MagicWalletMultiplier); // مبلغ × 2.5 var bonusAmount = creditAmount - depositAmount; // بونوس = مبلغ × 1.5 // 6. ثبت تراکنش واریز واقعی (MagicWalletDeposit) @@ -143,7 +152,7 @@ public class VerifyMagicWalletChargeCommandHandler var bonusTransaction = new Transaction { Amount = bonusAmount, - Description = $"شارژ کیف‌پول جادویی - بونوس ×{SystemConstants.MagicWalletMultiplier - 1} - کاربر {userId}", + Description = $"شارژ کیف‌پول جادویی - بونوس ×{package.MagicWalletMultiplier - 1} - کاربر {userId}", PaymentStatus = PaymentStatus.Success, PaymentDate = DateTime.UtcNow, RefId = $"MAGIC_BONUS_{depositTransaction.Id}", @@ -186,10 +195,10 @@ public class VerifyMagicWalletChargeCommandHandler userId, depositAmount, creditAmount, - SystemConstants.MagicWalletMultiplier, + package.MagicWalletMultiplier, bonusAmount, wallet.MagicTotalDeposited, - SystemConstants.MagicWalletMaxDeposit, + package.MagicWalletMaxDeposit, wallet.Balance ); diff --git a/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs b/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs index 5db7dff..3a8faa6 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs @@ -94,9 +94,12 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy var balancesList = new List(); var calculatedAt = DateTime.Now; - // استفاده از SystemConstants به جای دیتابیس - var maxBalancesPerLeg = SystemConstants.CommissionMaxWeeklyBalancesPerLeg; - var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel; + // بارگذاری پکیج پایه برای خواندن سقف‌ها + var package = await _context.Packages + .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); + var maxBalancesPerLeg = package.MaxBalancesPerLeg; + var maxNetworkLevel = package.MaxNetworkLevel; foreach (var user in usersInNetwork.OrderBy(o => o.Id)) { @@ -135,6 +138,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy { UserId = user.Id, WeekDefinitionId = weekDefinitionId, + PackageId = package.Id, LeftLegNewMembers = leftNewMembers, RightLegNewMembers = rightNewMembers, LeftLegCarryover = leftCarryover, @@ -189,6 +193,11 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy bool forceRecalculate, CancellationToken cancellationToken = default) { + // بارگذاری پکیج پایه برای تعیین PackageId روی پرداخت‌ها + var package = await _context.Packages + .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); + var weekDefinition = _weekRepository.GetGregorianWeekNumber(weekDefinitionId); if (weekDefinition == null) { @@ -284,6 +293,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy UserId = balance.UserId, WeekDefinitionId = weekDefinitionId, WeeklyPoolId = existingPool.Id, + PackageId = package.Id, BalancesEarned = userBalance, ValuePerBalance = valuePerBalance, TotalAmount = totalAmount, diff --git a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs index b2e4c7f..f6f0856 100644 --- a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs +++ b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs @@ -2,37 +2,42 @@ using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures; using CMSMicroservice.Application.Common.Authorization; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Common; +using CMSMicroservice.Domain.Entities; using CMSMicroservice.Protobuf.Protos.Configuration; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using MediatR; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace CMSMicroservice.WebApi.Services; /// -/// سرویس تنظیمات سیستم - خواندن از SystemConstants +/// سرویس تنظیمات سیستم — مقادیر پکیجی از Package entity خوانده می‌شوند /// public class ConfigurationService : ConfigurationContract.ConfigurationContractBase { private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; private readonly IMediator _mediator; + private readonly IApplicationDbContext _context; public ConfigurationService( ILogger logger, ICurrentUserService currentUserService, - IMediator mediator) + IMediator mediator, + IApplicationDbContext context) { _logger = logger; _currentUserService = currentUserService; _mediator = mediator; + _context = context; } /// /// دریافت تنظیمات با کلید خاص /// - public override Task GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context) + public override async Task GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context) { var response = new GetConfigurationByKeyResponse { @@ -43,30 +48,33 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB LastModified = Timestamp.FromDateTime(DateTime.UtcNow) }; - // خواندن مقدار از SystemConstants بر اساس کلید - response.Value = GetConfigurationValue(request.Key); + // برای کلیدهای پکیجی، از دیتابیس می‌خوانیم + var package = await GetBasePackageAsync(context.CancellationToken); + response.Value = GetConfigurationValue(request.Key, package); response.Description = GetConfigurationDescription(request.Key); _logger.LogDebug("Configuration requested: Key={Key}, Value={Value}", request.Key, response.Value); - return Task.FromResult(response); + return response; } /// /// دریافت تنظیمات باشگاه مشتریان /// - public override Task GetClubConfiguration(Empty request, ServerCallContext context) + public override async Task GetClubConfiguration(Empty request, ServerCallContext context) { + var package = await GetBasePackageAsync(context.CancellationToken); + var response = new GetClubConfigurationResponse { - ActivationFee = SystemConstants.ClubActivationFee, - MembershipGiftValue = SystemConstants.ClubMembershipGiftValue + ActivationFee = package.ActivationFee, + MembershipGiftValue = package.ActivationFee }; _logger.LogDebug("Club configuration requested: ActivationFee={ActivationFee}, GiftValue={GiftValue}", response.ActivationFee, response.MembershipGiftValue); - return Task.FromResult(response); + return response; } /// @@ -111,18 +119,19 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB /// دریافت تمام تنظیمات /// [RequiresPermission(PermissionNames.SettingsView)] - public override Task GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context) + public override async Task GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context) { + var package = await GetBasePackageAsync(context.CancellationToken); var response = new GetAllConfigurationsResponse(); - // Club Settings - response.Models.Add(CreateConfigModel("Club.ActivationFee", SystemConstants.ClubActivationFee.ToString(), "هزینه فعال‌سازی عضویت باشگاه", 2)); - response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", SystemConstants.ClubMembershipGiftValue.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2)); + // Club Settings (از Package) + response.Models.Add(CreateConfigModel("Club.ActivationFee", package.ActivationFee.ToString(), "هزینه فعال‌سازی عضویت باشگاه", 2)); + response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", package.ActivationFee.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2)); - // Commission Settings + // Commission Settings (سقف‌ها از Package) response.Models.Add(CreateConfigModel("Commission.MinWithdrawalAmount", SystemConstants.CommissionMinWithdrawalAmount.ToString(), "حداقل مبلغ برداشت", 3)); - response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3)); - response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", SystemConstants.CommissionMaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3)); + response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", package.MaxBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3)); + response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", package.MaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3)); response.Models.Add(CreateConfigModel("Commission.CashWithdrawalEnabled", SystemConstants.CommissionCashWithdrawalEnabled.ToString(), "امکان برداشت نقدی", 3)); response.Models.Add(CreateConfigModel("Commission.CalculationStrategy", SystemConstants.CommissionCalculationStrategy, "روش محاسبه کمیسیون", 3)); @@ -130,9 +139,9 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB response.Models.Add(CreateConfigModel("Network.AllowOrphanNodes", SystemConstants.NetworkAllowOrphanNodes.ToString(), "اجازه حذف والدین با فرزند", 1)); response.Models.Add(CreateConfigModel("Network.MaxChildrenPerLeg", SystemConstants.NetworkMaxChildrenPerLeg.ToString(), "حداکثر تعداد فرزند مستقیم در هر پا", 1)); - // Package Settings - response.Models.Add(CreateConfigModel("Package.BasePackageAmount", SystemConstants.BasePackageAmount.ToString(), "مبلغ پکیج پایه", 0)); - response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", SystemConstants.DayaLoanAmount.ToString(), "مبلغ وام دایا", 0)); + // Package Settings (از Package) + response.Models.Add(CreateConfigModel("Package.BasePackageAmount", package.Price.ToString(), "مبلغ پکیج پایه", 0)); + response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", package.Price.ToString(), "مبلغ وام دایا", 0)); // System Settings response.Models.Add(CreateConfigModel("System.MaintenanceMode", SystemConstants.SystemMaintenanceMode.ToString(), "حالت تعمیر و نگهداری", 0)); @@ -141,7 +150,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB // Shop Settings response.Models.Add(CreateConfigModel("Shop.VAT", SystemConstants.ShopVAT.ToString(), "مالیات بر ارزش افزوده", 0)); - return Task.FromResult(response); + return response; } /// @@ -173,21 +182,21 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB #region Private Helpers - private static string GetConfigurationValue(string key) + private static string GetConfigurationValue(string key, Package? package) { return key switch { - "Club.ActivationFee" => SystemConstants.ClubActivationFee.ToString(), - "Club.MembershipGiftValue" => SystemConstants.ClubMembershipGiftValue.ToString(), + "Club.ActivationFee" => (package?.ActivationFee ?? 0).ToString(), + "Club.MembershipGiftValue" => (package?.ActivationFee ?? 0).ToString(), "Commission.MinWithdrawalAmount" => SystemConstants.CommissionMinWithdrawalAmount.ToString(), - "Commission.MaxWeeklyBalancesPerLeg" => SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(), - "Commission.MaxNetworkLevel" => SystemConstants.CommissionMaxNetworkLevel.ToString(), + "Commission.MaxWeeklyBalancesPerLeg" => (package?.MaxBalancesPerLeg ?? 0).ToString(), + "Commission.MaxNetworkLevel" => (package?.MaxNetworkLevel ?? 0).ToString(), "Commission.CashWithdrawalEnabled" => SystemConstants.CommissionCashWithdrawalEnabled.ToString(), "Commission.CalculationStrategy" => SystemConstants.CommissionCalculationStrategy, "Network.AllowOrphanNodes" => SystemConstants.NetworkAllowOrphanNodes.ToString(), "Network.MaxChildrenPerLeg" => SystemConstants.NetworkMaxChildrenPerLeg.ToString(), - "Package.BasePackageAmount" => SystemConstants.BasePackageAmount.ToString(), - "Package.DayaLoanAmount" => SystemConstants.DayaLoanAmount.ToString(), + "Package.BasePackageAmount" => (package?.Price ?? 0).ToString(), + "Package.DayaLoanAmount" => (package?.Price ?? 0).ToString(), "System.MaintenanceMode" => SystemConstants.SystemMaintenanceMode.ToString(), "System.EnableAuditLog" => SystemConstants.SystemEnableAuditLog.ToString(), "Shop.VAT" => SystemConstants.ShopVAT.ToString(), @@ -195,6 +204,13 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB }; } + private async Task GetBasePackageAsync(CancellationToken cancellationToken) + { + return await _context.Packages + .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) + ?? throw new RpcException(new Status(StatusCode.Internal, "پکیج پایه یافت نشد")); + } + private static string GetConfigurationDescription(string key) { return key switch diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index c55abcc..81eca8d 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -344,9 +344,18 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase .Include(u => u.ClubMembership) .FirstOrDefaultAsync(u => u.Id == userId, context.CancellationToken); - if (wallet.WalletMode == WalletMode.Magic - && wallet.MagicTotalDeposited >= SystemConstants.MagicWalletMaxDeposit) + if (wallet.WalletMode == WalletMode.Magic) { + // بارگذاری پکیج کاربر برای سقف کیف‌پول جادویی + var userCycle = await _context.ClubMembershipCycles + .FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken); + var userPackage = userCycle != null + ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == userCycle.PackageId, context.CancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken); + var magicMaxDeposit = userPackage?.MagicWalletMaxDeposit ?? 1_000_000_000; + + if (wallet.MagicTotalDeposited >= magicMaxDeposit) + { // ── EXIT Magic Mode ── // هر دو شرط: Balance=0 و سقف 100M پر شده wallet.WalletMode = WalletMode.Normal; @@ -366,6 +375,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase { user.PackagePurchaseMethod = PackagePurchaseMethod.None; } + } } else if (wallet.WalletMode == WalletMode.Normal && user != null diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs index f4760cc..5c8fc70 100644 --- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs @@ -227,13 +227,21 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase var cycleCount = await _context.ClubMembershipCycles .CountAsync(c => c.UserId == userId, context.CancellationToken); + // بارگذاری پکیج کاربر برای سقف کیف‌پول جادویی + var currentCycle = await _context.ClubMembershipCycles + .FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken); + var package = currentCycle != null + ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken); + var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? 1_000_000_000; + var response = new GetMagicWalletStatusResponse { WalletMode = (int)wallet.WalletMode, MagicTotalDeposited = wallet.MagicTotalDeposited, MagicTotalCredited = wallet.MagicTotalCredited, - MagicMaxDeposit = SystemConstants.MagicWalletMaxDeposit, - MagicRemainingDeposit = Math.Max(0, SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited), + MagicMaxDeposit = magicMaxDeposit, + MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - wallet.MagicTotalDeposited), Balance = wallet.Balance, PurchaseCycleCount = cycleCount }; From ccb938e9ba64cf5654d80ad9aedc2af103dabcb8 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 25 Feb 2026 23:50:09 +0330 Subject: [PATCH 21/44] =?UTF-8?q?Phase=203:=20Package=20layer=20overhaul?= =?UTF-8?q?=20=E2=80=94=20fix=20critical=20bugs=20+=20proto=20enhancement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proto (package.proto): - Add 11 new Package fields to Create/Update/Get/GetAll messages - Add 8 new fields to CustomerPackageModel for frontend Critical Bug Fixes: - VerifyGoldenPackagePurchase: Replace hardcoded ×2 with package.DiscountMultiplier from DB Query Fixes: - GetAllPackageByFilter: Add IsDeleted filter (was returning soft-deleted packages) - GetCustomerPackages: Apply IsDeleted+IncludeInactive filters, sort by SortOrder, return new fields - GetCustomerPackageDetails: Load PackageFeatures from DB (was hardcoded), add IsDeleted filter, return new fields - GetCustomerPurchaseHistory: Include Transaction (was null → ReferenceCode always empty) UpdatePackage: Add 12 new fields (SortOrder, IsActive, IsBasePackage, etc.) Build: 0 errors --- .../UpdatePackage/UpdatePackageCommand.cs | 14 ++- ...rifyGoldenPackagePurchaseCommandHandler.cs | 16 +++- .../GetAllPackageByFilterQueryHandler.cs | 1 + .../GetCustomerPackageDetailsQueryHandler.cs | 76 ++++++++++------- .../GetCustomerPackageDetailsResponseDto.cs | 9 ++ .../GetCustomerPackagesQueryHandler.cs | 62 ++++++++------ .../GetCustomerPackagesResponseDto.cs | 9 ++ .../GetCustomerPurchaseHistoryQueryHandler.cs | 1 + .../GetUserPackageStatusQueryHandler.cs | 85 ++++++++++--------- .../Protos/package.proto | 61 ++++++++++++- .../Services/PackageService.cs | 42 +++++++++ 11 files changed, 271 insertions(+), 105 deletions(-) diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs index caf5896..0c2da24 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs @@ -11,5 +11,17 @@ public record UpdatePackageCommand : IRequest public string ImagePath { get; init; } //قیمت public long Price { get; init; } - + // فیلدهای جدید پکیج + public int SortOrder { get; init; } + public bool IsActive { get; init; } + public bool IsBasePackage { get; init; } + public bool SupportsDayaPurchase { get; init; } + public bool SupportsDirectPurchase { get; init; } + public long ActivationFee { get; init; } + public double DiscountMultiplier { get; init; } + public double MagicWalletMultiplier { get; init; } + public int MaxBalancesPerLeg { get; init; } + public int MaxNetworkLevel { get; init; } + public long MagicWalletMaxDeposit { get; init; } + public long MagicWalletMaxCredit { get; init; } } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs index 7ce8bf0..08aa7ba 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs @@ -100,7 +100,14 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler p.Id == order.PackageId.Value, cancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + + var discountMultiplier = package?.DiscountMultiplier ?? 2.0m; + + // 5. شارژ کیف پول (Balance فقط طبق سناریوی پکیج) var wallet = await _context.UserWallets .FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken); @@ -113,9 +120,10 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}", @@ -163,7 +171,7 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler Handle(GetAllPackageByFilterQuery request, CancellationToken cancellationToken) { var query = _context.Packages + .Where(x => !x.IsDeleted) .ApplyOrder(sortBy: request.SortBy) .AsNoTracking() .AsQueryable(); diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs index b4122ea..2abf39a 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs @@ -18,44 +18,60 @@ public class GetCustomerPackageDetailsQueryHandler : IRequestHandler x.Id == request.PackageId) - .ProjectToType() - .FirstOrDefaultAsync(cancellationToken); + .Include(p => p.PackageFeatures) + .ThenInclude(pf => pf.ClubFeature) + .FirstOrDefaultAsync(x => x.Id == request.PackageId && !x.IsDeleted, cancellationToken); if (package == null) throw new NotFoundException(nameof(Package), request.PackageId); - // Add features based on package (this could be stored in DB in future) - package.Features = new List + var response = new GetCustomerPackageDetailsResponseDto { - new PackageFeatureDto - { - Title = "درآمد کمیسیون", - Description = "دریافت کمیسیون از فروش محصولات", - Icon = "commission", - IsHighlighted = true - }, - new PackageFeatureDto - { - Title = "پشتیبانی 24/7", - Description = "دسترسی به پشتیبانی در تمام ساعات شبانه روز", - Icon = "support", - IsHighlighted = false - }, - new PackageFeatureDto - { - Title = "آموزش‌های تخصصی", - Description = "دسترسی به دوره‌های آموزشی و وبینارها", - Icon = "education", - IsHighlighted = true - } + Id = package.Id, + Title = package.Title, + Description = package.Description, + Price = package.Price, + ImagePath = package.ImagePath, + ActivationFee = package.ActivationFee, + DiscountMultiplier = (double)package.DiscountMultiplier, + MagicWalletMultiplier = (double)package.MagicWalletMultiplier, + MagicWalletMaxDeposit = package.MagicWalletMaxDeposit, + MagicWalletMaxCredit = package.MagicWalletMaxCredit, + IsBasePackage = package.IsBasePackage, + SupportsDayaPurchase = package.SupportsDayaPurchase, + SupportsDirectPurchase = package.SupportsDirectPurchase }; - // Set purchase requirements - package.Requirements = new PurchaseRequirementsDto + // بارگذاری ویژگی‌ها از DB (PackageFeatures → ClubFeature) + if (package.PackageFeatures?.Any() == true) + { + response.Features = package.PackageFeatures + .Where(pf => pf.ClubFeature != null) + .Select(pf => new PackageFeatureDto + { + Title = pf.ClubFeature.Title ?? string.Empty, + Description = pf.ClubFeature.Description ?? string.Empty, + Icon = "feature", + IsHighlighted = pf.ClubFeature.IsActive + }) + .ToList(); + } + else + { + // فال‌بک به ویژگی‌های پیش‌فرض + response.Features = new List + { + new() { Title = "درآمد کمیسیون", Description = "دریافت کمیسیون از فروش محصولات", Icon = "commission", IsHighlighted = true }, + new() { Title = "پشتیبانی 24/7", Description = "دسترسی به پشتیبانی در تمام ساعات شبانه روز", Icon = "support", IsHighlighted = false }, + new() { Title = "آموزش‌های تخصصی", Description = "دسترسی به دوره‌های آموزشی و وبینارها", Icon = "education", IsHighlighted = true } + }; + } + + // شرایط خرید + response.Requirements = new PurchaseRequirementsDto { RequiresMembership = false, - MinimumWalletBalance = package.Price / 10, // 10% minimum + MinimumWalletBalance = package.Price / 10, Restrictions = new List { "باید حداقل 18 سال سن داشته باشید", @@ -63,6 +79,6 @@ public class GetCustomerPackageDetailsQueryHandler : IRequestHandler Features { get; set; } = new(); public PurchaseRequirementsDto Requirements { get; set; } + // New package-based fields + public long ActivationFee { get; set; } + public double DiscountMultiplier { get; set; } + public double MagicWalletMultiplier { get; set; } + public long MagicWalletMaxDeposit { get; set; } + public long MagicWalletMaxCredit { get; set; } + public bool IsBasePackage { get; set; } + public bool SupportsDayaPurchase { get; set; } + public bool SupportsDirectPurchase { get; set; } } public class PackageFeatureDto diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs index 6374498..840fc1e 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs @@ -17,35 +17,43 @@ public class GetCustomerPackagesQueryHandler : IRequestHandler !p.IsDeleted) .AsQueryable(); - // Filter by PackageType if specified - if (request.PackageTypeFilter.HasValue) + // فیلتر IsActive + if (!request.IncludeInactive) + query = query.Where(p => p.IsActive); + + // مرتب‌سازی بر اساس SortOrder + query = query.OrderBy(p => p.SortOrder); + + var packages = await query.ToListAsync(cancellationToken); + + return packages.Select(p => new GetCustomerPackagesResponseDto { - // Note: Package entity doesn't have PackageType enum, so we filter by convention - // Assuming Title or Description contains the package type indicator - // If Package entity needs PackageType field, it should be added to migration - } - - // Get all packages (assuming all are available unless marked otherwise) - var packages = await query - .ProjectToType() - .ToListAsync(cancellationToken); - - // Map additional fields - foreach (var package in packages) - { - package.Name = package.Title; - package.ImageUrl = package.ImagePath; - package.Currency = "IRR"; - package.IsAvailable = true; - package.ValidityDays = 365; // Default validity - package.IsPopular = false; - package.ShortDescription = package.Description?.Length > 100 - ? package.Description.Substring(0, 100) + "..." - : package.Description; - } - - return packages; + Id = p.Id, + Name = p.Title, + Title = p.Title, + Description = p.Description, + Price = p.Price, + ImageUrl = p.ImagePath, + ImagePath = p.ImagePath, + Currency = "IRR", + IsAvailable = p.IsActive, + ValidityDays = 365, + IsPopular = p.IsBasePackage, + ShortDescription = p.Description?.Length > 100 + ? p.Description.Substring(0, 100) + "..." + : p.Description, + // New fields + ActivationFee = p.ActivationFee, + DiscountMultiplier = (double)p.DiscountMultiplier, + MagicWalletMultiplier = (double)p.MagicWalletMultiplier, + MagicWalletMaxDeposit = p.MagicWalletMaxDeposit, + MagicWalletMaxCredit = p.MagicWalletMaxCredit, + IsBasePackage = p.IsBasePackage, + SupportsDayaPurchase = p.SupportsDayaPurchase, + SupportsDirectPurchase = p.SupportsDirectPurchase + }).ToList(); } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs index dfbf55d..4f4a058 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs @@ -15,4 +15,13 @@ public class GetCustomerPackagesResponseDto public int ValidityDays { get; set; } public bool IsPopular { get; set; } public string ShortDescription { get; set; } + // New package-based fields + public long ActivationFee { get; set; } + public double DiscountMultiplier { get; set; } + public double MagicWalletMultiplier { get; set; } + public long MagicWalletMaxDeposit { get; set; } + public long MagicWalletMaxCredit { get; set; } + public bool IsBasePackage { get; set; } + public bool SupportsDayaPurchase { get; set; } + public bool SupportsDirectPurchase { get; set; } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs index c923293..cc02c25 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs @@ -33,6 +33,7 @@ public class GetCustomerPurchaseHistoryQueryHandler : IRequestHandler x.UserId == userId && x.PackageId != null) .Include(x => x.Package) + .Include(x => x.Transaction) .AsQueryable(); // Apply date filters if specified diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs index daf2885..1e11601 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.Exceptions; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Enums; using MediatR; @@ -15,47 +16,49 @@ public class GetUserPackageStatusQueryHandler : IRequestHandler Handle(GetUserPackageStatusQuery request, CancellationToken cancellationToken) { - // TODO: پیاده‌سازی دریافت وضعیت پکیج کاربر - // - // 1. دریافت اطلاعات کاربر: - // - var user = await _context.Users - // .Include(u => u.UserWallet) - // .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken) - // - if (user == null) throw new NotFoundException("کاربر یافت نشد") - // - // 2. دریافت عضویت باشگاه: - // - var clubMembership = await _context.ClubMemberships - // .FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken) - // - // 3. دریافت آخرین سفارش پکیج: - // - var lastPackageOrder = await _context.UserOrders - // .Where(o => o.UserId == user.Id && o.PackageId != null) - // .OrderByDescending(o => o.Created) - // .FirstOrDefaultAsync(cancellationToken) - // - // 4. بررسی شرایط فعالسازی باشگاه: - // - var wallet = user.UserWallet - // - bool canActivate = - // user.PackagePurchaseMethod != PackagePurchaseMethod.None && - // clubMembership == null && - // wallet != null && - // wallet.Balance >= 56_000_000 - // - // 5. برگشت DTO: - // - return new UserPackageStatusDto { - // UserId = user.Id, - // PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(), - // HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None, - // IsClubMemberActive = clubMembership != null, - // WalletBalance = wallet?.Balance ?? 0, - // DiscountBalance = wallet?.DiscountBalance ?? 0, - // CanActivateClubMembership = canActivate, - // LastOrderNumber = lastPackageOrder?.OrderNumber, - // LastPurchaseDate = lastPackageOrder?.Created - // } - // - // نکته: این query برای UI مفید است تا وضعیت کاربر را نمایش دهد + // 1. دریافت اطلاعات کاربر + var user = await _context.Users + .Include(u => u.UserWallets) + .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); - throw new NotImplementedException("GetUserPackageStatus needs implementation"); + if (user == null) + throw new NotFoundException(nameof(User), request.UserId); + + // 2. دریافت عضویت باشگاه + var clubMembership = await _context.ClubMemberships + .FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken); + + // 3. دریافت آخرین سفارش پکیج + var lastPackageOrder = await _context.UserOrders + .Where(o => o.UserId == user.Id && o.PackageId != null) + .OrderByDescending(o => o.Created) + .FirstOrDefaultAsync(cancellationToken); + + // 4. بارگذاری پکیج پایه برای بررسی شرایط فعالسازی + var basePackage = await _context.Packages + .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + + var wallet = user.UserWallets.FirstOrDefault(); + + // 5. بررسی شرایط فعالسازی باشگاه + var canActivate = + user.PackagePurchaseMethod != PackagePurchaseMethod.None && + clubMembership == null && + wallet != null && + basePackage != null && + wallet.Balance >= basePackage.Price; + + return new UserPackageStatusDto + { + UserId = user.Id, + PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(), + HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None, + IsClubMemberActive = clubMembership?.IsActive ?? false, + WalletBalance = wallet?.Balance ?? 0, + DiscountBalance = wallet?.DiscountBalance ?? 0, + CanActivateClubMembership = canActivate, + LastOrderNumber = lastPackageOrder?.Id.ToString(), + LastPurchaseDate = lastPackageOrder?.Created + }; } } diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto index 2cc189d..588ab77 100644 --- a/src/CMSMicroservice.Protobuf/Protos/package.proto +++ b/src/CMSMicroservice.Protobuf/Protos/package.proto @@ -114,6 +114,18 @@ message CreateNewPackageRequest string image_path = 3; int64 price = 4; BoostCardFileModel image_file = 5; + int32 sort_order = 6; + bool is_active = 7; + bool is_base_package = 8; + bool supports_daya_purchase = 9; + bool supports_direct_purchase = 10; + int64 activation_fee = 11; + double discount_multiplier = 12; + double magic_wallet_multiplier = 13; + int32 max_balances_per_leg = 14; + int32 max_network_level = 15; + int64 magic_wallet_max_deposit = 16; + int64 magic_wallet_max_credit = 17; } message CreateNewPackageResponse { @@ -127,6 +139,18 @@ message UpdatePackageRequest string image_path = 4; int64 price = 5; BoostCardFileModel image_file = 6; + int32 sort_order = 7; + bool is_active = 8; + bool is_base_package = 9; + bool supports_daya_purchase = 10; + bool supports_direct_purchase = 11; + int64 activation_fee = 12; + double discount_multiplier = 13; + double magic_wallet_multiplier = 14; + int32 max_balances_per_leg = 15; + int32 max_network_level = 16; + int64 magic_wallet_max_deposit = 17; + int64 magic_wallet_max_credit = 18; } message DeletePackageRequest { @@ -143,6 +167,18 @@ message GetPackageResponse string description = 3; string image_path = 4; int64 price = 5; + int32 sort_order = 6; + bool is_active = 7; + bool is_base_package = 8; + bool supports_daya_purchase = 9; + bool supports_direct_purchase = 10; + int64 activation_fee = 11; + double discount_multiplier = 12; + double magic_wallet_multiplier = 13; + int32 max_balances_per_leg = 14; + int32 max_network_level = 15; + int64 magic_wallet_max_deposit = 16; + int64 magic_wallet_max_credit = 17; } message GetAllPackageByFilterRequest { @@ -170,6 +206,18 @@ message GetAllPackageByFilterResponseModel string description = 3; string image_path = 4; int64 price = 5; + int32 sort_order = 6; + bool is_active = 7; + bool is_base_package = 8; + bool supports_daya_purchase = 9; + bool supports_direct_purchase = 10; + int64 activation_fee = 11; + double discount_multiplier = 12; + double magic_wallet_multiplier = 13; + int32 max_balances_per_leg = 14; + int32 max_network_level = 15; + int64 magic_wallet_max_deposit = 16; + int64 magic_wallet_max_credit = 17; } // Package Purchase Messages @@ -350,8 +398,17 @@ message CustomerPackageModel int32 validity_days = 9; bool is_popular = 10; string short_description = 11; - string title = 12; // Alias for frontend compatibility (uses name) - string image_path = 13; // Alias for frontend compatibility (uses image_url) + string title = 12; + string image_path = 13; + // New package-based fields + int64 activation_fee = 14; + double discount_multiplier = 15; + double magic_wallet_multiplier = 16; + int64 magic_wallet_max_deposit = 17; + int64 magic_wallet_max_credit = 18; + bool is_base_package = 19; + bool supports_daya_purchase = 20; + bool supports_direct_purchase = 21; } message PackageFeature diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 6949c93..3d99a43 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -317,6 +317,48 @@ public class PackageService : PackageContract.PackageContractBase transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success; transaction.PaymentDate = DateTime.UtcNow; transaction.RefId = verifyResult.RefId; + + // شارژ کیف پول کاربر + var wallet = await _context.UserWallets + .FirstOrDefaultAsync(w => w.UserId == purchase.UserId, context.CancellationToken); + + if (wallet == null) + { + wallet = new CMSMicroservice.Domain.Entities.UserWallet + { + UserId = purchase.UserId, + Balance = 0, + DiscountBalance = 0, + NetworkBalance = 0 + }; + _context.UserWallets.Add(wallet); + await _context.SaveChangesAsync(context.CancellationToken); + } + + var discountAmount = (long)(purchase.Amount * (double)(purchase.Package?.DiscountMultiplier ?? 2.0m)); + wallet.Balance += purchase.Amount; + wallet.DiscountBalance += discountAmount; + + // ثبت لاگ کیف پول + var walletLog = new CMSMicroservice.Domain.Entities.UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = purchase.Amount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = wallet.DiscountBalance, + ChangeDiscountValue = discountAmount, + IsIncrease = true, + RefrenceId = transaction.Id + }; + _context.UserWalletChangeLogs.Add(walletLog); + + // به‌روزرسانی کاربر + var user = await _context.Users + .FirstOrDefaultAsync(u => u.Id == purchase.UserId, context.CancellationToken); + if (user != null) + user.PackagePurchaseMethod = Domain.Enums.PackagePurchaseMethod.DirectPurchase; } else if (transaction != null) { From 0002a5a6f2cfbd2e400545c66edc9abd23d9d6c4 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 00:24:35 +0330 Subject: [PATCH 22/44] Phase 4: Complete Package CRUD DTOs + Legacy Fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CreateNewPackageCommand: Add 12 new Package fields (SortOrder, IsActive, IsBasePackage, SupportsDayaPurchase, SupportsDirectPurchase, ActivationFee, DiscountMultiplier, MagicWalletMultiplier, MaxBalancesPerLeg, MaxNetworkLevel, MagicWalletMaxDeposit, MagicWalletMaxCredit) with sensible defaults - GetPackageResponseDto: Add 12 new fields (Mapster auto-maps via ProjectToType) - GetAllPackageByFilterResponseModel: Add 12 new fields (Mapster auto-maps) - PurchaseGoldenPackage: Replace fragile Title string match ('طلایی'/'golden') with proper IsDeleted/IsActive/SupportsDirectPurchase validation - VerifyPackagePurchase: Replace hardcoded order.Amount*2 with package.DiscountMultiplier from DB (fallback 2.0 for null Package) --- .../CreateNewPackage/CreateNewPackageCommand.cs | 14 +++++++++++++- .../PurchaseGoldenPackageCommandHandler.cs | 17 +++++++++++------ .../VerifyPackagePurchaseCommandHandler.cs | 10 ++++++---- .../GetAllPackageByFilterResponseDto.cs | 13 +++++++++++++ .../Queries/GetPackage/GetPackageResponseDto.cs | 14 +++++++++++++- 5 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs index 5d8b6e6..c0fd523 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs @@ -9,5 +9,17 @@ public record CreateNewPackageCommand : IRequest public string ImagePath { get; init; } //قیمت public long Price { get; init; } - + // فیلدهای جدید پکیج + public int SortOrder { get; init; } + public bool IsActive { get; init; } = true; + public bool IsBasePackage { get; init; } + public bool SupportsDayaPurchase { get; init; } = true; + public bool SupportsDirectPurchase { get; init; } = true; + public long ActivationFee { get; init; } + public double DiscountMultiplier { get; init; } = 2.0; + public double MagicWalletMultiplier { get; init; } = 2.5; + public int MaxBalancesPerLeg { get; init; } = 300; + public int MaxNetworkLevel { get; init; } = 15; + public long MagicWalletMaxDeposit { get; init; } = 1_000_000_000; + public long MagicWalletMaxCredit { get; init; } = 2_500_000_000; } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs index 8d2aae5..667daf2 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs @@ -65,16 +65,21 @@ public class PurchaseGoldenPackageCommandHandler : IRequestHandler {NewBalance}", @@ -132,7 +134,7 @@ public class VerifyPackagePurchaseCommandHandler ChangeValue = order.Amount, CurrentNetworkBalance = wallet.NetworkBalance, ChangeNerworkValue = 0, - CurrentDiscountBalance = wallet.DiscountBalance - (order.Amount * 2), // قبل از شارژ DiscountBalance + CurrentDiscountBalance = wallet.DiscountBalance - discountAmount, // قبل از شارژ DiscountBalance ChangeDiscountValue = 0, IsIncrease = true, RefrenceId = transaction.Id @@ -148,7 +150,7 @@ public class VerifyPackagePurchaseCommandHandler CurrentNetworkBalance = wallet.NetworkBalance, ChangeNerworkValue = 0, CurrentDiscountBalance = wallet.DiscountBalance, - ChangeDiscountValue = order.Amount * 2, + ChangeDiscountValue = discountAmount, IsIncrease = true, RefrenceId = transaction.Id }; diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs index f8d3b39..76eb16d 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs @@ -18,4 +18,17 @@ public class GetAllPackageByFilterResponseDto public string ImagePath { get; set; } //قیمت public long Price { get; set; } + // فیلدهای جدید پکیج + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public bool IsBasePackage { get; set; } + public bool SupportsDayaPurchase { get; set; } + public bool SupportsDirectPurchase { get; set; } + public long ActivationFee { get; set; } + public decimal DiscountMultiplier { get; set; } + public decimal MagicWalletMultiplier { get; set; } + public int MaxBalancesPerLeg { get; set; } + public int MaxNetworkLevel { get; set; } + public long MagicWalletMaxDeposit { get; set; } + public long MagicWalletMaxCredit { get; set; } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs index 04c3252..ec118f5 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs @@ -11,5 +11,17 @@ public class GetPackageResponseDto public string ImagePath { get; set; } //قیمت public long Price { get; set; } - + // فیلدهای جدید پکیج + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public bool IsBasePackage { get; set; } + public bool SupportsDayaPurchase { get; set; } + public bool SupportsDirectPurchase { get; set; } + public long ActivationFee { get; set; } + public decimal DiscountMultiplier { get; set; } + public decimal MagicWalletMultiplier { get; set; } + public int MaxBalancesPerLeg { get; set; } + public int MaxNetworkLevel { get; set; } + public long MagicWalletMaxDeposit { get; set; } + public long MagicWalletMaxCredit { get; set; } } \ No newline at end of file From 607f791b6575795964db80ae098139a2ce602eff Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 00:57:20 +0330 Subject: [PATCH 23/44] Phase 5: Per-package commission + golden ref cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix golden/طلایی string refs in user-facing messages (ActivateClubMembership) - Rename HasPurchasedGoldenPackage → HasPurchasedPackage (DTO, Handler, Proto, Mapping) - ORM commission: per-user-package calculation via ClubMembership.LastPackageId - Build userPackageMap, per-user maxBalancesPerLeg/maxNetworkLevel - Carryover keyed by (UserId, PackageId) tuple - SP commission: loop over packages, pass @PackageId/@InputMaxBalancesPerLeg/@InputMaxNetworkLevel - sp_CalculateWeeklyBalances: accept 3 new params, filter by PackageId, insert PackageId column --- .../ActivateClubMembershipCommandHandler.cs | 6 +- .../GetUserNetworkPositionQueryHandler.cs | 2 +- .../UserNetworkPositionDto.cs | 2 +- .../sp_CalculateWeeklyBalances.sql | 45 ++++++++-- .../OrmCommissionCalculationStrategy.cs | 65 +++++++++------ ...dProcedureCommissionCalculationStrategy.cs | 83 ++++++++++++++----- .../Protos/networkmembership.proto | 2 +- .../Mappings/NetworkMembershipProfile.cs | 2 +- 8 files changed, 144 insertions(+), 63 deletions(-) diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index b4e8e84..91a6e43 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -64,11 +64,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler w.UserId) .ToHashSetAsync(cancellationToken); - var activeClubMemberUserIds = await _context.ClubMemberships + // دریافت عضویت‌های فعال به همراه PackageId هر کاربر + var activeClubMemberships = await _context.ClubMemberships .Where(c => c.IsActive && !magicModeUserIds.Contains(c.UserId)) + .Select(c => new { c.UserId, PackageId = c.LastPackageId }) + .ToListAsync(cancellationToken); + + var activeClubMemberUserIds = activeClubMemberships .Select(c => c.UserId) - .ToHashSetAsync(cancellationToken); + .ToHashSet(); + + // نگاشت کاربر → PackageId + var userPackageMap = activeClubMemberships + .Where(c => c.PackageId.HasValue) + .ToDictionary(c => c.UserId, c => c.PackageId!.Value); // دریافت کاربران فعال در شبکه که عضو باشگاه هستند var usersInNetwork = await _context.Users @@ -73,43 +83,52 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy .Select(x => new { x.Id }) .ToListAsync(cancellationToken); - // دریافت باقیمانده‌های هفته قبل + // بارگذاری همه پکیج‌های فعال (bulk load) + var allPackages = await _context.Packages + .Where(p => !p.IsDeleted) + .ToDictionaryAsync(p => p.Id, cancellationToken); + + // پکیج پیش‌فرض (fallback برای کاربرانی که PackageId ندارند) + var defaultPackage = allPackages.Values + .FirstOrDefault(p => p.IsBasePackage) + ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); + + // دریافت باقیمانده‌های هفته قبل — شامل PackageId var previousWeekDefinitionId = GetPreviousWeekDefinitionId(weekDefinitionId); - Dictionary previousWeekCarryovers; + Dictionary<(long UserId, long PackageId), (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers; if (previousWeekDefinitionId.HasValue) { previousWeekCarryovers = await _context.NetworkWeeklyBalances .Where(x => x.WeekDefinitionId == previousWeekDefinitionId.Value) .ToDictionaryAsync( - x => x.UserId, + x => (x.UserId, x.PackageId), x => (x.LeftLegRemainder, x.RightLegRemainder), cancellationToken); } else { - previousWeekCarryovers = new Dictionary(); + previousWeekCarryovers = new Dictionary<(long, long), (int, int)>(); } var balancesList = new List(); var calculatedAt = DateTime.Now; - // بارگذاری پکیج پایه برای خواندن سقف‌ها - var package = await _context.Packages - .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) - ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); - var maxBalancesPerLeg = package.MaxBalancesPerLeg; - var maxNetworkLevel = package.MaxNetworkLevel; - foreach (var user in usersInNetwork.OrderBy(o => o.Id)) { - // دریافت باقیمانده هفته قبل + // تعیین پکیج هر کاربر (per-user) + var userPackageId = userPackageMap.GetValueOrDefault(user.Id, defaultPackage.Id); + var package = allPackages.GetValueOrDefault(userPackageId, defaultPackage); + var maxBalancesPerLeg = package.MaxBalancesPerLeg; + var maxNetworkLevel = package.MaxNetworkLevel; + // دریافت باقیمانده هفته قبل — فیلتر per-package var leftCarryover = 0; var rightCarryover = 0; - if (previousWeekCarryovers.ContainsKey(user.Id)) + var carryoverKey = (UserId: user.Id, PackageId: userPackageId); + if (previousWeekCarryovers.ContainsKey(carryoverKey)) { - leftCarryover = previousWeekCarryovers[user.Id].LeftLegRemainder; - rightCarryover = previousWeekCarryovers[user.Id].RightLegRemainder; + leftCarryover = previousWeekCarryovers[carryoverKey].LeftLegRemainder; + rightCarryover = previousWeekCarryovers[carryoverKey].RightLegRemainder; } // محاسبه تعداد اعضای جدید در این هفته (تا maxNetworkLevel لول پایین‌تر) @@ -166,15 +185,16 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy await _context.NetworkWeeklyBalances.AddRangeAsync(balancesList, cancellationToken); await _context.SaveChangesAsync(cancellationToken); - // محاسبه تعادل زیرمجموعه + // محاسبه تعادل زیرمجموعه — per-user maxNetworkLevel var balancesDictionary = balancesList.ToDictionary(x => x.UserId); foreach (var balance in balancesList) { + var balancePackage = allPackages.GetValueOrDefault(balance.PackageId, defaultPackage); var subordinateBalances = await CalculateSubordinateBalancesAsync( balance.UserId, balancesDictionary, - maxNetworkLevel, + balancePackage.MaxNetworkLevel, cancellationToken ); @@ -193,11 +213,6 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy bool forceRecalculate, CancellationToken cancellationToken = default) { - // بارگذاری پکیج پایه برای تعیین PackageId روی پرداخت‌ها - var package = await _context.Packages - .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) - ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); - var weekDefinition = _weekRepository.GetGregorianWeekNumber(weekDefinitionId); if (weekDefinition == null) { @@ -293,7 +308,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy UserId = balance.UserId, WeekDefinitionId = weekDefinitionId, WeeklyPoolId = existingPool.Id, - PackageId = package.Id, + PackageId = balance.PackageId, // per-user PackageId از محاسبه تعادل BalancesEarned = userBalance, ValuePerBalance = valuePerBalance, TotalAmount = totalAmount, diff --git a/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs b/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs index 221537d..71ea602 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs @@ -6,6 +6,7 @@ namespace CMSMicroservice.Infrastructure.Services.Commission; /// /// پیاده‌سازی محاسبه کمیسیون با استفاده از Stored Procedure /// این روش برای داده‌های زیاد بهینه‌تر است و از CTE استفاده می‌کند +/// حلقه روی پکیج‌های فعال — هر پکیج با تنظیمات خودش /// public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculationStrategy { @@ -22,45 +23,81 @@ public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculati bool forceRecalculate, CancellationToken cancellationToken = default) { - // دسترسی به DbContext واقعی برای اجرای SP var dbContext = _context as DbContext; if (dbContext == null) { throw new InvalidOperationException("DbContext برای اجرای Stored Procedure در دسترس نیست"); } + // بارگذاری پکیج‌های فعال + var activePackages = await _context.Packages + .Where(p => !p.IsDeleted && p.IsActive) + .ToListAsync(cancellationToken); + + if (!activePackages.Any()) + { + throw new InvalidOperationException("هیچ پکیج فعالی یافت نشد"); + } + + var totalRowCount = 0; + var connection = dbContext.Database.GetDbConnection(); await connection.OpenAsync(cancellationToken); try { - using var command = connection.CreateCommand(); - command.CommandText = "CMS.sp_CalculateWeeklyBalances"; - command.CommandType = System.Data.CommandType.StoredProcedure; - command.CommandTimeout = 300; // 5 دقیقه timeout + // حلقه روی پکیج‌ها — هر پکیج با تنظیمات خودش + foreach (var package in activePackages) + { + using var command = connection.CreateCommand(); + command.CommandText = "CMS.sp_CalculateWeeklyBalances"; + command.CommandType = System.Data.CommandType.StoredProcedure; + command.CommandTimeout = 300; // 5 دقیقه timeout - // پارامترها - var paramWeekDefinitionId = command.CreateParameter(); - paramWeekDefinitionId.ParameterName = "@WeekDefinitionId"; - paramWeekDefinitionId.Value = weekDefinitionId; - command.Parameters.Add(paramWeekDefinitionId); + // پارامتر WeekDefinitionId + var paramWeekDefinitionId = command.CreateParameter(); + paramWeekDefinitionId.ParameterName = "@WeekDefinitionId"; + paramWeekDefinitionId.Value = weekDefinitionId; + command.Parameters.Add(paramWeekDefinitionId); - var paramForceRecalculate = command.CreateParameter(); - paramForceRecalculate.ParameterName = "@ForceRecalculate"; - paramForceRecalculate.Value = forceRecalculate; - command.Parameters.Add(paramForceRecalculate); + // پارامتر ForceRecalculate + var paramForceRecalculate = command.CreateParameter(); + paramForceRecalculate.ParameterName = "@ForceRecalculate"; + paramForceRecalculate.Value = forceRecalculate; + command.Parameters.Add(paramForceRecalculate); - // پارامتر خروجی - var paramRowCount = command.CreateParameter(); - paramRowCount.ParameterName = "@RowCount"; - paramRowCount.Direction = System.Data.ParameterDirection.Output; - paramRowCount.DbType = System.Data.DbType.Int32; - command.Parameters.Add(paramRowCount); + // پارامتر PackageId — per-package + var paramPackageId = command.CreateParameter(); + paramPackageId.ParameterName = "@PackageId"; + paramPackageId.Value = package.Id; + command.Parameters.Add(paramPackageId); - await command.ExecuteNonQueryAsync(cancellationToken); + // پارامتر MaxBalancesPerLeg — per-package + var paramMaxBalances = command.CreateParameter(); + paramMaxBalances.ParameterName = "@InputMaxBalancesPerLeg"; + paramMaxBalances.Value = package.MaxBalancesPerLeg; + command.Parameters.Add(paramMaxBalances); - var rowCount = (int)(paramRowCount.Value ?? 0); - return rowCount; + // پارامتر MaxNetworkLevel — per-package + var paramMaxLevel = command.CreateParameter(); + paramMaxLevel.ParameterName = "@InputMaxNetworkLevel"; + paramMaxLevel.Value = package.MaxNetworkLevel; + command.Parameters.Add(paramMaxLevel); + + // پارامتر خروجی + var paramRowCount = command.CreateParameter(); + paramRowCount.ParameterName = "@RowCount"; + paramRowCount.Direction = System.Data.ParameterDirection.Output; + paramRowCount.DbType = System.Data.DbType.Int32; + command.Parameters.Add(paramRowCount); + + await command.ExecuteNonQueryAsync(cancellationToken); + + var rowCount = (int)(paramRowCount.Value ?? 0); + totalRowCount += rowCount; + } + + return totalRowCount; } finally { diff --git a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto index be37c2a..45ca30b 100644 --- a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto @@ -153,7 +153,7 @@ message GetUserNetworkResponse bool has_received_daya_credit = 32; google.protobuf.Timestamp daya_credit_received_at = 33; int32 package_purchase_method = 34; - bool has_purchased_golden_package = 35; + bool has_purchased_package = 35; // آمار مالی double total_earned_commission = 36; diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs index 9035a68..e50fe94 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs @@ -56,7 +56,7 @@ public class NetworkMembershipProfile : IRegister .Map(dest => dest.MaxNetworkDepth, src => src.MaxNetworkDepth) .Map(dest => dest.HasReceivedDayaCredit, src => src.HasReceivedDayaCredit) .Map(dest => dest.PackagePurchaseMethod, src => (int)src.PackagePurchaseMethod) - .Map(dest => dest.HasPurchasedGoldenPackage, src => src.HasPurchasedGoldenPackage) + .Map(dest => dest.HasPurchasedPackage, src => src.HasPurchasedPackage) .Map(dest => dest.TotalEarnedCommission, src => (double)src.TotalEarnedCommission) .Map(dest => dest.TotalPaidCommission, src => (double)src.TotalPaidCommission) .Map(dest => dest.PendingCommission, src => (double)src.PendingCommission) From 7176fe44ee548d2451ba23fbbc1c1694d5c8ded7 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 00:59:59 +0330 Subject: [PATCH 24/44] =?UTF-8?q?Fix=20SP:=20cm.PackageId=20=E2=86=92=20cm?= =?UTF-8?q?.LastPackageId=20(match=20actual=20DB=20column=20name)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Persistence/StoredProcedures/sp_CalculateWeeklyBalances.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMSMicroservice.Infrastructure/Persistence/StoredProcedures/sp_CalculateWeeklyBalances.sql b/src/CMSMicroservice.Infrastructure/Persistence/StoredProcedures/sp_CalculateWeeklyBalances.sql index 91ce642..8a8ae02 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/StoredProcedures/sp_CalculateWeeklyBalances.sql +++ b/src/CMSMicroservice.Infrastructure/Persistence/StoredProcedures/sp_CalculateWeeklyBalances.sql @@ -121,7 +121,7 @@ BEGIN SELECT DISTINCT u.Id FROM CMS.Users u INNER JOIN CMS.ClubMemberships cm ON cm.UserId = u.Id AND cm.IsActive = 1 - WHERE cm.PackageId = @PackageId; + WHERE cm.LastPackageId = @PackageId; -- ============================================= -- 7. دریافت باقیمانده هفته قبل From d19c569aaea87ae4cd315419c0c8469131a87c87 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 01:10:43 +0330 Subject: [PATCH 25/44] Phase 6: Deprecation cleanup + ConfigurationService MagicWallet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mark PurchaseGoldenPackage/VerifyGoldenPackagePurchase RPCs as deprecated - Mark InitiateBasePackagePayment/VerifyBasePackagePayment RPCs as deprecated - Remove deprecated SystemConstants from GetAllAsDict/GetAllWithDescriptions helpers - Add MagicWallet per-package values to ConfigurationService (Multiplier, MaxDeposit, MaxCredit) - All deprecated constants have zero active code usages — safe dead code --- .../Common/SystemConstants.cs | 29 ++++--------------- .../Protos/package.proto | 8 +++-- .../Services/ConfigurationService.cs | 11 +++++++ 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/CMSMicroservice.Domain/Common/SystemConstants.cs b/src/CMSMicroservice.Domain/Common/SystemConstants.cs index 2d02837..cc06ffa 100644 --- a/src/CMSMicroservice.Domain/Common/SystemConstants.cs +++ b/src/CMSMicroservice.Domain/Common/SystemConstants.cs @@ -146,6 +146,8 @@ public static class SystemConstants /// /// دریافت مقدار به صورت دیکشنری برای نمایش در Admin Panel + /// Note: Per-package values (Price, MaxBalancesPerLeg, etc.) are now read from Package entity + /// via ConfigurationService — only static/global constants remain here. /// public static Dictionary GetAllAsDict() { @@ -155,26 +157,15 @@ public static class SystemConstants ["Network.AllowOrphanNodes"] = NetworkAllowOrphanNodes, ["Network.MaxChildrenPerLeg"] = NetworkMaxChildrenPerLeg, - // Club - ["Club.MembershipGiftValue"] = ClubMembershipGiftValue, - ["Club.ActivationFee"] = ClubActivationFee, - - // Commission + // Commission (global settings only — per-package limits are in Package entity) ["Commission.CashWithdrawalEnabled"] = CommissionCashWithdrawalEnabled, ["Commission.MinWithdrawalAmount"] = CommissionMinWithdrawalAmount, - ["Commission.MaxWeeklyBalancesPerLeg"] = CommissionMaxWeeklyBalancesPerLeg, - ["Commission.MaxNetworkLevel"] = CommissionMaxNetworkLevel, ["Commission.CalculationStrategy"] = CommissionCalculationStrategy, // System ["System.MaintenanceMode"] = SystemMaintenanceMode, ["System.EnableAuditLog"] = SystemEnableAuditLog, - // Magic Wallet - ["MagicWallet.Multiplier"] = MagicWalletMultiplier, - ["MagicWallet.MaxDeposit"] = MagicWalletMaxDeposit, - ["MagicWallet.MaxCredit"] = MagicWalletMaxCredit, - // Shop ["Shop.VAT"] = ShopVAT, ["Shop.VATEnabled"] = ShopVATEnabled @@ -183,6 +174,7 @@ public static class SystemConstants /// /// دریافت لیست تنظیمات با توضیحات + /// Note: Per-package values are served from Package entity via ConfigurationService. /// public static List<(string Key, object Value, string Description)> GetAllWithDescriptions() { @@ -192,26 +184,15 @@ public static class SystemConstants ("Network.AllowOrphanNodes", NetworkAllowOrphanNodes, "اجازه حذف والدین که فرزند دارند"), ("Network.MaxChildrenPerLeg", NetworkMaxChildrenPerLeg, "حداکثر تعداد فرزند مستقیم در هر پا"), - // Club - ("Club.MembershipGiftValue", ClubMembershipGiftValue, "مبلغ هدیه حق عضویت باشگاه (ریال)"), - ("Club.ActivationFee", ClubActivationFee, "هزینه فعال‌سازی عضویت باشگاه (ریال)"), - - // Commission + // Commission (global settings only) ("Commission.CashWithdrawalEnabled", CommissionCashWithdrawalEnabled, "امکان برداشت نقدی فعال باشد"), ("Commission.MinWithdrawalAmount", CommissionMinWithdrawalAmount, "حداقل مبلغ برداشت (ریال)"), - ("Commission.MaxWeeklyBalancesPerLeg", CommissionMaxWeeklyBalancesPerLeg, "سقف تعادل هفتگی برای هر دست"), - ("Commission.MaxNetworkLevel", CommissionMaxNetworkLevel, "حداکثر عمق شبکه برای محاسبه کمیسیون"), ("Commission.CalculationStrategy", CommissionCalculationStrategy, "روش محاسبه (ORM/SP)"), // System ("System.MaintenanceMode", SystemMaintenanceMode, "حالت تعمیر و نگهداری سیستم"), ("System.EnableAuditLog", SystemEnableAuditLog, "فعال‌سازی لاگ تغییرات"), - // Magic Wallet - ("MagicWallet.Multiplier", MagicWalletMultiplier, "ضریب شارژ کیف‌پول جادویی (×2.5)"), - ("MagicWallet.MaxDeposit", MagicWalletMaxDeposit, "سقف واریز هر دور جادویی (ریال)"), - ("MagicWallet.MaxCredit", MagicWalletMaxCredit, "سقف اعتبار هر دور جادویی (ریال)"), - // Shop ("Shop.VAT", ShopVAT, "مالیات بر ارزش افزوده"), ("Shop.VATEnabled", ShopVATEnabled, "مالیات فعال است؟") diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto index 588ab77..3e6e731 100644 --- a/src/CMSMicroservice.Protobuf/Protos/package.proto +++ b/src/CMSMicroservice.Protobuf/Protos/package.proto @@ -44,14 +44,16 @@ service PackageContract }; }; - // Package Purchase System + // Package Purchase System (Legacy — use Customer* RPCs instead) rpc PurchaseGoldenPackage(PurchaseGoldenPackageRequest) returns (PurchaseGoldenPackageResponse){ + option deprecated = true; option (google.api.http) = { post: "/PurchaseGoldenPackage" body: "*" }; }; rpc VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest) returns (VerifyGoldenPackagePurchaseResponse){ + option deprecated = true; option (google.api.http) = { post: "/VerifyGoldenPackagePurchase" body: "*" @@ -63,14 +65,16 @@ service PackageContract }; }; - // Base Package Payment (56M) + // Base Package Payment (Legacy — migrate to Customer* RPCs, then remove) rpc InitiateBasePackagePayment(InitiateBasePackagePaymentRequest) returns (InitiateBasePackagePaymentResponse){ + option deprecated = true; option (google.api.http) = { post: "/InitiateBasePackagePayment" body: "*" }; }; rpc VerifyBasePackagePayment(VerifyBasePackagePaymentRequest) returns (VerifyBasePackagePaymentResponse){ + option deprecated = true; option (google.api.http) = { post: "/VerifyBasePackagePayment" body: "*" diff --git a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs index f6f0856..05eb8dd 100644 --- a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs +++ b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs @@ -143,6 +143,11 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB response.Models.Add(CreateConfigModel("Package.BasePackageAmount", package.Price.ToString(), "مبلغ پکیج پایه", 0)); response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", package.Price.ToString(), "مبلغ وام دایا", 0)); + // Magic Wallet Settings (از Package) + response.Models.Add(CreateConfigModel("MagicWallet.Multiplier", package.MagicWalletMultiplier.ToString(), "ضریب شارژ کیف‌پول جادویی", 0)); + response.Models.Add(CreateConfigModel("MagicWallet.MaxDeposit", package.MagicWalletMaxDeposit.ToString(), "سقف واریز هر دور جادویی", 0)); + response.Models.Add(CreateConfigModel("MagicWallet.MaxCredit", package.MagicWalletMaxCredit.ToString(), "سقف اعتبار هر دور جادویی", 0)); + // System Settings response.Models.Add(CreateConfigModel("System.MaintenanceMode", SystemConstants.SystemMaintenanceMode.ToString(), "حالت تعمیر و نگهداری", 0)); response.Models.Add(CreateConfigModel("System.EnableAuditLog", SystemConstants.SystemEnableAuditLog.ToString(), "فعال‌سازی لاگ تغییرات", 0)); @@ -197,6 +202,9 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB "Network.MaxChildrenPerLeg" => SystemConstants.NetworkMaxChildrenPerLeg.ToString(), "Package.BasePackageAmount" => (package?.Price ?? 0).ToString(), "Package.DayaLoanAmount" => (package?.Price ?? 0).ToString(), + "MagicWallet.Multiplier" => (package?.MagicWalletMultiplier ?? 0).ToString(), + "MagicWallet.MaxDeposit" => (package?.MagicWalletMaxDeposit ?? 0).ToString(), + "MagicWallet.MaxCredit" => (package?.MagicWalletMaxCredit ?? 0).ToString(), "System.MaintenanceMode" => SystemConstants.SystemMaintenanceMode.ToString(), "System.EnableAuditLog" => SystemConstants.SystemEnableAuditLog.ToString(), "Shop.VAT" => SystemConstants.ShopVAT.ToString(), @@ -226,6 +234,9 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB "Network.MaxChildrenPerLeg" => "حداکثر تعداد فرزند مستقیم در هر پا", "Package.BasePackageAmount" => "مبلغ پکیج پایه (ریال)", "Package.DayaLoanAmount" => "مبلغ وام دایا (ریال)", + "MagicWallet.Multiplier" => "ضریب شارژ کیف‌پول جادویی", + "MagicWallet.MaxDeposit" => "سقف واریز هر دور جادویی (ریال)", + "MagicWallet.MaxCredit" => "سقف اعتبار هر دور جادویی (ریال)", "System.MaintenanceMode" => "حالت تعمیر و نگهداری سیستم", "System.EnableAuditLog" => "فعال‌سازی لاگ تغییرات", "Shop.VAT" => "مالیات بر ارزش افزوده", From 469d97bb60239dfaba1692eec8f9f376c021a5b1 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 02:01:33 +0330 Subject: [PATCH 26/44] Phase 7a: Cosmetic cleanup + delete orphaned PurchasePackage handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete orphaned PurchasePackageCommand handler (no RPC dispatch, no callers) - Fix doc-comments: طلایی → پکیج in enums, entities, handlers - Remove stale 56M inline comments - Clean SystemConstants deprecated field docs --- .../CheckAndProcessDayaLoansCommandHandler.cs | 2 +- .../CreateManualPaymentCommandHandler.cs | 4 +- .../PurchasePackage/PurchasePackageCommand.cs | 18 -- .../PurchasePackageCommandHandler.cs | 164 ------------------ .../PurchasePackageCommandValidator.cs | 13 -- .../Common/SystemConstants.cs | 4 +- src/CMSMicroservice.Domain/Entities/User.cs | 2 +- .../Entities/UserWallet.cs | 2 +- .../Enums/CommissionPayoutStatus.cs | 2 +- .../Enums/PackagePurchaseMethod.cs | 2 +- 10 files changed, 9 insertions(+), 204 deletions(-) delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommand.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandValidator.cs diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs index c4e1690..93d494a 100644 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs @@ -12,7 +12,7 @@ namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckAndProcessDayaLoa /// 1. استعلام از API دایا /// 2. ذخیره/به‌روزرسانی DayaLoanContract /// 3. شارژ کیف پول برای وام‌های تأیید شده -/// 4. ثبت Order پکیج طلایی +/// 4. ثبت Order پکیج /// 5. ارسال SMS /// public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs index 9402612..b2c57ce 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs @@ -116,8 +116,8 @@ public class CreateManualPaymentCommandHandler : IRequestHandler -/// دستور خرید پکیج از طریق درگاه بانکی -/// -public class PurchasePackageCommand : IRequest -{ - /// - /// شناسه کاربر - /// - public long UserId { get; set; } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs deleted file mode 100644 index 3427031..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs +++ /dev/null @@ -1,164 +0,0 @@ -using CMSMicroservice.Application.Common.Exceptions; -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Application.Common.Models; -using CMSMicroservice.Domain.Entities; -using CMSMicroservice.Domain.Enums; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using ValidationException = FluentValidation.ValidationException; - -namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage; - -public class PurchasePackageCommandHandler - : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IPaymentGatewayService _paymentGateway; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - - public PurchasePackageCommandHandler( - IApplicationDbContext context, - IPaymentGatewayService paymentGateway, - IConfiguration configuration, - ILogger logger) - { - _context = context; - _paymentGateway = paymentGateway; - _configuration = configuration; - _logger = logger; - } - - public async Task Handle( - PurchasePackageCommand request, - CancellationToken cancellationToken) - { - try - { - _logger.LogInformation( - "Starting package purchase for UserId: {UserId}", - request.UserId - ); - - // 1. بررسی وجود کاربر - var user = await _context.Users - .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); - - if (user == null) - { - _logger.LogWarning("User not found: {UserId}", request.UserId); - throw new NotFoundException(nameof(User), request.UserId); - } - - // 2. بررسی اینکه قبلاً پکیج نخریده باشد - if (user.PackagePurchaseMethod != PackagePurchaseMethod.None) - { - _logger.LogWarning( - "User {UserId} has already purchased package via {Method}", - request.UserId, - user.PackagePurchaseMethod - ); - throw new ValidationException( - "شما قبلاً پکیج را خریداری کرده‌اید" - ); - } - - // 3. پیدا کردن پکیج (فعلاً پکیج طلایی) - var goldenPackage = await _context.Packages - .FirstOrDefaultAsync( - p => p.Title.Contains("طلایی") || p.Title.Contains("Golden"), - cancellationToken - ); - - if (goldenPackage == null) - { - _logger.LogError("Package not found in database"); - throw new NotFoundException("پکیج یافت نشد"); - } - - // 4. پیدا کردن آدرس پیش‌فرض کاربر (برای فیلد اجباری) - var defaultAddress = await _context.UserAddresses - .Where(a => a.UserId == request.UserId) - .OrderByDescending(a => a.Created) - .FirstOrDefaultAsync(cancellationToken); - - if (defaultAddress == null) - { - _logger.LogWarning("No address found for user {UserId}", request.UserId); - throw new ValidationException( - "لطفاً ابتدا یک آدرس برای خود ثبت کنید" - ); - } - - // 5. ایجاد سفارش - var order = new UserOrder - { - UserId = user.Id, - PackageId = goldenPackage.Id, - Amount = goldenPackage.Price, // 56,000,000 تومان - PaymentStatus = PaymentStatus.Pending, - DeliveryStatus = DeliveryStatus.None, - UserAddressId = defaultAddress.Id, - PaymentMethod = PaymentMethod.IPG - }; - - _context.UserOrders.Add(order); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Created UserOrder {OrderId} for UserId {UserId}, Amount: {Amount}", - order.Id, - request.UserId, - order.Amount - ); - - // 6. ایجاد درخواست پرداخت از درگاه - var paymentRequest = new PaymentRequest - { - Amount = order.Amount, - UserId = user.Id, - Mobile = user.Mobile ?? "", - CallbackUrl = $"{_configuration["CmsBaseUrl"] ?? "https://localhost:32846"}/api/package/verify-package", - Description = $"خرید پکیج - سفارش #{order.Id}" - }; - - var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest); - - if (!paymentResult.IsSuccess) - { - _logger.LogError( - "Payment gateway failed for OrderId {OrderId}: {ErrorMessage}", - order.Id, - paymentResult.ErrorMessage - ); - - // به‌روزرسانی وضعیت سفارش - order.PaymentStatus = PaymentStatus.Reject; - await _context.SaveChangesAsync(cancellationToken); - - throw new Exception( - $"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}" - ); - } - - _logger.LogInformation( - "Payment initiated successfully. OrderId: {OrderId}, RefId: {RefId}", - order.Id, - paymentResult.RefId - ); - - return paymentResult; - } - catch (Exception ex) - { - _logger.LogError( - ex, - "Error in PurchasePackageCommand for UserId: {UserId}", - request.UserId - ); - throw; - } - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandValidator.cs deleted file mode 100644 index 002c022..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandValidator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FluentValidation; - -namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage; - -public class PurchasePackageCommandValidator : AbstractValidator -{ - public PurchasePackageCommandValidator() - { - RuleFor(x => x.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد"); - } -} diff --git a/src/CMSMicroservice.Domain/Common/SystemConstants.cs b/src/CMSMicroservice.Domain/Common/SystemConstants.cs index cc06ffa..e424383 100644 --- a/src/CMSMicroservice.Domain/Common/SystemConstants.cs +++ b/src/CMSMicroservice.Domain/Common/SystemConstants.cs @@ -41,14 +41,14 @@ public static class SystemConstants #region Package Settings /// - /// مبلغ پکیج طلایی / پایه (ریال) - 56 میلیون تومان + /// مبلغ پکیج پایه (ریال) /// [DEPRECATED] از Package.Price استفاده کنید /// [Obsolete("Moved to Package.Price. Read from Package entity instead.")] public const long BasePackageAmount = 56_000_000; /// - /// مبلغ وام دایا (ریال) - همان مبلغ پکیج طلایی + /// مبلغ وام دایا (ریال) /// [DEPRECATED] از Package.Price استفاده کنید /// [Obsolete("Moved to Package.Price. Read from Package entity with SupportsDayaPurchase=true.")] diff --git a/src/CMSMicroservice.Domain/Entities/User.cs b/src/CMSMicroservice.Domain/Entities/User.cs index 3ceb0fd..1f1a7de 100644 --- a/src/CMSMicroservice.Domain/Entities/User.cs +++ b/src/CMSMicroservice.Domain/Entities/User.cs @@ -63,7 +63,7 @@ public class User : BaseAuditableEntity public DateTime? DayaCreditReceivedAt { get; set; } /// - /// نحوه خرید پکیج طلایی (برای جلوگیری از خرید مجدد) + /// نحوه خرید پکیج (برای جلوگیری از خرید مجدد) /// public PackagePurchaseMethod PackagePurchaseMethod { get; set; } = PackagePurchaseMethod.None; diff --git a/src/CMSMicroservice.Domain/Entities/UserWallet.cs b/src/CMSMicroservice.Domain/Entities/UserWallet.cs index a3c1e27..ce14154 100644 --- a/src/CMSMicroservice.Domain/Entities/UserWallet.cs +++ b/src/CMSMicroservice.Domain/Entities/UserWallet.cs @@ -10,7 +10,7 @@ public class UserWallet : BaseAuditableEntity public long Balance { get; set; } /// - /// موجودی شبکه/کارمزد (کیف پول طلایی) - قابل برداشت نقدی یا خرید الماس + /// موجودی شبکه/کارمزد — قابل برداشت نقدی یا خرید الماس /// public long NetworkBalance { get; set; } diff --git a/src/CMSMicroservice.Domain/Enums/CommissionPayoutStatus.cs b/src/CMSMicroservice.Domain/Enums/CommissionPayoutStatus.cs index 63868a0..e345134 100644 --- a/src/CMSMicroservice.Domain/Enums/CommissionPayoutStatus.cs +++ b/src/CMSMicroservice.Domain/Enums/CommissionPayoutStatus.cs @@ -11,7 +11,7 @@ public enum CommissionPayoutStatus Pending = 0, /// - /// واریز شده به کیف پول طلایی + /// واریز شده به کیف پول /// Paid = 1, diff --git a/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs b/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs index 3d7dbce..8ff177f 100644 --- a/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs +++ b/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs @@ -1,7 +1,7 @@ namespace CMSMicroservice.Domain.Enums; /// -/// نحوه خرید پکیج طلایی توسط کاربر +/// نحوه خرید پکیج توسط کاربر /// public enum PackagePurchaseMethod { From 161f796cd4fe1daa6243ccf19db2c7578236a1cd Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 02:34:47 +0330 Subject: [PATCH 27/44] Phase 7b: Embed orderId in callback URL for CustomerPurchasePackage - CustomerPurchasePackage: append orderId to callback URL before sending to gateway - Ensures PaymentCallback page receives orderId via query string for verification --- src/CMSMicroservice.WebApi/Services/PackageService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 3d99a43..d8e961a 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -207,13 +207,17 @@ public class PackageService : PackageContract.PackageContractBase .Select(u => new { u.Mobile }) .FirstOrDefaultAsync(context.CancellationToken); + // Embed orderId in callback URL so FrontOffice can pass it back for verification + var separator = request.CallbackUrl.Contains('?') ? "&" : "?"; + var callbackWithOrder = $"{request.CallbackUrl}{separator}orderId={purchase.Id}"; + var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest { Amount = package.Price, UserId = userId, Mobile = user?.Mobile ?? string.Empty, Description = $"خرید پکیج {package.Title}", - CallbackUrl = request.CallbackUrl + CallbackUrl = callbackWithOrder }, context.CancellationToken); if (!paymentResult.IsSuccess) From 8446e0e3b57744b10d27c9e321a0bc53a8105970 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 03:02:17 +0330 Subject: [PATCH 28/44] Phase 7c: Delete deprecated handlers + clean service/mapping refs - Delete 4 deprecated CQRS handler folders (12 files): - PurchaseGoldenPackage/ (Command, Handler, Validator) - VerifyGoldenPackagePurchase/ (Command, Handler, Validator) - InitiateBasePackagePayment/ (Command, Handler, Validator) - VerifyBasePackagePayment/ (Command, Handler, Validator) - PackageService.cs: Remove 4 gRPC override methods that dispatched to deleted handlers (proto RPCs now auto-throw Unimplemented) - PackageProfile.cs: Remove all Mapster mappings for deleted types (6 mapping blocks + 4 using directives removed) - Customer* RPCs remain fully functional - Build: 0 errors --- .../InitiateBasePackagePaymentCommand.cs | 43 ---- ...nitiateBasePackagePaymentCommandHandler.cs | 171 ------------- ...tiateBasePackagePaymentCommandValidator.cs | 13 - .../PurchaseGoldenPackageCommand.cs | 22 -- .../PurchaseGoldenPackageCommandHandler.cs | 166 ------------- .../PurchaseGoldenPackageCommandValidator.cs | 23 -- .../VerifyBasePackagePaymentCommand.cs | 72 ------ .../VerifyBasePackagePaymentCommandHandler.cs | 233 ----------------- ...erifyBasePackagePaymentCommandValidator.cs | 17 -- .../VerifyGoldenPackagePurchaseCommand.cs | 23 -- ...rifyGoldenPackagePurchaseCommandHandler.cs | 235 ------------------ ...fyGoldenPackagePurchaseCommandValidator.cs | 23 -- .../Common/Mappings/PackageProfile.cs | 59 ----- .../Services/PackageService.cs | 25 -- 14 files changed, 1125 deletions(-) delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommand.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandValidator.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommand.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandValidator.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommand.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandValidator.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommand.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandValidator.cs diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommand.cs deleted file mode 100644 index 29449f7..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommand.cs +++ /dev/null @@ -1,43 +0,0 @@ -using MediatR; - -namespace CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment; - -/// -/// ثبت اولیه تراکنش و سفارش پکیج پایه (56 میلیون تومان) -/// این Command توسط BFF فراخوانی می‌شود قبل از ارسال کاربر به درگاه -/// -public record InitiateBasePackagePaymentCommand : IRequest -{ - /// - /// شناسه کاربر - /// - public long UserId { get; init; } -} - -public class InitiateBasePackagePaymentResponseDto -{ - /// - /// موفقیت عملیات - /// - public bool Success { get; set; } - - /// - /// پیام - /// - public string Message { get; set; } = string.Empty; - - /// - /// شناسه سفارش - /// - public long OrderId { get; set; } - - /// - /// شناسه تراکنش CMS - /// - public long TransactionId { get; set; } - - /// - /// مبلغ پکیج - /// - public long Amount { get; set; } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs deleted file mode 100644 index 0fde9b1..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandHandler.cs +++ /dev/null @@ -1,171 +0,0 @@ -using CMSMicroservice.Application.Common.Exceptions; -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Domain.Entities; -using CMSMicroservice.Domain.Enums; -using CMSMicroservice.Domain.Common; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using ValidationException = FluentValidation.ValidationException; - -namespace CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment; - -/// -/// Handler برای ثبت اولیه تراکنش و سفارش پکیج پایه -/// این Handler فقط Transaction و Order را با وضعیت Pending ایجاد می‌کند -/// BFF سپس کاربر را به درگاه PYMS هدایت می‌کند -/// -public class InitiateBasePackagePaymentCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - // شناسه پکیج پایه از دیتابیس خوانده می‌شود (Package.IsBasePackage) - - public InitiateBasePackagePaymentCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle( - InitiateBasePackagePaymentCommand request, - CancellationToken cancellationToken) - { - try - { - _logger.LogInformation( - "Initiating base package payment for UserId: {UserId}", - request.UserId); - - // 1. پیدا کردن کاربر - var user = await _context.Users - .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); - - if (user == null) - { - _logger.LogWarning("User not found. UserId: {UserId}", request.UserId); - throw new NotFoundException(nameof(User), request.UserId); - } - - // 2. بررسی عدم خرید قبلی پکیج پایه - if (user.PackagePurchaseMethod != PackagePurchaseMethod.None) - { - _logger.LogWarning( - "User {UserId} has already purchased base package via {Method}", - request.UserId, - user.PackagePurchaseMethod); - - return new InitiateBasePackagePaymentResponseDto - { - Success = false, - Message = "شما قبلاً پکیج پایه را خریداری کرده‌اید." - }; - } - - // 2.5. بارگذاری پکیج پایه - var package = await _context.Packages - .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) - ?? throw new NotFoundException("پکیج پایه یافت نشد"); - - // 3. بررسی عدم وجود سفارش Pending - var pendingOrder = await _context.UserOrders - .FirstOrDefaultAsync(o => - o.UserId == request.UserId && - o.PaymentStatus == PaymentStatus.Pending && - o.PackageId == package.Id, - cancellationToken); - - if (pendingOrder != null) - { - _logger.LogWarning( - "User {UserId} has pending order {OrderId}", - request.UserId, - pendingOrder.Id); - - // برگرداندن سفارش موجود برای ادامه پرداخت - var existingTransaction = await _context.Transactions - .FirstOrDefaultAsync(t => t.Id == pendingOrder.TransactionId, cancellationToken); - - return new InitiateBasePackagePaymentResponseDto - { - Success = true, - Message = "سفارش قبلی در انتظار پرداخت یافت شد.", - OrderId = pendingOrder.Id, - TransactionId = existingTransaction?.Id ?? 0, - Amount = package.Price - }; - } - - // 4. پیدا کردن آدرس پیش‌فرض کاربر - var defaultAddress = await _context.UserAddresses - .Where(a => a.UserId == request.UserId) - .OrderByDescending(a => a.Created) - .FirstOrDefaultAsync(cancellationToken); - - if (defaultAddress == null) - { - _logger.LogWarning("No address found for user {UserId}", request.UserId); - throw new ValidationException("لطفاً ابتدا یک آدرس برای خود ثبت کنید."); - } - - // 5. ایجاد Transaction با وضعیت Pending - var transaction = new Transaction - { - Amount = package.Price, - Description = $"خرید پکیج {package.Title} - کاربر #{user.Id}", - PaymentStatus = PaymentStatus.Pending, - Type = TransactionType.DepositIpg - }; - - _context.Transactions.Add(transaction); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Created Transaction {TransactionId} with Pending status for UserId {UserId}", - transaction.Id, - request.UserId); - - // 6. ایجاد UserOrder با وضعیت Pending - var order = new UserOrder - { - UserId = user.Id, - PackageId = package.Id, - Amount = package.Price, - PaymentStatus = PaymentStatus.Pending, - DeliveryStatus = DeliveryStatus.None, - UserAddressId = defaultAddress.Id, - PaymentMethod = PaymentMethod.IPG, - TransactionId = transaction.Id - }; - - _context.UserOrders.Add(order); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Created UserOrder {OrderId} for UserId {UserId}, TransactionId: {TransactionId}", - order.Id, - request.UserId, - transaction.Id); - - return new InitiateBasePackagePaymentResponseDto - { - Success = true, - Message = "تراکنش و سفارش با موفقیت ثبت شد.", - OrderId = order.Id, - TransactionId = transaction.Id, - Amount = package.Price - }; - } - catch (Exception ex) - { - _logger.LogError( - ex, - "Error in InitiateBasePackagePaymentCommand for UserId: {UserId}", - request.UserId); - throw; - } - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandValidator.cs deleted file mode 100644 index 6639b26..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/InitiateBasePackagePayment/InitiateBasePackagePaymentCommandValidator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FluentValidation; - -namespace CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment; - -public class InitiateBasePackagePaymentCommandValidator : AbstractValidator -{ - public InitiateBasePackagePaymentCommandValidator() - { - RuleFor(x => x.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر معتبر نیست."); - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommand.cs deleted file mode 100644 index e05e252..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommand.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; - -namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage; - -/// -/// خرید پکیج طلایی (شروع فرآیند پرداخت) -/// -public record PurchaseGoldenPackageCommand : IRequest -{ - public long UserId { get; init; } - public long PackageId { get; init; } - public string ReturnUrl { get; init; } = string.Empty; -} - -public class PurchaseGoldenPackageResponseDto -{ - public bool Success { get; set; } - public string Message { get; set; } = string.Empty; - public long OrderId { get; set; } - public string PaymentGatewayUrl { get; set; } = string.Empty; - public string TrackingCode { get; set; } = string.Empty; -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs deleted file mode 100644 index 667daf2..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs +++ /dev/null @@ -1,166 +0,0 @@ -using CMSMicroservice.Application.Common.Exceptions; -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Domain.Entities; -using CMSMicroservice.Domain.Enums; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using ValidationException = FluentValidation.ValidationException; - -namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage; - -public class PurchaseGoldenPackageCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IPaymentGatewayService _paymentGateway; - private readonly ILogger _logger; - - public PurchaseGoldenPackageCommandHandler( - IApplicationDbContext context, - IPaymentGatewayService paymentGateway, - ILogger logger) - { - _context = context; - _paymentGateway = paymentGateway; - _logger = logger; - } - - public async Task Handle(PurchaseGoldenPackageCommand request, CancellationToken cancellationToken) - { - try - { - _logger.LogInformation( - "Starting golden package purchase for UserId: {UserId}, PackageId: {PackageId}", - request.UserId, - request.PackageId); - - // 1. پیدا کردن کاربر - var user = await _context.Users - .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); - - if (user == null) - { - _logger.LogWarning("User not found for golden package purchase. UserId: {UserId}", request.UserId); - throw new NotFoundException(nameof(User), request.UserId); - } - - // 2. جلوگیری از خرید مجدد پکیج طلایی - if (user.PackagePurchaseMethod != PackagePurchaseMethod.None) - { - _logger.LogWarning( - "User {UserId} has already purchased golden package via {Method}", - request.UserId, - user.PackagePurchaseMethod); - - throw new ValidationException("شما قبلاً پکیج طلایی را خریداری کرده‌اید."); - } - - // 3. پیدا کردن پکیج - var package = await _context.Packages - .FirstOrDefaultAsync(p => p.Id == request.PackageId, cancellationToken); - - if (package == null) - { - _logger.LogWarning("Golden package not found. PackageId: {PackageId}", request.PackageId); - throw new NotFoundException(nameof(Package), request.PackageId); - } - - // اطمینان از فعال بودن و قابل خرید بودن پکیج - if (package.IsDeleted || !package.IsActive) - { - _logger.LogWarning( - "PackageId {PackageId} is not available. IsDeleted: {IsDeleted}, IsActive: {IsActive}", - request.PackageId, - package.IsDeleted, - package.IsActive); - - throw new ValidationException("این پکیج در حال حاضر قابل خرید نیست."); - } - - if (!package.SupportsDirectPurchase) - { - throw new ValidationException("این پکیج فقط از طریق وام دایا قابل خرید است."); - } - - // 4. پیدا کردن آدرس پیش‌فرض کاربر (الزامی برای UserOrder) - var defaultAddress = await _context.UserAddresses - .Where(a => a.UserId == request.UserId) - .OrderByDescending(a => a.Created) - .FirstOrDefaultAsync(cancellationToken); - - if (defaultAddress == null) - { - _logger.LogWarning("No address found for user {UserId} in golden package purchase", request.UserId); - throw new ValidationException("لطفاً ابتدا یک آدرس برای خود ثبت کنید."); - } - - // 5. ایجاد سفارش - var order = new UserOrder - { - UserId = user.Id, - PackageId = package.Id, - Amount = package.Price, - PaymentStatus = PaymentStatus.Pending, - DeliveryStatus = DeliveryStatus.None, - UserAddressId = defaultAddress.Id, - PaymentMethod = PaymentMethod.IPG - }; - - _context.UserOrders.Add(order); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Created golden package UserOrder {OrderId} for UserId {UserId}, Amount: {Amount}", - order.Id, - request.UserId, - order.Amount); - - // 6. شروع پرداخت با درگاه - var paymentRequest = new PaymentRequest - { - Amount = order.Amount, - UserId = user.Id, - Mobile = user.Mobile ?? string.Empty, - CallbackUrl = request.ReturnUrl, - Description = $"خرید پکیج طلایی - سفارش #{order.Id}" - }; - - var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest, cancellationToken); - - if (!paymentResult.IsSuccess) - { - _logger.LogError( - "Payment gateway initiation failed for golden package. OrderId {OrderId}: {ErrorMessage}", - order.Id, - paymentResult.ErrorMessage); - - order.PaymentStatus = PaymentStatus.Reject; - await _context.SaveChangesAsync(cancellationToken); - - throw new Exception($"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}"); - } - - _logger.LogInformation( - "Golden package payment initiated successfully. OrderId: {OrderId}, RefId: {RefId}", - order.Id, - paymentResult.RefId); - - return new PurchaseGoldenPackageResponseDto - { - Success = true, - Message = "لطفاً به درگاه پرداخت منتقل شوید.", - OrderId = order.Id, - PaymentGatewayUrl = paymentResult.GatewayUrl ?? string.Empty, - TrackingCode = paymentResult.RefId ?? string.Empty - }; - } - catch (Exception ex) - { - _logger.LogError( - ex, - "Error in PurchaseGoldenPackageCommand for UserId: {UserId}", - request.UserId); - throw; - } - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandValidator.cs deleted file mode 100644 index b9f4411..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandValidator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using FluentValidation; - -namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage; - -public class PurchaseGoldenPackageCommandValidator : AbstractValidator -{ - public PurchaseGoldenPackageCommandValidator() - { - RuleFor(x => x.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر باید بزرگتر از 0 باشد"); - - RuleFor(x => x.PackageId) - .GreaterThan(0) - .WithMessage("شناسه پکیج باید بزرگتر از 0 باشد"); - - RuleFor(x => x.ReturnUrl) - .NotEmpty() - .WithMessage("آدرس بازگشت الزامی است") - .Must(url => Uri.TryCreate(url, UriKind.Absolute, out _)) - .WithMessage("آدرس بازگشت معتبر نیست"); - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommand.cs deleted file mode 100644 index 9de3357..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommand.cs +++ /dev/null @@ -1,72 +0,0 @@ -using MediatR; - -namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment; - -/// -/// تکمیل یا رد پرداخت پکیج پایه (فراخوانی توسط BFF بعد از Callback) -/// -public record VerifyBasePackagePaymentCommand : IRequest -{ - /// - /// شناسه سفارش CMS - /// - public long OrderId { get; init; } - - /// - /// شناسه تراکنش CMS - /// - public long TransactionId { get; init; } - - /// - /// آیا پرداخت موفق بوده؟ (نتیجه Verify از BFF) - /// - public bool PaymentSuccess { get; init; } - - /// - /// کد پیگیری بانکی (در صورت موفقیت) - /// - public string? RefId { get; init; } - - /// - /// پیام از درگاه - /// - public string? Message { get; init; } -} - -public class VerifyBasePackagePaymentResponseDto -{ - /// - /// موفقیت عملیات - /// - public bool Success { get; set; } - - /// - /// پیام - /// - public string Message { get; set; } = string.Empty; - - /// - /// شناسه سفارش - /// - public long OrderId { get; set; } - - /// - /// شناسه تراکنش - /// - public long TransactionId { get; set; } - - /// - /// کد پیگیری بانکی - /// - public string? ReferenceCode { get; set; } - - /// - /// موجودی کیف پول بعد از شارژ - /// - public long WalletBalance { get; set; } - - /// - /// موجودی تخفیف بعد از شارژ - /// - public long DiscountBalance { get; set; } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs deleted file mode 100644 index 7708c9f..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandHandler.cs +++ /dev/null @@ -1,233 +0,0 @@ -using CMSMicroservice.Application.Common.Exceptions; -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Domain.Entities; -using CMSMicroservice.Domain.Enums; -using CMSMicroservice.Domain.Common; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment; - -/// -/// Handler برای تکمیل یا رد پرداخت پکیج پایه -/// فراخوانی توسط BFF بعد از اینکه نتیجه Verify از PYMS مشخص شد -/// -/// اگر PaymentSuccess = true: -/// - شارژ کیف پول (Balance + DiscountBalance) -/// - ثبت لاگ تغییر کیف پول -/// - به‌روزرسانی سفارش و کاربر -/// -/// اگر PaymentSuccess = false: -/// - فقط آپدیت وضعیت تراکنش و سفارش به Reject -/// -public class VerifyBasePackagePaymentCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public VerifyBasePackagePaymentCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle( - VerifyBasePackagePaymentCommand request, - CancellationToken cancellationToken) - { - try - { - _logger.LogInformation( - "Processing base package payment result. OrderId: {OrderId}, TransactionId: {TransactionId}, PaymentSuccess: {PaymentSuccess}", - request.OrderId, - request.TransactionId, - request.PaymentSuccess); - - // 1. پیدا کردن سفارش با کاربر - var order = await _context.UserOrders - .Include(o => o.User) - .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken); - - if (order == null) - { - _logger.LogWarning("Order not found. OrderId: {OrderId}", request.OrderId); - throw new NotFoundException(nameof(UserOrder), request.OrderId); - } - - // 2. پیدا کردن تراکنش - var transaction = await _context.Transactions - .FirstOrDefaultAsync(t => t.Id == request.TransactionId, cancellationToken); - - if (transaction == null) - { - _logger.LogWarning("Transaction not found. TransactionId: {TransactionId}", request.TransactionId); - throw new NotFoundException(nameof(Transaction), request.TransactionId); - } - - // 3. بررسی Idempotency - اگر قبلاً تایید شده باشد - if (order.PaymentStatus == PaymentStatus.Success) - { - _logger.LogInformation( - "Order {OrderId} already verified successfully", - request.OrderId); - - var existingWallet = await _context.UserWallets - .FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken); - - return new VerifyBasePackagePaymentResponseDto - { - Success = true, - Message = "پرداخت قبلاً با موفقیت تایید شده است.", - OrderId = order.Id, - TransactionId = transaction.Id, - ReferenceCode = transaction.RefId, - WalletBalance = existingWallet?.Balance ?? 0, - DiscountBalance = existingWallet?.DiscountBalance ?? 0 - }; - } - - // 4. اگر پرداخت ناموفق بود - فقط وضعیت را Reject می‌کنیم - if (!request.PaymentSuccess) - { - _logger.LogWarning( - "Payment failed. OrderId: {OrderId}, Message: {Message}", - request.OrderId, - request.Message); - - transaction.PaymentStatus = PaymentStatus.Reject; - order.PaymentStatus = PaymentStatus.Reject; - await _context.SaveChangesAsync(cancellationToken); - - return new VerifyBasePackagePaymentResponseDto - { - Success = false, - Message = request.Message ?? "پرداخت ناموفق بود.", - OrderId = order.Id, - TransactionId = transaction.Id - }; - } - - // 5. پرداخت موفق - پیدا کردن یا ایجاد کیف پول - var userWallet = await _context.UserWallets - .FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken); - - if (userWallet == null) - { - _logger.LogInformation("Creating new wallet for UserId: {UserId}", order.UserId); - userWallet = new UserWallet - { - UserId = order.UserId, - Balance = 0, - DiscountBalance = 0, - NetworkBalance = 0 - }; - _context.UserWallets.Add(userWallet); - await _context.SaveChangesAsync(cancellationToken); - } - - // 5.5. بارگذاری پکیج از سفارش - var package = order.PackageId.HasValue - ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == order.PackageId.Value, cancellationToken) - : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); - - if (package == null) - throw new NotFoundException("پکیج یافت نشد"); - - // 6. شارژ کیف پول (هم Balance و هم DiscountBalance) - var oldBalance = userWallet.Balance; - var oldDiscountBalance = userWallet.DiscountBalance; - - userWallet.Balance += package.Price; - userWallet.DiscountBalance += (long)(package.Price * package.DiscountMultiplier); - - _logger.LogInformation( - "Charging wallet for user {UserId}. Balance: {OldBalance} -> {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}", - order.UserId, - oldBalance, - userWallet.Balance, - oldDiscountBalance, - userWallet.DiscountBalance); - - // 7. به‌روزرسانی Transaction - transaction.PaymentStatus = PaymentStatus.Success; - transaction.PaymentDate = DateTime.Now; - transaction.RefId = request.RefId; - - // 8. ثبت لاگ تغییر کیف پول - var changeLog = new UserWalletChangeLog - { - WalletId = userWallet.Id, - CurrentBalance = userWallet.Balance, - ChangeValue = package.Price, - CurrentNetworkBalance = userWallet.NetworkBalance, - ChangeNerworkValue = 0, - CurrentDiscountBalance = userWallet.DiscountBalance, - ChangeDiscountValue = (long)(package.Price * package.DiscountMultiplier), - IsIncrease = true, - RefrenceId = transaction.Id - }; - - await _context.UserWalletChangeLogs.AddAsync(changeLog, cancellationToken); - - // 9. ثبت UserPackagePurchase - if (order.PackageId.HasValue) - { - var packagePurchase = new UserPackagePurchase - { - UserId = order.UserId, - PackageId = order.PackageId.Value, - PurchaseMethod = PackagePurchaseMethod.DirectPurchase, - PurchasedAt = DateTime.Now, - Amount = package.Price, - OrderId = order.Id, - TransactionId = transaction.Id - }; - await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken); - - _logger.LogInformation( - "Created UserPackagePurchase for UserId {UserId}, PackageId {PackageId}", - order.UserId, order.PackageId.Value); - } - - // 10. به‌روزرسانی Order - order.TransactionId = transaction.Id; - order.PaymentStatus = PaymentStatus.Success; - order.PaymentDate = DateTime.Now; - order.PaymentMethod = PaymentMethod.IPG; - - // 11. به‌روزرسانی User - order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase; - - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Base package payment completed successfully. OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}", - order.Id, - order.UserId, - transaction.Id, - request.RefId); - - return new VerifyBasePackagePaymentResponseDto - { - Success = true, - Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید.", - OrderId = order.Id, - TransactionId = transaction.Id, - ReferenceCode = request.RefId, - WalletBalance = userWallet.Balance, - DiscountBalance = userWallet.DiscountBalance - }; - } - catch (Exception ex) - { - _logger.LogError( - ex, - "Error in VerifyBasePackagePaymentCommand. OrderId: {OrderId}", - request.OrderId); - throw; - } - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandValidator.cs deleted file mode 100644 index 52e03a3..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyBasePackagePayment/VerifyBasePackagePaymentCommandValidator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FluentValidation; - -namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment; - -public class VerifyBasePackagePaymentCommandValidator : AbstractValidator -{ - public VerifyBasePackagePaymentCommandValidator() - { - RuleFor(x => x.OrderId) - .GreaterThan(0) - .WithMessage("شناسه سفارش معتبر نیست."); - - RuleFor(x => x.TransactionId) - .GreaterThan(0) - .WithMessage("شناسه تراکنش معتبر نیست."); - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommand.cs deleted file mode 100644 index d2ec876..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommand.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediatR; - -namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase; - -/// -/// تایید پرداخت پکیج طلایی (پس از بازگشت از درگاه) -/// -public record VerifyGoldenPackagePurchaseCommand : IRequest -{ - public long OrderId { get; init; } - public string Authority { get; init; } = string.Empty; - public string Status { get; init; } = string.Empty; // OK یا NOK -} - -public class VerifyGoldenPackagePurchaseResponseDto -{ - public bool Success { get; set; } - public string Message { get; set; } = string.Empty; - public long OrderId { get; set; } - public long TransactionId { get; set; } - public string ReferenceCode { get; set; } = string.Empty; - public long WalletBalance { get; set; } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs deleted file mode 100644 index 08aa7ba..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs +++ /dev/null @@ -1,235 +0,0 @@ -using CMSMicroservice.Application.Common.Exceptions; -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Domain.Entities; -using CMSMicroservice.Domain.Enums; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using ValidationException = FluentValidation.ValidationException; - -namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase; - -public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IPaymentGatewayService _paymentGateway; - private readonly ILogger _logger; - - public VerifyGoldenPackagePurchaseCommandHandler( - IApplicationDbContext context, - IPaymentGatewayService paymentGateway, - ILogger logger) - { - _context = context; - _paymentGateway = paymentGateway; - _logger = logger; - } - - public async Task Handle(VerifyGoldenPackagePurchaseCommand request, CancellationToken cancellationToken) - { - try - { - _logger.LogInformation( - "Verifying golden package purchase. OrderId: {OrderId}, Authority: {Authority}, Status: {Status}", - request.OrderId, - request.Authority, - request.Status); - - // 1. اگر پرداخت از سمت درگاه موفق گزارش نشده باشد - if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase)) - { - var pendingOrder = await _context.UserOrders - .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken); - - if (pendingOrder != null && pendingOrder.PaymentStatus == PaymentStatus.Pending) - { - pendingOrder.PaymentStatus = PaymentStatus.Reject; - await _context.SaveChangesAsync(cancellationToken); - } - - throw new ValidationException("پرداخت توسط کاربر لغو شد."); - } - - // 2. پیدا کردن سفارش به همراه کاربر - var order = await _context.UserOrders - .Include(o => o.User) - .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken); - - if (order == null) - { - _logger.LogWarning("Golden package order not found. OrderId: {OrderId}", request.OrderId); - throw new NotFoundException(nameof(UserOrder), request.OrderId); - } - - // اگر قبلاً با موفقیت پرداخت شده، پاسخ idempotent برگردانیم - if (order.PaymentStatus == PaymentStatus.Success && order.TransactionId.HasValue) - { - var existingWallet = await _context.UserWallets - .FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken); - - var existingTransaction = await _context.Transactions - .FirstOrDefaultAsync(t => t.Id == order.TransactionId.Value, cancellationToken); - - return new VerifyGoldenPackagePurchaseResponseDto - { - Success = true, - Message = "پرداخت قبلاً با موفقیت تایید شده است.", - OrderId = order.Id, - TransactionId = existingTransaction?.Id ?? order.TransactionId.Value, - ReferenceCode = existingTransaction?.RefId ?? string.Empty, - WalletBalance = existingWallet?.Balance ?? 0 - }; - } - - // 3. Verify با درگاه پرداخت - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - request.Authority, - request.Authority, - cancellationToken); - - if (!verifyResult.IsSuccess) - { - _logger.LogWarning( - "Golden package payment verification failed. OrderId: {OrderId}, Message: {Message}", - request.OrderId, - verifyResult.Message); - - order.PaymentStatus = PaymentStatus.Reject; - await _context.SaveChangesAsync(cancellationToken); - - throw new ValidationException($"تراکنش ناموفق: {verifyResult.Message}"); - } - - // 4. بارگذاری پکیج برای ضریب تخفیف - var package = order.PackageId.HasValue - ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == order.PackageId.Value, cancellationToken) - : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); - - var discountMultiplier = package?.DiscountMultiplier ?? 2.0m; - - // 5. شارژ کیف پول (Balance فقط طبق سناریوی پکیج) - var wallet = await _context.UserWallets - .FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken); - - if (wallet == null) - { - _logger.LogError("Wallet not found for UserId: {UserId}", order.UserId); - throw new NotFoundException($"کیف پول کاربر با شناسه {order.UserId} یافت نشد"); - } - - var oldBalance = wallet.Balance; - wallet.Balance += order.Amount; - - // شارژ DiscountBalance (موجودی اعتباری) — ضریب تخفیف از پکیج - var oldDiscountBalance = wallet.DiscountBalance; - var discountAmount = (long)(order.Amount * discountMultiplier); - wallet.DiscountBalance += discountAmount; - - _logger.LogInformation( - "Charging wallet for user {UserId}. Balance: {OldBalance} -> {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}", - order.UserId, - oldBalance, - wallet.Balance, - oldDiscountBalance, - wallet.DiscountBalance); - - // 5. ثبت Transaction - var transaction = new Transaction - { - Amount = order.Amount, - Description = $"خرید پکیج از درگاه - سفارش #{order.Id}", - PaymentStatus = PaymentStatus.Success, - PaymentDate = DateTime.Now, - RefId = verifyResult.RefId, - Type = TransactionType.DepositIpg - }; - - _context.Transactions.Add(transaction); - await _context.SaveChangesAsync(cancellationToken); - - // 6. ثبت لاگ تغییر Balance - var balanceLog = new UserWalletChangeLog - { - WalletId = wallet.Id, - CurrentBalance = wallet.Balance, - ChangeValue = order.Amount, - CurrentNetworkBalance = wallet.NetworkBalance, - ChangeNerworkValue = 0, - CurrentDiscountBalance = oldDiscountBalance, - ChangeDiscountValue = 0, - IsIncrease = true, - RefrenceId = transaction.Id - }; - await _context.UserWalletChangeLogs.AddAsync(balanceLog, cancellationToken); - - // 7. ثبت لاگ تغییر DiscountBalance - var discountLog = new UserWalletChangeLog - { - WalletId = wallet.Id, - CurrentBalance = wallet.Balance, - ChangeValue = 0, - CurrentNetworkBalance = wallet.NetworkBalance, - ChangeNerworkValue = 0, - CurrentDiscountBalance = wallet.DiscountBalance, - ChangeDiscountValue = discountAmount, - IsIncrease = true, - RefrenceId = transaction.Id - }; - await _context.UserWalletChangeLogs.AddAsync(discountLog, cancellationToken); - - // 8. ثبت UserPackagePurchase - if (order.PackageId.HasValue) - { - var packagePurchase = new UserPackagePurchase - { - UserId = order.UserId, - PackageId = order.PackageId.Value, - PurchaseMethod = PackagePurchaseMethod.DirectPurchase, - PurchasedAt = DateTime.Now, - Amount = order.Amount, - OrderId = order.Id, - TransactionId = transaction.Id - }; - await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken); - - _logger.LogInformation( - "Created UserPackagePurchase for UserId {UserId}, PackageId {PackageId}", - order.UserId, order.PackageId.Value); - } - - // 9. به‌روزرسانی سفارش و کاربر - order.TransactionId = transaction.Id; - order.PaymentStatus = PaymentStatus.Success; - order.PaymentDate = DateTime.Now; - order.PaymentMethod = PaymentMethod.IPG; - order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase; - - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Golden package purchase verified successfully. OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}", - order.Id, - order.UserId, - transaction.Id, - verifyResult.RefId); - - return new VerifyGoldenPackagePurchaseResponseDto - { - Success = true, - Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید.", - OrderId = order.Id, - TransactionId = transaction.Id, - ReferenceCode = verifyResult.RefId, - WalletBalance = wallet.Balance - }; - } - catch (Exception ex) - { - _logger.LogError( - ex, - "Error in VerifyGoldenPackagePurchaseCommand. OrderId: {OrderId}", - request.OrderId); - throw; - } - } -} diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandValidator.cs deleted file mode 100644 index 8442702..0000000 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandValidator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using FluentValidation; - -namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase; - -public class VerifyGoldenPackagePurchaseCommandValidator : AbstractValidator -{ - public VerifyGoldenPackagePurchaseCommandValidator() - { - RuleFor(x => x.OrderId) - .GreaterThan(0) - .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد"); - - RuleFor(x => x.Authority) - .NotEmpty() - .WithMessage("کد Authority الزامی است"); - - RuleFor(x => x.Status) - .NotEmpty() - .WithMessage("وضعیت پرداخت الزامی است") - .Must(s => s == "OK" || s == "NOK") - .WithMessage("وضعیت باید OK یا NOK باشد"); - } -} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs index 915d1c5..d8702dc 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs @@ -1,7 +1,3 @@ -using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage; -using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase; -using CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment; -using CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment; using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus; using CMSMicroservice.Protobuf.Protos.Package; using Google.Protobuf.WellKnownTypes; @@ -12,33 +8,6 @@ public class PackageProfile : IRegister { void IRegister.Register(TypeAdapterConfig config) { - // PurchaseGoldenPackage - config.NewConfig() - .Map(dest => dest.UserId, src => src.UserId) - .Map(dest => dest.PackageId, src => src.PackageId) - .Map(dest => dest.ReturnUrl, src => src.ReturnUrl); - - config.NewConfig() - .Map(dest => dest.Success, src => src.Success) - .Map(dest => dest.Message, src => src.Message) - .Map(dest => dest.OrderId, src => src.OrderId) - .Map(dest => dest.PaymentGatewayUrl, src => src.PaymentGatewayUrl) - .Map(dest => dest.TrackingCode, src => src.TrackingCode); - - // VerifyGoldenPackagePurchase - config.NewConfig() - .Map(dest => dest.OrderId, src => src.OrderId) - .Map(dest => dest.Authority, src => src.Authority) - .Map(dest => dest.Status, src => src.Status); - - config.NewConfig() - .Map(dest => dest.Success, src => src.Success) - .Map(dest => dest.Message, src => src.Message) - .Map(dest => dest.OrderId, src => src.OrderId) - .Map(dest => dest.TransactionId, src => src.TransactionId) - .Map(dest => dest.ReferenceCode, src => src.ReferenceCode) - .Map(dest => dest.WalletBalance, src => src.WalletBalance); - // GetUserPackageStatus config.NewConfig() .Map(dest => dest.UserId, src => src.UserId); @@ -55,34 +24,6 @@ public class PackageProfile : IRegister .Map(dest => dest.LastPurchaseDate, src => src.LastPurchaseDate.HasValue ? Timestamp.FromDateTime(src.LastPurchaseDate.Value.ToUniversalTime()) : null); - - // InitiateBasePackagePayment (56M پکیج پایه) - config.NewConfig() - .Map(dest => dest.UserId, src => src.UserId); - - config.NewConfig() - .Map(dest => dest.Success, src => src.Success) - .Map(dest => dest.Message, src => src.Message) - .Map(dest => dest.OrderId, src => src.OrderId) - .Map(dest => dest.TransactionId, src => src.TransactionId) - .Map(dest => dest.Amount, src => src.Amount); - - // VerifyBasePackagePayment (تأیید پرداخت پکیج پایه) - config.NewConfig() - .Map(dest => dest.OrderId, src => src.OrderId) - .Map(dest => dest.TransactionId, src => src.TransactionId) - .Map(dest => dest.PaymentSuccess, src => src.PaymentSuccess) - .Map(dest => dest.RefId, src => src.RefId) - .Map(dest => dest.Message, src => src.Message); - - config.NewConfig() - .Map(dest => dest.Success, src => src.Success) - .Map(dest => dest.Message, src => src.Message) - .Map(dest => dest.OrderId, src => src.OrderId) - .Map(dest => dest.TransactionId, src => src.TransactionId) - .Map(dest => dest.ReferenceCode, src => src.ReferenceCode) - .Map(dest => dest.WalletBalance, src => src.WalletBalance) - .Map(dest => dest.DiscountBalance, src => src.DiscountBalance); } } diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index d8e961a..b8dcf21 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -3,10 +3,6 @@ using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.PackageCQ.Commands.CreateNewPackage; using CMSMicroservice.Application.PackageCQ.Commands.UpdatePackage; using CMSMicroservice.Application.PackageCQ.Commands.DeletePackage; -using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage; -using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase; -using CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment; -using CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment; using CMSMicroservice.Application.PackageCQ.Queries.GetPackage; using CMSMicroservice.Application.PackageCQ.Queries.GetAllPackageByFilter; using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus; @@ -72,32 +68,11 @@ public class PackageService : PackageContract.PackageContractBase return await _dispatchRequestToCQRS.Handle(request, context); } - public override async Task PurchaseGoldenPackage(PurchaseGoldenPackageRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - - public override async Task VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - public override async Task GetUserPackageStatus(GetUserPackageStatusRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } - // Base Package Payment (56M) - public override async Task InitiateBasePackagePayment(InitiateBasePackagePaymentRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - - public override async Task VerifyBasePackagePayment(VerifyBasePackagePaymentRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - // ============= Customer-specific Method Implementations ============= public override async Task GetCustomerPackages(GetCustomerPackagesRequest request, ServerCallContext context) From ce8e248843105a64e32067902d9c64045612d7ac Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 17:42:59 +0330 Subject: [PATCH 29/44] Phase 8b: Bump Protobuf NuGet to v0.0.185 --- src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index fd03f97..28fe06a 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.184 + 0.0.185 None False False From 7554d7054ade23bc0195c5712d2f8e7cdc0e00a7 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 18:00:40 +0330 Subject: [PATCH 30/44] =?UTF-8?q?Phase=208d:=20Proto=20cleanup=20=E2=80=94?= =?UTF-8?q?=20delete=204=20deprecated=20RPCs=20+=208=20deprecated=20messag?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Deleted RPCs: PurchaseGoldenPackage, VerifyGoldenPackagePurchase, InitiateBasePackagePayment, VerifyBasePackagePayment - Deleted messages: PurchaseGoldenPackage{Request,Response}, VerifyGoldenPackagePurchase{Request,Response}, InitiateBasePackagePayment{Request,Response}, VerifyBasePackagePayment{Request,Response} - Kept: GetUserPackageStatus (still in use) - NuGet version bump: 0.0.185 → 0.0.186 --- .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/package.proto | 102 ------------------ 2 files changed, 1 insertion(+), 103 deletions(-) diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 28fe06a..be7ffd8 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.185 + 0.0.186 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto index 3e6e731..65dc3cf 100644 --- a/src/CMSMicroservice.Protobuf/Protos/package.proto +++ b/src/CMSMicroservice.Protobuf/Protos/package.proto @@ -44,43 +44,12 @@ service PackageContract }; }; - // Package Purchase System (Legacy — use Customer* RPCs instead) - rpc PurchaseGoldenPackage(PurchaseGoldenPackageRequest) returns (PurchaseGoldenPackageResponse){ - option deprecated = true; - option (google.api.http) = { - post: "/PurchaseGoldenPackage" - body: "*" - }; - }; - rpc VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest) returns (VerifyGoldenPackagePurchaseResponse){ - option deprecated = true; - option (google.api.http) = { - post: "/VerifyGoldenPackagePurchase" - body: "*" - }; - }; rpc GetUserPackageStatus(GetUserPackageStatusRequest) returns (GetUserPackageStatusResponse){ option (google.api.http) = { get: "/GetUserPackageStatus" }; }; - // Base Package Payment (Legacy — migrate to Customer* RPCs, then remove) - rpc InitiateBasePackagePayment(InitiateBasePackagePaymentRequest) returns (InitiateBasePackagePaymentResponse){ - option deprecated = true; - option (google.api.http) = { - post: "/InitiateBasePackagePayment" - body: "*" - }; - }; - rpc VerifyBasePackagePayment(VerifyBasePackagePaymentRequest) returns (VerifyBasePackagePaymentResponse){ - option deprecated = true; - option (google.api.http) = { - post: "/VerifyBasePackagePayment" - body: "*" - }; - }; - // ============= Customer-specific Methods ============= rpc GetCustomerPackages(GetCustomerPackagesRequest) returns (GetCustomerPackagesResponse){ @@ -224,40 +193,6 @@ message GetAllPackageByFilterResponseModel int64 magic_wallet_max_credit = 17; } -// Package Purchase Messages -message PurchaseGoldenPackageRequest -{ - int64 user_id = 1; - int64 package_id = 2; - string return_url = 3; -} - -message PurchaseGoldenPackageResponse -{ - bool success = 1; - string message = 2; - int64 order_id = 3; - string payment_gateway_url = 4; - string tracking_code = 5; -} - -message VerifyGoldenPackagePurchaseRequest -{ - int64 order_id = 1; - string authority = 2; - string status = 3; -} - -message VerifyGoldenPackagePurchaseResponse -{ - bool success = 1; - string message = 2; - int64 order_id = 3; - int64 transaction_id = 4; - string reference_code = 5; - int64 wallet_balance = 6; -} - message GetUserPackageStatusRequest { int64 user_id = 1; @@ -276,43 +211,6 @@ message GetUserPackageStatusResponse google.protobuf.Timestamp last_purchase_date = 9; } -// Base Package Payment Messages -message InitiateBasePackagePaymentRequest -{ - int64 user_id = 1; - string callback_url = 2; // Payment gateway callback URL -} - -message InitiateBasePackagePaymentResponse -{ - bool success = 1; - string message = 2; - int64 order_id = 3; - int64 transaction_id = 4; - int64 amount = 5; - string payment_gateway_url = 6; -} - -message VerifyBasePackagePaymentRequest -{ - int64 order_id = 1; - int64 transaction_id = 2; - bool payment_success = 3; - google.protobuf.StringValue ref_id = 4; - google.protobuf.StringValue message = 5; -} - -message VerifyBasePackagePaymentResponse -{ - bool success = 1; - string message = 2; - int64 order_id = 3; - int64 transaction_id = 4; - google.protobuf.StringValue reference_code = 5; - int64 wallet_balance = 6; - int64 discount_balance = 7; -} - // ============= Customer Message Types ============= message GetCustomerPackagesRequest From aaaf7fc1ca37d72acb056d4eb633ee772b7094e9 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 21:07:59 +0330 Subject: [PATCH 31/44] feat(commission): add per-package filtering to commission & balance queries - Proto: add package_id filter to 4 request messages, package_id/package_title to 4 response models - Queries: add PackageId filter to GetUserWeeklyBalances, GetUserCommissionPayouts, GetMyCommissionPayouts, GetMyWeeklyBalances - Handlers: include Package navigation, filter by PackageId, map PackageId/PackageTitle - DTOs: add PackageId + PackageTitle to all response models - Mappings: update CommissionProfile with PackageId/PackageTitle for all admin+customer mappings - NuGet: bump proto version to 0.0.187 --- .../GetMyCommissionPayoutsQuery.cs | 5 +++++ .../GetMyCommissionPayoutsQueryHandler.cs | 11 ++++++++++- .../GetMyCommissionPayoutsResponseDto.cs | 4 ++++ .../GetMyWeeklyBalancesQuery.cs | 5 +++++ .../GetMyWeeklyBalancesQueryHandler.cs | 1 + .../GetUserCommissionPayoutsQuery.cs | 5 +++++ .../GetUserCommissionPayoutsQueryHandler.cs | 11 ++++++++++- .../GetUserCommissionPayoutsResponseDto.cs | 4 ++++ .../GetUserWeeklyBalancesQuery.cs | 4 ++++ .../GetUserWeeklyBalancesQueryHandler.cs | 11 ++++++++++- .../GetUserWeeklyBalancesResponseDto.cs | 4 ++++ .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/commission.proto | 12 ++++++++++++ .../Common/Mappings/CommissionProfile.cs | 18 ++++++++++++++---- 14 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQuery.cs index 46ec9cc..07ae6bf 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQuery.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQuery.cs @@ -18,6 +18,11 @@ public record GetMyCommissionPayoutsQuery : IRequest public long? WeekDefinitionId { get; init; } + /// + /// فیلتر بر اساس پکیج (اختیاری) + /// + public long? PackageId { get; init; } + /// /// Pagination /// diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs index 6011d84..ab6e60a 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs @@ -28,6 +28,7 @@ public class GetMyCommissionPayoutsQueryHandler : IRequestHandler x.WeekDefinition) + .Include(x => x.Package) .Where(x => x.UserId == userId) .AsNoTracking() .AsQueryable(); @@ -43,6 +44,12 @@ public class GetMyCommissionPayoutsQueryHandler : IRequestHandler x.WeekDefinitionId == request.WeekDefinitionId.Value); } + // فیلتر بر اساس پکیج + if (request.PackageId.HasValue && request.PackageId.Value > 0) + { + query = query.Where(x => x.PackageId == request.PackageId.Value); + } + // مرتب‌سازی: جدیدترین اول query = query.OrderByDescending(x => x.Created); @@ -60,7 +67,9 @@ public class GetMyCommissionPayoutsQueryHandler : IRequestHandler public bool OnlyActive { get; init; } + /// + /// فیلتر بر اساس پکیج (اختیاری) + /// + public long? PackageId { get; init; } + /// /// Pagination /// diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQueryHandler.cs index ae27b89..ee046fd 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQueryHandler.cs @@ -38,6 +38,7 @@ public class GetMyWeeklyBalancesQueryHandler : IRequestHandler public long? WeekDefinitionId { get; init; } + /// + /// فیلتر بر اساس پکیج (اختیاری) + /// + public long? PackageId { get; init; } + /// /// مرتب‌سازی /// diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs index 8674908..9f02fad 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs @@ -18,6 +18,7 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler x.WeekDefinition) .Include(x => x.User) + .Include(x => x.Package) .AsNoTracking() .AsQueryable(); @@ -41,6 +42,12 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler x.WeekDefinitionId == request.WeekDefinitionId); } + // فیلتر بر اساس پکیج + if (request.PackageId.HasValue && request.PackageId.Value > 0) + { + query = query.Where(x => x.PackageId == request.PackageId.Value); + } + query = query.ApplyOrder(sortBy: request.SortBy ?? "Created"); var meta = await query.GetMetaData(request.PaginationState, cancellationToken); @@ -64,7 +71,9 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler public long? WeekDefinitionId { get; init; } + /// + /// فیلتر بر اساس پکیج (اختیاری) + /// + public long? PackageId { get; init; } /// /// فقط موارد Expired نشده؟ diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs index 2ffe742..7d83266 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs @@ -18,6 +18,7 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler x.WeekDefinition) .Include(x => x.User) + .Include(x => x.Package) .AsNoTracking() .AsQueryable(); @@ -43,6 +44,12 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler !x.IsExpired); } + // فیلتر بر اساس پکیج + if (request.PackageId.HasValue && request.PackageId.Value > 0) + { + query = query.Where(x => x.PackageId == request.PackageId.Value); + } + // مرتب‌سازی بر اساس WeekDefinitionId (نزولی = جدیدترین اول) query = query.ApplyOrder(sortBy: request.SortBy ?? "-WeekDefinitionId"); @@ -67,7 +74,9 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandlernet9.0 enable enable - 0.0.186 + 0.0.187 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/commission.proto b/src/CMSMicroservice.Protobuf/Protos/commission.proto index 5cf3999..1315e1d 100644 --- a/src/CMSMicroservice.Protobuf/Protos/commission.proto +++ b/src/CMSMicroservice.Protobuf/Protos/commission.proto @@ -217,6 +217,7 @@ message GetUserCommissionPayoutsRequest google.protobuf.Int64Value user_id = 1; google.protobuf.Int32Value status = 2; // CommissionPayoutStatus enum google.protobuf.Int64Value week_definition_id = 3; // Preferred filter + google.protobuf.Int64Value package_id = 4; // فیلتر بر اساس پکیج int32 page_index = 5; int32 page_size = 6; } @@ -244,6 +245,8 @@ message UserCommissionPayoutModel string iban_number = 13; google.protobuf.Timestamp withdrawn_at = 14; google.protobuf.Timestamp created = 15; + int64 package_id = 16; // شناسه پکیج + string package_title = 17; // نام پکیج } // GetCommissionPayoutHistory Query @@ -314,6 +317,7 @@ message GetUserWeeklyBalancesRequest { google.protobuf.Int64Value user_id = 1; google.protobuf.Int64Value week_definition_id = 2; // Preferred filter + google.protobuf.Int64Value package_id = 3; // فیلتر بر اساس پکیج bool only_active = 4; // Only non-expired balances int32 page_index = 5; int32 page_size = 6; @@ -343,6 +347,8 @@ message UserWeeklyBalanceModel google.protobuf.Timestamp calculated_at = 14; bool is_expired = 15; google.protobuf.Timestamp created = 16; + int64 package_id = 17; // شناسه پکیج + string package_title = 18; // نام پکیج } // GetAllWeeklyPools Query @@ -598,6 +604,7 @@ message GetMyCommissionPayoutsRequest int32 page_size = 2; google.protobuf.Int64Value week_definition_id = 3; google.protobuf.Int32Value status = 4; // 0=Pending, 1=Calculated, 2=Paid, 3=Withdrawn + google.protobuf.Int64Value package_id = 5; // فیلتر بر اساس پکیج } message GetMyCommissionPayoutsResponse @@ -622,6 +629,8 @@ message CustomerCommissionPayoutModel int32 status = 7; // 0=Pending, 1=Paid, 2=WithdrawRequested, 3=Withdrawn, 4=PaymentFailed, 5=Cancelled google.protobuf.Timestamp calculated_date = 8; string date_persian = 9; + int64 package_id = 10; // شناسه پکیج + string package_title = 11; // نام پکیج } // GetMyWeeklyBalances - for frontend customer display @@ -631,6 +640,7 @@ message GetMyWeeklyBalancesRequest int32 page_size = 2; google.protobuf.Int64Value week_definition_id = 3; bool only_active = 4; + google.protobuf.Int64Value package_id = 5; // فیلتر بر اساس پکیج } message GetMyWeeklyBalancesResponse @@ -658,4 +668,6 @@ message CustomerWeeklyBalanceModel int32 right_leg_carryover = 12; int32 left_leg_new_members = 13; int32 right_leg_new_members = 14; + int64 package_id = 15; // شناسه پکیج + string package_title = 16; // نام پکیج } diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs index c31a0db..03a9d8b 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs @@ -68,7 +68,9 @@ public class CommissionProfile : IRegister .Map(dest => dest.WithdrawnAt, src => src.WithdrawnAt.HasValue ? Timestamp.FromDateTime(src.WithdrawnAt.Value.ToUniversalTime()) : null) - .Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created)); + .Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created)) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PackageTitle, src => src.PackageTitle); // GetWeekDefinitions Request Mapping config.NewConfig() @@ -127,7 +129,9 @@ public class CommissionProfile : IRegister ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc)) : null) .Map(dest => dest.IsExpired, src => src.IsExpired) - .Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created)); + .Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created)) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PackageTitle, src => src.PackageTitle); // GetMyCommissionPayouts Request Mapping config.NewConfig() @@ -135,6 +139,7 @@ public class CommissionProfile : IRegister ? (CommissionPayoutStatus?)src.Status.Value : null) .Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null) + .Map(dest => dest.PackageId, src => src.PackageId != null ? src.PackageId.Value : (long?)null) .Map(dest => dest.PaginationState, src => new PaginationState { PageNumber = src.PageNumber > 0 ? src.PageNumber : 1, @@ -160,12 +165,15 @@ public class CommissionProfile : IRegister .Map(dest => dest.CalculatedDate, src => src.CalculatedDate.HasValue ? Timestamp.FromDateTime(src.CalculatedDate.Value.ToUniversalTime()) : null) - .Map(dest => dest.DatePersian, src => src.DatePersian); + .Map(dest => dest.DatePersian, src => src.DatePersian) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PackageTitle, src => src.PackageTitle); // GetMyWeeklyBalances Request Mapping config.NewConfig() .Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null) .Map(dest => dest.OnlyActive, src => src.OnlyActive) + .Map(dest => dest.PackageId, src => src.PackageId != null ? src.PackageId.Value : (long?)null) .Map(dest => dest.PaginationState, src => new PaginationState { PageNumber = src.PageNumber > 0 ? src.PageNumber : 1, @@ -199,6 +207,8 @@ public class CommissionProfile : IRegister .Map(dest => dest.IsExpired, src => src.IsExpired) .Map(dest => dest.CalculatedAt, src => src.CalculatedAt.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc)) - : null); + : null) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PackageTitle, src => src.PackageTitle); } } From dcd11351a3a594749b94c002d04840df3cb27c2b Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 21:59:24 +0330 Subject: [PATCH 32/44] feat(T4.13): add PackageFeature CRUD support - Proto: add feature_ids to Create/Update/Get package messages - Commands: add FeatureIds to Create/Update commands - Handlers: sync PackageFeature join entities on create/update - Queries: include PackageFeatures, map FeatureIds in responses - Bump NuGet to v0.0.188 --- .../CreateNewPackageCommand.cs | 1 + .../CreateNewPackageCommandHandler.cs | 16 ++++++++++++++++ .../UpdatePackage/UpdatePackageCommand.cs | 1 + .../UpdatePackageCommandHandler.cs | 17 +++++++++++++++++ .../GetAllPackageByFilterQueryHandler.cs | 18 ++++++++++++++++-- .../GetAllPackageByFilterResponseDto.cs | 1 + .../GetPackage/GetPackageQueryHandler.cs | 15 +++++++++++---- .../GetPackage/GetPackageResponseDto.cs | 1 + .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/package.proto | 4 ++++ 10 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs index c0fd523..e54995f 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommand.cs @@ -22,4 +22,5 @@ public record CreateNewPackageCommand : IRequest public int MaxNetworkLevel { get; init; } = 15; public long MagicWalletMaxDeposit { get; init; } = 1_000_000_000; public long MagicWalletMaxCredit { get; init; } = 2_500_000_000; + public List FeatureIds { get; init; } = new(); } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandHandler.cs index 6ac4da7..290d60f 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/CreateNewPackage/CreateNewPackageCommandHandler.cs @@ -16,6 +16,22 @@ public class CreateNewPackageCommandHandler : IRequestHandler(); } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs index 0c2da24..5e57ded 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs @@ -24,4 +24,5 @@ public record UpdatePackageCommand : IRequest public int MaxNetworkLevel { get; init; } public long MagicWalletMaxDeposit { get; init; } public long MagicWalletMaxCredit { get; init; } + public List FeatureIds { get; init; } = new(); } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandHandler.cs index 417f29b..2a8035c 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommandHandler.cs @@ -16,6 +16,23 @@ public class UpdatePackageCommandHandler : IRequestHandler pf.PackageId == request.Id) + .ToListAsync(cancellationToken); + _context.PackageFeatures.RemoveRange(existingFeatures); + + foreach (var featureId in request.FeatureIds) + { + _context.PackageFeatures.Add(new PackageFeature + { + PackageId = request.Id, + ClubFeatureId = featureId, + IsIncluded = true + }); + } + await _context.SaveChangesAsync(cancellationToken); return Unit.Value; } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryHandler.cs index 93fe78a..193b602 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterQueryHandler.cs @@ -14,6 +14,7 @@ public class GetAllPackageByFilterQueryHandler : IRequestHandler !x.IsDeleted) .ApplyOrder(sortBy: request.SortBy) .AsNoTracking() + .Include(x => x.PackageFeatures) .AsQueryable(); if (request.Filter is not null) { @@ -25,11 +26,24 @@ public class GetAllPackageByFilterQueryHandler : IRequestHandler request.Filter.Price == null || x.Price == request.Filter.Price) ; } + var packages = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .ToListAsync(cancellationToken); + + var models = packages.Select(p => + { + var model = p.Adapt(); + model.FeatureIds = p.PackageFeatures? + .Where(pf => pf.IsIncluded) + .Select(pf => pf.ClubFeatureId) + .ToList() ?? new(); + return model; + }).ToList(); + return new GetAllPackageByFilterResponseDto { MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), - Models = await query.PaginatedListAsync(paginationState: request.PaginationState) - .ProjectToType().ToListAsync(cancellationToken) + Models = models }; } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs index 76eb16d..35ac204 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetAllPackageByFilter/GetAllPackageByFilterResponseDto.cs @@ -31,4 +31,5 @@ public class GetAllPackageByFilterResponseDto public int MaxNetworkLevel { get; set; } public long MagicWalletMaxDeposit { get; set; } public long MagicWalletMaxCredit { get; set; } + public List FeatureIds { get; set; } = new(); } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageQueryHandler.cs index 0a86195..91d230a 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageQueryHandler.cs @@ -11,12 +11,19 @@ public class GetPackageQueryHandler : IRequestHandler Handle(GetPackageQuery request, CancellationToken cancellationToken) { - var response = await _context.Packages + var package = await _context.Packages .AsNoTracking() + .Include(x => x.PackageFeatures) .Where(x => x.Id == request.Id) - .ProjectToType() - .FirstOrDefaultAsync(cancellationToken); + .FirstOrDefaultAsync(cancellationToken) + ?? throw new NotFoundException(nameof(Package), request.Id); - return response ?? throw new NotFoundException(nameof(Package), request.Id); + var response = package.Adapt(); + response.FeatureIds = package.PackageFeatures? + .Where(pf => pf.IsIncluded) + .Select(pf => pf.ClubFeatureId) + .ToList() ?? new(); + + return response; } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs index ec118f5..de06fd9 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetPackage/GetPackageResponseDto.cs @@ -24,4 +24,5 @@ public class GetPackageResponseDto public int MaxNetworkLevel { get; set; } public long MagicWalletMaxDeposit { get; set; } public long MagicWalletMaxCredit { get; set; } + public List FeatureIds { get; set; } = new(); } \ No newline at end of file diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 053fd42..d87bd05 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.187 + 0.0.188 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto index 65dc3cf..3b88874 100644 --- a/src/CMSMicroservice.Protobuf/Protos/package.proto +++ b/src/CMSMicroservice.Protobuf/Protos/package.proto @@ -99,6 +99,7 @@ message CreateNewPackageRequest int32 max_network_level = 15; int64 magic_wallet_max_deposit = 16; int64 magic_wallet_max_credit = 17; + repeated int64 feature_ids = 18; } message CreateNewPackageResponse { @@ -124,6 +125,7 @@ message UpdatePackageRequest int32 max_network_level = 16; int64 magic_wallet_max_deposit = 17; int64 magic_wallet_max_credit = 18; + repeated int64 feature_ids = 19; } message DeletePackageRequest { @@ -152,6 +154,7 @@ message GetPackageResponse int32 max_network_level = 15; int64 magic_wallet_max_deposit = 16; int64 magic_wallet_max_credit = 17; + repeated int64 feature_ids = 18; } message GetAllPackageByFilterRequest { @@ -191,6 +194,7 @@ message GetAllPackageByFilterResponseModel int32 max_network_level = 15; int64 magic_wallet_max_deposit = 16; int64 magic_wallet_max_credit = 17; + repeated int64 feature_ids = 18; } message GetUserPackageStatusRequest From 1ac23667e2e69a9992dd0ddb0013e03573198132 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 02:35:41 +0330 Subject: [PATCH 33/44] =?UTF-8?q?fix:=20package-based=20compliance=20audit?= =?UTF-8?q?=20=E2=80=94=20feature=20DIFF,=20remove=20hardcoded=20fallbacks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ActivateClubMembership: replace GetAllFeatureIds with PackageFeature DIFF (Q20) - ActivateClubMembership: fix re-activation early return — check current cycle - AcceptClubMembershipContract: same feature DIFF logic - VerifyPackagePurchase: remove ?? 2.0m fallback, throw if package null - PackageService: remove ?? 2.0m fallback for DiscountMultiplier - UserOrderService: remove ?? 1B fallback for MagicWalletMaxDeposit - SystemConstants: remove 9 dead [Obsolete] constants (per-package values) --- ...eptClubMembershipContractCommandHandler.cs | 87 ++++++++----- .../ActivateClubMembershipCommandHandler.cs | 120 ++++++++++++------ .../VerifyPackagePurchaseCommandHandler.cs | 4 +- .../Common/SystemConstants.cs | 72 +---------- .../Services/PackageService.cs | 4 +- .../Services/UserOrderService.cs | 3 +- 6 files changed, 147 insertions(+), 143 deletions(-) diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs index 5e403c8..a899d9a 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs @@ -262,36 +262,8 @@ public class AcceptClubMembershipContractCommandHandler await _context.SaveChangesAsync(cancellationToken); - // 10. اعطای ویژگی‌های باشگاه برای کاربر (فقط برای عضویت جدید) - if (isNewMembership) - { - var featureIds = ClubFeatureTypeExtensions.GetAllFeatureIds(); - var clubFeatures = await _context.ClubFeatures - .Where(f => !f.IsDeleted && featureIds.Contains(f.Id)) - .ToListAsync(cancellationToken); - - if (clubFeatures.Any()) - { - var userClubFeatures = clubFeatures.Select(feature => new UserClubFeature - { - UserId = user.Id, - ClubMembershipId = clubMembership.Id, - ClubFeatureId = feature.Id, - GrantedAt = activationDate, - IsActive = true, - Notes = "اعطا شده هنگام امضای قرارداد" - }).ToList(); - - _context.UserClubFeatures.AddRange(userClubFeatures); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Granted {Count} club features to UserId {UserId}", - clubFeatures.Count, - user.Id - ); - } - } + // 10. اعمال ویژگی‌های پکیج — DIFF/تفاضل (Q20) + await ApplyFeatureDiffAsync(user.Id, clubMembership.Id, package.Id, isNewMembership, activationDate, cancellationToken); _logger.LogInformation( "Club membership contract accepted and activated for UserId: {UserId}, ContractId: {ContractId}, MembershipId: {MembershipId}", @@ -321,6 +293,61 @@ public class AcceptClubMembershipContractCommandHandler return week.Id; } + private async Task ApplyFeatureDiffAsync( + long userId, long membershipId, long packageId, + bool isNewMembership, DateTime activationDate, + CancellationToken cancellationToken) + { + var newFeatureIds = await _context.PackageFeatures + .Where(pf => pf.PackageId == packageId && pf.IsIncluded && !pf.IsDeleted) + .Select(pf => pf.ClubFeatureId) + .ToListAsync(cancellationToken); + + if (!newFeatureIds.Any()) + { + _logger.LogWarning("No PackageFeatures for PackageId {PackageId}", packageId); + return; + } + + var currentFeatures = await _context.UserClubFeatures + .Where(ucf => ucf.UserId == userId && ucf.IsActive) + .ToListAsync(cancellationToken); + + var currentFeatureIds = currentFeatures.Select(f => f.ClubFeatureId).ToHashSet(); + var toAdd = newFeatureIds.Where(id => !currentFeatureIds.Contains(id)).ToList(); + var toRemove = currentFeatures.Where(f => !newFeatureIds.Contains(f.ClubFeatureId)).ToList(); + + foreach (var feature in toRemove) + { + feature.IsActive = false; + feature.Notes = $"حذف شده بابت تغییر پکیج (PackageId: {packageId})"; + } + + if (toAdd.Any()) + { + var newUserFeatures = toAdd.Select(featureId => new UserClubFeature + { + UserId = userId, + ClubMembershipId = membershipId, + ClubFeatureId = featureId, + GrantedAt = activationDate, + IsActive = true, + Notes = isNewMembership + ? "اعطا شده هنگام امضای قرارداد" + : $"اضافه شده بابت خرید مجدد پکیج (PackageId: {packageId})" + }).ToList(); + + _context.UserClubFeatures.AddRange(newUserFeatures); + } + + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation( + "Feature DIFF applied for UserId={UserId}: Added={Added}, Removed={Removed}, Kept={Kept}", + userId, toAdd.Count, toRemove.Count, + currentFeatureIds.Count - toRemove.Count); + } + private async Task<(bool Success, string Message)> VerifyOtpAsync( string mobile, string code, diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index 91a6e43..b7842e1 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -221,16 +221,20 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler c.UserId == user.Id && c.IsCurrentCycle, cancellationToken); + + if (existingMembership.IsActive && hasCurrentCycle) { _logger.LogInformation( - "User {UserId} is already an active club member", + "User {UserId} is already an active club member with a running cycle", user.Id ); return true; } - // فعال‌سازی مجدد — FirstActivation حفظ میشه (Q21), Last بروزرسانی میشه + // فعال‌سازی مجدد یا خرید مجدد (Q19/Q20) — FirstActivation حفظ میشه (Q21), Last بروزرسانی میشه entity = existingMembership; entity.IsActive = true; entity.LastActivationDate = activationDate; @@ -240,8 +244,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler !f.IsDeleted && featureIds.Contains(f.Id)) - .ToListAsync(cancellationToken); - - if (clubFeatures.Any()) - { - var userClubFeatures = clubFeatures.Select(feature => new UserClubFeature - { - UserId = user.Id, - ClubMembershipId = entity.Id, - ClubFeatureId = feature.Id, - GrantedAt = activationDate, - IsActive = true, - Notes = "اعطا شده به‌طور خودکار هنگام فعالسازی" - }).ToList(); _context.UserClubFeatures.AddRange(userClubFeatures); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Granted {Count} club features to UserId {UserId}", - clubFeatures.Count, - user.Id - ); - } - else - { - _logger.LogWarning( - "No club features found to grant to UserId {UserId}", - user.Id - ); - } - } + // 9. اعمال ویژگی‌های پکیج — DIFF/تفاضل (Q20) + await ApplyFeatureDiffAsync(user.Id, entity.Id, package.Id, isNewMembership, activationDate, cancellationToken); _logger.LogInformation( "Club membership activated successfully. UserId: {UserId}, MembershipId: {MembershipId}", @@ -398,6 +370,74 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler + /// اعمال DIFF فیچرها بر اساس پکیج (Q20) + /// عضویت جدید: همه فیچرهای پکیج اضافه میشه + /// خرید مجدد: تفاضل فیچرهای فعلی و پکیج جدید + /// + private async Task ApplyFeatureDiffAsync( + long userId, long membershipId, long packageId, + bool isNewMembership, DateTime activationDate, + CancellationToken cancellationToken) + { + // فیچرهای پکیج جدید + var newFeatureIds = await _context.PackageFeatures + .Where(pf => pf.PackageId == packageId && pf.IsIncluded && !pf.IsDeleted) + .Select(pf => pf.ClubFeatureId) + .ToListAsync(cancellationToken); + + if (!newFeatureIds.Any()) + { + _logger.LogWarning( + "No PackageFeatures found for PackageId {PackageId} — skipping feature assignment for UserId {UserId}", + packageId, userId); + return; + } + + // فیچرهای فعال فعلی کاربر + var currentFeatures = await _context.UserClubFeatures + .Where(ucf => ucf.UserId == userId && ucf.IsActive) + .ToListAsync(cancellationToken); + + var currentFeatureIds = currentFeatures.Select(f => f.ClubFeatureId).ToHashSet(); + + // DIFF + var toAdd = newFeatureIds.Where(id => !currentFeatureIds.Contains(id)).ToList(); + var toRemove = currentFeatures.Where(f => !newFeatureIds.Contains(f.ClubFeatureId)).ToList(); + + // حذف فیچرهای قدیمی که پکیج جدید ندارد + foreach (var feature in toRemove) + { + feature.IsActive = false; + feature.Notes = $"حذف شده بابت تغییر پکیج (PackageId: {packageId})"; + } + + // اضافه فیچرهای جدید + if (toAdd.Any()) + { + var newUserFeatures = toAdd.Select(featureId => new UserClubFeature + { + UserId = userId, + ClubMembershipId = membershipId, + ClubFeatureId = featureId, + GrantedAt = activationDate, + IsActive = true, + Notes = isNewMembership + ? "اعطا شده هنگام فعالسازی اولیه" + : $"اضافه شده بابت خرید مجدد پکیج (PackageId: {packageId})" + }).ToList(); + + _context.UserClubFeatures.AddRange(newUserFeatures); + } + + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation( + "Feature DIFF applied for UserId={UserId}: Added={Added}, Removed={Removed}, Kept={Kept}", + userId, toAdd.Count, toRemove.Count, + currentFeatureIds.Count - toRemove.Count); + } + /// /// دریافت شناسه تعریف هفته جاری /// diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs index 8ca5177..419eb03 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs @@ -101,7 +101,9 @@ public class VerifyPackagePurchaseCommandHandler // شارژ DiscountBalance (موجودی تخفیف) — ضریب تخفیف از پکیج var oldDiscountBalance = wallet.DiscountBalance; - var discountMultiplier = order.Package?.DiscountMultiplier ?? 2.0m; + if (order.Package == null) + throw new NotFoundException("پکیج مرتبط با سفارش یافت نشد"); + var discountMultiplier = order.Package.DiscountMultiplier; var discountAmount = (long)(order.Amount * discountMultiplier); wallet.DiscountBalance += discountAmount; diff --git a/src/CMSMicroservice.Domain/Common/SystemConstants.cs b/src/CMSMicroservice.Domain/Common/SystemConstants.cs index e424383..662d79b 100644 --- a/src/CMSMicroservice.Domain/Common/SystemConstants.cs +++ b/src/CMSMicroservice.Domain/Common/SystemConstants.cs @@ -20,42 +20,6 @@ public static class SystemConstants #endregion - #region Club Settings - - /// - /// مبلغ هدیه حق عضویت باشگاه (ریال) - این مبلغ از کیف پول کم نمی‌شود - /// [DEPRECATED] از Package.Price استفاده کنید - /// - [Obsolete("Moved to Package entity. Use package.Price or package.ActivationFee instead.")] - public const long ClubMembershipGiftValue = 25_200_000; - - /// - /// هزینه فعال‌سازی عضویت باشگاه (ریال) - /// [DEPRECATED] از Package.ActivationFee استفاده کنید - /// - [Obsolete("Moved to Package.ActivationFee. Read from Package entity instead.")] - public const long ClubActivationFee = 25_200_000; - - #endregion - - #region Package Settings - - /// - /// مبلغ پکیج پایه (ریال) - /// [DEPRECATED] از Package.Price استفاده کنید - /// - [Obsolete("Moved to Package.Price. Read from Package entity instead.")] - public const long BasePackageAmount = 56_000_000; - - /// - /// مبلغ وام دایا (ریال) - /// [DEPRECATED] از Package.Price استفاده کنید - /// - [Obsolete("Moved to Package.Price. Read from Package entity with SupportsDayaPurchase=true.")] - public const long DayaLoanAmount = 56_000_000; - - #endregion - #region Commission Settings /// @@ -68,20 +32,6 @@ public static class SystemConstants /// public const long CommissionMinWithdrawalAmount = 1_000_000; - /// - /// سقف تعادل هفتگی برای هر دست (چپ یا راست) - حداکثر کل = 600 - /// [DEPRECATED] از Package.MaxBalancesPerLeg استفاده کنید - /// - [Obsolete("Moved to Package.MaxBalancesPerLeg. Read from Package entity instead.")] - public const int CommissionMaxWeeklyBalancesPerLeg = 300; - - /// - /// حداکثر عمق شبکه برای محاسبه کمیسیون (تعداد لول زیرمجموعه) - /// [DEPRECATED] از Package.MaxNetworkLevel استفاده کنید - /// - [Obsolete("Moved to Package.MaxNetworkLevel. Read from Package entity instead.")] - public const int CommissionMaxNetworkLevel = 15; - /// /// روش محاسبه (ORM یا SP) /// @@ -105,26 +55,8 @@ public static class SystemConstants #region Magic Wallet Settings - /// - /// ضریب شارژ کیف‌پول جادویی — واریز × 2.5 = اعتبار - /// [DEPRECATED] از Package.MagicWalletMultiplier استفاده کنید - /// - [Obsolete("Moved to Package.MagicWalletMultiplier. Read from Package entity instead.")] - public const decimal MagicWalletMultiplier = 2.5m; - - /// - /// سقف واریز در هر دور جادویی (ریال) — 100M تومان - /// [DEPRECATED] از Package.MagicWalletMaxDeposit استفاده کنید - /// - [Obsolete("Moved to Package.MagicWalletMaxDeposit. Read from Package entity instead.")] - public const long MagicWalletMaxDeposit = 1_000_000_000; - - /// - /// سقف اعتبار در هر دور جادویی (ریال) — 250M تومان - /// [DEPRECATED] از Package.MagicWalletMaxCredit استفاده کنید - /// - [Obsolete("Moved to Package.MagicWalletMaxCredit. Read from Package entity instead.")] - public const long MagicWalletMaxCredit = 2_500_000_000; + // Per-package magic wallet settings are now in Package entity: + // Package.MagicWalletMultiplier, Package.MagicWalletMaxDeposit, Package.MagicWalletMaxCredit #endregion diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index b8dcf21..2effbe3 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -314,7 +314,9 @@ public class PackageService : PackageContract.PackageContractBase await _context.SaveChangesAsync(context.CancellationToken); } - var discountAmount = (long)(purchase.Amount * (double)(purchase.Package?.DiscountMultiplier ?? 2.0m)); + var discountMultiplier = purchase.Package?.DiscountMultiplier + ?? throw new RpcException(new Status(StatusCode.Internal, "پکیج سفارش یافت نشد")); + var discountAmount = (long)(purchase.Amount * (double)discountMultiplier); wallet.Balance += purchase.Amount; wallet.DiscountBalance += discountAmount; diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index 81eca8d..113e5b6 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -352,7 +352,8 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase var userPackage = userCycle != null ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == userCycle.PackageId, context.CancellationToken) : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken); - var magicMaxDeposit = userPackage?.MagicWalletMaxDeposit ?? 1_000_000_000; + var magicMaxDeposit = userPackage?.MagicWalletMaxDeposit + ?? throw new InvalidOperationException($"Package not found for magic wallet exit check, UserId={userId}"); if (wallet.MagicTotalDeposited >= magicMaxDeposit) { From a1024a3e180fb667069130abffe35ee0c0be4c2a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 04:22:24 +0330 Subject: [PATCH 34/44] feat(Q24+Q26): balance threshold + SP auto-deploy worker Q24: Magic wallet entry/exit trigger now uses Balance <= 1,000,000 Rials instead of Balance == 0 (products have varying prices so exact zero is unreachable). Added MagicWalletEntryThreshold to SystemConstants. Q26: StoredProcedureDeploymentService (IHostedService) auto-deploys stored procedures on startup via embedded SQL resources + SHA256 checksum comparison. Creates [CMS].[__StoredProcedureVersions] table automatically. Supports GO batch separators. --- .../Common/SystemConstants.cs | 14 + .../StoredProcedureDeploymentService.cs | 288 ++++++++++++++++++ .../CMSMicroservice.Infrastructure.csproj | 4 + .../ConfigureServices.cs | 3 + .../Services/UserOrderService.cs | 7 +- 5 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 src/CMSMicroservice.Infrastructure/BackgroundServices/StoredProcedureDeploymentService.cs diff --git a/src/CMSMicroservice.Domain/Common/SystemConstants.cs b/src/CMSMicroservice.Domain/Common/SystemConstants.cs index 662d79b..634a85e 100644 --- a/src/CMSMicroservice.Domain/Common/SystemConstants.cs +++ b/src/CMSMicroservice.Domain/Common/SystemConstants.cs @@ -58,6 +58,14 @@ public static class SystemConstants // Per-package magic wallet settings are now in Package entity: // Package.MagicWalletMultiplier, Package.MagicWalletMaxDeposit, Package.MagicWalletMaxCredit + /// + /// آستانه ورود/خروج کیف‌پول جادویی (ریال). + /// وقتی موجودی کیف‌پول به این مقدار یا کمتر برسد، تریگر Entry/Exit اجرا می‌شود. + /// مقدار ۱٬۰۰۰٬۰۰۰ ریال = ۱۰۰ هزار تومان + /// (Q24: چون قیمت محصولات متفاوت است، موجودی دقیقاً صفر نمی‌شود) + /// + public const long MagicWalletEntryThreshold = 1_000_000; + #endregion #region Shop Settings @@ -98,6 +106,9 @@ public static class SystemConstants ["System.MaintenanceMode"] = SystemMaintenanceMode, ["System.EnableAuditLog"] = SystemEnableAuditLog, + // Magic Wallet (global threshold) + ["MagicWallet.EntryThreshold"] = MagicWalletEntryThreshold, + // Shop ["Shop.VAT"] = ShopVAT, ["Shop.VATEnabled"] = ShopVATEnabled @@ -125,6 +136,9 @@ public static class SystemConstants ("System.MaintenanceMode", SystemMaintenanceMode, "حالت تعمیر و نگهداری سیستم"), ("System.EnableAuditLog", SystemEnableAuditLog, "فعال‌سازی لاگ تغییرات"), + // Magic Wallet (global threshold) + ("MagicWallet.EntryThreshold", MagicWalletEntryThreshold, "آستانه ورود/خروج کیف‌پول جادویی (ریال) — Q24"), + // Shop ("Shop.VAT", ShopVAT, "مالیات بر ارزش افزوده"), ("Shop.VATEnabled", ShopVATEnabled, "مالیات فعال است؟") diff --git a/src/CMSMicroservice.Infrastructure/BackgroundServices/StoredProcedureDeploymentService.cs b/src/CMSMicroservice.Infrastructure/BackgroundServices/StoredProcedureDeploymentService.cs new file mode 100644 index 0000000..3410d15 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/BackgroundServices/StoredProcedureDeploymentService.cs @@ -0,0 +1,288 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using CMSMicroservice.Application.Common.Interfaces; + +namespace CMSMicroservice.Infrastructure.BackgroundServices; + +/// +/// سرویس دیپلوی خودکار Stored Procedureها در استارتاپ (Q26). +/// فایل‌های .sql از EmbeddedResource خوانده شده، SHA256 محاسبه و با جدول +/// [CMS].[__StoredProcedureVersions] مقایسه می‌شود. +/// اگر checksum تغییر کرده باشد، SP دوباره اجرا (CREATE OR ALTER) می‌شود. +/// این سرویس فقط یکبار در استارتاپ اجرا شده و سپس متوقف می‌شود. +/// +public class StoredProcedureDeploymentService : BackgroundService +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + + /// + /// پیشوند نام‌فضای EmbeddedResource — فقط فایل‌های .sql واقعی (بدون README) + /// + private const string ResourcePrefix = "CMSMicroservice.Infrastructure.Persistence.StoredProcedures."; + + public StoredProcedureDeploymentService( + IServiceScopeFactory scopeFactory, + ILogger logger) + { + _scopeFactory = scopeFactory; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // صبر برای آماده شدن دیتابیس و اتمام Migrationها + await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken); + + _logger.LogInformation("StoredProcedureDeploymentService started — checking stored procedures..."); + + try + { + using var scope = _scopeFactory.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + var connection = context.Database.GetDbConnection(); + await connection.OpenAsync(stoppingToken); + + // ── ساخت جدول ورژن اگر وجود ندارد ── + await EnsureVersionTableAsync(connection, stoppingToken); + + // ── خواندن فایل‌های SP از Embedded Resources ── + var assembly = Assembly.GetExecutingAssembly(); + var resourceNames = assembly.GetManifestResourceNames() + .Where(r => r.StartsWith(ResourcePrefix, StringComparison.OrdinalIgnoreCase) + && r.EndsWith(".sql", StringComparison.OrdinalIgnoreCase) + && !r.Contains("README", StringComparison.OrdinalIgnoreCase)) + .OrderBy(r => r) + .ToList(); + + _logger.LogInformation("Found {Count} SQL resources to check", resourceNames.Count); + + var deployed = 0; + var skipped = 0; + + foreach (var resourceName in resourceNames) + { + if (stoppingToken.IsCancellationRequested) break; + + var fileName = resourceName[ResourcePrefix.Length..]; // e.g. "sp_CalculateWeeklyBalances.sql" + var sqlContent = await ReadResourceAsync(assembly, resourceName); + var checksum = ComputeSha256(sqlContent); + + var existingChecksum = await GetStoredChecksumAsync(connection, fileName, stoppingToken); + + if (string.Equals(existingChecksum, checksum, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("SP '{FileName}' is up-to-date (checksum match), skipping", fileName); + skipped++; + continue; + } + + // ── اجرای SP (CREATE OR ALTER) ── + _logger.LogInformation("Deploying SP '{FileName}' (checksum changed: {Old} → {New})", + fileName, + existingChecksum ?? "NEW", + checksum[..12]); + + try + { + await ExecuteSqlAsync(connection, sqlContent, stoppingToken); + await UpsertChecksumAsync(connection, fileName, checksum, stoppingToken); + deployed++; + + _logger.LogInformation("Successfully deployed SP '{FileName}'", fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to deploy SP '{FileName}' — skipping", fileName); + } + } + + _logger.LogInformation( + "StoredProcedureDeploymentService completed — deployed: {Deployed}, skipped: {Skipped}, total: {Total}", + deployed, skipped, resourceNames.Count); + } + catch (Exception ex) + { + _logger.LogError(ex, "StoredProcedureDeploymentService encountered a fatal error"); + } + } + + // ══════════════════════════════════════════════════════════════ + // Helper Methods + // ══════════════════════════════════════════════════════════════ + + /// + /// ساخت جدول [CMS].[__StoredProcedureVersions] اگر وجود ندارد. + /// از Raw SQL استفاده می‌کنیم تا نیازی به Entity و Migration نباشد. + /// + private static async Task EnsureVersionTableAsync( + System.Data.Common.DbConnection connection, + CancellationToken ct) + { + const string sql = """ + IF NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'CMS' AND TABLE_NAME = '__StoredProcedureVersions' + ) + BEGIN + CREATE TABLE [CMS].[__StoredProcedureVersions] ( + [FileName] NVARCHAR(256) NOT NULL PRIMARY KEY, + [Checksum] NVARCHAR(64) NOT NULL, + [DeployedAt] DATETIME2 NOT NULL DEFAULT GETUTCDATE(), + [DeployCount] INT NOT NULL DEFAULT 1 + ); + END + """; + + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + await cmd.ExecuteNonQueryAsync(ct); + } + + /// + /// خواندن checksum ذخیره‌شده برای یک فایل + /// + private static async Task GetStoredChecksumAsync( + System.Data.Common.DbConnection connection, + string fileName, + CancellationToken ct) + { + const string sql = "SELECT [Checksum] FROM [CMS].[__StoredProcedureVersions] WHERE [FileName] = @FileName"; + + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + + var param = cmd.CreateParameter(); + param.ParameterName = "@FileName"; + param.Value = fileName; + cmd.Parameters.Add(param); + + var result = await cmd.ExecuteScalarAsync(ct); + return result as string; + } + + /// + /// ذخیره/به‌روزرسانی checksum بعد از دیپلوی موفق + /// + private static async Task UpsertChecksumAsync( + System.Data.Common.DbConnection connection, + string fileName, + string checksum, + CancellationToken ct) + { + const string sql = """ + MERGE [CMS].[__StoredProcedureVersions] AS target + USING (SELECT @FileName AS [FileName]) AS source + ON target.[FileName] = source.[FileName] + WHEN MATCHED THEN + UPDATE SET [Checksum] = @Checksum, + [DeployedAt] = GETUTCDATE(), + [DeployCount] = target.[DeployCount] + 1 + WHEN NOT MATCHED THEN + INSERT ([FileName], [Checksum], [DeployedAt], [DeployCount]) + VALUES (@FileName, @Checksum, GETUTCDATE(), 1); + """; + + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + + var pFileName = cmd.CreateParameter(); + pFileName.ParameterName = "@FileName"; + pFileName.Value = fileName; + cmd.Parameters.Add(pFileName); + + var pChecksum = cmd.CreateParameter(); + pChecksum.ParameterName = "@Checksum"; + pChecksum.Value = checksum; + cmd.Parameters.Add(pChecksum); + + await cmd.ExecuteNonQueryAsync(ct); + } + + /// + /// اجرای محتوای SQL روی کانکشن فعلی + /// + private static async Task ExecuteSqlAsync( + System.Data.Common.DbConnection connection, + string sqlContent, + CancellationToken ct) + { + // SP ها ممکن است GO separator داشته باشند — هر بلاک را جدا اجرا می‌کنیم + var batches = SplitBatches(sqlContent); + + foreach (var batch in batches) + { + var trimmed = batch.Trim(); + if (string.IsNullOrWhiteSpace(trimmed)) continue; + + await using var cmd = connection.CreateCommand(); + cmd.CommandText = trimmed; + cmd.CommandTimeout = 120; // SP های بزرگ ممکن است زمان ببرند + await cmd.ExecuteNonQueryAsync(ct); + } + } + + /// + /// تقسیم SQL به بلاک‌ها بر اساس GO separator + /// + private static List SplitBatches(string sql) + { + // GO باید تنها در خط خودش باشد (case-insensitive) + var lines = sql.Split('\n'); + var batch = new StringBuilder(); + var result = new List(); + + foreach (var line in lines) + { + if (line.Trim().Equals("GO", StringComparison.OrdinalIgnoreCase)) + { + if (batch.Length > 0) + { + result.Add(batch.ToString()); + batch.Clear(); + } + } + else + { + batch.AppendLine(line); + } + } + + if (batch.Length > 0) + result.Add(batch.ToString()); + + return result; + } + + /// + /// خواندن محتوای یک Embedded Resource + /// + private static async Task ReadResourceAsync(Assembly assembly, string resourceName) + { + await using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Embedded resource not found: {resourceName}"); + using var reader = new StreamReader(stream, Encoding.UTF8); + return await reader.ReadToEndAsync(); + } + + /// + /// محاسبه SHA256 از محتوای SQL + /// + private static string ComputeSha256(string content) + { + var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(content)); + return Convert.ToHexString(bytes).ToLowerInvariant(); + } +} diff --git a/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj b/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj index 6522bf9..96ee9ad 100644 --- a/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj +++ b/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj @@ -30,4 +30,8 @@ + + + + \ No newline at end of file diff --git a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs index 016fb9d..edd7a68 100644 --- a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs +++ b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs @@ -130,6 +130,9 @@ public static class ConfigureServices if (configuration.GetValue("SeedWorkers:MagicWalletCycleSeed:Enabled")) services.AddHostedService(); + // Q26: Auto-deploy stored procedures on startup (checksum-based) + services.AddHostedService(); + if (configuration.GetValue("UseInMemoryDatabase")) { services.AddDbContext(options => diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index 113e5b6..2c87922 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -338,7 +338,8 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase _context.UserWalletChangeLogs.Add(walletLog); // ═══ Magic Wallet: Entry / Exit Trigger ═══ - if (wallet.Balance == 0) + // Q24: آستانه <= 1,000,000 ریال به جای == 0 (چون قیمت محصولات متفاوت است) + if (wallet.Balance <= SystemConstants.MagicWalletEntryThreshold) { var user = await _context.Users .Include(u => u.ClubMembership) @@ -358,7 +359,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase if (wallet.MagicTotalDeposited >= magicMaxDeposit) { // ── EXIT Magic Mode ── - // هر دو شرط: Balance=0 و سقف 100M پر شده + // هر دو شرط: Balance<=Threshold و سقف MagicMaxDeposit پر شده wallet.WalletMode = WalletMode.Normal; wallet.MagicCompletedAt = DateTime.UtcNow; @@ -398,7 +399,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase currentCycle.MagicStartedAt = DateTime.UtcNow; } } - // ⚠️ اگه Magic باشه و Balance=0 ولی سقف پر نشده → هنوز Magic! + // ⚠️ اگه Magic باشه و Balance<=Threshold ولی سقف پر نشده → هنوز Magic! // کاربر میتونه دوباره شارژ کنه } From fdbb91d2e1f1f4ce91c820a305fb46215bf0ce77 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 05:31:21 +0330 Subject: [PATCH 35/44] feat(Q27): PackageHistory + ClubMembershipCycleHistory entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Q27: History Tables standardization — added missing history entities: - PackageHistory: tracks price, activation fee, magic settings changes - ClubMembershipCycleHistory: tracks cycle state transitions - PackageAction + ClubMembershipCycleAction enums - EF configurations with proper indexes and FK relationships - DbSets registered in IApplicationDbContext + ApplicationDbContext - Navigation properties on Package + ClubMembershipCycle entities --- .../Interfaces/IApplicationDbContext.cs | 2 + .../Entities/Club/ClubMembershipCycle.cs | 3 + .../History/ClubMembershipCycleHistory.cs | 73 +++++++++++++++ .../Entities/History/PackageHistory.cs | 93 +++++++++++++++++++ .../Entities/Package.cs | 3 + .../Enums/ClubMembershipCycleAction.cs | 14 +++ .../Enums/PackageAction.cs | 16 ++++ .../Persistence/ApplicationDbContext.cs | 2 + ...ClubMembershipCycleHistoryConfiguration.cs | 51 ++++++++++ .../PackageHistoryConfiguration.cs | 51 ++++++++++ 10 files changed, 308 insertions(+) create mode 100644 src/CMSMicroservice.Domain/Entities/History/ClubMembershipCycleHistory.cs create mode 100644 src/CMSMicroservice.Domain/Entities/History/PackageHistory.cs create mode 100644 src/CMSMicroservice.Domain/Enums/ClubMembershipCycleAction.cs create mode 100644 src/CMSMicroservice.Domain/Enums/PackageAction.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleHistoryConfiguration.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageHistoryConfiguration.cs diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs index 17b0b88..e219676 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs @@ -39,6 +39,8 @@ public interface IApplicationDbContext DbSet ClubMemberships { get; } DbSet ClubMembershipHistories { get; } DbSet ClubMembershipCycles { get; } + DbSet ClubMembershipCycleHistories { get; } + DbSet PackageHistories { get; } DbSet PackageFeatures { get; } DbSet ClubFeatures { get; } DbSet UserClubFeatures { get; } diff --git a/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs b/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs index 2b1f787..05efe12 100644 --- a/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs +++ b/src/CMSMicroservice.Domain/Entities/Club/ClubMembershipCycle.cs @@ -71,4 +71,7 @@ public class ClubMembershipCycle : BaseAuditableEntity /// آیا این دور فعلی است؟ فقط یک رکورد true می‌باشد /// public bool IsCurrentCycle { get; set; } + + /// تاریخچه تغییرات دوره (Q27) + public virtual ICollection? CycleHistories { get; set; } } diff --git a/src/CMSMicroservice.Domain/Entities/History/ClubMembershipCycleHistory.cs b/src/CMSMicroservice.Domain/Entities/History/ClubMembershipCycleHistory.cs new file mode 100644 index 0000000..4de59d2 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/History/ClubMembershipCycleHistory.cs @@ -0,0 +1,73 @@ +namespace CMSMicroservice.Domain.Entities.History; + +/// +/// تاریخچه تغییرات دوره عضویت باشگاه (Q27) +/// هر بار تغییر وضعیت چرخه (شروع، ورود/خروج جادویی، تکمیل)، یک رکورد ذخیره می‌شود. +/// +public class ClubMembershipCycleHistory : BaseAuditableEntity +{ + /// + /// شناسه چرخه + /// + public long ClubMembershipCycleId { get; set; } + + /// + /// ClubMembershipCycle Navigation Property + /// + public virtual Club.ClubMembershipCycle? ClubMembershipCycle { get; set; } + + /// + /// شناسه کاربر + /// + public long UserId { get; set; } + + /// + /// شماره دور + /// + public int CycleNumber { get; set; } + + /// + /// IsCurrentCycle قبل از تغییر + /// + public bool OldIsCurrentCycle { get; set; } + + /// + /// IsCurrentCycle بعد از تغییر + /// + public bool NewIsCurrentCycle { get; set; } + + /// + /// تاریخ شروع جادویی قبل از تغییر + /// + public DateTime? OldMagicStartedAt { get; set; } + + /// + /// تاریخ شروع جادویی بعد از تغییر + /// + public DateTime? NewMagicStartedAt { get; set; } + + /// + /// تاریخ تکمیل جادویی قبل از تغییر + /// + public DateTime? OldMagicCompletedAt { get; set; } + + /// + /// تاریخ تکمیل جادویی بعد از تغییر + /// + public DateTime? NewMagicCompletedAt { get; set; } + + /// + /// نوع عملیات + /// + public ClubMembershipCycleAction Action { get; set; } + + /// + /// چه کسی انجام داده (UserId یا "System") + /// + public string? PerformedBy { get; set; } + + /// + /// دلیل تغییر (اختیاری) + /// + public string? Reason { get; set; } +} diff --git a/src/CMSMicroservice.Domain/Entities/History/PackageHistory.cs b/src/CMSMicroservice.Domain/Entities/History/PackageHistory.cs new file mode 100644 index 0000000..9b8e081 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/History/PackageHistory.cs @@ -0,0 +1,93 @@ +namespace CMSMicroservice.Domain.Entities.History; + +/// +/// تاریخچه تغییرات پکیج (Q27) +/// هر بار تغییر قیمت، تنظیمات جادویی یا کمیسیون، یک رکورد ذخیره می‌شود. +/// +public class PackageHistory : BaseAuditableEntity +{ + /// + /// شناسه پکیج + /// + public long PackageId { get; set; } + + /// + /// Package Navigation Property + /// + public virtual Package? Package { get; set; } + + /// + /// قیمت قبل از تغییر (ریال) + /// + public long? OldPrice { get; set; } + + /// + /// قیمت بعد از تغییر (ریال) + /// + public long? NewPrice { get; set; } + + /// + /// سهم Commission Pool قبل از تغییر + /// + public long? OldActivationFee { get; set; } + + /// + /// سهم Commission Pool بعد از تغییر + /// + public long? NewActivationFee { get; set; } + + /// + /// ضریب جادویی قبل از تغییر + /// + public decimal? OldMagicMultiplier { get; set; } + + /// + /// ضریب جادویی بعد از تغییر + /// + public decimal? NewMagicMultiplier { get; set; } + + /// + /// سقف شارژ جادویی قبل از تغییر + /// + public long? OldMagicMaxDeposit { get; set; } + + /// + /// سقف شارژ جادویی بعد از تغییر + /// + public long? NewMagicMaxDeposit { get; set; } + + /// + /// سقف تعادل هر پا قبل از تغییر + /// + public int? OldMaxBalancesPerLeg { get; set; } + + /// + /// سقف تعادل هر پا بعد از تغییر + /// + public int? NewMaxBalancesPerLeg { get; set; } + + /// + /// وضعیت فعال قبل از تغییر + /// + public bool? OldIsActive { get; set; } + + /// + /// وضعیت فعال بعد از تغییر + /// + public bool? NewIsActive { get; set; } + + /// + /// نوع عملیات + /// + public PackageAction Action { get; set; } + + /// + /// چه کسی انجام داده (UserId یا "System") + /// + public string? PerformedBy { get; set; } + + /// + /// دلیل تغییر (اختیاری) + /// + public string? Reason { get; set; } +} diff --git a/src/CMSMicroservice.Domain/Entities/Package.cs b/src/CMSMicroservice.Domain/Entities/Package.cs index 394e899..9950886 100644 --- a/src/CMSMicroservice.Domain/Entities/Package.cs +++ b/src/CMSMicroservice.Domain/Entities/Package.cs @@ -73,4 +73,7 @@ public class Package : BaseAuditableEntity /// خریدهای پکیج public virtual ICollection? Purchases { get; set; } + + /// تاریخچه تغییرات پکیج (Q27) + public virtual ICollection? PackageHistories { get; set; } } diff --git a/src/CMSMicroservice.Domain/Enums/ClubMembershipCycleAction.cs b/src/CMSMicroservice.Domain/Enums/ClubMembershipCycleAction.cs new file mode 100644 index 0000000..5d40c05 --- /dev/null +++ b/src/CMSMicroservice.Domain/Enums/ClubMembershipCycleAction.cs @@ -0,0 +1,14 @@ +namespace CMSMicroservice.Domain.Enums; + +/// +/// نوع عملیات تغییر دوره عضویت باشگاه (Q27) +/// +public enum ClubMembershipCycleAction +{ + Started = 0, + MagicEntered = 1, + MagicCompleted = 2, + Completed = 3, + Cancelled = 4, + ManualFix = 5 +} diff --git a/src/CMSMicroservice.Domain/Enums/PackageAction.cs b/src/CMSMicroservice.Domain/Enums/PackageAction.cs new file mode 100644 index 0000000..d74f691 --- /dev/null +++ b/src/CMSMicroservice.Domain/Enums/PackageAction.cs @@ -0,0 +1,16 @@ +namespace CMSMicroservice.Domain.Enums; + +/// +/// نوع عملیات تغییر پکیج (Q27) +/// +public enum PackageAction +{ + Created = 0, + Updated = 1, + PriceChanged = 2, + Deactivated = 3, + Reactivated = 4, + MagicSettingsChanged = 5, + CommissionSettingsChanged = 6, + ManualFix = 7 +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs index 5d8ca45..3d8b33a 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs @@ -110,6 +110,8 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public DbSet UserClubFeatures => Set(); public DbSet ClubMembershipHistories => Set(); public DbSet ClubMembershipCycles => Set(); + public DbSet ClubMembershipCycleHistories => Set(); + public DbSet PackageHistories => Set(); public DbSet PackageFeatures => Set(); // Network diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleHistoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleHistoryConfiguration.cs new file mode 100644 index 0000000..08cbbb4 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ClubMembershipCycleHistoryConfiguration.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Domain.Entities.History; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +/// +/// تاریخچه تغییرات دوره عضویت باشگاه (Q27) +/// +public class ClubMembershipCycleHistoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasQueryFilter(p => !p.IsDeleted); + builder.Ignore(entity => entity.DomainEvents); + + builder.HasKey(entity => entity.Id); + builder.Property(entity => entity.Id).UseIdentityColumn(); + + builder.Property(entity => entity.ClubMembershipCycleId).IsRequired(); + builder.Property(entity => entity.UserId).IsRequired(); + builder.Property(entity => entity.CycleNumber).IsRequired(); + builder.Property(entity => entity.OldIsCurrentCycle).IsRequired(); + builder.Property(entity => entity.NewIsCurrentCycle).IsRequired(); + builder.Property(entity => entity.OldMagicStartedAt).IsRequired(false); + builder.Property(entity => entity.NewMagicStartedAt).IsRequired(false); + builder.Property(entity => entity.OldMagicCompletedAt).IsRequired(false); + builder.Property(entity => entity.NewMagicCompletedAt).IsRequired(false); + builder.Property(entity => entity.Action).IsRequired(); + builder.Property(entity => entity.PerformedBy).IsRequired(false).HasMaxLength(100); + builder.Property(entity => entity.Reason).IsRequired(false).HasMaxLength(500); + + // رابطه با ClubMembershipCycle + builder.HasOne(entity => entity.ClubMembershipCycle) + .WithMany(c => c.CycleHistories) + .HasForeignKey(entity => entity.ClubMembershipCycleId) + .OnDelete(DeleteBehavior.Restrict); + + // Index برای UserId و Created + builder.HasIndex(e => new { e.UserId, e.Created }) + .HasDatabaseName("IX_ClubMembershipCycleHistory_UserId_Created"); + + // Index برای ClubMembershipCycleId + builder.HasIndex(e => e.ClubMembershipCycleId) + .HasDatabaseName("IX_ClubMembershipCycleHistory_CycleId"); + + // Index برای Action + builder.HasIndex(e => e.Action) + .HasDatabaseName("IX_ClubMembershipCycleHistory_Action"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageHistoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageHistoryConfiguration.cs new file mode 100644 index 0000000..77e6f8c --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PackageHistoryConfiguration.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Domain.Entities.History; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +/// +/// تاریخچه تغییرات پکیج (Q27) +/// +public class PackageHistoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasQueryFilter(p => !p.IsDeleted); + builder.Ignore(entity => entity.DomainEvents); + + builder.HasKey(entity => entity.Id); + builder.Property(entity => entity.Id).UseIdentityColumn(); + + builder.Property(entity => entity.PackageId).IsRequired(); + builder.Property(entity => entity.OldPrice).IsRequired(false); + builder.Property(entity => entity.NewPrice).IsRequired(false); + builder.Property(entity => entity.OldActivationFee).IsRequired(false); + builder.Property(entity => entity.NewActivationFee).IsRequired(false); + builder.Property(entity => entity.OldMagicMultiplier).IsRequired(false).HasPrecision(18, 4); + builder.Property(entity => entity.NewMagicMultiplier).IsRequired(false).HasPrecision(18, 4); + builder.Property(entity => entity.OldMagicMaxDeposit).IsRequired(false); + builder.Property(entity => entity.NewMagicMaxDeposit).IsRequired(false); + builder.Property(entity => entity.OldMaxBalancesPerLeg).IsRequired(false); + builder.Property(entity => entity.NewMaxBalancesPerLeg).IsRequired(false); + builder.Property(entity => entity.OldIsActive).IsRequired(false); + builder.Property(entity => entity.NewIsActive).IsRequired(false); + builder.Property(entity => entity.Action).IsRequired(); + builder.Property(entity => entity.PerformedBy).IsRequired(false).HasMaxLength(100); + builder.Property(entity => entity.Reason).IsRequired(false).HasMaxLength(500); + + // رابطه با Package + builder.HasOne(entity => entity.Package) + .WithMany(p => p.PackageHistories) + .HasForeignKey(entity => entity.PackageId) + .OnDelete(DeleteBehavior.Restrict); + + // Index برای PackageId و Created + builder.HasIndex(e => new { e.PackageId, e.Created }) + .HasDatabaseName("IX_PackageHistory_PackageId_Created"); + + // Index برای Action + builder.HasIndex(e => e.Action) + .HasDatabaseName("IX_PackageHistory_Action"); + } +} From 10d2ca20d1336f50cf6e46f9e00b27cd4dfbd524 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 06:22:15 +0330 Subject: [PATCH 36/44] =?UTF-8?q?refactor:=20rename=20UserWalletChangeLog?= =?UTF-8?q?=E2=86=92UserWalletHistory,=20add=20History=20interceptor=20&?= =?UTF-8?q?=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename UserWalletChangeLog to UserWalletHistory across 54+ files (entities, configs, DTOs, commands, queries, protos, services) - Rename 34 files and 11 directories accordingly - Rename proto file userwalletchangelog.proto → userwallethistory.proto - Add IHasHistory generic interface for history auto-tracking - Implement IHasHistory on Package entity - Add HistoryTrackingSaveChangesInterceptor (reflection-based, auto-fills Old* values from OriginalValues) - Wire interceptor in DI and ApplicationDbContext - Add EF migration Q27_HistoryTables_And_RenameWalletHistory: * RenameTable UserWalletChangeLogs → UserWalletHistories (preserves data) * Rename PK, FK constraints and indexes via sp_rename * CreateTable ClubMembershipCycleHistories + PackageHistories --- ...ulateWeeklyCommissionPoolCommandHandler.cs | 10 +- .../Interfaces/IApplicationDbContext.cs | 2 +- .../Mappings/UserWalletChangeLogProfile.cs | 16 - .../Mappings/UserWalletHistoryProfile.cs | 16 + .../CheckAndProcessDayaLoansCommandHandler.cs | 8 +- .../CompleteOrderPaymentCommandHandler.cs | 2 +- .../PlaceOrder/PlaceOrderCommandHandler.cs | 2 +- .../ApproveManualPaymentCommandHandler.cs | 12 +- .../CreateManualPaymentCommandHandler.cs | 4 +- ...ssManualMembershipPaymentCommandHandler.cs | 4 +- .../CancelOrderByAdminCommandHandler.cs | 4 +- .../VerifyPackagePurchaseCommandHandler.cs | 8 +- .../GetCustomerReferralsQueryHandler.cs | 2 +- .../GetCustomerWalletHistoryQuery.cs} | 4 +- .../GetCustomerWalletHistoryQueryHandler.cs} | 14 +- .../GetCustomerWalletHistoryResponseDto.cs} | 4 +- ...ateNewUserWalletChangeLogCommandHandler.cs | 21 - ...CreateNewUserWalletChangeLogResponseDto.cs | 7 - .../DeleteUserWalletChangeLogCommand.cs | 7 - ...DeleteUserWalletChangeLogCommandHandler.cs | 22 - ...leteUserWalletChangeLogCommandValidator.cs | 16 - ...UpdateUserWalletChangeLogCommandHandler.cs | 22 - ...reateNewUserWalletChangeLogEventHandler.cs | 22 - .../DeleteUserWalletChangeLogEventHandler.cs | 22 - .../UpdateUserWalletChangeLogEventHandler.cs | 22 - ...erWalletChangeLogByFilterQueryValidator.cs | 14 - .../GetUserWalletChangeLogQuery.cs | 7 - .../GetUserWalletChangeLogQueryHandler.cs | 22 - .../CreateNewUserWalletHistoryCommand.cs} | 4 +- ...reateNewUserWalletHistoryCommandHandler.cs | 21 + ...teNewUserWalletHistoryCommandValidator.cs} | 8 +- .../CreateNewUserWalletHistoryResponseDto.cs | 7 + .../DeleteUserWalletHistoryCommand.cs | 7 + .../DeleteUserWalletHistoryCommandHandler.cs | 22 + ...DeleteUserWalletHistoryCommandValidator.cs | 16 + .../UpdateUserWalletHistoryCommand.cs} | 4 +- .../UpdateUserWalletHistoryCommandHandler.cs | 22 + ...pdateUserWalletHistoryCommandValidator.cs} | 8 +- .../CreateNewUserWalletHistoryEventHandler.cs | 22 + .../DeleteUserWalletHistoryEventHandler.cs | 22 + .../UpdateUserWalletHistoryEventHandler.cs | 22 + .../GetAllUserWalletHistoryByFilterQuery.cs} | 8 +- ...lUserWalletHistoryByFilterQueryHandler.cs} | 14 +- ...UserWalletHistoryByFilterQueryValidator.cs | 14 + ...llUserWalletHistoryByFilterResponseDto.cs} | 8 +- .../GetUserWalletHistoryQuery.cs | 7 + .../GetUserWalletHistoryQueryHandler.cs | 22 + .../GetUserWalletHistoryQueryValidator.cs} | 8 +- .../GetUserWalletHistoryResponseDto.cs} | 4 +- ...erifyDiscountWalletChargeCommandHandler.cs | 2 +- .../VerifyMagicWalletChargeCommandHandler.cs | 6 +- .../Common/IHasHistory.cs | 19 + .../Entities/Package.cs | 24 +- .../Entities/UserWallet.cs | 4 +- ...alletChangeLog.cs => UserWalletHistory.cs} | 2 +- .../CreateNewUserWalletChangeLogEvent.cs | 8 - .../DeleteUserWalletChangeLogEvent.cs | 8 - .../UpdateUserWalletChangeLogEvent.cs | 8 - .../CreateNewUserWalletHistoryEvent.cs | 8 + .../DeleteUserWalletHistoryEvent.cs | 8 + .../UpdateUserWalletHistoryEvent.cs | 8 + .../ConfigureServices.cs | 1 + .../Persistence/ApplicationDbContext.cs | 12 +- ...n.cs => UserWalletHistoryConfiguration.cs} | 6 +- .../HistoryTrackingSaveChangesInterceptor.cs | 150 + ...Tables_And_RenameWalletHistory.Designer.cs | 5203 +++++++++++++++++ ...7_HistoryTables_And_RenameWalletHistory.cs | 190 + .../ApplicationDbContextModelSnapshot.cs | 200 +- .../OrmCommissionCalculationStrategy.cs | 10 +- .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/userwallet.proto | 12 +- ...hangelog.proto => userwallethistory.proto} | 50 +- ...leteUserWalletChangeLogRequestValidator.cs | 19 - ...WalletChangeLogByFilterRequestValidator.cs | 17 - ...teNewUserWalletHistoryRequestValidator.cs} | 10 +- ...DeleteUserWalletHistoryRequestValidator.cs | 19 + ...erWalletHistoryByFilterRequestValidator.cs | 17 + .../GetUserWalletHistoryRequestValidator.cs} | 10 +- ...pdateUserWalletHistoryRequestValidator.cs} | 10 +- ...Profile.cs => UserWalletHistoryProfile.cs} | 2 +- src/CMSMicroservice.WebApi/Program.cs | 2 +- .../Services/PackageService.cs | 4 +- .../Services/UserOrderService.cs | 6 +- .../Services/UserWalletChangeLogService.cs | 37 - .../Services/UserWalletHistoryService.cs | 37 + .../Services/UserWalletService.cs | 10 +- 86 files changed, 6255 insertions(+), 472 deletions(-) delete mode 100644 src/CMSMicroservice.Application/Common/Mappings/UserWalletChangeLogProfile.cs create mode 100644 src/CMSMicroservice.Application/Common/Mappings/UserWalletHistoryProfile.cs rename src/CMSMicroservice.Application/UserWalletCQ/Queries/{GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQuery.cs => GetCustomerWalletHistory/GetCustomerWalletHistoryQuery.cs} (76%) rename src/CMSMicroservice.Application/UserWalletCQ/Queries/{GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQueryHandler.cs => GetCustomerWalletHistory/GetCustomerWalletHistoryQueryHandler.cs} (77%) rename src/CMSMicroservice.Application/UserWalletCQ/Queries/{GetCustomerWalletChangeLog/GetCustomerWalletChangeLogResponseDto.cs => GetCustomerWalletHistory/GetCustomerWalletHistoryResponseDto.cs} (94%) delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogResponseDto.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommand.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandValidator.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/CreateNewUserWalletChangeLogEventHandlers/CreateNewUserWalletChangeLogEventHandler.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/DeleteUserWalletChangeLogEventHandlers/DeleteUserWalletChangeLogEventHandler.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/UpdateUserWalletChangeLogEventHandlers/UpdateUserWalletChangeLogEventHandler.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryValidator.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQuery.cs delete mode 100644 src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryHandler.cs rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommand.cs => UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommand.cs} (72%) create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandHandler.cs rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandValidator.cs => UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandValidator.cs} (64%) create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryResponseDto.cs create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommand.cs create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandHandler.cs create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandValidator.cs rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommand.cs => UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommand.cs} (78%) create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandHandler.cs rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandValidator.cs => UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandValidator.cs} (67%) create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/CreateNewUserWalletHistoryEventHandlers/CreateNewUserWalletHistoryEventHandler.cs create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/DeleteUserWalletHistoryEventHandlers/DeleteUserWalletHistoryEventHandler.cs create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/UpdateUserWalletHistoryEventHandlers/UpdateUserWalletHistoryEventHandler.cs rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQuery.cs => UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQuery.cs} (66%) rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryHandler.cs => UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryHandler.cs} (63%) create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryValidator.cs rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterResponseDto.cs => UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterResponseDto.cs} (70%) create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQuery.cs create mode 100644 src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryHandler.cs rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryValidator.cs => UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryValidator.cs} (51%) rename src/CMSMicroservice.Application/{UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogResponseDto.cs => UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryResponseDto.cs} (82%) create mode 100644 src/CMSMicroservice.Domain/Common/IHasHistory.cs rename src/CMSMicroservice.Domain/Entities/{UserWalletChangeLog.cs => UserWalletHistory.cs} (95%) delete mode 100644 src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/CreateNewUserWalletChangeLogEvent.cs delete mode 100644 src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/DeleteUserWalletChangeLogEvent.cs delete mode 100644 src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/UpdateUserWalletChangeLogEvent.cs create mode 100644 src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/CreateNewUserWalletHistoryEvent.cs create mode 100644 src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/DeleteUserWalletHistoryEvent.cs create mode 100644 src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/UpdateUserWalletHistoryEvent.cs rename src/CMSMicroservice.Infrastructure/Persistence/Configurations/{UserWalletChangeLogConfiguration.cs => UserWalletHistoryConfiguration.cs} (85%) create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.Designer.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.cs rename src/CMSMicroservice.Protobuf/Protos/{userwalletchangelog.proto => userwallethistory.proto} (61%) delete mode 100644 src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/DeleteUserWalletChangeLogRequestValidator.cs delete mode 100644 src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/GetAllUserWalletChangeLogByFilterRequestValidator.cs rename src/CMSMicroservice.Protobuf/Validator/{UserWalletChangeLog/CreateNewUserWalletChangeLogRequestValidator.cs => UserWalletHistory/CreateNewUserWalletHistoryRequestValidator.cs} (64%) create mode 100644 src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/DeleteUserWalletHistoryRequestValidator.cs create mode 100644 src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/GetAllUserWalletHistoryByFilterRequestValidator.cs rename src/CMSMicroservice.Protobuf/Validator/{UserWalletChangeLog/GetUserWalletChangeLogRequestValidator.cs => UserWalletHistory/GetUserWalletHistoryRequestValidator.cs} (50%) rename src/CMSMicroservice.Protobuf/Validator/{UserWalletChangeLog/UpdateUserWalletChangeLogRequestValidator.cs => UserWalletHistory/UpdateUserWalletHistoryRequestValidator.cs} (66%) rename src/CMSMicroservice.WebApi/Common/Mappings/{UserWalletChangeLogProfile.cs => UserWalletHistoryProfile.cs} (83%) delete mode 100644 src/CMSMicroservice.WebApi/Services/UserWalletChangeLogService.cs create mode 100644 src/CMSMicroservice.WebApi/Services/UserWalletHistoryService.cs diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs index 3435261..38e814f 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyCommissionPool/CalculateWeeklyCommissionPoolCommandHandler.cs @@ -192,7 +192,7 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler w.UserId, cancellationToken); var newWallets = new List(); - var walletLogs = new List(); + var walletLogs = new List(); foreach (var payout in payouts) { @@ -241,7 +241,7 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler l.RefrenceId.HasValue && oldPayoutIds.Contains(l.RefrenceId.Value)) .ToListAsync(cancellationToken); @@ -300,7 +300,7 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler OrderVATs { get; } DbSet UserPackagePurchases { get; } DbSet UserWallets { get; } - DbSet UserWalletChangeLogs { get; } + DbSet UserWalletHistories { get; } DbSet ManualPayments { get; } DbSet PaymentTransactions { get; } DbSet PublicMessages { get; } diff --git a/src/CMSMicroservice.Application/Common/Mappings/UserWalletChangeLogProfile.cs b/src/CMSMicroservice.Application/Common/Mappings/UserWalletChangeLogProfile.cs deleted file mode 100644 index 5f7c28d..0000000 --- a/src/CMSMicroservice.Application/Common/Mappings/UserWalletChangeLogProfile.cs +++ /dev/null @@ -1,16 +0,0 @@ -using CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetAllUserWalletChangeLogByFilter; -using CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetUserWalletChangeLog; - -namespace CMSMicroservice.Application.Common.Mappings; - -public class UserWalletChangeLogProfile : IRegister -{ - void IRegister.Register(TypeAdapterConfig config) - { - config.NewConfig() - .Map(dest => dest.CreatedAt, src => src.Created); - - config.NewConfig() - .Map(dest => dest.CreatedAt, src => src.Created); - } -} diff --git a/src/CMSMicroservice.Application/Common/Mappings/UserWalletHistoryProfile.cs b/src/CMSMicroservice.Application/Common/Mappings/UserWalletHistoryProfile.cs new file mode 100644 index 0000000..a8cbfdb --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Mappings/UserWalletHistoryProfile.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetAllUserWalletHistoryByFilter; +using CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetUserWalletHistory; + +namespace CMSMicroservice.Application.Common.Mappings; + +public class UserWalletHistoryProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.CreatedAt, src => src.Created); + + config.NewConfig() + .Map(dest => dest.CreatedAt, src => src.Created); + } +} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs index 93d494a..fb0aa33 100644 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs @@ -223,7 +223,7 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler x.Wallet) .Where(x => x.Wallet.UserId == userId && x.Created >= startOfMonth) diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQuery.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryQuery.cs similarity index 76% rename from src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQuery.cs rename to src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryQuery.cs index 87a3109..80037eb 100644 --- a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQuery.cs +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryQuery.cs @@ -1,6 +1,6 @@ -namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory; -public class GetCustomerWalletChangeLogQuery : IRequest> +public class GetCustomerWalletHistoryQuery : IRequest> { /// /// فیلتر بر اساس شناسه ارجاع (اختیاری) diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryQueryHandler.cs similarity index 77% rename from src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQueryHandler.cs rename to src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryQueryHandler.cs index bb09d35..4c7fdaf 100644 --- a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQueryHandler.cs +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryQueryHandler.cs @@ -1,11 +1,11 @@ -namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory; -public class GetCustomerWalletChangeLogQueryHandler : IRequestHandler> +public class GetCustomerWalletHistoryQueryHandler : IRequestHandler> { private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUser; - public GetCustomerWalletChangeLogQueryHandler( + public GetCustomerWalletHistoryQueryHandler( IApplicationDbContext context, ICurrentUserService currentUser) { @@ -13,8 +13,8 @@ public class GetCustomerWalletChangeLogQueryHandler : IRequestHandler> Handle( - GetCustomerWalletChangeLogQuery request, + public async Task> Handle( + GetCustomerWalletHistoryQuery request, CancellationToken cancellationToken) { // Get current user's ID from JWT @@ -35,7 +35,7 @@ public class GetCustomerWalletChangeLogQueryHandler : IRequestHandler x.WalletId == userWallet.Id); @@ -53,7 +53,7 @@ public class GetCustomerWalletChangeLogQueryHandler : IRequestHandler x.Created) - .ProjectToType() + .ProjectToType() .ToListAsync(cancellationToken); return result; diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogResponseDto.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryResponseDto.cs similarity index 94% rename from src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogResponseDto.cs rename to src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryResponseDto.cs index 0460c89..468e64d 100644 --- a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogResponseDto.cs +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletHistory/GetCustomerWalletHistoryResponseDto.cs @@ -1,6 +1,6 @@ -namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory; -public class GetCustomerWalletChangeLogResponseDto +public class GetCustomerWalletHistoryResponseDto { /// /// موجودی جاری diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandHandler.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandHandler.cs deleted file mode 100644 index 0e3cd6b..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.CreateNewUserWalletChangeLog; -public class CreateNewUserWalletChangeLogCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public CreateNewUserWalletChangeLogCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(CreateNewUserWalletChangeLogCommand request, - CancellationToken cancellationToken) - { - var entity = request.Adapt(); - await _context.UserWalletChangeLogs.AddAsync(entity, cancellationToken); - entity.AddDomainEvent(new CreateNewUserWalletChangeLogEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return entity.Adapt(); - } -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogResponseDto.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogResponseDto.cs deleted file mode 100644 index 5b83bf8..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogResponseDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.CreateNewUserWalletChangeLog; -public class CreateNewUserWalletChangeLogResponseDto -{ - //شناسه - public long Id { get; set; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommand.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommand.cs deleted file mode 100644 index 2c7e243..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.DeleteUserWalletChangeLog; -public record DeleteUserWalletChangeLogCommand : IRequest -{ - //شناسه - public long Id { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandHandler.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandHandler.cs deleted file mode 100644 index 722888a..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.DeleteUserWalletChangeLog; -public class DeleteUserWalletChangeLogCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public DeleteUserWalletChangeLogCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(DeleteUserWalletChangeLogCommand request, CancellationToken cancellationToken) - { - var entity = await _context.UserWalletChangeLogs - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserWalletChangeLog), request.Id); - entity.IsDeleted = true; - _context.UserWalletChangeLogs.Update(entity); - entity.AddDomainEvent(new DeleteUserWalletChangeLogEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return Unit.Value; - } -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandValidator.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandValidator.cs deleted file mode 100644 index f4116a0..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/DeleteUserWalletChangeLog/DeleteUserWalletChangeLogCommandValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.DeleteUserWalletChangeLog; -public class DeleteUserWalletChangeLogCommandValidator : AbstractValidator -{ - public DeleteUserWalletChangeLogCommandValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserWalletChangeLogCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandHandler.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandHandler.cs deleted file mode 100644 index 4ab0f67..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.UpdateUserWalletChangeLog; -public class UpdateUserWalletChangeLogCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public UpdateUserWalletChangeLogCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(UpdateUserWalletChangeLogCommand request, CancellationToken cancellationToken) - { - var entity = await _context.UserWalletChangeLogs - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserWalletChangeLog), request.Id); - request.Adapt(entity); - _context.UserWalletChangeLogs.Update(entity); - entity.AddDomainEvent(new UpdateUserWalletChangeLogEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return Unit.Value; - } -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/CreateNewUserWalletChangeLogEventHandlers/CreateNewUserWalletChangeLogEventHandler.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/CreateNewUserWalletChangeLogEventHandlers/CreateNewUserWalletChangeLogEventHandler.cs deleted file mode 100644 index 3a8f158..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/CreateNewUserWalletChangeLogEventHandlers/CreateNewUserWalletChangeLogEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.EventHandlers; - -public class CreateNewUserWalletChangeLogEventHandler : INotificationHandler -{ - private readonly ILogger< - CreateNewUserWalletChangeLogEventHandler> _logger; - - public CreateNewUserWalletChangeLogEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(CreateNewUserWalletChangeLogEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/DeleteUserWalletChangeLogEventHandlers/DeleteUserWalletChangeLogEventHandler.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/DeleteUserWalletChangeLogEventHandlers/DeleteUserWalletChangeLogEventHandler.cs deleted file mode 100644 index e9cd2fa..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/DeleteUserWalletChangeLogEventHandlers/DeleteUserWalletChangeLogEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.EventHandlers; - -public class DeleteUserWalletChangeLogEventHandler : INotificationHandler -{ - private readonly ILogger< - DeleteUserWalletChangeLogEventHandler> _logger; - - public DeleteUserWalletChangeLogEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(DeleteUserWalletChangeLogEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/UpdateUserWalletChangeLogEventHandlers/UpdateUserWalletChangeLogEventHandler.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/UpdateUserWalletChangeLogEventHandlers/UpdateUserWalletChangeLogEventHandler.cs deleted file mode 100644 index e2d5ff3..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/EventHandlers/UpdateUserWalletChangeLogEventHandlers/UpdateUserWalletChangeLogEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.EventHandlers; - -public class UpdateUserWalletChangeLogEventHandler : INotificationHandler -{ - private readonly ILogger< - UpdateUserWalletChangeLogEventHandler> _logger; - - public UpdateUserWalletChangeLogEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(UpdateUserWalletChangeLogEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryValidator.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryValidator.cs deleted file mode 100644 index a4d1626..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetAllUserWalletChangeLogByFilter; -public class GetAllUserWalletChangeLogByFilterQueryValidator : AbstractValidator -{ - public GetAllUserWalletChangeLogByFilterQueryValidator() - { - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserWalletChangeLogByFilterQuery)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQuery.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQuery.cs deleted file mode 100644 index 575a8d2..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQuery.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetUserWalletChangeLog; -public record GetUserWalletChangeLogQuery : IRequest -{ - //شناسه - public long Id { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryHandler.cs deleted file mode 100644 index b731d47..0000000 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetUserWalletChangeLog; -public class GetUserWalletChangeLogQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public GetUserWalletChangeLogQueryHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(GetUserWalletChangeLogQuery request, - CancellationToken cancellationToken) - { - var response = await _context.UserWalletChangeLogs - .AsNoTracking() - .Where(x => x.Id == request.Id) - .ProjectToType() - .FirstOrDefaultAsync(cancellationToken); - - return response ?? throw new NotFoundException(nameof(UserWalletChangeLog), request.Id); - } -} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommand.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommand.cs similarity index 72% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommand.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommand.cs index cb48da4..4598aaf 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommand.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommand.cs @@ -1,5 +1,5 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.CreateNewUserWalletChangeLog; -public record CreateNewUserWalletChangeLogCommand : IRequest +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.CreateNewUserWalletHistory; +public record CreateNewUserWalletHistoryCommand : IRequest { //شناسه کیف پول public long WalletId { get; init; } diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandHandler.cs new file mode 100644 index 0000000..098230b --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandHandler.cs @@ -0,0 +1,21 @@ +using CMSMicroservice.Domain.Events; +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.CreateNewUserWalletHistory; +public class CreateNewUserWalletHistoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateNewUserWalletHistoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateNewUserWalletHistoryCommand request, + CancellationToken cancellationToken) + { + var entity = request.Adapt(); + await _context.UserWalletHistories.AddAsync(entity, cancellationToken); + entity.AddDomainEvent(new CreateNewUserWalletHistoryEvent(entity)); + await _context.SaveChangesAsync(cancellationToken); + return entity.Adapt(); + } +} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandValidator.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandValidator.cs similarity index 64% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandValidator.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandValidator.cs index bccc486..4587668 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/CreateNewUserWalletChangeLog/CreateNewUserWalletChangeLogCommandValidator.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryCommandValidator.cs @@ -1,7 +1,7 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.CreateNewUserWalletChangeLog; -public class CreateNewUserWalletChangeLogCommandValidator : AbstractValidator +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.CreateNewUserWalletHistory; +public class CreateNewUserWalletHistoryCommandValidator : AbstractValidator { - public CreateNewUserWalletChangeLogCommandValidator() + public CreateNewUserWalletHistoryCommandValidator() { RuleFor(model => model.WalletId) .NotNull(); @@ -18,7 +18,7 @@ public class CreateNewUserWalletChangeLogCommandValidator : AbstractValidator>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserWalletChangeLogCommand)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserWalletHistoryCommand)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryResponseDto.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryResponseDto.cs new file mode 100644 index 0000000..42e6f90 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/CreateNewUserWalletHistory/CreateNewUserWalletHistoryResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.CreateNewUserWalletHistory; +public class CreateNewUserWalletHistoryResponseDto +{ + //شناسه + public long Id { get; set; } + +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommand.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommand.cs new file mode 100644 index 0000000..b2513c3 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommand.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.DeleteUserWalletHistory; +public record DeleteUserWalletHistoryCommand : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandHandler.cs new file mode 100644 index 0000000..f22e9e6 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Events; +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.DeleteUserWalletHistory; +public class DeleteUserWalletHistoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteUserWalletHistoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteUserWalletHistoryCommand request, CancellationToken cancellationToken) + { + var entity = await _context.UserWalletHistories + .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserWalletHistory), request.Id); + entity.IsDeleted = true; + _context.UserWalletHistories.Update(entity); + entity.AddDomainEvent(new DeleteUserWalletHistoryEvent(entity)); + await _context.SaveChangesAsync(cancellationToken); + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandValidator.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandValidator.cs new file mode 100644 index 0000000..73fe3d5 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/DeleteUserWalletHistory/DeleteUserWalletHistoryCommandValidator.cs @@ -0,0 +1,16 @@ +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.DeleteUserWalletHistory; +public class DeleteUserWalletHistoryCommandValidator : AbstractValidator +{ + public DeleteUserWalletHistoryCommandValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserWalletHistoryCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommand.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommand.cs similarity index 78% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommand.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommand.cs index d41be96..adb8d32 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommand.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommand.cs @@ -1,5 +1,5 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.UpdateUserWalletChangeLog; -public record UpdateUserWalletChangeLogCommand : IRequest +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.UpdateUserWalletHistory; +public record UpdateUserWalletHistoryCommand : IRequest { //شناسه public long Id { get; init; } diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandHandler.cs new file mode 100644 index 0000000..31ba567 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Events; +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.UpdateUserWalletHistory; +public class UpdateUserWalletHistoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateUserWalletHistoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateUserWalletHistoryCommand request, CancellationToken cancellationToken) + { + var entity = await _context.UserWalletHistories + .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserWalletHistory), request.Id); + request.Adapt(entity); + _context.UserWalletHistories.Update(entity); + entity.AddDomainEvent(new UpdateUserWalletHistoryEvent(entity)); + await _context.SaveChangesAsync(cancellationToken); + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandValidator.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandValidator.cs similarity index 67% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandValidator.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandValidator.cs index 3a6c32d..7159ef0 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Commands/UpdateUserWalletChangeLog/UpdateUserWalletChangeLogCommandValidator.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Commands/UpdateUserWalletHistory/UpdateUserWalletHistoryCommandValidator.cs @@ -1,7 +1,7 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.UpdateUserWalletChangeLog; -public class UpdateUserWalletChangeLogCommandValidator : AbstractValidator +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Commands.UpdateUserWalletHistory; +public class UpdateUserWalletHistoryCommandValidator : AbstractValidator { - public UpdateUserWalletChangeLogCommandValidator() + public UpdateUserWalletHistoryCommandValidator() { RuleFor(model => model.Id) .NotNull(); @@ -20,7 +20,7 @@ public class UpdateUserWalletChangeLogCommandValidator : AbstractValidator>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserWalletChangeLogCommand)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserWalletHistoryCommand)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/CreateNewUserWalletHistoryEventHandlers/CreateNewUserWalletHistoryEventHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/CreateNewUserWalletHistoryEventHandlers/CreateNewUserWalletHistoryEventHandler.cs new file mode 100644 index 0000000..d1640dc --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/CreateNewUserWalletHistoryEventHandlers/CreateNewUserWalletHistoryEventHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Events; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.UserWalletHistoryCQ.EventHandlers; + +public class CreateNewUserWalletHistoryEventHandler : INotificationHandler +{ + private readonly ILogger< + CreateNewUserWalletHistoryEventHandler> _logger; + + public CreateNewUserWalletHistoryEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(CreateNewUserWalletHistoryEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/DeleteUserWalletHistoryEventHandlers/DeleteUserWalletHistoryEventHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/DeleteUserWalletHistoryEventHandlers/DeleteUserWalletHistoryEventHandler.cs new file mode 100644 index 0000000..a9b244e --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/DeleteUserWalletHistoryEventHandlers/DeleteUserWalletHistoryEventHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Events; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.UserWalletHistoryCQ.EventHandlers; + +public class DeleteUserWalletHistoryEventHandler : INotificationHandler +{ + private readonly ILogger< + DeleteUserWalletHistoryEventHandler> _logger; + + public DeleteUserWalletHistoryEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DeleteUserWalletHistoryEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/UpdateUserWalletHistoryEventHandlers/UpdateUserWalletHistoryEventHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/UpdateUserWalletHistoryEventHandlers/UpdateUserWalletHistoryEventHandler.cs new file mode 100644 index 0000000..02a7758 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/EventHandlers/UpdateUserWalletHistoryEventHandlers/UpdateUserWalletHistoryEventHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Events; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.UserWalletHistoryCQ.EventHandlers; + +public class UpdateUserWalletHistoryEventHandler : INotificationHandler +{ + private readonly ILogger< + UpdateUserWalletHistoryEventHandler> _logger; + + public UpdateUserWalletHistoryEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(UpdateUserWalletHistoryEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQuery.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQuery.cs similarity index 66% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQuery.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQuery.cs index 22bf531..840a661 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQuery.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQuery.cs @@ -1,14 +1,14 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetAllUserWalletChangeLogByFilter; -public record GetAllUserWalletChangeLogByFilterQuery : IRequest +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetAllUserWalletHistoryByFilter; +public record GetAllUserWalletHistoryByFilterQuery : IRequest { //موقعیت صفحه بندی public PaginationState? PaginationState { get; init; } //مرتب سازی بر اساس public string? SortBy { get; init; } //فیلتر - public GetAllUserWalletChangeLogByFilterFilter? Filter { get; init; } + public GetAllUserWalletHistoryByFilterFilter? Filter { get; init; } -}public class GetAllUserWalletChangeLogByFilterFilter +}public class GetAllUserWalletHistoryByFilterFilter { //شناسه public long? Id { get; set; } diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryHandler.cs similarity index 63% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryHandler.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryHandler.cs index 2989c8b..ce0c96d 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterQueryHandler.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryHandler.cs @@ -1,16 +1,16 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetAllUserWalletChangeLogByFilter; -public class GetAllUserWalletChangeLogByFilterQueryHandler : IRequestHandler +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetAllUserWalletHistoryByFilter; +public class GetAllUserWalletHistoryByFilterQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; - public GetAllUserWalletChangeLogByFilterQueryHandler(IApplicationDbContext context) + public GetAllUserWalletHistoryByFilterQueryHandler(IApplicationDbContext context) { _context = context; } - public async Task Handle(GetAllUserWalletChangeLogByFilterQuery request, CancellationToken cancellationToken) + public async Task Handle(GetAllUserWalletHistoryByFilterQuery request, CancellationToken cancellationToken) { - var query = _context.UserWalletChangeLogs + var query = _context.UserWalletHistories .Include(i=>i.Wallet) .ApplyOrder(sortBy: request.SortBy) .AsNoTracking() @@ -27,11 +27,11 @@ public class GetAllUserWalletChangeLogByFilterQueryHandler : IRequestHandler request.Filter.UserId == null || x.Wallet.UserId == request.Filter.UserId) ; } - return new GetAllUserWalletChangeLogByFilterResponseDto + return new GetAllUserWalletHistoryByFilterResponseDto { MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), Models = await query.PaginatedListAsync(paginationState: request.PaginationState) - .ProjectToType().ToListAsync(cancellationToken) + .ProjectToType().ToListAsync(cancellationToken) }; } } diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryValidator.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryValidator.cs new file mode 100644 index 0000000..5b59f37 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterQueryValidator.cs @@ -0,0 +1,14 @@ +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetAllUserWalletHistoryByFilter; +public class GetAllUserWalletHistoryByFilterQueryValidator : AbstractValidator +{ + public GetAllUserWalletHistoryByFilterQueryValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserWalletHistoryByFilterQuery)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterResponseDto.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterResponseDto.cs similarity index 70% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterResponseDto.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterResponseDto.cs index d145fe4..81cfc31 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetAllUserWalletChangeLogByFilter/GetAllUserWalletChangeLogByFilterResponseDto.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetAllUserWalletHistoryByFilter/GetAllUserWalletHistoryByFilterResponseDto.cs @@ -1,12 +1,12 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetAllUserWalletChangeLogByFilter; -public class GetAllUserWalletChangeLogByFilterResponseDto +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetAllUserWalletHistoryByFilter; +public class GetAllUserWalletHistoryByFilterResponseDto { //متادیتا public MetaData MetaData { get; set; } //مدل خروجی - public List? Models { get; set; } + public List? Models { get; set; } -}public class GetAllUserWalletChangeLogByFilterResponseModel +}public class GetAllUserWalletHistoryByFilterResponseModel { //شناسه public long Id { get; set; } diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQuery.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQuery.cs new file mode 100644 index 0000000..a71c939 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQuery.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetUserWalletHistory; +public record GetUserWalletHistoryQuery : IRequest +{ + //شناسه + public long Id { get; init; } + +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryHandler.cs new file mode 100644 index 0000000..13c58cd --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryHandler.cs @@ -0,0 +1,22 @@ +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetUserWalletHistory; +public class GetUserWalletHistoryQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetUserWalletHistoryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetUserWalletHistoryQuery request, + CancellationToken cancellationToken) + { + var response = await _context.UserWalletHistories + .AsNoTracking() + .Where(x => x.Id == request.Id) + .ProjectToType() + .FirstOrDefaultAsync(cancellationToken); + + return response ?? throw new NotFoundException(nameof(UserWalletHistory), request.Id); + } +} diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryValidator.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryValidator.cs similarity index 51% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryValidator.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryValidator.cs index 69d3282..df18151 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogQueryValidator.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryQueryValidator.cs @@ -1,14 +1,14 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetUserWalletChangeLog; -public class GetUserWalletChangeLogQueryValidator : AbstractValidator +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetUserWalletHistory; +public class GetUserWalletHistoryQueryValidator : AbstractValidator { - public GetUserWalletChangeLogQueryValidator() + public GetUserWalletHistoryQueryValidator() { RuleFor(model => model.Id) .NotNull(); } public Func>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserWalletChangeLogQuery)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserWalletHistoryQuery)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogResponseDto.cs b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryResponseDto.cs similarity index 82% rename from src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogResponseDto.cs rename to src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryResponseDto.cs index 28896ae..d2e672f 100644 --- a/src/CMSMicroservice.Application/UserWalletChangeLogCQ/Queries/GetUserWalletChangeLog/GetUserWalletChangeLogResponseDto.cs +++ b/src/CMSMicroservice.Application/UserWalletHistoryCQ/Queries/GetUserWalletHistory/GetUserWalletHistoryResponseDto.cs @@ -1,5 +1,5 @@ -namespace CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetUserWalletChangeLog; -public class GetUserWalletChangeLogResponseDto +namespace CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetUserWalletHistory; +public class GetUserWalletHistoryResponseDto { //شناسه public long Id { get; set; } diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs index 3dac1d8..400b8dd 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs @@ -115,7 +115,7 @@ public class VerifyDiscountWalletChargeCommandHandler await _context.SaveChangesAsync(cancellationToken); // ثبت لاگ تغییرات کیف پول (بعد از ایجاد Transaction برای داشتن TransactionId) - _context.UserWalletChangeLogs.Add(new Domain.Entities.UserWalletChangeLog + _context.UserWalletHistories.Add(new Domain.Entities.UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs index ab78b4e..17425b6 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs @@ -167,8 +167,8 @@ public class VerifyMagicWalletChargeCommandHandler wallet.MagicTotalDeposited += depositAmount; wallet.MagicTotalCredited += creditAmount; - // 9. ثبت WalletChangeLog (اجباری) - var walletLog = new UserWalletChangeLog + // 9. ثبت WalletHistory (اجباری) + var walletLog = new UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, @@ -181,7 +181,7 @@ public class VerifyMagicWalletChargeCommandHandler RefrenceId = depositTransaction.Id }; - _context.UserWalletChangeLogs.Add(walletLog); + _context.UserWalletHistories.Add(walletLog); // 10. لینک PaymentTransaction به Transaction داخلی paymentTx.TransactionId = depositTransaction.Id; diff --git a/src/CMSMicroservice.Domain/Common/IHasHistory.cs b/src/CMSMicroservice.Domain/Common/IHasHistory.cs new file mode 100644 index 0000000..dfa52ca --- /dev/null +++ b/src/CMSMicroservice.Domain/Common/IHasHistory.cs @@ -0,0 +1,19 @@ +namespace CMSMicroservice.Domain.Common; + +/// +/// اینترفیس مارکر برای entityهایی که تاریخچه تغییرات خودکار دارند (Q27). +/// هر entity که این اینترفیس رو implement کنه، در SaveChanges interceptor +/// به‌صورت خودکار یک snapshot از تغییرات ثبت می‌شه. +/// +/// نوع entity تاریخچه (مثلاً PackageHistory) +public interface IHasHistory where THistory : BaseAuditableEntity, new() +{ + /// + /// ساخت snapshot تاریخچه از وضعیت فعلی entity. + /// اطلاعات Original (قبل از تغییر) و Current (بعد از تغییر) باید مقایسه شوند. + /// + /// نوع عملیات (Created, Updated, etc.) — as string to be generic + /// چه کسی انجام داده + /// یک رکورد تاریخچه آماده ذخیره + THistory CreateHistorySnapshot(string action, string? performedBy); +} diff --git a/src/CMSMicroservice.Domain/Entities/Package.cs b/src/CMSMicroservice.Domain/Entities/Package.cs index 9950886..158a150 100644 --- a/src/CMSMicroservice.Domain/Entities/Package.cs +++ b/src/CMSMicroservice.Domain/Entities/Package.cs @@ -1,9 +1,12 @@ +using CMSMicroservice.Domain.Common; +using CMSMicroservice.Domain.Enums; + namespace CMSMicroservice.Domain.Entities; /// /// پکیج — هر پکیج قیمت، ویژگی‌ها و تنظیمات مستقل دارد /// -public class Package : BaseAuditableEntity +public class Package : BaseAuditableEntity, IHasHistory { // === فیلدهای فعلی (حفظ) === @@ -76,4 +79,23 @@ public class Package : BaseAuditableEntity /// تاریخچه تغییرات پکیج (Q27) public virtual ICollection? PackageHistories { get; set; } + + /// + /// ساخت snapshot تاریخچه — Q27 auto-tracking + /// + public History.PackageHistory CreateHistorySnapshot(string action, string? performedBy) + { + return new History.PackageHistory + { + PackageId = Id, + NewPrice = Price, + NewActivationFee = ActivationFee, + NewMagicMultiplier = MagicWalletMultiplier, + NewMagicMaxDeposit = MagicWalletMaxDeposit, + NewMaxBalancesPerLeg = MaxBalancesPerLeg, + NewIsActive = IsActive, + Action = Enum.TryParse(action, out var a) ? a : PackageAction.Updated, + PerformedBy = performedBy + }; + } } diff --git a/src/CMSMicroservice.Domain/Entities/UserWallet.cs b/src/CMSMicroservice.Domain/Entities/UserWallet.cs index ce14154..a6dd001 100644 --- a/src/CMSMicroservice.Domain/Entities/UserWallet.cs +++ b/src/CMSMicroservice.Domain/Entities/UserWallet.cs @@ -48,6 +48,6 @@ public class UserWallet : BaseAuditableEntity #endregion - //UserWalletChangeLog Collection Navigation Reference - public virtual ICollection UserWalletChangeLogs { get; set; } + //UserWalletHistory Collection Navigation Reference + public virtual ICollection UserWalletHistories { get; set; } } diff --git a/src/CMSMicroservice.Domain/Entities/UserWalletChangeLog.cs b/src/CMSMicroservice.Domain/Entities/UserWalletHistory.cs similarity index 95% rename from src/CMSMicroservice.Domain/Entities/UserWalletChangeLog.cs rename to src/CMSMicroservice.Domain/Entities/UserWalletHistory.cs index a5b6471..c4faf01 100644 --- a/src/CMSMicroservice.Domain/Entities/UserWalletChangeLog.cs +++ b/src/CMSMicroservice.Domain/Entities/UserWalletHistory.cs @@ -1,6 +1,6 @@ namespace CMSMicroservice.Domain.Entities; //آدرس کاربر -public class UserWalletChangeLog : BaseAuditableEntity +public class UserWalletHistory : BaseAuditableEntity { //شناسه کیف پول public long WalletId { get; set; } diff --git a/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/CreateNewUserWalletChangeLogEvent.cs b/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/CreateNewUserWalletChangeLogEvent.cs deleted file mode 100644 index 8e268c6..0000000 --- a/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/CreateNewUserWalletChangeLogEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CMSMicroservice.Domain.Events; -public class CreateNewUserWalletChangeLogEvent : BaseEvent -{ - public CreateNewUserWalletChangeLogEvent(UserWalletChangeLog item) - { - } - public UserWalletChangeLog Item { get; } -} diff --git a/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/DeleteUserWalletChangeLogEvent.cs b/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/DeleteUserWalletChangeLogEvent.cs deleted file mode 100644 index 742f8d8..0000000 --- a/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/DeleteUserWalletChangeLogEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CMSMicroservice.Domain.Events; -public class DeleteUserWalletChangeLogEvent : BaseEvent -{ - public DeleteUserWalletChangeLogEvent(UserWalletChangeLog item) - { - } - public UserWalletChangeLog Item { get; } -} diff --git a/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/UpdateUserWalletChangeLogEvent.cs b/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/UpdateUserWalletChangeLogEvent.cs deleted file mode 100644 index 4a4a903..0000000 --- a/src/CMSMicroservice.Domain/Events/UserWalletChangeLogEvents/UpdateUserWalletChangeLogEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CMSMicroservice.Domain.Events; -public class UpdateUserWalletChangeLogEvent : BaseEvent -{ - public UpdateUserWalletChangeLogEvent(UserWalletChangeLog item) - { - } - public UserWalletChangeLog Item { get; } -} diff --git a/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/CreateNewUserWalletHistoryEvent.cs b/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/CreateNewUserWalletHistoryEvent.cs new file mode 100644 index 0000000..41d38ef --- /dev/null +++ b/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/CreateNewUserWalletHistoryEvent.cs @@ -0,0 +1,8 @@ +namespace CMSMicroservice.Domain.Events; +public class CreateNewUserWalletHistoryEvent : BaseEvent +{ + public CreateNewUserWalletHistoryEvent(UserWalletHistory item) + { + } + public UserWalletHistory Item { get; } +} diff --git a/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/DeleteUserWalletHistoryEvent.cs b/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/DeleteUserWalletHistoryEvent.cs new file mode 100644 index 0000000..bb87753 --- /dev/null +++ b/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/DeleteUserWalletHistoryEvent.cs @@ -0,0 +1,8 @@ +namespace CMSMicroservice.Domain.Events; +public class DeleteUserWalletHistoryEvent : BaseEvent +{ + public DeleteUserWalletHistoryEvent(UserWalletHistory item) + { + } + public UserWalletHistory Item { get; } +} diff --git a/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/UpdateUserWalletHistoryEvent.cs b/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/UpdateUserWalletHistoryEvent.cs new file mode 100644 index 0000000..83cf2e2 --- /dev/null +++ b/src/CMSMicroservice.Domain/Events/UserWalletHistoryEvents/UpdateUserWalletHistoryEvent.cs @@ -0,0 +1,8 @@ +namespace CMSMicroservice.Domain.Events; +public class UpdateUserWalletHistoryEvent : BaseEvent +{ + public UpdateUserWalletHistoryEvent(UserWalletHistory item) + { + } + public UserWalletHistory Item { get; } +} diff --git a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs index edd7a68..63a7707 100644 --- a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs +++ b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs @@ -31,6 +31,7 @@ public static class ConfigureServices services.Configure(configuration.GetSection(SmsSettings.SectionName)); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs index 3d8b33a..4216ee8 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs @@ -18,6 +18,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext { private readonly IMediator? _mediator; private readonly AuditableEntitySaveChangesInterceptor? _auditableEntitySaveChangesInterceptor; + private readonly HistoryTrackingSaveChangesInterceptor? _historyTrackingInterceptor; /// /// Constructor برای design-time (migrations) @@ -33,11 +34,13 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public ApplicationDbContext( DbContextOptions options, IMediator mediator, - AuditableEntitySaveChangesInterceptor auditableEntitySaveChangesInterceptor) + AuditableEntitySaveChangesInterceptor auditableEntitySaveChangesInterceptor, + HistoryTrackingSaveChangesInterceptor historyTrackingInterceptor) : base(options) { _mediator = mediator; _auditableEntitySaveChangesInterceptor = auditableEntitySaveChangesInterceptor; + _historyTrackingInterceptor = historyTrackingInterceptor; } protected override void OnModelCreating(ModelBuilder builder) { @@ -57,6 +60,11 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext optionsBuilder.AddInterceptors(_auditableEntitySaveChangesInterceptor); } + if (_historyTrackingInterceptor != null) + { + optionsBuilder.AddInterceptors(_historyTrackingInterceptor); + } + // Suppress PendingModelChangesWarning in EF Core 9 optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); } @@ -89,7 +97,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public DbSet OrderVATs => Set(); public DbSet UserPackagePurchases => Set(); public DbSet UserWallets => Set(); - public DbSet UserWalletChangeLogs => Set(); + public DbSet UserWalletHistories => Set(); public DbSet DayaLoanContracts => Set(); // Payment diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletChangeLogConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletHistoryConfiguration.cs similarity index 85% rename from src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletChangeLogConfiguration.cs rename to src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletHistoryConfiguration.cs index f06d48f..2ee1304 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletChangeLogConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserWalletHistoryConfiguration.cs @@ -3,9 +3,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace CMSMicroservice.Infrastructure.Persistence.Configurations; //آدرس کاربر -public class UserWalletChangeLogConfiguration : IEntityTypeConfiguration +public class UserWalletHistoryConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { builder.HasQueryFilter(p => !p.IsDeleted); builder.Ignore(entity => entity.DomainEvents); @@ -13,7 +13,7 @@ public class UserWalletChangeLogConfiguration : IEntityTypeConfiguration entity.Id).UseIdentityColumn(); builder .HasOne(entity => entity.Wallet) - .WithMany(entity => entity.UserWalletChangeLogs) + .WithMany(entity => entity.UserWalletHistories) .HasForeignKey(entity => entity.WalletId) .IsRequired(true); builder.Property(entity => entity.CurrentBalance).IsRequired(true); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs b/src/CMSMicroservice.Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs new file mode 100644 index 0000000..c67cf1d --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using System.Linq; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Common; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.Persistence.Interceptors; + +/// +/// Interceptor ثبت خودکار تاریخچه تغییرات (Q27). +/// هر entity که IHasHistory<T> رو implement کرده باشه، +/// هنگام Modified یا Added شدن یک رکورد history ثبت می‌شه. +/// +public class HistoryTrackingSaveChangesInterceptor : SaveChangesInterceptor +{ + private readonly ICurrentUserService _currentUserService; + private readonly ILogger _logger; + + public HistoryTrackingSaveChangesInterceptor( + ICurrentUserService currentUserService, + ILogger logger) + { + _currentUserService = currentUserService; + _logger = logger; + } + + public override InterceptionResult SavingChanges( + DbContextEventData eventData, + InterceptionResult result) + { + TrackHistoryChanges(eventData.Context); + return base.SavingChanges(eventData, result); + } + + public override ValueTask> SavingChangesAsync( + DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + TrackHistoryChanges(eventData.Context); + return base.SavingChangesAsync(eventData, result, cancellationToken); + } + + private void TrackHistoryChanges(DbContext? context) + { + if (context == null) return; + + var performedBy = _currentUserService.GetPerformedBy(); + var historyEntries = new List(); + + foreach (var entry in context.ChangeTracker.Entries()) + { + // فقط Modified و Added — Deleted رو ignore می‌کنیم (soft delete) + if (entry.State != EntityState.Modified && entry.State != EntityState.Added) + continue; + + var entityType = entry.Entity.GetType(); + + // بررسی اینکه entity آیا IHasHistory رو implement کرده + var historyInterface = entityType.GetInterfaces() + .FirstOrDefault(i => i.IsGenericType + && i.GetGenericTypeDefinition() == typeof(IHasHistory<>)); + + if (historyInterface == null) + continue; + + // تعیین نوع عملیات + var action = entry.State == EntityState.Added ? "Created" : "Updated"; + + try + { + // فراخوانی CreateHistorySnapshot از طریق reflection + var method = historyInterface.GetMethod("CreateHistorySnapshot"); + if (method == null) continue; + + var historyEntity = method.Invoke(entry.Entity, new object?[] { action, performedBy }); + if (historyEntity == null) continue; + + // برای Modified: مقادیر Original رو ست کنیم + if (entry.State == EntityState.Modified) + { + SetOriginalValues(entry, historyEntity); + } + + historyEntries.Add(historyEntity); + } + catch (Exception ex) + { + _logger.LogWarning(ex, + "Failed to create history snapshot for {EntityType} (Id={Id})", + entityType.Name, + entry.Property("Id").CurrentValue); + } + } + + // اضافه کردن history entities به context + foreach (var historyEntry in historyEntries) + { + context.Add(historyEntry); + } + + if (historyEntries.Count > 0) + { + _logger.LogDebug("HistoryTrackingInterceptor: {Count} history records queued", historyEntries.Count); + } + } + + /// + /// ست کردن مقادیر Original (قبل از تغییر) روی history entity. + /// از روی نام property: "OldX" ← OriginalValue("X"), "NewX" ← CurrentValue("X") + /// + private static void SetOriginalValues( + Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry entry, + object historyEntity) + { + var historyType = historyEntity.GetType(); + var historyProps = historyType.GetProperties(); + + foreach (var historyProp in historyProps) + { + // الگو: OldPrice ← entry.OriginalValues["Price"] + if (!historyProp.Name.StartsWith("Old") || !historyProp.CanWrite) + continue; + + var sourcePropertyName = historyProp.Name[3..]; // "OldPrice" → "Price" + + try + { + var entryProperty = entry.Properties + .FirstOrDefault(p => p.Metadata.Name == sourcePropertyName); + + if (entryProperty != null) + { + var originalValue = entryProperty.OriginalValue; + // تبدیل نوع اگه nullable باشه + if (originalValue != null || Nullable.GetUnderlyingType(historyProp.PropertyType) != null) + { + historyProp.SetValue(historyEntity, originalValue); + } + } + } + catch + { + // اگه property match نداشت، skip — ممکنه OldIsActive با bool? باشه + } + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.Designer.cs new file mode 100644 index 0000000..9a5a420 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.Designer.cs @@ -0,0 +1,5203 @@ +// +using System; +using CMSMicroservice.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260227024734_Q27_HistoryTables_And_RenameWalletHistory")] + partial class Q27_HistoryTables_And_RenameWalletHistory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("CMS") + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_BlogCategories_IsActive"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogCategories_Slug"); + + b.ToTable("BlogCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsFeatured") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("ScheduledPublishAt") + .HasColumnType("datetime2"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId") + .HasDatabaseName("IX_BlogPosts_AuthorUserId"); + + b.HasIndex("IsFeatured") + .HasDatabaseName("IX_BlogPosts_IsFeatured"); + + b.HasIndex("PublishedAt") + .HasDatabaseName("IX_BlogPosts_PublishedAt"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogPosts_Slug"); + + b.HasIndex("Status") + .HasDatabaseName("IX_BlogPosts_Status"); + + b.HasIndex("Status", "PublishedAt") + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + + b.ToTable("BlogPosts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogCategoryId") + .HasColumnType("bigint"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogCategoryId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("Caption") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("TagId"); + + b.ToTable("BlogPostTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive", "SortOrder") + .HasDatabaseName("IX_ClubFeature_IsActive_SortOrder"); + + b.ToTable("ClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstActivationDate") + .HasColumnType("datetime2"); + + b.Property("FirstPackageId") + .HasColumnType("bigint"); + + b.Property("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastActivationDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastPackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FirstPackageId"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("LastActivationDate") + .HasDatabaseName("IX_ClubMembership_LastActivationDate"); + + b.HasIndex("LastPackageId"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CycleNumber") + .HasColumnType("int"); + + b.Property("IsCurrentCycle") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("MagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("PackageAmount") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PackagePurchasedAt") + .HasColumnType("datetime2"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubMembershipId"); + + b.HasIndex("PackageId"); + + b.HasIndex("PackagePurchasedAt") + .HasDatabaseName("IX_ClubMembershipCycle_PackagePurchasedAt"); + + b.HasIndex("UserId", "IsCurrentCycle") + .HasDatabaseName("IX_ClubMembershipCycle_UserId_IsCurrentCycle"); + + b.ToTable("ClubMembershipCycles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_UserClubFeature_ClubMembershipId"); + + b.HasIndex("UserId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_UserClubFeature_UserId_ClubFeatureId"); + + b.ToTable("UserClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BalancesEarned") + .HasColumnType("int"); + + b.Property("BankReferenceId") + .HasColumnType("nvarchar(max)"); + + b.Property("BankTrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IbanNumber") + .HasMaxLength(26) + .HasColumnType("nvarchar(26)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("PaymentFailureReason") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedAt") + .HasColumnType("datetime2"); + + b.Property("ProcessedBy") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolId") + .HasColumnType("bigint"); + + b.Property("WithdrawalMethod") + .HasColumnType("int"); + + b.Property("WithdrawnAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package"); + + b.ToTable("UserCommissionPayouts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCalculated") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalPoolAmount") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsCalculated") + .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated"); + + b.HasIndex("PackageId"); + + b.HasIndex("WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package"); + + b.ToTable("WeeklyCommissionPools", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Details") + .HasColumnType("nvarchar(max)"); + + b.Property("DurationMs") + .HasColumnType("bigint"); + + b.Property("ErrorCount") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ErrorStackTrace") + .HasColumnType("nvarchar(max)"); + + b.Property("ExecutionId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedCount") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StartedAt"); + + b.HasIndex("Status"); + + b.HasIndex("WeekDefinitionId"); + + b.ToTable("WorkerExecutionLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.AppVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AppName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MinRequiredVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("RequiresFullCacheClear") + .HasColumnType("bit"); + + b.Property("UpdateMessage") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AppVersions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePages_PageKey"); + + b.ToTable("SitePages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImageGroup") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("SitePageSettingsId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageSettingsId", "ImageGroup") + .HasDatabaseName("IX_SitePageImages_SettingsId_Group"); + + b.ToTable("SitePageImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtraData") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SectionKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SitePageId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageId", "SectionKey") + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + + b.ToTable("SitePageSections", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SettingsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePageSettings_PageKey"); + + b.ToTable("SitePageSettings", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Contracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsProcessed") + .HasColumnType("bit"); + + b.Property("LastCheckDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NationalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("DayaLoanContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentCategoryId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("DiscountCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("DiscountBalanceUsed") + .HasColumnType("bigint"); + + b.Property("GatewayAmountPaid") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("TrackingCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VatAmount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("DiscountOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountAmount") + .HasColumnType("bigint"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("DiscountPercentUsed") + .HasColumnType("int"); + + b.Property("FinalPrice") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DiscountOrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("DiscountOrderDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxDiscountPercent") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DiscountProducts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId", "CategoryId") + .IsUnique(); + + b.ToTable("DiscountProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId"); + + b.HasIndex("DiscountProductId", "SortOrder"); + + b.ToTable("DiscountProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId", "ProductId") + .IsUnique(); + + b.ToTable("DiscountShoppingCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsChangePrice") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitDiscount") + .HasColumnType("int"); + + b.Property("UnitDiscountPrice") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("FactorDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StateId") + .HasDatabaseName("IX_Cities_StateId"); + + b.ToTable("Cities", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Capital") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("CurrencyName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CurrencySymbol") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Emoji") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("EmojiU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PhoneCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Subregion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Tld") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.HasKey("Id"); + + b.ToTable("Countries", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CountryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId") + .HasDatabaseName("IX_States_CountryId"); + + b.ToTable("States", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipCycleHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipCycleId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CycleNumber") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewIsCurrentCycle") + .HasColumnType("bit"); + + b.Property("NewMagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("NewMagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("OldIsCurrentCycle") + .HasColumnType("bit"); + + b.Property("OldMagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("OldMagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipCycleHistory_Action"); + + b.HasIndex("ClubMembershipCycleId") + .HasDatabaseName("IX_ClubMembershipCycleHistory_CycleId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipCycleHistory_UserId_Created"); + + b.ToTable("ClubMembershipCycleHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewInitialContribution") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("OldInitialContribution") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipHistory_Action"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_ClubMembershipHistory_ClubMembershipId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipHistory_UserId_Created"); + + b.ToTable("ClubMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("AmountAfter") + .HasColumnType("bigint"); + + b.Property("AmountBefore") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("OldStatus") + .HasColumnType("int"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserCommissionPayoutId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_CommissionPayoutHistory_Action"); + + b.HasIndex("UserCommissionPayoutId") + .HasDatabaseName("IX_CommissionPayoutHistory_PayoutId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_CommissionPayoutHistory_WeekDefinitionId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created"); + + b.ToTable("CommissionPayoutHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.NetworkMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewLegPosition") + .HasColumnType("int"); + + b.Property("NewParentId") + .HasColumnType("bigint"); + + b.Property("OldLegPosition") + .HasColumnType("int"); + + b.Property("OldParentId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_NetworkMembershipHistory_Action"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_NetworkMembershipHistory_UserId_Created"); + + b.ToTable("NetworkMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.PackageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewActivationFee") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("NewMagicMaxDeposit") + .HasColumnType("bigint"); + + b.Property("NewMagicMultiplier") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxBalancesPerLeg") + .HasColumnType("int"); + + b.Property("NewPrice") + .HasColumnType("bigint"); + + b.Property("OldActivationFee") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("OldMagicMaxDeposit") + .HasColumnType("bigint"); + + b.Property("OldMagicMultiplier") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxBalancesPerLeg") + .HasColumnType("int"); + + b.Property("OldPrice") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_PackageHistory_Action"); + + b.HasIndex("PackageId", "Created") + .HasDatabaseName("IX_PackageHistory_PackageId_Created"); + + b.ToTable("PackageHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastRestockedAt") + .HasColumnType("datetime2"); + + b.Property("LastSoldAt") + .HasColumnType("datetime2"); + + b.Property("LowStockThreshold") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("MaxStockLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1000); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductType") + .HasColumnType("int"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ReorderPoint") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(5); + + b.Property("ReservedQuantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1L); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId") + .HasDatabaseName("IX_InventoryItems_DiscountProductId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_InventoryItems_ProductId"); + + b.HasIndex("WarehouseId") + .HasDatabaseName("IX_InventoryItems_WarehouseId"); + + b.HasIndex("ProductType", "Quantity") + .HasDatabaseName("IX_InventoryItems_ProductType_Quantity"); + + b.ToTable("InventoryItems", "CMS", t => + { + t.HasCheckConstraint("CK_InventoryItem_ProductReference", "(ProductId IS NOT NULL AND DiscountProductId IS NULL) OR (ProductId IS NULL AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_ProductType_Match", "(ProductType = 1 AND ProductId IS NOT NULL) OR (ProductType = 2 AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_Quantity_NonNegative", "Quantity >= 0"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_LessOrEqualQuantity", "ReservedQuantity <= Quantity"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_NonNegative", "ReservedQuantity >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FlushedPerSide") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsExpired") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LeftLegBalances") + .HasColumnType("int"); + + b.Property("LeftLegCarryover") + .HasColumnType("int"); + + b.Property("LeftLegNewMembers") + .HasColumnType("int"); + + b.Property("LeftLegRemainder") + .HasColumnType("int"); + + b.Property("LeftLegTotal") + .HasColumnType("int"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("RightLegBalances") + .HasColumnType("int"); + + b.Property("RightLegCarryover") + .HasColumnType("int"); + + b.Property("RightLegNewMembers") + .HasColumnType("int"); + + b.Property("RightLegRemainder") + .HasColumnType("int"); + + b.Property("RightLegTotal") + .HasColumnType("int"); + + b.Property("SubordinateBalances") + .HasColumnType("int"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalFlushed") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolContribution") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsExpired") + .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); + + b.HasIndex("PackageId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package"); + + b.ToTable("NetworkWeeklyBalances", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BaseAmount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsPaid") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("VATAmount") + .HasColumnType("bigint"); + + b.Property("VATRate") + .HasColumnType("decimal(5,4)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_OrderVATs_Created"); + + b.HasIndex("IsPaid") + .HasDatabaseName("IX_OrderVATs_IsPaid"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderVATs_OrderId"); + + b.ToTable("OrderVATs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.OtpToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Attempts") + .HasColumnType("int"); + + b.Property("CodeHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Purpose") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OtpTokens", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivationFee") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.0m); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsBasePackage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicWalletMaxCredit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(2500000000L); + + b.Property("MagicWalletMaxDeposit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1000000000L); + + b.Property("MagicWalletMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.5m); + + b.Property("MaxBalancesPerLeg") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(300); + + b.Property("MaxNetworkLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(15); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("SupportsDayaPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SupportsDirectPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Package_IsActive"); + + b.HasIndex("SortOrder") + .HasDatabaseName("IX_Package_SortOrder"); + + b.ToTable("Packages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncluded") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("PackageId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId"); + + b.ToTable("PackageFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedBy") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImageDocumentId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("Created"); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Status"); + + b.ToTable("ManualPayments", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.PaymentTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Authority") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CallbackUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("CardHash") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CardPan") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("GatewayProvider") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MerchantId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("OrderId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PaymentStatus") + .HasColumnType("bit"); + + b.Property("RefId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RequestStatusCode") + .HasColumnType("int"); + + b.Property("RequestStatusMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VerificationStatusCode") + .HasColumnType("int"); + + b.Property("VerificationStatusMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("Authority"); + + b.HasIndex("GatewayProvider"); + + b.HasIndex("RefId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("PaymentTransactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubDiscountPercent") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Discount") + .HasColumnType("int"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsClubExclusive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IsClubExclusive") + .HasDatabaseName("IX_Products_IsClubExclusive"); + + b.ToTable("Products", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductImageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("ProductImageId"); + + b.ToTable("ProductGalleries", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("TagId"); + + b.ToTable("ProductTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PublicMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArchivedAt") + .HasColumnType("datetime2"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedByUserId") + .HasColumnType("bigint"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsArchived") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkText") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId") + .HasDatabaseName("IX_PublicMessages_CreatedByUserId"); + + b.HasIndex("ExpiresAt") + .HasDatabaseName("IX_PublicMessages_ExpiresAt"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_PublicMessages_IsActive"); + + b.HasIndex("Priority") + .HasDatabaseName("IX_PublicMessages_Priority"); + + b.HasIndex("StartsAt") + .HasDatabaseName("IX_PublicMessages_StartsAt"); + + b.HasIndex("Type") + .HasDatabaseName("IX_PublicMessages_Type"); + + b.HasIndex("IsActive", "ExpiresAt") + .HasDatabaseName("IX_PublicMessages_IsActive_ExpiresAt"); + + b.ToTable("PublicMessages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("InventoryItemId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MovementType") + .HasColumnType("int"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PerformedByUserId") + .HasColumnType("bigint"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityAfter") + .HasColumnType("int"); + + b.Property("QuantityBefore") + .HasColumnType("int"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_StockMovements_Created"); + + b.HasIndex("DiscountOrderId") + .HasDatabaseName("IX_StockMovements_DiscountOrderId") + .HasFilter("[DiscountOrderId] IS NOT NULL"); + + b.HasIndex("InventoryItemId") + .HasDatabaseName("IX_StockMovements_InventoryItemId"); + + b.HasIndex("MovementType") + .HasDatabaseName("IX_StockMovements_MovementType"); + + b.HasIndex("OrderId") + .HasDatabaseName("IX_StockMovements_OrderId") + .HasFilter("[OrderId] IS NOT NULL"); + + b.HasIndex("ReferenceNumber") + .HasDatabaseName("IX_StockMovements_ReferenceNumber") + .HasFilter("[ReferenceNumber] IS NOT NULL"); + + b.HasIndex("InventoryItemId", "MovementType", "Created") + .HasDatabaseName("IX_StockMovements_Item_Type_Date"); + + b.ToTable("StockMovements", "CMS", t => + { + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_Calculation", "QuantityAfter = QuantityBefore + Quantity"); + + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_NonNegative", "QuantityAfter >= 0"); + + t.HasCheckConstraint("CK_StockMovement_QuantityBefore_NonNegative", "QuantityBefore >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("RefId") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Transactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AvatarPath") + .HasColumnType("nvarchar(max)"); + + b.Property("BirthDate") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DayaCreditReceivedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailNotifications") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("HasReceivedDayaCredit") + .HasColumnType("bit"); + + b.Property("HashPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsMobileVerified") + .HasColumnType("bit"); + + b.Property("IsRulesAccepted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LegPosition") + .HasColumnType("int"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("MobileVerifiedAt") + .HasColumnType("datetime2"); + + b.Property("NationalCode") + .HasColumnType("nvarchar(max)"); + + b.Property("NetworkParentId") + .HasColumnType("bigint"); + + b.Property("PackagePurchaseMethod") + .HasColumnType("int"); + + b.Property("PushNotifications") + .HasColumnType("bit"); + + b.Property("ReferralCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RulesAcceptedAt") + .HasColumnType("datetime2"); + + b.Property("SmsNotifications") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("LegPosition") + .HasDatabaseName("IX_User_LegPosition"); + + b.HasIndex("NetworkParentId") + .HasDatabaseName("IX_User_NetworkParentId"); + + b.ToTable("Users", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CityId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserAddresses", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("UserCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SignGuid") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SignedPdfFile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("UserId"); + + b.ToTable("UserContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("HasVAT") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("PurchasedAt") + .HasColumnType("datetime2"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("PackageId") + .HasDatabaseName("IX_UserPackagePurchase_PackageId"); + + b.HasIndex("PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_PurchasedAt"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_UserPackagePurchase_UserId"); + + b.HasIndex("UserId", "PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_UserId_PurchasedAt"); + + b.ToTable("UserPackagePurchases", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("UserRoles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicActivatedAt") + .HasColumnType("datetime2"); + + b.Property("MagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("MagicTotalCredited") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("MagicTotalDeposited") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WalletMode") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserWallets", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangeDiscountValue") + .HasColumnType("bigint"); + + b.Property("ChangeNerworkValue") + .HasColumnType("bigint"); + + b.Property("ChangeValue") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentBalance") + .HasColumnType("bigint"); + + b.Property("CurrentDiscountBalance") + .HasColumnType("bigint"); + + b.Property("CurrentNetworkBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncrease") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("WalletId"); + + b.ToTable("UserWalletHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("IX_Warehouses_Code_Unique"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Warehouses_IsActive"); + + b.HasIndex("IsDefault") + .HasDatabaseName("IX_Warehouses_IsDefault") + .HasFilter("[IsDefault] = 1"); + + b.ToTable("Warehouses", "CMS"); + + b.HasData( + new + { + Id = 1L, + Address = "تهران - انبار مرکزی فروشگاه", + Code = "WH-001", + Created = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + CreatedBy = "System", + IsActive = true, + IsDefault = true, + IsDeleted = false, + Name = "انبار اصلی" + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("GregorianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("GregorianYear") + .HasColumnType("int"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PersianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("WeekOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GregorianWeekNumber") + .IsUnique() + .HasDatabaseName("IX_WeekDefinition_GregorianWeekNumber"); + + b.HasIndex("GregorianYear") + .HasDatabaseName("IX_WeekDefinition_GregorianYear"); + + b.HasIndex("PersianWeekNumber") + .HasDatabaseName("IX_WeekDefinition_PersianWeekNumber"); + + b.HasIndex("PersianYear") + .HasDatabaseName("IX_WeekDefinition_PersianYear"); + + b.HasIndex("StartDate") + .HasDatabaseName("IX_WeekDefinition_StartDate"); + + b.ToTable("WeekDefinitions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogCategory", "BlogCategory") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogCategory"); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostImages") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostTags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent") + .WithMany("Categories") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "FirstPackage") + .WithMany() + .HasForeignKey("FirstPackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "LastPackage") + .WithMany() + .HasForeignKey("LastPackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FirstPackage"); + + b.Navigation("LastPackage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("Cycles") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + + b.Navigation("Package"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserClubFeatures") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("ClubMembership"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("CommissionPayouts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", "WeeklyPool") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeeklyPoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WorkerExecutionLogs") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePageSettings", "SitePageSettings") + .WithMany("Images") + .HasForeignKey("SitePageSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePageSettings"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePage", "SitePage") + .WithMany("Sections") + .HasForeignKey("SitePageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DayaLoanContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany() + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", "DiscountOrder") + .WithMany("OrderDetails") + .HasForeignKey("DiscountOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("OrderDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountOrder"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany("Images") + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DiscountProduct"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ShoppingCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountShoppingCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany("FactorDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("FactorDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.State", "State") + .WithMany("Cities") + .HasForeignKey("StateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("State"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.Country", "Country") + .WithMany("States") + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipCycleHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", "ClubMembershipCycle") + .WithMany("CycleHistories") + .HasForeignKey("ClubMembershipCycleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembershipCycle"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("ClubMembershipHistories") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", "UserCommissionPayout") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("UserCommissionPayoutId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("UserCommissionPayout"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.PackageHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("PackageHistories") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany() + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Warehouse", "Warehouse") + .WithMany("InventoryItems") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountProduct"); + + b.Navigation("Product"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne("OrderVAT") + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany() + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("PackageFeatures") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("Package"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductGalleries") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.ProductImage", "ProductImage") + .WithMany("ProductGalleries") + .HasForeignKey("ProductImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("ProductImage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductTags") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany("ProductTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.InventoryItem", "InventoryItem") + .WithMany("StockMovements") + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InventoryItem"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "NetworkParent") + .WithMany("NetworkChildren") + .HasForeignKey("NetworkParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("NetworkParent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserAddresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("UserCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Contract", "Contract") + .WithMany("UserContracts") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("UserOrders") + .HasForeignKey("PackageId"); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany("UserOrders") + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany("UserOrders") + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("Purchases") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Role", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserWallets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet") + .WithMany("UserWalletHistories") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Navigation("BlogPostCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Navigation("BlogPostCategories"); + + b.Navigation("BlogPostImages"); + + b.Navigation("BlogPostTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Navigation("Categories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Navigation("ClubMembershipHistories"); + + b.Navigation("Cycles"); + + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.Navigation("CycleHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Navigation("CommissionPayoutHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Navigation("UserCommissionPayouts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Navigation("Sections"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSettings", b => + { + b.Navigation("Images"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Navigation("UserContracts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Navigation("ChildCategories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Navigation("Images"); + + b.Navigation("OrderDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ShoppingCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Navigation("States"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Navigation("Cities"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Navigation("StockMovements"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Navigation("PackageFeatures"); + + b.Navigation("PackageHistories"); + + b.Navigation("Purchases"); + + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ProductGalleries"); + + b.Navigation("ProductTags"); + + b.Navigation("UserCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Navigation("ProductGalleries"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Navigation("ProductTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Navigation("ClubMembership"); + + b.Navigation("CommissionPayouts"); + + b.Navigation("DayaLoanContracts"); + + b.Navigation("DiscountOrders"); + + b.Navigation("DiscountShoppingCarts"); + + b.Navigation("NetworkChildren"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserAddresses"); + + b.Navigation("UserCarts"); + + b.Navigation("UserClubFeatures"); + + b.Navigation("UserContracts"); + + b.Navigation("UserOrders"); + + b.Navigation("UserRoles"); + + b.Navigation("UserWallets"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("OrderVAT"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Navigation("UserWalletHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Navigation("CommissionPayoutHistories"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserCommissionPayouts"); + + b.Navigation("WeeklyCommissionPools"); + + b.Navigation("WorkerExecutionLogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.cs new file mode 100644 index 0000000..c4498c2 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260227024734_Q27_HistoryTables_And_RenameWalletHistory.cs @@ -0,0 +1,190 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class Q27_HistoryTables_And_RenameWalletHistory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameTable( + name: "UserWalletChangeLogs", + schema: "CMS", + newName: "UserWalletHistories", + newSchema: "CMS"); + + migrationBuilder.RenameIndex( + name: "IX_UserWalletChangeLogs_PackageId", + schema: "CMS", + table: "UserWalletHistories", + newName: "IX_UserWalletHistories_PackageId"); + + migrationBuilder.RenameIndex( + name: "IX_UserWalletChangeLogs_WalletId", + schema: "CMS", + table: "UserWalletHistories", + newName: "IX_UserWalletHistories_WalletId"); + + migrationBuilder.Sql( + "EXEC sp_rename N'CMS.PK_UserWalletChangeLogs', N'PK_UserWalletHistories', N'OBJECT'"); + + migrationBuilder.Sql( + "EXEC sp_rename N'CMS.FK_UserWalletChangeLogs_Packages_PackageId', N'FK_UserWalletHistories_Packages_PackageId', N'OBJECT'"); + + migrationBuilder.Sql( + "EXEC sp_rename N'CMS.FK_UserWalletChangeLogs_UserWallets_WalletId', N'FK_UserWalletHistories_UserWallets_WalletId', N'OBJECT'"); + + migrationBuilder.CreateTable( + name: "ClubMembershipCycleHistories", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ClubMembershipCycleId = table.Column(type: "bigint", nullable: false), + UserId = table.Column(type: "bigint", nullable: false), + CycleNumber = table.Column(type: "int", nullable: false), + OldIsCurrentCycle = table.Column(type: "bit", nullable: false), + NewIsCurrentCycle = table.Column(type: "bit", nullable: false), + OldMagicStartedAt = table.Column(type: "datetime2", nullable: true), + NewMagicStartedAt = table.Column(type: "datetime2", nullable: true), + OldMagicCompletedAt = table.Column(type: "datetime2", nullable: true), + NewMagicCompletedAt = table.Column(type: "datetime2", nullable: true), + Action = table.Column(type: "int", nullable: false), + PerformedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Reason = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubMembershipCycleHistories", x => x.Id); + table.ForeignKey( + name: "FK_ClubMembershipCycleHistories_ClubMembershipCycles_ClubMembershipCycleId", + column: x => x.ClubMembershipCycleId, + principalSchema: "CMS", + principalTable: "ClubMembershipCycles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "PackageHistories", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + PackageId = table.Column(type: "bigint", nullable: false), + OldPrice = table.Column(type: "bigint", nullable: true), + NewPrice = table.Column(type: "bigint", nullable: true), + OldActivationFee = table.Column(type: "bigint", nullable: true), + NewActivationFee = table.Column(type: "bigint", nullable: true), + OldMagicMultiplier = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: true), + NewMagicMultiplier = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: true), + OldMagicMaxDeposit = table.Column(type: "bigint", nullable: true), + NewMagicMaxDeposit = table.Column(type: "bigint", nullable: true), + OldMaxBalancesPerLeg = table.Column(type: "int", nullable: true), + NewMaxBalancesPerLeg = table.Column(type: "int", nullable: true), + OldIsActive = table.Column(type: "bit", nullable: true), + NewIsActive = table.Column(type: "bit", nullable: true), + Action = table.Column(type: "int", nullable: false), + PerformedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Reason = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageHistories", x => x.Id); + table.ForeignKey( + name: "FK_PackageHistories_Packages_PackageId", + column: x => x.PackageId, + principalSchema: "CMS", + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ClubMembershipCycleHistory_Action", + schema: "CMS", + table: "ClubMembershipCycleHistories", + column: "Action"); + + migrationBuilder.CreateIndex( + name: "IX_ClubMembershipCycleHistory_CycleId", + schema: "CMS", + table: "ClubMembershipCycleHistories", + column: "ClubMembershipCycleId"); + + migrationBuilder.CreateIndex( + name: "IX_ClubMembershipCycleHistory_UserId_Created", + schema: "CMS", + table: "ClubMembershipCycleHistories", + columns: new[] { "UserId", "Created" }); + + migrationBuilder.CreateIndex( + name: "IX_PackageHistory_Action", + schema: "CMS", + table: "PackageHistories", + column: "Action"); + + migrationBuilder.CreateIndex( + name: "IX_PackageHistory_PackageId_Created", + schema: "CMS", + table: "PackageHistories", + columns: new[] { "PackageId", "Created" }); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ClubMembershipCycleHistories", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "PackageHistories", + schema: "CMS"); + + migrationBuilder.RenameTable( + name: "UserWalletHistories", + schema: "CMS", + newName: "UserWalletChangeLogs", + newSchema: "CMS"); + + migrationBuilder.RenameIndex( + name: "IX_UserWalletHistories_PackageId", + schema: "CMS", + table: "UserWalletChangeLogs", + newName: "IX_UserWalletChangeLogs_PackageId"); + + migrationBuilder.RenameIndex( + name: "IX_UserWalletHistories_WalletId", + schema: "CMS", + table: "UserWalletChangeLogs", + newName: "IX_UserWalletChangeLogs_WalletId"); + + migrationBuilder.Sql( + "EXEC sp_rename N'CMS.PK_UserWalletHistories', N'PK_UserWalletChangeLogs', N'OBJECT'"); + + migrationBuilder.Sql( + "EXEC sp_rename N'CMS.FK_UserWalletHistories_Packages_PackageId', N'FK_UserWalletChangeLogs_Packages_PackageId', N'OBJECT'"); + + migrationBuilder.Sql( + "EXEC sp_rename N'CMS.FK_UserWalletHistories_UserWallets_WalletId', N'FK_UserWalletChangeLogs_UserWallets_WalletId', N'OBJECT'"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 2c65473..20a6ee8 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1935,6 +1935,81 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.ToTable("States", "GMS"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipCycleHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipCycleId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CycleNumber") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewIsCurrentCycle") + .HasColumnType("bit"); + + b.Property("NewMagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("NewMagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("OldIsCurrentCycle") + .HasColumnType("bit"); + + b.Property("OldMagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("OldMagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipCycleHistory_Action"); + + b.HasIndex("ClubMembershipCycleId") + .HasDatabaseName("IX_ClubMembershipCycleHistory_CycleId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipCycleHistory_UserId_Created"); + + b.ToTable("ClubMembershipCycleHistories", "CMS"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => { b.Property("Id") @@ -2133,6 +2208,92 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.ToTable("NetworkMembershipHistories", "CMS"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.PackageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewActivationFee") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("NewMagicMaxDeposit") + .HasColumnType("bigint"); + + b.Property("NewMagicMultiplier") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxBalancesPerLeg") + .HasColumnType("int"); + + b.Property("NewPrice") + .HasColumnType("bigint"); + + b.Property("OldActivationFee") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("OldMagicMaxDeposit") + .HasColumnType("bigint"); + + b.Property("OldMagicMultiplier") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxBalancesPerLeg") + .HasColumnType("int"); + + b.Property("OldPrice") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_PackageHistory_Action"); + + b.HasIndex("PackageId", "Created") + .HasDatabaseName("IX_PackageHistory_PackageId_Created"); + + b.ToTable("PackageHistories", "CMS"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => { b.Property("Id") @@ -3826,7 +3987,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.ToTable("UserWallets", "CMS"); }); - modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletHistory", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -3885,7 +4046,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasIndex("WalletId"); - b.ToTable("UserWalletChangeLogs", "CMS"); + b.ToTable("UserWalletHistories", "CMS"); }); modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => @@ -4430,6 +4591,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("Country"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipCycleHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", "ClubMembershipCycle") + .WithMany("CycleHistories") + .HasForeignKey("ClubMembershipCycleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembershipCycle"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => { b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") @@ -4460,6 +4632,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("WeekDefinition"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.PackageHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("PackageHistories") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => { b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") @@ -4781,7 +4964,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("User"); }); - modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletHistory", b => { b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") .WithMany() @@ -4789,7 +4972,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Restrict); b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet") - .WithMany("UserWalletChangeLogs") + .WithMany("UserWalletHistories") .HasForeignKey("WalletId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -4834,6 +5017,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("UserClubFeatures"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.Navigation("CycleHistories"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => { b.Navigation("CommissionPayoutHistories"); @@ -4901,6 +5089,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations { b.Navigation("PackageFeatures"); + b.Navigation("PackageHistories"); + b.Navigation("Purchases"); b.Navigation("UserOrders"); @@ -4984,7 +5174,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => { - b.Navigation("UserWalletChangeLogs"); + b.Navigation("UserWalletHistories"); }); modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => diff --git a/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs b/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs index 48a7378..b6c4010 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Commission/OrmCommissionCalculationStrategy.cs @@ -471,7 +471,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy .ToDictionaryAsync(w => w.UserId, cancellationToken); var newWallets = new List(); - var walletLogs = new List(); + var walletLogs = new List(); foreach (var payout in payouts) { @@ -510,7 +510,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy var wallet = existingWallets[payout.UserId]; wallet.NetworkBalance += payout.TotalAmount; - var walletLog = new UserWalletChangeLog + var walletLog = new UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, @@ -526,7 +526,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy walletLogs.Add(walletLog); } - await _context.UserWalletChangeLogs.AddRangeAsync(walletLogs, cancellationToken); + await _context.UserWalletHistories.AddRangeAsync(walletLogs, cancellationToken); await _context.SaveChangesAsync(cancellationToken); } @@ -534,7 +534,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy List oldPayoutIds, CancellationToken cancellationToken) { - var oldWalletLogs = await _context.UserWalletChangeLogs + var oldWalletLogs = await _context.UserWalletHistories .Where(l => l.RefrenceId.HasValue && oldPayoutIds.Contains(l.RefrenceId.Value)) .ToListAsync(cancellationToken); @@ -557,7 +557,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy } } - _context.UserWalletChangeLogs.RemoveRange(oldWalletLogs); + _context.UserWalletHistories.RemoveRange(oldWalletLogs); await _context.SaveChangesAsync(cancellationToken); } diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index d87bd05..672fe4f 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto index bd7ff2c..096f3ea 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto @@ -51,9 +51,9 @@ service UserWalletContract get: "/Customer/GetWallet" }; }; - rpc GetCustomerWalletChangeLog(GetCustomerWalletChangeLogRequest) returns (GetCustomerWalletChangeLogResponse){ + rpc GetCustomerWalletHistory(GetCustomerWalletHistoryRequest) returns (GetCustomerWalletHistoryResponse){ option (google.api.http) = { - post: "/Customer/GetWalletChangeLog" + post: "/Customer/GetWalletHistory" body: "*" }; }; @@ -167,19 +167,19 @@ message GetCustomerWalletResponse int32 wallet_mode = 4; // 0=Normal, 1=Magic } -message GetCustomerWalletChangeLogRequest +message GetCustomerWalletHistoryRequest { google.protobuf.Int64Value reference_id = 1; google.protobuf.BoolValue is_increase = 2; } -message GetCustomerWalletChangeLogResponse +message GetCustomerWalletHistoryResponse { messages.MetaData meta_data = 1; - repeated CustomerWalletChangeLogModel models = 2; + repeated CustomerWalletHistoryModel models = 2; } -message CustomerWalletChangeLogModel +message CustomerWalletHistoryModel { int64 current_balance = 1; int64 change_value = 2; diff --git a/src/CMSMicroservice.Protobuf/Protos/userwalletchangelog.proto b/src/CMSMicroservice.Protobuf/Protos/userwallethistory.proto similarity index 61% rename from src/CMSMicroservice.Protobuf/Protos/userwalletchangelog.proto rename to src/CMSMicroservice.Protobuf/Protos/userwallethistory.proto index 7749f46..010841f 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwalletchangelog.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallethistory.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package userwalletchangelog; +package userwallethistory; import "public_messages.proto"; import "google/protobuf/empty.proto"; @@ -9,42 +9,42 @@ import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; -option csharp_namespace = "CMSMicroservice.Protobuf.Protos.UserWalletChangeLog"; +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.UserWalletHistory"; -service UserWalletChangeLogContract +service UserWalletHistoryContract { - rpc CreateNewUserWalletChangeLog(CreateNewUserWalletChangeLogRequest) returns (CreateNewUserWalletChangeLogResponse){ + rpc CreateNewUserWalletHistory(CreateNewUserWalletHistoryRequest) returns (CreateNewUserWalletHistoryResponse){ option (google.api.http) = { - post: "/CreateNewUserWalletChangeLog" + post: "/CreateNewUserWalletHistory" body: "*" }; }; - rpc UpdateUserWalletChangeLog(UpdateUserWalletChangeLogRequest) returns (google.protobuf.Empty){ + rpc UpdateUserWalletHistory(UpdateUserWalletHistoryRequest) returns (google.protobuf.Empty){ option (google.api.http) = { - put: "/UpdateUserWalletChangeLog" + put: "/UpdateUserWalletHistory" body: "*" }; }; - rpc DeleteUserWalletChangeLog(DeleteUserWalletChangeLogRequest) returns (google.protobuf.Empty){ + rpc DeleteUserWalletHistory(DeleteUserWalletHistoryRequest) returns (google.protobuf.Empty){ option (google.api.http) = { - delete: "/DeleteUserWalletChangeLog" + delete: "/DeleteUserWalletHistory" body: "*" }; }; - rpc GetUserWalletChangeLog(GetUserWalletChangeLogRequest) returns (GetUserWalletChangeLogResponse){ + rpc GetUserWalletHistory(GetUserWalletHistoryRequest) returns (GetUserWalletHistoryResponse){ option (google.api.http) = { - get: "/GetUserWalletChangeLog" + get: "/GetUserWalletHistory" }; }; - rpc GetAllUserWalletChangeLogByFilter(GetAllUserWalletChangeLogByFilterRequest) returns (GetAllUserWalletChangeLogByFilterResponse){ + rpc GetAllUserWalletHistoryByFilter(GetAllUserWalletHistoryByFilterRequest) returns (GetAllUserWalletHistoryByFilterResponse){ option (google.api.http) = { - get: "/GetAllUserWalletChangeLogByFilter" + get: "/GetAllUserWalletHistoryByFilter" }; }; } -message CreateNewUserWalletChangeLogRequest +message CreateNewUserWalletHistoryRequest { int64 wallet_id = 1; int64 current_balance = 2; @@ -54,11 +54,11 @@ message CreateNewUserWalletChangeLogRequest bool is_increase = 6; google.protobuf.Int64Value refrence_id = 7; } -message CreateNewUserWalletChangeLogResponse +message CreateNewUserWalletHistoryResponse { int64 id = 1; } -message UpdateUserWalletChangeLogRequest +message UpdateUserWalletHistoryRequest { int64 id = 1; int64 wallet_id = 2; @@ -69,15 +69,15 @@ message UpdateUserWalletChangeLogRequest bool is_increase = 7; google.protobuf.Int64Value refrence_id = 8; } -message DeleteUserWalletChangeLogRequest +message DeleteUserWalletHistoryRequest { int64 id = 1; } -message GetUserWalletChangeLogRequest +message GetUserWalletHistoryRequest { int64 id = 1; } -message GetUserWalletChangeLogResponse +message GetUserWalletHistoryResponse { int64 id = 1; int64 wallet_id = 2; @@ -89,13 +89,13 @@ message GetUserWalletChangeLogResponse google.protobuf.Int64Value refrence_id = 8; google.protobuf.Timestamp created_at = 9; } -message GetAllUserWalletChangeLogByFilterRequest +message GetAllUserWalletHistoryByFilterRequest { messages.PaginationState pagination_state = 1; google.protobuf.StringValue sort_by = 2; - GetAllUserWalletChangeLogByFilterFilter filter = 3; + GetAllUserWalletHistoryByFilterFilter filter = 3; } -message GetAllUserWalletChangeLogByFilterFilter +message GetAllUserWalletHistoryByFilterFilter { google.protobuf.Int64Value id = 1; google.protobuf.Int64Value wallet_id = 2; @@ -105,12 +105,12 @@ message GetAllUserWalletChangeLogByFilterFilter google.protobuf.Int64Value refrence_id = 6; google.protobuf.Int64Value user_id = 7; } -message GetAllUserWalletChangeLogByFilterResponse +message GetAllUserWalletHistoryByFilterResponse { messages.MetaData meta_data = 1; - repeated GetAllUserWalletChangeLogByFilterResponseModel models = 2; + repeated GetAllUserWalletHistoryByFilterResponseModel models = 2; } -message GetAllUserWalletChangeLogByFilterResponseModel +message GetAllUserWalletHistoryByFilterResponseModel { int64 id = 1; int64 wallet_id = 2; diff --git a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/DeleteUserWalletChangeLogRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/DeleteUserWalletChangeLogRequestValidator.cs deleted file mode 100644 index 1f7f8e0..0000000 --- a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/DeleteUserWalletChangeLogRequestValidator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FluentValidation; -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; -namespace CMSMicroservice.Protobuf.Validator.UserWalletChangeLog; - -public class DeleteUserWalletChangeLogRequestValidator : AbstractValidator -{ - public DeleteUserWalletChangeLogRequestValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserWalletChangeLogRequest)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/GetAllUserWalletChangeLogByFilterRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/GetAllUserWalletChangeLogByFilterRequestValidator.cs deleted file mode 100644 index 5c4305f..0000000 --- a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/GetAllUserWalletChangeLogByFilterRequestValidator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FluentValidation; -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; -namespace CMSMicroservice.Protobuf.Validator.UserWalletChangeLog; - -public class GetAllUserWalletChangeLogByFilterRequestValidator : AbstractValidator -{ - public GetAllUserWalletChangeLogByFilterRequestValidator() - { - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserWalletChangeLogByFilterRequest)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/CreateNewUserWalletChangeLogRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/CreateNewUserWalletHistoryRequestValidator.cs similarity index 64% rename from src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/CreateNewUserWalletChangeLogRequestValidator.cs rename to src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/CreateNewUserWalletHistoryRequestValidator.cs index e3535ba..f8364c4 100644 --- a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/CreateNewUserWalletChangeLogRequestValidator.cs +++ b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/CreateNewUserWalletHistoryRequestValidator.cs @@ -1,10 +1,10 @@ using FluentValidation; -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; -namespace CMSMicroservice.Protobuf.Validator.UserWalletChangeLog; +using CMSMicroservice.Protobuf.Protos.UserWalletHistory; +namespace CMSMicroservice.Protobuf.Validator.UserWalletHistory; -public class CreateNewUserWalletChangeLogRequestValidator : AbstractValidator +public class CreateNewUserWalletHistoryRequestValidator : AbstractValidator { - public CreateNewUserWalletChangeLogRequestValidator() + public CreateNewUserWalletHistoryRequestValidator() { RuleFor(model => model.WalletId) .NotNull(); @@ -21,7 +21,7 @@ public class CreateNewUserWalletChangeLogRequestValidator : AbstractValidator>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserWalletChangeLogRequest)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserWalletHistoryRequest)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/DeleteUserWalletHistoryRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/DeleteUserWalletHistoryRequestValidator.cs new file mode 100644 index 0000000..d73bce3 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/DeleteUserWalletHistoryRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using CMSMicroservice.Protobuf.Protos.UserWalletHistory; +namespace CMSMicroservice.Protobuf.Validator.UserWalletHistory; + +public class DeleteUserWalletHistoryRequestValidator : AbstractValidator +{ + public DeleteUserWalletHistoryRequestValidator() + { + RuleFor(model => model.Id) + .NotNull(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserWalletHistoryRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/GetAllUserWalletHistoryByFilterRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/GetAllUserWalletHistoryByFilterRequestValidator.cs new file mode 100644 index 0000000..a79cd62 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/GetAllUserWalletHistoryByFilterRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using CMSMicroservice.Protobuf.Protos.UserWalletHistory; +namespace CMSMicroservice.Protobuf.Validator.UserWalletHistory; + +public class GetAllUserWalletHistoryByFilterRequestValidator : AbstractValidator +{ + public GetAllUserWalletHistoryByFilterRequestValidator() + { + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserWalletHistoryByFilterRequest)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/GetUserWalletChangeLogRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/GetUserWalletHistoryRequestValidator.cs similarity index 50% rename from src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/GetUserWalletChangeLogRequestValidator.cs rename to src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/GetUserWalletHistoryRequestValidator.cs index 5b9e205..11894c7 100644 --- a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/GetUserWalletChangeLogRequestValidator.cs +++ b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/GetUserWalletHistoryRequestValidator.cs @@ -1,17 +1,17 @@ using FluentValidation; -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; -namespace CMSMicroservice.Protobuf.Validator.UserWalletChangeLog; +using CMSMicroservice.Protobuf.Protos.UserWalletHistory; +namespace CMSMicroservice.Protobuf.Validator.UserWalletHistory; -public class GetUserWalletChangeLogRequestValidator : AbstractValidator +public class GetUserWalletHistoryRequestValidator : AbstractValidator { - public GetUserWalletChangeLogRequestValidator() + public GetUserWalletHistoryRequestValidator() { RuleFor(model => model.Id) .NotNull(); } public Func>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserWalletChangeLogRequest)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserWalletHistoryRequest)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/UpdateUserWalletChangeLogRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/UpdateUserWalletHistoryRequestValidator.cs similarity index 66% rename from src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/UpdateUserWalletChangeLogRequestValidator.cs rename to src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/UpdateUserWalletHistoryRequestValidator.cs index 004b2e5..11020bf 100644 --- a/src/CMSMicroservice.Protobuf/Validator/UserWalletChangeLog/UpdateUserWalletChangeLogRequestValidator.cs +++ b/src/CMSMicroservice.Protobuf/Validator/UserWalletHistory/UpdateUserWalletHistoryRequestValidator.cs @@ -1,10 +1,10 @@ using FluentValidation; -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; -namespace CMSMicroservice.Protobuf.Validator.UserWalletChangeLog; +using CMSMicroservice.Protobuf.Protos.UserWalletHistory; +namespace CMSMicroservice.Protobuf.Validator.UserWalletHistory; -public class UpdateUserWalletChangeLogRequestValidator : AbstractValidator +public class UpdateUserWalletHistoryRequestValidator : AbstractValidator { - public UpdateUserWalletChangeLogRequestValidator() + public UpdateUserWalletHistoryRequestValidator() { RuleFor(model => model.Id) .NotNull(); @@ -23,7 +23,7 @@ public class UpdateUserWalletChangeLogRequestValidator : AbstractValidator>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserWalletChangeLogRequest)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserWalletHistoryRequest)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/UserWalletChangeLogProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/UserWalletHistoryProfile.cs similarity index 83% rename from src/CMSMicroservice.WebApi/Common/Mappings/UserWalletChangeLogProfile.cs rename to src/CMSMicroservice.WebApi/Common/Mappings/UserWalletHistoryProfile.cs index ad2c16d..b91a7ee 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/UserWalletChangeLogProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/UserWalletHistoryProfile.cs @@ -1,6 +1,6 @@ namespace CMSMicroservice.WebApi.Common.Mappings; -public class UserWalletChangeLogProfile : IRegister +public class UserWalletHistoryProfile : IRegister { void IRegister.Register(TypeAdapterConfig config) { diff --git a/src/CMSMicroservice.WebApi/Program.cs b/src/CMSMicroservice.WebApi/Program.cs index c674c6f..55f800b 100644 --- a/src/CMSMicroservice.WebApi/Program.cs +++ b/src/CMSMicroservice.WebApi/Program.cs @@ -281,7 +281,7 @@ static bool IsCustomerService(string serviceName) "User", "UserAddress", "Commission", "NetworkMembership", "ClubMembership", "UserOrder", "UserWallet", "UserCarts", "DiscountShoppingCart", "City", "Package", "Transactions", - "UserContract", "UserWalletChangeLog" + "UserContract", "UserWalletHistory" }; return customerServices.Any(customer => serviceName.Contains(customer, StringComparison.OrdinalIgnoreCase)) && diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 2effbe3..0942b26 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -321,7 +321,7 @@ public class PackageService : PackageContract.PackageContractBase wallet.DiscountBalance += discountAmount; // ثبت لاگ کیف پول - var walletLog = new CMSMicroservice.Domain.Entities.UserWalletChangeLog + var walletLog = new CMSMicroservice.Domain.Entities.UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, @@ -333,7 +333,7 @@ public class PackageService : PackageContract.PackageContractBase IsIncrease = true, RefrenceId = transaction.Id }; - _context.UserWalletChangeLogs.Add(walletLog); + _context.UserWalletHistories.Add(walletLog); // به‌روزرسانی کاربر var user = await _context.Users diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index 2c87922..38681c7 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -322,7 +322,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase wallet.Balance -= totalAmount; // Create wallet change log - var walletLog = new UserWalletChangeLog + var walletLog = new UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, @@ -335,7 +335,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase RefrenceId = transaction.Id }; - _context.UserWalletChangeLogs.Add(walletLog); + _context.UserWalletHistories.Add(walletLog); // ═══ Magic Wallet: Entry / Exit Trigger ═══ // Q24: آستانه <= 1,000,000 ریال به جای == 0 (چون قیمت محصولات متفاوت است) @@ -833,7 +833,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase if (wallet != null) { wallet.Balance += refundAmount; - _context.UserWalletChangeLogs.Add(new UserWalletChangeLog + _context.UserWalletHistories.Add(new UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletChangeLogService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletChangeLogService.cs deleted file mode 100644 index 91b230b..0000000 --- a/src/CMSMicroservice.WebApi/Services/UserWalletChangeLogService.cs +++ /dev/null @@ -1,37 +0,0 @@ -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; -using CMSMicroservice.WebApi.Common.Services; -using CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.CreateNewUserWalletChangeLog; -using CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.UpdateUserWalletChangeLog; -using CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.DeleteUserWalletChangeLog; -using CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetUserWalletChangeLog; -using CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetAllUserWalletChangeLogByFilter; -namespace CMSMicroservice.WebApi.Services; -public class UserWalletChangeLogService : UserWalletChangeLogContract.UserWalletChangeLogContractBase -{ - private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; - - public UserWalletChangeLogService(IDispatchRequestToCQRS dispatchRequestToCQRS) - { - _dispatchRequestToCQRS = dispatchRequestToCQRS; - } - public override async Task CreateNewUserWalletChangeLog(CreateNewUserWalletChangeLogRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - public override async Task UpdateUserWalletChangeLog(UpdateUserWalletChangeLogRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - public override async Task DeleteUserWalletChangeLog(DeleteUserWalletChangeLogRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - public override async Task GetUserWalletChangeLog(GetUserWalletChangeLogRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } - public override async Task GetAllUserWalletChangeLogByFilter(GetAllUserWalletChangeLogByFilterRequest request, ServerCallContext context) - { - return await _dispatchRequestToCQRS.Handle(request, context); - } -} diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletHistoryService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletHistoryService.cs new file mode 100644 index 0000000..99c8917 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/UserWalletHistoryService.cs @@ -0,0 +1,37 @@ +using CMSMicroservice.Protobuf.Protos.UserWalletHistory; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.UserWalletHistoryCQ.Commands.CreateNewUserWalletHistory; +using CMSMicroservice.Application.UserWalletHistoryCQ.Commands.UpdateUserWalletHistory; +using CMSMicroservice.Application.UserWalletHistoryCQ.Commands.DeleteUserWalletHistory; +using CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetUserWalletHistory; +using CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetAllUserWalletHistoryByFilter; +namespace CMSMicroservice.WebApi.Services; +public class UserWalletHistoryService : UserWalletHistoryContract.UserWalletHistoryContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public UserWalletHistoryService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + public override async Task CreateNewUserWalletHistory(CreateNewUserWalletHistoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task UpdateUserWalletHistory(UpdateUserWalletHistoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task DeleteUserWalletHistory(DeleteUserWalletHistoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetUserWalletHistory(GetUserWalletHistoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetAllUserWalletHistoryByFilter(GetAllUserWalletHistoryByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs index 5c8fc70..8ce2b1e 100644 --- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs @@ -7,7 +7,7 @@ using CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet; using CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter; -using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; +using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings; using CMSMicroservice.Application.Common.Interfaces; @@ -102,9 +102,9 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase }; } - public override async Task GetCustomerWalletChangeLog(GetCustomerWalletChangeLogRequest request, ServerCallContext context) + public override async Task GetCustomerWalletHistory(GetCustomerWalletHistoryRequest request, ServerCallContext context) { - var query = new GetCustomerWalletChangeLogQuery + var query = new GetCustomerWalletHistoryQuery { ReferenceId = request.ReferenceId, IsIncrease = request.IsIncrease @@ -112,7 +112,7 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase var changeLogs = await _sender.Send(query, context.CancellationToken); - var response = new GetCustomerWalletChangeLogResponse + var response = new GetCustomerWalletHistoryResponse { MetaData = new CMSMicroservice.Protobuf.Protos.MetaData { @@ -127,7 +127,7 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase foreach (var log in changeLogs) { - response.Models.Add(new CustomerWalletChangeLogModel + response.Models.Add(new CustomerWalletHistoryModel { CurrentBalance = log.CurrentBalance, ChangeValue = log.ChangeValue, From e5bc3a952a4948e9ff2ff8c3e0f2ceb4c724e4a5 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 07:07:30 +0330 Subject: [PATCH 37/44] feat: ClubMembershipCycle IHasHistory + PackageId in WalletHistory (F1) - ClubMembershipCycle now implements IHasHistory - CreateHistorySnapshot: New* fields from current state, Old* auto-filled by interceptor - Parses action string to ClubMembershipCycleAction enum - Add PackageId to 6 UserWalletHistory creation sites (F1 fix): - VerifyPackagePurchaseCommandHandler: balanceLog + discountLog (order.PackageId) - CheckAndProcessDayaLoansCommandHandler: mainLog + discountLog (package.Id) - CreateManualPaymentCommandHandler: walletLog (package.Id) - PackageService.CustomerVerifyPackagePurchase: walletLog (purchase.PackageId) - Non-package flows (orders, commissions, manual) correctly keep PackageId=null --- .../CheckAndProcessDayaLoansCommandHandler.cs | 6 +++-- .../CreateManualPaymentCommandHandler.cs | 3 ++- .../VerifyPackagePurchaseCommandHandler.cs | 6 +++-- .../Entities/Club/ClubMembershipCycle.cs | 27 ++++++++++++++++++- .../Services/PackageService.cs | 3 ++- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs index fb0aa33..68defb2 100644 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs @@ -231,7 +231,8 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler /// دوره خرید پکیج — هر بار خرید پکیج ۵۶M یک Cycle جدید ایجاد می‌شود. /// برای حل مشکل overwrite شدن ClubMembership.ActivatedAt در محاسبه کمیسیون. /// -public class ClubMembershipCycle : BaseAuditableEntity +public class ClubMembershipCycle : BaseAuditableEntity, IHasHistory { /// /// شناسه کاربر @@ -74,4 +77,26 @@ public class ClubMembershipCycle : BaseAuditableEntity /// تاریخچه تغییرات دوره (Q27) public virtual ICollection? CycleHistories { get; set; } + + /// + /// ساخت snapshot تاریخچه از وضعیت فعلی چرخه. + /// مقادیر New* از وضعیت فعلی پر می‌شوند. + /// مقادیر Old* توسط HistoryTrackingSaveChangesInterceptor از OriginalValues پر می‌شوند. + /// + public History.ClubMembershipCycleHistory CreateHistorySnapshot(string action, string? performedBy) + { + return new History.ClubMembershipCycleHistory + { + ClubMembershipCycleId = Id, + UserId = UserId, + CycleNumber = CycleNumber, + NewIsCurrentCycle = IsCurrentCycle, + NewMagicStartedAt = MagicStartedAt, + NewMagicCompletedAt = MagicCompletedAt, + Action = Enum.TryParse(action, out var a) + ? a + : ClubMembershipCycleAction.ManualFix, + PerformedBy = performedBy + }; + } } diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 0942b26..3ad2be6 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -331,7 +331,8 @@ public class PackageService : PackageContract.PackageContractBase CurrentDiscountBalance = wallet.DiscountBalance, ChangeDiscountValue = discountAmount, IsIncrease = true, - RefrenceId = transaction.Id + RefrenceId = transaction.Id, + PackageId = purchase.PackageId }; _context.UserWalletHistories.Add(walletLog); From 61b7e4f8f2c50331000f269fa88c1f19d7192378 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 08:47:46 +0330 Subject: [PATCH 38/44] =?UTF-8?q?F2-F7:=20=D9=86=D9=88=D8=AA=DB=8C=D9=81?= =?UTF-8?q?=DB=8C=DA=A9=DB=8C=D8=B4=D9=86=E2=80=8C=D9=87=D8=A7+=D9=86?= =?UTF-8?q?=D8=A7=D9=85=E2=80=8C=D9=BE=DA=A9=DB=8C=D8=AC=D8=8C=20=D8=B3?= =?UTF-8?q?=D8=AA=D9=88=D9=86=20=D9=BE=DA=A9=DB=8C=D8=AC=20=D8=AF=D8=B1=20?= =?UTF-8?q?CSV=D8=8C=20=D9=85=D9=82=D8=A7=D8=AF=DB=8C=D8=B1=20=D8=AF=D8=A7?= =?UTF-8?q?=DB=8C=D9=86=D8=A7=D9=85=DB=8C=DA=A9=20=DA=A9=DB=8C=D9=81?= =?UTF-8?q?=E2=80=8C=D9=BE=D9=88=D9=84=20=D8=AC=D8=A7=D8=AF=D9=88=DB=8C?= =?UTF-8?q?=DB=8C=D8=8C=20=D9=88=D9=84=DB=8C=D8=AF=DB=8C=D8=AA=D9=88=D8=B1?= =?UTF-8?q?=20SystemConstants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F2: اضافه شدن packageName به SmsTemplates، IUserNotificationService، UserNotificationService - DayaLoan، ClubActivated، CommissionDeposited حالا نام پکیج را نشان می‌دهند F4: اضافه شدن ستون پکیج به CSV خروجی‌ها - commission.proto: package_name در WithdrawalRequestModel - manualpayment.proto: package_name در ManualPaymentModel - کوئری‌هندلرها UserPackagePurchases لوکاپ اضافه شد F6: مقادیر داینامیک کیف‌پول جادویی از پکیج - userwallet.proto: magic_multiplier + magic_max_credit - UserWalletService: پاپیولیت فیلدهای جدید + فالبک به SystemConstants F7: ولیدیتورها از SystemConstants استفاده می‌کنند - WalletMaxSafeAmount (10B ریال) به عنوان حصار ایمنی - MagicWalletMinCharge، DiscountWalletMinCharge ثابت‌های مرکزی - سقف واقعی per-package در هندلرها اعمال می‌شود Proto: v0.0.189 --- .../GetWithdrawalRequestsQueryHandler.cs | 12 ++++++- .../GetWithdrawalRequestsResponseDto.cs | 1 + .../Common/Interfaces/IAlertService.cs | 9 +++-- .../CheckAndProcessDayaLoansCommandHandler.cs | 13 +++---- .../CheckAndProcessDayaLoansResponseDto.cs | 5 +++ .../CreateManualPaymentCommandValidator.cs | 6 ++-- .../GetAllManualPaymentsQueryHandler.cs | 15 ++++++++ .../GetAllManualPaymentsResponseDto.cs | 1 + .../ChargeDiscountWalletCommandValidator.cs | 10 +++--- .../ChargeMagicWalletCommandValidator.cs | 8 +++-- .../Common/SmsTemplates.cs | 18 ++++++---- .../Common/SystemConstants.cs | 35 +++++++++++++++++++ .../Monitoring/UserNotificationService.cs | 15 +++++--- .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/commission.proto | 1 + .../Protos/manualpayment.proto | 1 + .../Protos/userwallet.proto | 2 ++ .../Services/UserWalletService.cs | 8 +++-- 18 files changed, 129 insertions(+), 33 deletions(-) diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs index 67fe1f9..aaa91b1 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs @@ -51,6 +51,15 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler x.UserId).Distinct().ToList(); + var userPackageNames = await _context.UserPackagePurchases + .Where(p => userIds.Contains(p.UserId)) + .Include(p => p.Package) + .GroupBy(p => p.UserId) + .Select(g => new { UserId = g.Key, PackageName = g.OrderByDescending(x => x.PurchasedAt).First().Package.Title }) + .ToDictionaryAsync(x => x.UserId, x => x.PackageName, cancellationToken); + var result = models.Select(x => new WithdrawalRequestModel { Id = x.Id, @@ -69,7 +78,8 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler /// ارسال اعلان فعال‌سازی عضویت باشگاه /// Task SendClubActivationNotificationAsync( - long userId, + long userId, + string? packageName = null, CancellationToken cancellationToken = default); /// @@ -49,6 +51,7 @@ public interface IUserNotificationService /// Task SendPayoutErrorNotificationAsync( long userId, - string errorMessage, + string errorMessage, + string? packageName = null, CancellationToken cancellationToken = default); } diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs index 68defb2..f2d86b7 100644 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs @@ -122,6 +122,7 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler /// پردازش مالی وام دایا /// - private async Task<(long NewBalance, long TransactionId)> ProcessDayaLoanAsync( + private async Task<(long NewBalance, long TransactionId, string PackageName)> ProcessDayaLoanAsync( User user, string contractNumber, CancellationToken cancellationToken) @@ -279,25 +280,25 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler /// ارسال SMS اطلاع‌رسانی /// - private async Task SendDayaLoanSmsAsync(User user) + private async Task SendDayaLoanSmsAsync(User user, string packageName) { try { var userName = SmsTemplates.GetUserName(user.FirstName+" "+user.LastName); - var message = SmsTemplates.DayaLoanReceived(userName); + var message = SmsTemplates.DayaLoanReceived(userName, packageName); await _smsService.SendAsync(user.Mobile, message); // ارسال SMS به ادمین برای اطلاع - var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}"; + var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}\nپکیج: {packageName}"; await _smsService.SendAsync("09199877503", adminMessage); _logger.LogInformation("Daya loan SMS sent to User {UserId}", user.Id); diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs index fb7942c..74a0128 100644 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs @@ -63,6 +63,11 @@ public class DayaLoanProcessResult /// public long? NewWalletBalance { get; set; } + /// + /// نام پکیج + /// + public string? PackageName { get; set; } + /// /// پیام /// diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs index 325bec9..9dae429 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandValidator.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Domain.Common; using FluentValidation; namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment; @@ -10,11 +11,12 @@ public class CreateManualPaymentCommandValidator : AbstractValidator x.Amount) .GreaterThan(0) .WithMessage("مبلغ باید بزرگتر از صفر باشد") - .LessThanOrEqualTo(1_000_000_000) - .WithMessage("مبلغ نمی‌تواند بیشتر از 1 میلیارد ریال باشد"); + .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount) + .WithMessage("مبلغ وارد شده بیش از حد مجاز است"); RuleFor(x => x.Type) .IsInEnum() diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsQueryHandler.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsQueryHandler.cs index 15f5902..2a087ba 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsQueryHandler.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsQueryHandler.cs @@ -69,6 +69,14 @@ public class GetAllManualPaymentsQueryHandler // Pagination var skip = (request.PageNumber - 1) * request.PageSize; + + // دریافت PackageName از آخرین خرید پکیج کاربر + var userPackageNames = await _context.UserPackagePurchases + .Include(p => p.Package) + .GroupBy(p => p.UserId) + .Select(g => new { UserId = g.Key, PackageName = g.OrderByDescending(x => x.PurchasedAt).First().Package.Title }) + .ToDictionaryAsync(x => x.UserId, x => x.PackageName, cancellationToken); + var data = await query .Skip(skip) .Take(request.PageSize) @@ -95,6 +103,13 @@ public class GetAllManualPaymentsQueryHandler Created = m.Created }) .ToListAsync(cancellationToken); + + // تکمیل PackageName + foreach (var item in data) + { + if (userPackageNames.TryGetValue(item.UserId, out var pkgName)) + item.PackageName = pkgName; + } var metaData = new MetaData { diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsResponseDto.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsResponseDto.cs index 938b5fa..44e418d 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsResponseDto.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Queries/GetAllManualPayments/GetAllManualPaymentsResponseDto.cs @@ -30,4 +30,5 @@ public class ManualPaymentDto public string? RejectionReason { get; set; } public long? TransactionId { get; set; } public DateTime Created { get; set; } + public string? PackageName { get; set; } } diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandValidator.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandValidator.cs index e2f6c02..2e8af6f 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandValidator.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandValidator.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Domain.Common; using FluentValidation; namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet; @@ -10,10 +11,11 @@ public class ChargeDiscountWalletCommandValidator : AbstractValidator x.Amount) - .GreaterThanOrEqualTo(10_000) - .WithMessage("حداقل مبلغ شارژ 10,000 تومان است") - .LessThanOrEqualTo(1_000_000_000) - .WithMessage("حداکثر مبلغ شارژ 1,000,000,000 تومان است"); + .GreaterThanOrEqualTo(SystemConstants.DiscountWalletMinCharge) + .WithMessage("حداقل مبلغ شارژ ۱,۰۰۰ تومان است") + .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount) + .WithMessage("مبلغ وارد شده بیش از حد مجاز است"); } } diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandValidator.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandValidator.cs index 175f12f..dcda819 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandValidator.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandValidator.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Domain.Common; using FluentValidation; namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet; @@ -10,10 +11,11 @@ public class ChargeMagicWalletCommandValidator : AbstractValidator x.Amount) - .GreaterThanOrEqualTo(100_000) + .GreaterThanOrEqualTo(SystemConstants.MagicWalletMinCharge) .WithMessage("حداقل مبلغ شارژ جادویی ۱۰,۰۰۰ تومان است") - .LessThanOrEqualTo(1_000_000_000) - .WithMessage("حداکثر مبلغ شارژ جادویی ۱۰۰,۰۰۰,۰۰۰ تومان است"); + .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount) + .WithMessage("مبلغ وارد شده بیش از حد مجاز است"); } } diff --git a/src/CMSMicroservice.Domain/Common/SmsTemplates.cs b/src/CMSMicroservice.Domain/Common/SmsTemplates.cs index 680730c..ec945a4 100644 --- a/src/CMSMicroservice.Domain/Common/SmsTemplates.cs +++ b/src/CMSMicroservice.Domain/Common/SmsTemplates.cs @@ -8,14 +8,18 @@ public static class SmsTemplates /// /// پیام دریافت اعتبار دایا /// - public static string DayaLoanReceived(string userName) => - $"{userName} عزیز، تامین اعتبار شما انجام شد. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما"; + public static string DayaLoanReceived(string userName, string? packageName = null) => + packageName != null + ? $"{userName} عزیز، تامین اعتبار {packageName} شما انجام شد. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما" + : $"{userName} عزیز، تامین اعتبار شما انجام شد. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما"; /// /// پیام فعال‌سازی باشگاه مشتریان /// - public static string ClubActivated(string userName) => - $"{userName} گرامی، عضویت شما در باشگاه مشتریان کارابازار با موفقیت فعال شد. با تشکر از حسن انتخاب شما"; + public static string ClubActivated(string userName, string? packageName = null) => + packageName != null + ? $"{userName} گرامی، عضویت شما در باشگاه مشتریان کارابازار با {packageName} فعال شد. با تشکر از حسن انتخاب شما" + : $"{userName} گرامی، عضویت شما در باشگاه مشتریان کارابازار با موفقیت فعال شد. با تشکر از حسن انتخاب شما"; /// /// پیام خرید پکیج @@ -26,8 +30,10 @@ public static class SmsTemplates /// /// پیام واریز کمیسیون /// - public static string CommissionDeposited(string userName, long amount) => - $"{userName} گرامی، مبلغ {amount:N0} ریال بابت کمیسیون به کیف پول شما واریز شد."; + public static string CommissionDeposited(string userName, long amount, string? packageName = null) => + packageName != null + ? $"{userName} گرامی، مبلغ {amount:N0} ریال بابت کمیسیون {packageName} به کیف پول شما واریز شد." + : $"{userName} گرامی، مبلغ {amount:N0} ریال بابت کمیسیون به کیف پول شما واریز شد."; /// /// پیام برداشت موفق diff --git a/src/CMSMicroservice.Domain/Common/SystemConstants.cs b/src/CMSMicroservice.Domain/Common/SystemConstants.cs index 634a85e..0bf961c 100644 --- a/src/CMSMicroservice.Domain/Common/SystemConstants.cs +++ b/src/CMSMicroservice.Domain/Common/SystemConstants.cs @@ -66,6 +66,41 @@ public static class SystemConstants /// public const long MagicWalletEntryThreshold = 1_000_000; + /// + /// مقدار پیش‌فرض ضریب جادویی وقتی پکیج یافت نشود (بازگشت به پایه) + /// + public const double MagicWalletDefaultMultiplier = 2.5; + + /// + /// مقدار پیش‌فرض حداکثر واریز جادویی وقتی پکیج یافت نشود (ریال) + /// + public const long MagicWalletDefaultMaxDeposit = 1_000_000_000; + + /// + /// مقدار پیش‌فرض حداکثر اعتبار جادویی وقتی پکیج یافت نشود (ریال) + /// + public const long MagicWalletDefaultMaxCredit = 2_500_000_000; + + #endregion + + #region Wallet Validation Settings + + /// + /// حداکثر مبلغ مجاز در ولیدیتور (حصار ایمنی — مقدار واقعی از پکیج خوانده می‌شود). + /// ۱۰ میلیارد ریال = ۱ میلیارد تومان + /// + public const long WalletMaxSafeAmount = 10_000_000_000; + + /// + /// حداقل مبلغ شارژ جادویی (ریال) — ۱۰۰٬۰۰۰ ریال = ۱۰ هزار تومان + /// + public const long MagicWalletMinCharge = 100_000; + + /// + /// حداقل مبلغ شارژ تخفیفی (ریال) — ۱۰٬۰۰۰ ریال = ۱ هزار تومان + /// + public const long DiscountWalletMinCharge = 10_000; + #endregion #region Shop Settings diff --git a/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs b/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs index 865d6b3..9d1118b 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs @@ -49,6 +49,7 @@ public class UserNotificationService : IUserNotificationService long userId, decimal amount, int weekNumber, + string? packageName = null, CancellationToken cancellationToken = default) { _logger.LogInformation( @@ -69,14 +70,15 @@ public class UserNotificationService : IUserNotificationService if (string.IsNullOrEmpty(userFullName)) userFullName = "کاربر عزیز"; var formattedAmount = amount.ToString("N0", new System.Globalization.CultureInfo("fa-IR")); + var packageInfo = !string.IsNullOrEmpty(packageName) ? $" ({packageName})" : ""; // Send Email if (_emailSettings.Enabled && !string.IsNullOrEmpty(user.Email)) { - var emailSubject = $"واریز کمیسیون هفته {weekNumber}"; + var emailSubject = $"واریز کمیسیون هفته {weekNumber}{packageInfo}"; var emailBody = "
" + $"

سلام {userFullName}

" + - $"

کمیسیون هفته {weekNumber} شما به مبلغ {formattedAmount} ریال به کیف پول شما واریز شد.

" + + $"

کمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ {formattedAmount} ریال به کیف پول شما واریز شد.

" + "

از اعتماد شما سپاسگزاریم.

" + "
" + "

FourSat - سیستم مدیریت باشگاه مشتریان

" + @@ -95,7 +97,7 @@ public class UserNotificationService : IUserNotificationService { await SendSmsAsync( phoneNumber: user.Mobile, - message: $"سلام {userFullName}\nکمیسیون هفته {weekNumber} شما به مبلغ {formattedAmount} ریال واریز شد.\nFourSat", + message: $"سلام {userFullName}\nکمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ {formattedAmount} ریال واریز شد.\nFourSat", cancellationToken: cancellationToken); } @@ -109,6 +111,7 @@ public class UserNotificationService : IUserNotificationService public async Task SendClubActivationNotificationAsync( long userId, + string? packageName = null, CancellationToken cancellationToken = default) { _logger.LogInformation("🎉 Sending club activation notification: User={UserId}", userId); @@ -120,6 +123,7 @@ public class UserNotificationService : IUserNotificationService var userFullName = $"{user.FirstName} {user.LastName}".Trim(); if (string.IsNullOrEmpty(userFullName)) userFullName = "کاربر عزیز"; + var packageInfo = !string.IsNullOrEmpty(packageName) ? $" با {packageName}" : ""; // Send Email if (_emailSettings.Enabled && !string.IsNullOrEmpty(user.Email)) @@ -127,7 +131,7 @@ public class UserNotificationService : IUserNotificationService var emailSubject = "فعال‌سازی باشگاه مشتریان FourSat"; var emailBody = "
" + $"

تبریک {userFullName}!

" + - "

عضویت شما در باشگاه مشتریان FourSat با موفقیت فعال شد.

" + + $"

عضویت شما در باشگاه مشتریان FourSat{packageInfo} با موفقیت فعال شد.

" + "

از این پس می‌توانید از مزایای ویژه باشگاه بهره‌مند شوید.

" + "
" + "

FourSat - سیستم مدیریت باشگاه مشتریان

" + @@ -146,7 +150,7 @@ public class UserNotificationService : IUserNotificationService { await SendSmsAsync( phoneNumber: user.Mobile, - message: $"تبریک! عضویت شما در باشگاه مشتریان FourSat فعال شد.", + message: $"تبریک! عضویت شما در باشگاه مشتریان FourSat{packageInfo} فعال شد.", cancellationToken: cancellationToken); } } @@ -159,6 +163,7 @@ public class UserNotificationService : IUserNotificationService public async Task SendPayoutErrorNotificationAsync( long userId, string errorMessage, + string? packageName = null, CancellationToken cancellationToken = default) { _logger.LogWarning( diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 672fe4f..9f0ac68 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.188 + 0.0.189 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/commission.proto b/src/CMSMicroservice.Protobuf/Protos/commission.proto index 1315e1d..9518c04 100644 --- a/src/CMSMicroservice.Protobuf/Protos/commission.proto +++ b/src/CMSMicroservice.Protobuf/Protos/commission.proto @@ -531,6 +531,7 @@ message WithdrawalRequestModel string bank_reference_id = 15; string bank_tracking_code = 16; string payment_failure_reason = 17; + google.protobuf.StringValue package_name = 18; // نام پکیج کاربر } // ============ Week Definitions (for dropdowns) ============ diff --git a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto index 150e1ca..edd0cab 100644 --- a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto +++ b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto @@ -138,6 +138,7 @@ message ManualPaymentModel google.protobuf.Timestamp created = 19; google.protobuf.StringValue image_path = 20; google.protobuf.Int64Value image_document_id = 21; + google.protobuf.StringValue package_name = 22; // نام پکیج کاربر } message ProcessManualMembershipPaymentRequest diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto index 096f3ea..a48af72 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto @@ -251,6 +251,8 @@ message GetMagicWalletStatusResponse int64 balance = 6; google.protobuf.Timestamp magic_activated_at = 7; int32 purchase_cycle_count = 8; // تعداد دورهای خرید پکیج (0 = هنوز هیچ دوری تکمیل نشده) + double magic_multiplier = 9; // ضریب جادویی (مثلاً 2.5) + int64 magic_max_credit = 10; // حداکثر اعتبار (ریال) } // ============= Discount Wallet Messages ============= diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs index 8ce2b1e..96594c9 100644 --- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs @@ -233,7 +233,9 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase var package = currentCycle != null ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken) : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken); - var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? 1_000_000_000; + var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? SystemConstants.MagicWalletDefaultMaxDeposit; + var magicMultiplier = package != null ? (double)package.MagicWalletMultiplier : SystemConstants.MagicWalletDefaultMultiplier; + var magicMaxCredit = package?.MagicWalletMaxCredit ?? SystemConstants.MagicWalletDefaultMaxCredit; var response = new GetMagicWalletStatusResponse { @@ -243,7 +245,9 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase MagicMaxDeposit = magicMaxDeposit, MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - wallet.MagicTotalDeposited), Balance = wallet.Balance, - PurchaseCycleCount = cycleCount + PurchaseCycleCount = cycleCount, + MagicMultiplier = magicMultiplier, + MagicMaxCredit = magicMaxCredit }; if (wallet.MagicActivatedAt.HasValue) From aee7b59fc134992c8480821e77a0c168edc86402 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 21:03:27 +0330 Subject: [PATCH 39/44] =?UTF-8?q?fix:=20Toman/Rial=20=E2=80=94=20system=20?= =?UTF-8?q?uses=20Toman=20everywhere,=20only=20ZarinPal=20gets=20Rial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core change in ZarinPalPaymentService: - InitiatePaymentAsync: Amount (Toman) × 10 → Rial for gateway - VerifyPaymentWithAmountAsync: Amount (Toman) × 10 → Rial for verify - Verify result: gateway Rial ÷ 10 → Toman back to system Also fixed: - Package.Price doc: ریال → تومان - Error messages in 3 handlers: ریال → تومان - ConfigurationService descriptions: ریال → تومان (7 entries) - Notification SMS/email: ریال → تومان - Doc comments in 5 command files: ریال → تومان - PaymentCallback comments: ریال → تومان 14 files changed across Domain, Application, Infrastructure, WebApi --- .../ActivateClubMembershipCommandHandler.cs | 2 +- .../PlaceOrder/PlaceOrderCommandHandler.cs | 2 +- .../CreateManualPaymentCommand.cs | 2 +- .../ProcessManualMembershipPaymentCommand.cs | 2 +- ...etCustomerWithdrawalSettingsResponseDto.cs | 2 +- .../ChargeDiscountWalletCommand.cs | 2 +- .../ChargeMagicWalletCommand.cs | 2 +- .../ChargeMagicWalletCommandHandler.cs | 2 +- .../VerifyMagicWalletChargeCommandHandler.cs | 4 ++-- .../Entities/Package.cs | 2 +- .../Monitoring/UserNotificationService.cs | 4 ++-- .../Payment/ZarinPalPaymentService.cs | 24 +++++++++++-------- .../Controllers/PaymentCallbackController.cs | 4 ++-- .../Services/ConfigurationService.cs | 14 +++++------ 14 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index b7842e1..1d941c1 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -150,7 +150,7 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler public long UserId { get; set; } /// - /// مبلغ تراکنش (ریال) + /// مبلغ تراکنش (تومان) /// public long Amount { get; set; } diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs index 4a337b7..b9adfe4 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs @@ -8,7 +8,7 @@ public record ProcessManualMembershipPaymentCommand : IRequest - /// مبلغ پرداختی (ریال) + /// مبلغ پرداختی (تومان) ///
public long Amount { get; init; } diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsResponseDto.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsResponseDto.cs index 98695f2..8a5b8d8 100644 --- a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsResponseDto.cs +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsResponseDto.cs @@ -3,7 +3,7 @@ namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawal public class GetCustomerWithdrawalSettingsResponseDto { /// - /// حداقل مبلغ برداشت (ریال) + /// حداقل مبلغ برداشت (تومان) /// public long MinWithdrawalAmount { get; set; } } diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommand.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommand.cs index c2f4a80..6650dc0 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommand.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommand.cs @@ -15,7 +15,7 @@ public class ChargeDiscountWalletCommand : IRequest public long UserId { get; set; } /// - /// مبلغ مورد نظر برای شارژ (ریال) + /// مبلغ مورد نظر برای شارژ (تومان) /// public long Amount { get; set; } } diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommand.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommand.cs index 3e05022..411ecfb 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommand.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommand.cs @@ -15,7 +15,7 @@ public class ChargeMagicWalletCommand : IRequest public long UserId { get; set; } /// - /// مبلغ واریزی واقعی (ریال) — اعتبار = مبلغ × 2.5 + /// مبلغ واریزی واقعی (تومان) — اعتبار = مبلغ × 2.5 /// public long Amount { get; set; } } diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandHandler.cs index 1210313..cc8f5d6 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeMagicWallet/ChargeMagicWalletCommandHandler.cs @@ -109,7 +109,7 @@ public class ChargeMagicWalletCommandHandler remainingDeposit ); throw new BadRequestException( - $"مبلغ واریزی بیش از سقف باقیمانده است. حداکثر مبلغ قابل واریز: {remainingDeposit:N0} ریال" + $"مبلغ واریزی بیش از سقف باقیمانده است. حداکثر مبلغ قابل واریز: {remainingDeposit:N0} تومان" ); } diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs index 17425b6..c455e4e 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs @@ -57,7 +57,7 @@ public class VerifyMagicWalletChargeCommandHandler var userId = paymentTx.UserId ?? throw new BadRequestException("شناسه کاربر در تراکنش پرداخت یافت نشد"); - var depositAmount = paymentTx.Amount; // مبلغ واقعی واریزی (ریال) + var depositAmount = paymentTx.Amount; // مبلغ واقعی واریزی (تومان) // 2. بررسی وضعیت برگشتی از درگاه if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase)) @@ -74,7 +74,7 @@ public class VerifyMagicWalletChargeCommandHandler throw new BadRequestException("پرداخت توسط کاربر لغو شد"); } - // 3. Verify با درگاه (مبلغ به ریال) + // 3. Verify با درگاه (مبلغ به تومان — تبدیل به ریال در ZarinPalService) var verifyResult = await _paymentGateway.VerifyPaymentAsync( request.Authority, request.Status, diff --git a/src/CMSMicroservice.Domain/Entities/Package.cs b/src/CMSMicroservice.Domain/Entities/Package.cs index 158a150..9bfbe0f 100644 --- a/src/CMSMicroservice.Domain/Entities/Package.cs +++ b/src/CMSMicroservice.Domain/Entities/Package.cs @@ -19,7 +19,7 @@ public class Package : BaseAuditableEntity, IHasHistory /// آدرس تصویر public string ImagePath { get; set; } - /// قیمت پکیج (ریال) + /// قیمت پکیج (تومان) public long Price { get; set; } // === فیلدهای جدید v3 === diff --git a/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs b/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs index 9d1118b..a85d0ae 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs @@ -78,7 +78,7 @@ public class UserNotificationService : IUserNotificationService var emailSubject = $"واریز کمیسیون هفته {weekNumber}{packageInfo}"; var emailBody = "
" + $"

سلام {userFullName}

" + - $"

کمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ {formattedAmount} ریال به کیف پول شما واریز شد.

" + + $"

کمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ {formattedAmount} تومان به کیف پول شما واریز شد.

" + "

از اعتماد شما سپاسگزاریم.

" + "
" + "

FourSat - سیستم مدیریت باشگاه مشتریان

" + @@ -97,7 +97,7 @@ public class UserNotificationService : IUserNotificationService { await SendSmsAsync( phoneNumber: user.Mobile, - message: $"سلام {userFullName}\nکمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ {formattedAmount} ریال واریز شد.\nFourSat", + message: $"سلام {userFullName}\nکمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ {formattedAmount} تومان واریز شد.\nFourSat", cancellationToken: cancellationToken); } diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs index 9b790ee..a0b05ef 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs @@ -66,8 +66,9 @@ public class ZarinPalPaymentService : IPaymentGatewayService { try { - // مبلغ از caller به ریال می‌رسد — مستقیم ارسال به زرین‌پال - var amountInRials = (long)request.Amount; + // مبلغ از caller به تومان می‌رسد — تبدیل به ریال برای زرین‌پال (×10) + var amountInToman = (long)request.Amount; + var amountInRials = amountInToman * 10; var zarinPalRequest = new ZarinPalPaymentRequest { @@ -85,8 +86,8 @@ public class ZarinPalPaymentService : IPaymentGatewayService var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); _logger.LogInformation( - "ZarinPal payment request: Amount={AmountRial} Rial ({AmountToman} Toman), User={UserId}, Sandbox={Sandbox}", - amountInRials, amountInRials / 10m, request.UserId, _useSandbox); + "ZarinPal payment request: Amount={AmountToman} Toman ({AmountRial} Rial), User={UserId}, Sandbox={Sandbox}", + amountInToman, amountInRials, request.UserId, _useSandbox); var response = await _httpClient.PostAsync(RequestEndpoint, content, cancellationToken); var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); @@ -167,7 +168,7 @@ public class ZarinPalPaymentService : IPaymentGatewayService /// /// تأیید پرداخت با مبلغ — نسخه اصلی برای زرین‌پال - /// refId = Authority، verificationToken = Status (OK/NOK)، amount = مبلغ به ریال + /// refId = Authority، verificationToken = Status (OK/NOK)، amount = مبلغ به تومان /// public Task VerifyPaymentAsync( string refId, @@ -181,7 +182,7 @@ public class ZarinPalPaymentService : IPaymentGatewayService private async Task VerifyPaymentWithAmountAsync( string refId, string verificationToken, - decimal amountInRials, + decimal amountInToman, CancellationToken cancellationToken) { try @@ -198,18 +199,21 @@ public class ZarinPalPaymentService : IPaymentGatewayService }; } + // تبدیل تومان به ریال برای زرین‌پال (×10) + var amountInRials = (long)(amountInToman * 10); + var verifyRequest = new ZarinPalVerifyRequest { MerchantId = _merchantId, Authority = refId, - Amount = (long)amountInRials + Amount = amountInRials }; var jsonContent = JsonSerializer.Serialize(verifyRequest, JsonOptions); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); - _logger.LogInformation("ZarinPal verify request: Authority={Authority}, Amount={Amount} Rial ({AmountToman} Toman)", - refId, (long)amountInRials, amountInRials / 10m); + _logger.LogInformation("ZarinPal verify request: Authority={Authority}, Amount={AmountToman} Toman ({AmountRial} Rial)", + refId, (long)amountInToman, amountInRials); var response = await _httpClient.PostAsync(VerifyEndpoint, content, cancellationToken); var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); @@ -231,7 +235,7 @@ public class ZarinPalPaymentService : IPaymentGatewayService IsSuccess = true, RefId = refId, TrackingCode = result.Data.RefId?.ToString(), - Amount = result.Data.Amount ?? 0, // ریال — بدون تبدیل + Amount = (result.Data.Amount ?? 0) / 10, // تبدیل ریال → تومان CardPan = result.Data.CardPan, CardHash = result.Data.CardHash, VerificationCode = result.Data.Code, diff --git a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs index d553488..9898a31 100644 --- a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs +++ b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs @@ -82,11 +82,11 @@ public class PaymentCallbackController : ControllerBase if (string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(authority)) { - // Verify با مبلغ از دیتابیس (ریال) + // Verify با مبلغ از دیتابیس (تومان — تبدیل به ریال در ZarinPalService) var verifyResult = await _paymentGateway.VerifyPaymentAsync( authority, status!, - order.GatewayAmountPaid, // مبلغ به ریال + order.GatewayAmountPaid, // مبلغ به تومان cancellationToken); paymentSuccess = verifyResult.IsSuccess; diff --git a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs index 05eb8dd..cb8a812 100644 --- a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs +++ b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs @@ -223,20 +223,20 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB { return key switch { - "Club.ActivationFee" => "هزینه فعال‌سازی عضویت باشگاه (ریال)", - "Club.MembershipGiftValue" => "مبلغ هدیه حق عضویت باشگاه (ریال)", - "Commission.MinWithdrawalAmount" => "حداقل مبلغ برداشت (ریال)", + "Club.ActivationFee" => "هزینه فعال‌سازی عضویت باشگاه (تومان)", + "Club.MembershipGiftValue" => "مبلغ هدیه حق عضویت باشگاه (تومان)", + "Commission.MinWithdrawalAmount" => "حداقل مبلغ برداشت (تومان)", "Commission.MaxWeeklyBalancesPerLeg" => "سقف تعادل هفتگی برای هر دست", "Commission.MaxNetworkLevel" => "حداکثر عمق شبکه برای محاسبه کمیسیون", "Commission.CashWithdrawalEnabled" => "امکان برداشت نقدی", "Commission.CalculationStrategy" => "روش محاسبه کمیسیون", "Network.AllowOrphanNodes" => "اجازه حذف والدین با فرزند", "Network.MaxChildrenPerLeg" => "حداکثر تعداد فرزند مستقیم در هر پا", - "Package.BasePackageAmount" => "مبلغ پکیج پایه (ریال)", - "Package.DayaLoanAmount" => "مبلغ وام دایا (ریال)", + "Package.BasePackageAmount" => "مبلغ پکیج پایه (تومان)", + "Package.DayaLoanAmount" => "مبلغ وام دایا (تومان)", "MagicWallet.Multiplier" => "ضریب شارژ کیف‌پول جادویی", - "MagicWallet.MaxDeposit" => "سقف واریز هر دور جادویی (ریال)", - "MagicWallet.MaxCredit" => "سقف اعتبار هر دور جادویی (ریال)", + "MagicWallet.MaxDeposit" => "سقف واریز هر دور جادویی (تومان)", + "MagicWallet.MaxCredit" => "سقف اعتبار هر دور جادویی (تومان)", "System.MaintenanceMode" => "حالت تعمیر و نگهداری سیستم", "System.EnableAuditLog" => "فعال‌سازی لاگ تغییرات", "Shop.VAT" => "مالیات بر ارزش افزوده", From 721661af0f930508e5045220407f4a913c6c07cf Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 21:17:50 +0330 Subject: [PATCH 40/44] =?UTF-8?q?fix:=20pass=20amount=20to=20ZarinPal=20Ve?= =?UTF-8?q?rifyPaymentAsync=20=E2=80=94=20fix=20verify=20failure=20(amount?= =?UTF-8?q?=3D0=20bug)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PackageService.CustomerVerifyPackagePurchase: look up PaymentTransaction.Amount and pass to 3-arg overload - TransactionsService.CustomerPaymentVerification: same fix - VerifyDiscountWalletChargeCommandHandler: look up amount from PaymentTransaction - VerifyPackagePurchaseCommandHandler: fix copy-paste bug (Authority as verificationToken) + add amount - IPaymentGatewayService: throw NotImplementedException in default 3-arg impl to prevent silent amount=0 - MockPaymentGatewayService & DayaPaymentService: add 3-arg overload for compatibility Root cause: ZarinPal requires the exact amount in verify request. The 2-arg overload was sending amount=0 which caused Code=-1 (تأیید تراکنش ناموفق). --- .../Interfaces/IPaymentGatewayService.cs | 6 ++++-- .../VerifyPackagePurchaseCommandHandler.cs | 12 ++++++++++-- ...erifyDiscountWalletChargeCommandHandler.cs | 19 +++++++++++-------- .../Services/Payment/DayaPaymentService.cs | 10 ++++++++++ .../Payment/MockPaymentGatewayService.cs | 10 ++++++++++ .../Services/PackageService.cs | 12 +++++++----- .../Services/TransactionsService.cs | 12 +++++++----- 7 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs index 31fa543..bb9a668 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs @@ -41,8 +41,10 @@ public interface IPaymentGatewayService decimal amountInToman, CancellationToken cancellationToken = default) { - // پیش‌فرض: درگاه‌هایی که Amount نمی‌خواهند، از overload بدون amount استفاده کنند - return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + // ⚠️ هشدار: این پیاده‌سازی پیش‌فرض مبلغ را نادیده می‌گیرد. + // درگاه‌هایی مثل زرین‌پال باید حتماً این متد را override کنند. + throw new NotImplementedException( + "درگاه پرداخت باید متد VerifyPaymentAsync با مبلغ را پیاده‌سازی کند"); } /// diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs index 867b694..0221abf 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyPackagePurchase/VerifyPackagePurchaseCommandHandler.cs @@ -58,10 +58,18 @@ public class VerifyPackagePurchaseCommandHandler return true; } - // 3. Verify با درگاه بانکی + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify + var paymentTx = await _context.PaymentTransactions + .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken); + + var amountInToman = (decimal)(paymentTx?.Amount ?? order.Amount); + + // 3. Verify با درگاه بانکی (مبلغ به تومان — تبدیل به ریال در ZarinPalService) var verifyResult = await _paymentGateway.VerifyPaymentAsync( request.Authority, - request.Authority // verificationToken - در بعضی درگاه‌ها همان Authority است + "OK", // verificationToken — Status از درگاه + amountInToman, + cancellationToken ); if (!verifyResult.IsSuccess) diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs index 400b8dd..a5694d5 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs @@ -49,16 +49,19 @@ public class VerifyDiscountWalletChargeCommandHandler throw new NotFoundException(nameof(User), request.UserId); } - // 2. Verify با درگاه - // Authority = کد مرجع تراکنش، Status = "OK" (تأیید کاربر از درگاه) - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - request.Authority, - "OK" // وقتی این handler فراخوانی میشه یعنی کاربر از درگاه برگشته — Status باید OK باشه - ); - - // آپدیت PaymentTransaction با نتیجه verify + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken); + + var amountInToman = (decimal)(paymentTx?.Amount ?? 0); + + // 2. Verify با درگاه (مبلغ به تومان — تبدیل به ریال در ZarinPalService) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, + "OK", // وقتی این handler فراخوانی میشه یعنی کاربر از درگاه برگشته — Status باید OK باشه + amountInToman, + cancellationToken + ); if (paymentTx != null) { paymentTx.PaymentStatus = verifyResult.IsSuccess; diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs index d31972b..01effe4 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/DayaPaymentService.cs @@ -193,6 +193,16 @@ public class DayaPaymentService : IPaymentGatewayService } } + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + // دایا: درگاه دایا مبلغ را در verify نیاز ندارد — از overload بدون مبلغ استفاده می‌شود + return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + } + public async Task ProcessPayoutAsync( PayoutRequest request, CancellationToken cancellationToken = default) diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs index 46e22df..d4cf080 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/MockPaymentGatewayService.cs @@ -74,6 +74,16 @@ public class MockPaymentGatewayService : IPaymentGatewayService }; } + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + // Mock: مبلغ تفاوتی نمی‌کند، از همان overload بدون مبلغ استفاده می‌شود + return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + } + public async Task ProcessPayoutAsync( PayoutRequest request, CancellationToken cancellationToken = default) diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 3ad2be6..9b2ffae 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -274,13 +274,15 @@ public class PackageService : PackageContract.PackageContractBase }; } - // Verify with payment gateway - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - request.Authority, request.Status, context.CancellationToken); - - // آپدیت PaymentTransaction + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken); + + var amountInToman = paymentTx?.Amount ?? purchase.Amount; + + // Verify with payment gateway (مبلغ به تومان — سرویس زرین‌پال خودش ×۱۰ می‌کنه) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, request.Status, (decimal)amountInToman, context.CancellationToken); if (paymentTx != null) { paymentTx.PaymentStatus = verifyResult.IsSuccess; diff --git a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs index f1043cf..dbc4966 100644 --- a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs +++ b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs @@ -235,13 +235,15 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase }; } - // Verify with gateway - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - request.Authority, request.Status, context.CancellationToken); - - // آپدیت PaymentTransaction + // واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken); + + var amountInToman = paymentTx?.Amount ?? transaction.Amount; + + // Verify with gateway (مبلغ به تومان — سرویس زرین‌پال خودش ×۱۰ می‌کنه) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, request.Status, (decimal)amountInToman, context.CancellationToken); if (paymentTx != null) { paymentTx.PaymentStatus = verifyResult.IsSuccess; From ed2b20a98e3b82e881d75ce5571314401339349e Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 22:09:15 +0330 Subject: [PATCH 41/44] fix: wallet callback URL issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PaymentCallbackController: fix redirect paths /magic-wallet → /profile/magic-wallet - Add appsettings.Development.json with local CmsBaseUrl (localhost:32846) and FrontOfficeBaseUrl (localhost:5268) - Proto: fix comments ریال → تومان in wallet charge messages Root cause: CmsBaseUrl in appsettings.json points to stage (cms.se.kbs1.ir), so when testing locally, ZarinPal callback went to stage CMS instead of local. Also redirect after verify used wrong FO path (/magic-wallet instead of /profile/magic-wallet). --- .../Protos/userwallet.proto | 4 ++-- .../Controllers/PaymentCallbackController.cs | 6 +++--- .../appsettings.Development.json | 13 +++++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 src/CMSMicroservice.WebApi/appsettings.Development.json diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto index a48af72..dfb8593 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto @@ -231,7 +231,7 @@ message GetCustomerWithdrawalSettingsResponse message InitiateMagicChargeRequest { - int64 amount = 1; // مبلغ واریزی واقعی (ریال) + int64 amount = 1; // مبلغ واریزی (تومان) } message InitiateMagicChargeResponse @@ -259,7 +259,7 @@ message GetMagicWalletStatusResponse message InitiateDiscountChargeRequest { - int64 amount = 1; // مبلغ واریزی (ریال) + int64 amount = 1; // مبلغ واریزی (تومان) } message InitiateDiscountChargeResponse diff --git a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs index 9898a31..bac9e5e 100644 --- a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs +++ b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs @@ -166,7 +166,7 @@ public class PaymentCallbackController : ControllerBase if (string.IsNullOrEmpty(authority)) { _logger.LogError("Magic charge callback: Authority is missing"); - return Redirect($"{frontOfficeBaseUrl}/magic-wallet?payment=error&reason=no-authority"); + return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=error&reason=no-authority"); } var result = await _sender.Send(new VerifyMagicWalletChargeCommand @@ -179,12 +179,12 @@ public class PaymentCallbackController : ControllerBase "Magic charge completed successfully. Authority={Authority}", authority); - return Redirect($"{frontOfficeBaseUrl}/magic-wallet?payment=success"); + return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=success"); } catch (Exception ex) { _logger.LogError(ex, "Magic charge callback error. Authority={Authority}", authority); - return Redirect($"{frontOfficeBaseUrl}/magic-wallet?payment=failed"); + return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=failed"); } } diff --git a/src/CMSMicroservice.WebApi/appsettings.Development.json b/src/CMSMicroservice.WebApi/appsettings.Development.json new file mode 100644 index 0000000..df4f16b --- /dev/null +++ b/src/CMSMicroservice.WebApi/appsettings.Development.json @@ -0,0 +1,13 @@ +{ + "CmsBaseUrl": "https://localhost:32846", + "FrontOfficeBaseUrl": "http://localhost:5268", + "ZarinPal": { + "UseSandbox": true + }, + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Warning" + } + } +} From 01073084abf3edfd60d698a2f65abac084d54f09 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 22:20:04 +0330 Subject: [PATCH 42/44] =?UTF-8?q?security:=20remove=20callback=20URL=20fro?= =?UTF-8?q?m=20user=20input=20=E2=80=94=20read=20from=20FrontOfficeBaseUrl?= =?UTF-8?q?=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PackageService: build callback URL from FrontOfficeBaseUrl config instead of request.CallbackUrl - TransactionsService: same fix — callback URL from config - Prevents potential open redirect vulnerability (user could send arbitrary URL) All callback URLs now come from appsettings FrontOfficeBaseUrl or CmsBaseUrl. --- src/CMSMicroservice.WebApi/Services/PackageService.cs | 10 +++++----- .../Services/TransactionsService.cs | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 9b2ffae..c279901 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -182,9 +182,9 @@ public class PackageService : PackageContract.PackageContractBase .Select(u => new { u.Mobile }) .FirstOrDefaultAsync(context.CancellationToken); - // Embed orderId in callback URL so FrontOffice can pass it back for verification - var separator = request.CallbackUrl.Contains('?') ? "&" : "?"; - var callbackWithOrder = $"{request.CallbackUrl}{separator}orderId={purchase.Id}"; + // Callback URL از config — نه از ورودی کاربر (امنیت) + var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; + var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?orderId={purchase.Id}"; var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest { @@ -192,7 +192,7 @@ public class PackageService : PackageContract.PackageContractBase UserId = userId, Mobile = user?.Mobile ?? string.Empty, Description = $"خرید پکیج {package.Title}", - CallbackUrl = callbackWithOrder + CallbackUrl = callbackUrl }, context.CancellationToken); if (!paymentResult.IsSuccess) @@ -216,7 +216,7 @@ public class PackageService : PackageContract.PackageContractBase GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", Amount = package.Price, - CallbackUrl = request.CallbackUrl, + CallbackUrl = callbackUrl, Description = $"خرید پکیج {package.Title}", Mobile = user?.Mobile, UserId = userId, diff --git a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs index dbc4966..dfd2d25 100644 --- a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs +++ b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs @@ -162,6 +162,10 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase _context.Transactions.Add(transaction); await _context.SaveChangesAsync(context.CancellationToken); + // Callback URL از config — نه از ورودی کاربر (امنیت) + var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; + var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback"; + // Initiate payment with gateway var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest { @@ -169,7 +173,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase UserId = userId, Mobile = request.Mobile ?? user?.Mobile ?? string.Empty, Description = request.Description ?? "پرداخت آنلاین", - CallbackUrl = request.CallbackUrl + CallbackUrl = callbackUrl }, context.CancellationToken); if (!paymentResult.IsSuccess) @@ -191,7 +195,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", Amount = request.Amount, - CallbackUrl = request.CallbackUrl, + CallbackUrl = callbackUrl, Description = request.Description ?? "پرداخت آنلاین", Mobile = request.Mobile ?? user?.Mobile, UserId = userId, From 1e7c17f0907a2531272181624b89b016d1dc4679 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sat, 28 Feb 2026 03:27:46 +0330 Subject: [PATCH 43/44] feat: Update payment processing and callback mechanisms - Refactor ActivateClubMembershipCommandHandler to use UserPackagePurchases instead of UserOrders for package activation. - Modify PlaceOrderCommandHandler to redirect payment callbacks to the front office. - Update ChargeDiscountWalletCommandHandler and ChargeMagicWalletCommandHandler to direct payment callbacks to the front office. - Remove PaymentCallbackController and integrate payment verification directly into DiscountOrderService and UserWalletService. - Add CustomerVerifyDiscountOrderPayment RPC to DiscountOrderService for verifying discount order payments. - Implement VerifyMagicCharge and VerifyDiscountCharge methods in UserWalletService for wallet charge verifications. - Update appsettings.json to use local URLs for development. - Remove appsettings.Development.json as it is no longer needed. - Comment out history tracking methods in ClubMembershipCycle and Package classes. - Update PackageService to automatically activate club membership after successful payment verification. - Adjust UserService to generate JWT tokens with user details. --- .../ActivateClubMembershipCommandHandler.cs | 45 ++-- .../PlaceOrder/PlaceOrderCommandHandler.cs | 6 +- .../ChargeDiscountWalletCommandHandler.cs | 6 +- .../ChargeMagicWalletCommandHandler.cs | 6 +- .../Entities/Club/ClubMembershipCycle.cs | 35 +-- .../Entities/Package.cs | 33 +-- .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/discountorder.proto | 24 ++ .../Protos/userwallet.proto | 27 ++ .../Controllers/PaymentCallbackController.cs | 249 ------------------ .../Services/DiscountOrderService.cs | 115 +++++++- .../Services/PackageService.cs | 18 ++ .../Services/UserOrderService.cs | 2 +- .../Services/UserService.cs | 16 +- .../Services/UserWalletService.cs | 96 ++++++- .../appsettings.Development.json | 13 - src/CMSMicroservice.WebApi/appsettings.json | 4 +- 17 files changed, 363 insertions(+), 334 deletions(-) delete mode 100644 src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs delete mode 100644 src/CMSMicroservice.WebApi/appsettings.Development.json diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index 1d941c1..b960c41 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -97,43 +97,44 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler o.Transaction) - .Where(o => - o.UserId == user.Id && - o.PackageId != null && - o.PaymentStatus == PaymentStatus.Success) - .OrderByDescending(o => o.Created) + // 4. پیدا کردن آخرین خرید موفق پکیج + var packagePurchase = await _context.UserPackagePurchases + .Include(p => p.Transaction) + .Where(p => + p.UserId == user.Id && + p.Transaction != null && + p.Transaction.PaymentStatus == PaymentStatus.Success) + .OrderByDescending(p => p.Created) .FirstOrDefaultAsync(cancellationToken); - if (packageOrder == null) + if (packagePurchase == null) { _logger.LogWarning( - "No successful package order found for UserId: {UserId}", + "No successful package purchase found for UserId: {UserId}", request.UserId ); throw new NotFoundException("سفارش پکیج یافت نشد"); } // 5. بررسی Transaction - if (packageOrder.Transaction == null) + if (packagePurchase.Transaction == null) { _logger.LogError( - "Transaction not found for OrderId: {OrderId}", - packageOrder.Id + "Transaction not found for PurchaseId: {PurchaseId}", + packagePurchase.Id ); throw new NotFoundException("تراکنش مربوط به سفارش یافت نشد"); } - var transaction = packageOrder.Transaction; + var transaction = packagePurchase.Transaction; - if (transaction.Type != TransactionType.DepositIpg && + if (transaction.Type != TransactionType.Buy && + transaction.Type != TransactionType.DepositIpg && transaction.Type != TransactionType.DepositExternal1) { _logger.LogWarning( - "Invalid transaction type for OrderId {OrderId}: {Type}", - packageOrder.Id, + "Invalid transaction type for PurchaseId {PurchaseId}: {Type}", + packagePurchase.Id, transaction.Type ); throw new BadRequestException( @@ -141,9 +142,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler p.Id == packageOrder.PackageId && !p.IsDeleted, cancellationToken) + .FirstOrDefaultAsync(p => p.Id == packagePurchase.PackageId && !p.IsDeleted, cancellationToken) ?? throw new NotFoundException("پکیج یافت نشد"); // بررسی موجودی با مبلغ پکیج واقعی @@ -194,11 +195,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler -public class ClubMembershipCycle : BaseAuditableEntity, IHasHistory +public class ClubMembershipCycle : BaseAuditableEntity + // , IHasHistory { /// /// شناسه کاربر @@ -83,20 +84,20 @@ public class ClubMembershipCycle : BaseAuditableEntity, IHasHistory - public History.ClubMembershipCycleHistory CreateHistorySnapshot(string action, string? performedBy) - { - return new History.ClubMembershipCycleHistory - { - ClubMembershipCycleId = Id, - UserId = UserId, - CycleNumber = CycleNumber, - NewIsCurrentCycle = IsCurrentCycle, - NewMagicStartedAt = MagicStartedAt, - NewMagicCompletedAt = MagicCompletedAt, - Action = Enum.TryParse(action, out var a) - ? a - : ClubMembershipCycleAction.ManualFix, - PerformedBy = performedBy - }; - } + // public History.ClubMembershipCycleHistory CreateHistorySnapshot(string action, string? performedBy) + // { + // return new History.ClubMembershipCycleHistory + // { + // ClubMembershipCycleId = Id, + // UserId = UserId, + // CycleNumber = CycleNumber, + // NewIsCurrentCycle = IsCurrentCycle, + // NewMagicStartedAt = MagicStartedAt, + // NewMagicCompletedAt = MagicCompletedAt, + // Action = Enum.TryParse(action, out var a) + // ? a + // : ClubMembershipCycleAction.ManualFix, + // PerformedBy = performedBy + // }; + // } } diff --git a/src/CMSMicroservice.Domain/Entities/Package.cs b/src/CMSMicroservice.Domain/Entities/Package.cs index 9bfbe0f..e584de0 100644 --- a/src/CMSMicroservice.Domain/Entities/Package.cs +++ b/src/CMSMicroservice.Domain/Entities/Package.cs @@ -6,7 +6,8 @@ namespace CMSMicroservice.Domain.Entities; /// /// پکیج — هر پکیج قیمت، ویژگی‌ها و تنظیمات مستقل دارد /// -public class Package : BaseAuditableEntity, IHasHistory +public class Package : BaseAuditableEntity + // , IHasHistory { // === فیلدهای فعلی (حفظ) === @@ -83,19 +84,19 @@ public class Package : BaseAuditableEntity, IHasHistory /// /// ساخت snapshot تاریخچه — Q27 auto-tracking /// - public History.PackageHistory CreateHistorySnapshot(string action, string? performedBy) - { - return new History.PackageHistory - { - PackageId = Id, - NewPrice = Price, - NewActivationFee = ActivationFee, - NewMagicMultiplier = MagicWalletMultiplier, - NewMagicMaxDeposit = MagicWalletMaxDeposit, - NewMaxBalancesPerLeg = MaxBalancesPerLeg, - NewIsActive = IsActive, - Action = Enum.TryParse(action, out var a) ? a : PackageAction.Updated, - PerformedBy = performedBy - }; - } + // public History.PackageHistory CreateHistorySnapshot(string action, string? performedBy) + // { + // return new History.PackageHistory + // { + // PackageId = Id, + // NewPrice = Price, + // NewActivationFee = ActivationFee, + // NewMagicMultiplier = MagicWalletMultiplier, + // NewMagicMaxDeposit = MagicWalletMaxDeposit, + // NewMaxBalancesPerLeg = MaxBalancesPerLeg, + // NewIsActive = IsActive, + // Action = Enum.TryParse(action, out var a) ? a : PackageAction.Updated, + // PerformedBy = performedBy + // }; + // } } diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 9f0ac68..040b722 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.189 + 0.0.190 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/discountorder.proto b/src/CMSMicroservice.Protobuf/Protos/discountorder.proto index 8de7f08..4edbcb8 100644 --- a/src/CMSMicroservice.Protobuf/Protos/discountorder.proto +++ b/src/CMSMicroservice.Protobuf/Protos/discountorder.proto @@ -54,6 +54,14 @@ service DiscountOrderContract get: "/GetDiscountSalesReport" }; }; + + // Customer: Verify discount order payment after gateway callback + rpc CustomerVerifyDiscountOrderPayment(CustomerVerifyDiscountOrderPaymentRequest) returns (CustomerVerifyDiscountOrderPaymentResponse){ + option (google.api.http) = { + post: "/CustomerVerifyDiscountOrderPayment" + body: "*" + }; + }; } // Place Order (Initial Step - Create Order) @@ -309,3 +317,19 @@ message TopSellingProductDto int64 total_revenue = 5; int32 orders_count = 6; } + +// ===== Customer Verify Discount Order Payment ===== + +message CustomerVerifyDiscountOrderPaymentRequest +{ + int64 order_id = 1; + string authority = 2; + string status = 3; +} + +message CustomerVerifyDiscountOrderPaymentResponse +{ + bool success = 1; + string message = 2; + int64 order_id = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto index dfb8593..353e1a7 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto @@ -89,6 +89,13 @@ service UserWalletContract }; }; + rpc VerifyMagicCharge(VerifyWalletChargeRequest) returns (VerifyWalletChargeResponse){ + option (google.api.http) = { + post: "/Customer/VerifyMagicCharge" + body: "*" + }; + }; + // ============= Discount Wallet Methods ============= rpc InitiateDiscountCharge(InitiateDiscountChargeRequest) returns (InitiateDiscountChargeResponse){ @@ -97,6 +104,12 @@ service UserWalletContract body: "*" }; }; + rpc VerifyDiscountCharge(VerifyWalletChargeRequest) returns (VerifyWalletChargeResponse){ + option (google.api.http) = { + post: "/Customer/VerifyDiscountCharge" + body: "*" + }; + }; } message CreateNewUserWalletRequest { @@ -267,4 +280,18 @@ message InitiateDiscountChargeResponse bool is_success = 1; string gateway_url = 2; string error_message = 3; +} + +// ============= Wallet Verify Messages ============= + +message VerifyWalletChargeRequest +{ + string authority = 1; + string status = 2; +} + +message VerifyWalletChargeResponse +{ + bool success = 1; + string message = 2; } \ No newline at end of file diff --git a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs deleted file mode 100644 index bac9e5e..0000000 --- a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs +++ /dev/null @@ -1,249 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment; -using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge; -using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.WebApi.Controllers; - -/// -/// Callback endpoint for payment gateways (ZarinPal, etc.) -/// درگاه پرداخت بعد از پرداخت (یا لغو) کاربر را به اینجا redirect می‌کند -/// -[ApiController] -[AllowAnonymous] // کاربر از درگاه بانک برمی‌گردد — JWT ندارد -[ApiExplorerSettings(GroupName = "cms")] -public class PaymentCallbackController : ControllerBase -{ - private readonly ISender _sender; - private readonly IPaymentGatewayService _paymentGateway; - private readonly IApplicationDbContext _context; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - - public PaymentCallbackController( - ISender sender, - IPaymentGatewayService paymentGateway, - IApplicationDbContext context, - IConfiguration configuration, - ILogger logger) - { - _sender = sender; - _paymentGateway = paymentGateway; - _context = context; - _configuration = configuration; - _logger = logger; - } - - /// - /// Callback برای پرداخت سفارش فروشگاه تخفیفی - /// زرین‌پال کاربر را با Authority و Status به این endpoint برمی‌گرداند - /// - [HttpGet("/api/payment/discount-order/callback")] - public async Task DiscountOrderCallback( - [FromQuery] long orderId, - [FromQuery(Name = "Authority")] string? authority, - [FromQuery(Name = "Status")] string? status, - CancellationToken cancellationToken) - { - var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; - - _logger.LogInformation( - "Payment callback received: OrderId={OrderId}, Authority={Authority}, Status={Status}", - orderId, authority, status); - - try - { - // پیدا کردن سفارش و تراکنش - var order = await _context.DiscountOrders - .Include(o => o.OrderDetails) - .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); - - if (order == null) - { - _logger.LogError("Payment callback: Order #{OrderId} not found", orderId); - return Redirect($"{frontOfficeBaseUrl}/discount-store/orders?error=order-not-found"); - } - - var transaction = order.TransactionId.HasValue - ? await _context.Transactions.FirstOrDefaultAsync( - t => t.Id == order.TransactionId.Value, cancellationToken) - : null; - - // تأیید پرداخت از درگاه - bool paymentSuccess = false; - string? refId = null; - - if (string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase) - && !string.IsNullOrEmpty(authority)) - { - // Verify با مبلغ از دیتابیس (تومان — تبدیل به ریال در ZarinPalService) - var verifyResult = await _paymentGateway.VerifyPaymentAsync( - authority, - status!, - order.GatewayAmountPaid, // مبلغ به تومان - cancellationToken); - - paymentSuccess = verifyResult.IsSuccess; - refId = verifyResult.TrackingCode ?? verifyResult.RefId; - - // آپدیت PaymentTransaction با نتیجه verify - var paymentTx = await _context.PaymentTransactions - .FirstOrDefaultAsync(pt => pt.Authority == authority, cancellationToken); - if (paymentTx != null) - { - paymentTx.PaymentStatus = verifyResult.IsSuccess; - paymentTx.VerificationStatusCode = verifyResult.VerificationCode; - paymentTx.VerificationStatusMessage = verifyResult.Message; - paymentTx.CardPan = verifyResult.CardPan; - paymentTx.CardHash = verifyResult.CardHash; - paymentTx.RefId = verifyResult.TrackingCode; - await _context.SaveChangesAsync(cancellationToken); - } - - _logger.LogInformation( - "Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}, Message={Message}", - orderId, paymentSuccess, refId, verifyResult.Message); - } - else - { - _logger.LogWarning("Payment cancelled by user for Order #{OrderId}", orderId); - } - - // تکمیل سفارش از طریق CQRS - var completeResult = await _sender.Send(new CompleteOrderPaymentCommand - { - OrderId = orderId, - TransactionId = transaction?.Id ?? 0, - PaymentSuccess = paymentSuccess, - RefId = refId - }, cancellationToken); - - // Redirect به FrontOffice - if (paymentSuccess && completeResult.Success) - { - _logger.LogInformation("Payment completed successfully for Order #{OrderId}", orderId); - return Redirect( - $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=success"); - } - else - { - _logger.LogWarning("Payment failed for Order #{OrderId}", orderId); - return Redirect( - $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=failed"); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Payment callback error for Order #{OrderId}", orderId); - return Redirect( - $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=error"); - } - } - - /// - /// Callback برای شارژ کیف‌پول جادویی — زرین‌پال بعد از پرداخت کاربر را اینجا برمی‌گرداند - /// - [HttpGet("/api/wallet/verify-magic-charge")] - public async Task MagicChargeCallback( - [FromQuery(Name = "Authority")] string? authority, - [FromQuery(Name = "Status")] string? status, - CancellationToken cancellationToken) - { - var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; - - _logger.LogInformation( - "Magic charge callback received: Authority={Authority}, Status={Status}", - authority, status); - - try - { - if (string.IsNullOrEmpty(authority)) - { - _logger.LogError("Magic charge callback: Authority is missing"); - return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=error&reason=no-authority"); - } - - var result = await _sender.Send(new VerifyMagicWalletChargeCommand - { - Authority = authority, - Status = status ?? "NOK" - }, cancellationToken); - - _logger.LogInformation( - "Magic charge completed successfully. Authority={Authority}", - authority); - - return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=success"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Magic charge callback error. Authority={Authority}", authority); - return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=failed"); - } - } - - /// - /// Callback برای شارژ کیف‌پول تخفیفی — زرین‌پال بعد از پرداخت کاربر را اینجا برمی‌گرداند - /// - [HttpGet("/api/wallet/verify-discount-charge")] - public async Task DiscountChargeCallback( - [FromQuery(Name = "Authority")] string? authority, - [FromQuery(Name = "Status")] string? status, - CancellationToken cancellationToken) - { - var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; - - _logger.LogInformation( - "Discount charge callback received: Authority={Authority}, Status={Status}", - authority, status); - - try - { - if (string.IsNullOrEmpty(authority)) - { - _logger.LogError("Discount charge callback: Authority is missing"); - return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=no-authority"); - } - - // پیدا کردن PaymentTransaction برای استخراج UserId و Amount - var paymentTx = await _context.PaymentTransactions - .FirstOrDefaultAsync(pt => pt.Authority == authority, cancellationToken); - - if (paymentTx == null || !paymentTx.UserId.HasValue) - { - _logger.LogError("Discount charge callback: PaymentTransaction not found for Authority={Authority}", authority); - return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=tx-not-found"); - } - - if (!string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase)) - { - _logger.LogWarning("Discount charge cancelled by user. Authority={Authority}", authority); - return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=cancelled"); - } - - var result = await _sender.Send(new VerifyDiscountWalletChargeCommand - { - UserId = paymentTx.UserId.Value, - Amount = paymentTx.Amount, - Authority = authority - }, cancellationToken); - - _logger.LogInformation( - "Discount charge completed successfully. Authority={Authority}, UserId={UserId}", - authority, paymentTx.UserId.Value); - - return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=success"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Discount charge callback error. Authority={Authority}", authority); - return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=failed"); - } - } -} diff --git a/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs b/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs index 2fea3a3..4a37621 100644 --- a/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs @@ -13,6 +13,8 @@ using Grpc.Core; using Google.Protobuf.WellKnownTypes; using Mapster; using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace CMSMicroservice.WebApi.Services; @@ -21,15 +23,24 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; private readonly ISender _sender; private readonly ICurrentUserService _currentUserService; + private readonly IApplicationDbContext _context; + private readonly IPaymentGatewayService _paymentGateway; + private readonly ILogger _logger; public DiscountOrderService( IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender, - ICurrentUserService currentUserService) + ICurrentUserService currentUserService, + IApplicationDbContext context, + IPaymentGatewayService paymentGateway, + ILogger logger) { _dispatchRequestToCQRS = dispatchRequestToCQRS; _sender = sender; _currentUserService = currentUserService; + _context = context; + _paymentGateway = paymentGateway; + _logger = logger; } private long GetCurrentUserId() @@ -174,6 +185,108 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB return await _dispatchRequestToCQRS.Handle(request, context); } + // ============= Customer Verify Discount Order Payment ============= + + public override async Task CustomerVerifyDiscountOrderPayment( + CustomerVerifyDiscountOrderPaymentRequest request, ServerCallContext context) + { + _logger.LogInformation( + "CustomerVerifyDiscountOrderPayment called: OrderId={OrderId}, Authority={Authority}, Status={Status}", + request.OrderId, request.Authority, request.Status); + + try + { + // پیدا کردن سفارش و تراکنش + var order = await _context.DiscountOrders + .Include(o => o.OrderDetails) + .FirstOrDefaultAsync(o => o.Id == request.OrderId, context.CancellationToken); + + if (order == null) + { + _logger.LogError("Order #{OrderId} not found", request.OrderId); + return new CustomerVerifyDiscountOrderPaymentResponse + { + Success = false, + Message = "سفارش یافت نشد", + OrderId = request.OrderId + }; + } + + var transaction = order.TransactionId.HasValue + ? await _context.Transactions.FirstOrDefaultAsync( + t => t.Id == order.TransactionId.Value, context.CancellationToken) + : null; + + // تأیید پرداخت از درگاه + bool paymentSuccess = false; + string? refId = null; + + if (string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(request.Authority)) + { + // Verify با مبلغ از دیتابیس (تومان — تبدیل به ریال در ZarinPalService) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, + request.Status, + order.GatewayAmountPaid, + context.CancellationToken); + + paymentSuccess = verifyResult.IsSuccess; + refId = verifyResult.TrackingCode ?? verifyResult.RefId; + + // آپدیت PaymentTransaction با نتیجه verify + var paymentTx = await _context.PaymentTransactions + .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken); + if (paymentTx != null) + { + paymentTx.PaymentStatus = verifyResult.IsSuccess; + paymentTx.VerificationStatusCode = verifyResult.VerificationCode; + paymentTx.VerificationStatusMessage = verifyResult.Message; + paymentTx.CardPan = verifyResult.CardPan; + paymentTx.CardHash = verifyResult.CardHash; + paymentTx.RefId = verifyResult.TrackingCode; + await _context.SaveChangesAsync(context.CancellationToken); + } + + _logger.LogInformation( + "Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}", + request.OrderId, paymentSuccess, refId); + } + else + { + _logger.LogWarning("Payment cancelled by user for Order #{OrderId}", request.OrderId); + } + + // تکمیل سفارش از طریق CQRS + var completeResult = await _sender.Send(new CompleteOrderPaymentCommand + { + OrderId = request.OrderId, + TransactionId = transaction?.Id ?? 0, + PaymentSuccess = paymentSuccess, + RefId = refId + }, context.CancellationToken); + + return new CustomerVerifyDiscountOrderPaymentResponse + { + Success = paymentSuccess && completeResult.Success, + Message = paymentSuccess && completeResult.Success + ? "پرداخت سفارش با موفقیت انجام شد" + : "پرداخت سفارش ناموفق بود", + OrderId = request.OrderId + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Payment verify error for Order #{OrderId}", request.OrderId); + return new CustomerVerifyDiscountOrderPaymentResponse + { + Success = false, + Message = $"خطا در بررسی پرداخت: {ex.Message}", + OrderId = request.OrderId + }; + } + } + private static CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus MapPaymentStatus(DomainEnums.PaymentStatus status) => status switch { DomainEnums.PaymentStatus.Success => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentCompleted, diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index c279901..3cbae2f 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -351,6 +351,24 @@ public class PackageService : PackageContract.PackageContractBase await _context.SaveChangesAsync(context.CancellationToken); + // فعالسازی خودکار باشگاه مشتریان بعد از تأیید پرداخت موفق + if (verifyResult.IsSuccess) + { + try + { + await _sender.Send(new CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership.ActivateClubMembershipCommand + { + UserId = purchase.UserId, + ForceActivation = false + }, context.CancellationToken); + } + catch (Exception ex) + { + // لاگ خطا ولی پرداخت موفق بوده — کاربر می‌تونه دستی فعالسازی کنه + System.Console.WriteLine($"Auto club activation failed for UserId {purchase.UserId}: {ex.Message}"); + } + } + return new CustomerVerifyPackagePurchaseResponse { Success = verifyResult.IsSuccess, diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index 38681c7..ee43e6a 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -604,7 +604,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase }; } - [RequiresPermission(PermissionNames.OrdersView)] + // [RequiresPermission(PermissionNames.OrdersView)] public override async Task CalculateOrderPV(CalculateOrderPVRequest request, ServerCallContext context) { var order = await _context.UserOrders diff --git a/src/CMSMicroservice.WebApi/Services/UserService.cs b/src/CMSMicroservice.WebApi/Services/UserService.cs index 72d0f53..d27f2a5 100644 --- a/src/CMSMicroservice.WebApi/Services/UserService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserService.cs @@ -35,6 +35,7 @@ public class UserService : UserContract.UserContractBase private readonly ICurrentUserService _currentUserService; private readonly IHashService _hashService; private readonly CMSMicroservice.Application.Common.FileManager.IFileManager _fileManager; + private readonly IGenerateJwtToken _generateJwt; public UserService( IDispatchRequestToCQRS dispatchRequestToCQRS, @@ -42,7 +43,8 @@ public class UserService : UserContract.UserContractBase IApplicationDbContext context, ICurrentUserService currentUserService, IHashService hashService, - CMSMicroservice.Application.Common.FileManager.IFileManager fileManager) + CMSMicroservice.Application.Common.FileManager.IFileManager fileManager, + IGenerateJwtToken generateJwt) { _dispatchRequestToCQRS = dispatchRequestToCQRS; _sender = sender; @@ -50,6 +52,7 @@ public class UserService : UserContract.UserContractBase _currentUserService = currentUserService; _hashService = hashService; _fileManager = fileManager; + _generateJwt = generateJwt; } public override async Task CreateNewUser(CreateNewUserRequest request, ServerCallContext context) { @@ -115,12 +118,20 @@ public class UserService : UserContract.UserContractBase var user = await _context.Users .AsNoTracking() + .Include(u => u.UserContracts) + .ThenInclude(uc => uc.Contract) + .Include(u => u.UserRoles) + .ThenInclude(ur => ur.Role) + .Include(u => u.ClubMembership) .Where(u => u.Id == userId && !u.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (user == null) throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد")); + // تولید توکن JWT با آخرین اطلاعات کاربر + var token = await _generateJwt.GenerateJwtToken(user); + return new GetUserForCustomerResponse { Id = user.Id, @@ -141,7 +152,8 @@ public class UserService : UserContract.UserContractBase PushNotifications = user.PushNotifications, BirthDate = user.BirthDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(user.BirthDate.Value, DateTimeKind.Utc)) - : null + : null, + Token = token }; } diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs index 96594c9..3f0e015 100644 --- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs @@ -5,6 +5,8 @@ using CMSMicroservice.Application.UserWalletCQ.Commands.UpdateUserWallet; using CMSMicroservice.Application.UserWalletCQ.Commands.DeleteUserWallet; using CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet; using CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet; +using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge; +using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge; using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory; @@ -16,6 +18,7 @@ using CMSMicroservice.Domain.Enums; using Grpc.Core; using Google.Protobuf.WellKnownTypes; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using System.Linq; namespace CMSMicroservice.WebApi.Services; @@ -25,17 +28,20 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase private readonly ISender _sender; private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUserService; + private readonly ILogger _logger; public UserWalletService( IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender, IApplicationDbContext context, - ICurrentUserService currentUserService) + ICurrentUserService currentUserService, + ILogger logger) { _dispatchRequestToCQRS = dispatchRequestToCQRS; _sender = sender; _context = context; _currentUserService = currentUserService; + _logger = logger; } public override async Task CreateNewUserWallet(CreateNewUserWalletRequest request, ServerCallContext context) { @@ -213,6 +219,94 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase }; } + // ============= Wallet Verify Methods ============= + + public override async Task VerifyMagicCharge( + VerifyWalletChargeRequest request, ServerCallContext context) + { + _logger.LogInformation("VerifyMagicCharge called: Authority={Authority}, Status={Status}", + request.Authority, request.Status); + + try + { + if (string.IsNullOrEmpty(request.Authority)) + return new VerifyWalletChargeResponse { Success = false, Message = "کد Authority نامعتبر است" }; + + var result = await _sender.Send(new VerifyMagicWalletChargeCommand + { + Authority = request.Authority, + Status = request.Status ?? "NOK" + }, context.CancellationToken); + + _logger.LogInformation("VerifyMagicCharge result: {Result}, Authority={Authority}", result, request.Authority); + + return new VerifyWalletChargeResponse + { + Success = result, + Message = result + ? "شارژ کیف‌پول جادویی با موفقیت انجام شد" + : "شارژ کیف‌پول جادویی ناموفق بود" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "VerifyMagicCharge error: Authority={Authority}", request.Authority); + return new VerifyWalletChargeResponse { Success = false, Message = ex.Message }; + } + } + + public override async Task VerifyDiscountCharge( + VerifyWalletChargeRequest request, ServerCallContext context) + { + _logger.LogInformation("VerifyDiscountCharge called: Authority={Authority}, Status={Status}", + request.Authority, request.Status); + + try + { + if (string.IsNullOrEmpty(request.Authority)) + return new VerifyWalletChargeResponse { Success = false, Message = "کد Authority نامعتبر است" }; + + // پیدا کردن PaymentTransaction برای استخراج UserId و Amount + var paymentTx = await _context.PaymentTransactions + .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken); + + if (paymentTx == null || !paymentTx.UserId.HasValue) + { + _logger.LogError("VerifyDiscountCharge: PaymentTransaction not found for Authority={Authority}", request.Authority); + return new VerifyWalletChargeResponse { Success = false, Message = "تراکنش یافت نشد" }; + } + + if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning("VerifyDiscountCharge: Payment cancelled by user. Authority={Authority}", request.Authority); + return new VerifyWalletChargeResponse { Success = false, Message = "پرداخت توسط کاربر لغو شد" }; + } + + var result = await _sender.Send(new VerifyDiscountWalletChargeCommand + { + UserId = paymentTx.UserId.Value, + Amount = paymentTx.Amount, + Authority = request.Authority + }, context.CancellationToken); + + _logger.LogInformation("VerifyDiscountCharge result: {Result}, Authority={Authority}, UserId={UserId}", + result, request.Authority, paymentTx.UserId.Value); + + return new VerifyWalletChargeResponse + { + Success = result, + Message = result + ? "شارژ کیف پول تخفیفی با موفقیت انجام شد" + : "شارژ کیف پول تخفیفی ناموفق بود" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "VerifyDiscountCharge error: Authority={Authority}", request.Authority); + return new VerifyWalletChargeResponse { Success = false, Message = ex.Message }; + } + } + public override async Task GetMagicWalletStatus( Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) { diff --git a/src/CMSMicroservice.WebApi/appsettings.Development.json b/src/CMSMicroservice.WebApi/appsettings.Development.json deleted file mode 100644 index df4f16b..0000000 --- a/src/CMSMicroservice.WebApi/appsettings.Development.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "CmsBaseUrl": "https://localhost:32846", - "FrontOfficeBaseUrl": "http://localhost:5268", - "ZarinPal": { - "UseSandbox": true - }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index 13c0ffe..d4f558b 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -4,8 +4,8 @@ "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": true }, - "CmsBaseUrl": "https://cms.se.kbs1.ir", - "FrontOfficeBaseUrl": "https://foursat.se.kbs1.ir", + "CmsBaseUrl": "https://localhost:32846", + "FrontOfficeBaseUrl": "http://localhost:5268", "FMS": { "Address": "https://dl.afrino.co" }, From 1a0bb83c99e16e8dfd438c54e0dfa9c12a433b2c Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sat, 28 Feb 2026 04:01:45 +0330 Subject: [PATCH 44/44] feat: Enhance SMS verification with new token handling and update API key --- .../Common/Interfaces/IKavenegarService.cs | 7 +++- .../CreateNewOtpTokenEventHandler.cs | 6 +-- .../CreateNewOtpTokenCommandHandler.cs | 5 +-- .../Services/KavenegarService.cs | 39 ++++++++++++++++++- .../appsettings.Staging.json | 2 +- src/CMSMicroservice.WebApi/appsettings.json | 2 +- 6 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IKavenegarService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IKavenegarService.cs index a8982bc..b0d841d 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IKavenegarService.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IKavenegarService.cs @@ -13,5 +13,10 @@ public interface IKavenegarService /// /// ارسال پیامک با قالب (VerifyLookup) /// - Task VerifyLookupAsync(string mobile, string token, string template = "Afrino"); + Task VerifyLookupAsync(string mobile, string token, string template = "OTP"); + + /// + /// ارسال پیامک با قالب و توکن دوم (VerifyLookup) — مثلاً برای قراردادها + /// + Task VerifyLookupAsync(string mobile, string token, string token2, string template); } diff --git a/src/CMSMicroservice.Application/OtpTokenCQ/EventHandlers/CreateNewOtpTokenEventHandlers/CreateNewOtpTokenEventHandler.cs b/src/CMSMicroservice.Application/OtpTokenCQ/EventHandlers/CreateNewOtpTokenEventHandlers/CreateNewOtpTokenEventHandler.cs index 9a571c4..e695a18 100644 --- a/src/CMSMicroservice.Application/OtpTokenCQ/EventHandlers/CreateNewOtpTokenEventHandlers/CreateNewOtpTokenEventHandler.cs +++ b/src/CMSMicroservice.Application/OtpTokenCQ/EventHandlers/CreateNewOtpTokenEventHandlers/CreateNewOtpTokenEventHandler.cs @@ -26,12 +26,12 @@ public class CreateNewOtpTokenEventHandler : INotificationHandler - public async Task VerifyLookupAsync(string mobile, string token, string template = "Afrino") + public async Task VerifyLookupAsync(string mobile, string token, string template = "OTP") { if (!_smsSettings.Enabled) { @@ -106,4 +106,41 @@ public class KavenegarService : IKavenegarService throw; } } + + /// + public async Task VerifyLookupAsync(string mobile, string token, string token2, string template) + { + if (!_smsSettings.Enabled) + { + _logger.LogInformation("SMS is disabled. Skipping VerifyLookup to {Mobile}", mobile); + return; + } + + if (_kavenegarApi == null) + { + _logger.LogWarning("⚠️ Kavenegar API not initialized, cannot send VerifyLookup"); + return; + } + + try + { + await Task.Run(() => + { + var result = _kavenegarApi.VerifyLookup( + receptor: mobile, + token: token, + token2: token2, + token3: "", + template: template); + + _logger.LogInformation("📱 VerifyLookup SMS sent to {Mobile} with template {Template} (token2), MessageId: {MessageId}", + mobile, template, result.Messageid); + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "❌ Kavenegar error sending VerifyLookup to {Mobile}: {Message}", mobile, ex.Message); + throw; + } + } } diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index c8e1395..470741b 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -44,7 +44,7 @@ "Sms": { "Enabled": true, "Provider": "Kavenegar", - "KavenegarApiKey": "497263626F32626A48685A6137524C4F78575A766E4C74694A556B79317648424964655030682B554545413D", + "KavenegarApiKey": "43676A4E786A6C50452F2F6252507939346A5145764B33566B456454374E657A614468706658656D6534593D", "Sender": "1000001110100" }, "DayaPayment": { diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index d4f558b..29d144a 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -51,7 +51,7 @@ "Sms": { "Enabled": true, "Provider": "Kavenegar", - "KavenegarApiKey": "497263626F32626A48685A6137524C4F78575A766E4C74694A556B79317648424964655030682B554545413D", + "KavenegarApiKey": "43676A4E786A6C50452F2F6252507939346A5145764B33566B456454374E657A614468706658656D6534593D", "Sender": "1000001110100" }, "DayaPayment": {