diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index 95488cc..8ba2a3f 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -6,81 +6,88 @@ on: - kub-stage env: - REGISTRY: git.foursat.afrino.co + REGISTRY: 194.5.195.53:30080 IMAGE_NAME: admin/cms + K8S_SERVER: 194.5.195.53 jobs: build-and-deploy: runs-on: ubuntu-latest container: - image: docker:latest + image: 194.5.195.53:32082/docker-sshpass:latest options: --privileged - env: - HTTP_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128 - HTTPS_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128 - NO_PROXY: localhost,127.0.0.1,gitea-svc,194.5.195.53,10.0.0.0/8 steps: - - name: Install dependencies - run: | - apk add --no-cache git curl - - # Install kubectl - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" - chmod +x kubectl - mv kubectl /usr/local/bin/ - - - name: Start Docker daemon with insecure registry + - name: Start Docker daemon run: | mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { - "insecure-registries": ["git.foursat.afrino.co", "gitea-svc:3000"] + "insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] } DAEMON - mkdir -p ~/.docker - cat > ~/.docker/config.json << 'CONF' - { - "proxies": { - "default": { - "httpProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128", - "httpsProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128", - "noProxy": "localhost,127.0.0.1,gitea-svc,194.5.195.53,10.0.0.0/8" - } - } - } - CONF - dockerd & - for i in $(seq 1 30); do - docker info >/dev/null 2>&1 && break || sleep 2 + echo "🚀 Starting Docker daemon..." + dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & + + # Wait up to 3 minutes for Docker to be ready + for i in $(seq 1 90); do + if docker info >/dev/null 2>&1; then + echo "✅ Docker daemon is ready (attempt $i)" + docker version + break + else + echo "⏳ Waiting for Docker daemon... (attempt $i/90)" + sleep 2 + fi done - docker info - + + # Final check + if ! docker info >/dev/null 2>&1; then + echo "❌ Docker daemon failed to start after 3 minutes" + exit 1 + fi + - name: Checkout code run: | git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/CMS.git . - git log -1 --format="%H %s" + + - name: Login to Docker registries + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin + + - name: Publish Protobuf packages + run: | + echo "📦 Publishing Protobuf packages..." + docker run --rm --network host -v $(pwd):/src -w /src \ + 194.5.195.53:32082/dotnet/sdk:9.0 sh -c ' + for proj in $(find . -name "*Protobuf*.csproj" -type f); do + echo "📦 $proj" + dotnet restore "$proj" --configfile src/NuGet.config + dotnet build "$proj" -c Release --no-restore + dotnet pack "$proj" -c Release --no-build -o "$(dirname $proj)/nupkg" + for nupkg in $(dirname $proj)/nupkg/*.nupkg; do + [ -f "$nupkg" ] && dotnet nuget push "$nupkg" \ + --source "http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json" \ + --api-key "admin:87zH26nbqT" \ + --skip-duplicate --allow-insecure-connections || true + done + done + ' + echo "✅ Protobuf packages done!" + - name: Build Docker Image run: | - docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ - -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ - --build-arg HTTP_PROXY=http://proxyuser:87zH26nbqT2@46.249.98.211:3128 \ - --build-arg HTTPS_PROXY=http://proxyuser:87zH26nbqT2@46.249.98.211:3128 \ - . + DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest . - name: Push to Registry run: | - echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin - docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - + - name: Deploy to Kubernetes run: | - # Setup kubeconfig - mkdir -p ~/.kube - echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config - - # Restart deployment to pull new image - kubectl rollout restart deployment/cms || echo "Deployment doesn't exist yet" - - # Wait for rollout to complete - kubectl rollout status deployment/cms --timeout=5m || echo "Deployment rollout pending" + export SSHPASS="${{ secrets.SERVER_PASSWORD }}" + sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl rollout restart deployment/cms + kubectl rollout status deployment/cms --timeout=180s + " + echo "✅ Deployed!" diff --git a/Dockerfile b/Dockerfile index 96b8d02..68dcb91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY src/NuGet.config ./ # Copy solution and project files COPY src/ ./ -# Restore and publish +# Restore with Nexus config and publish RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" --configfile NuGet.config RUN dotnet publish "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" -c Release -o /app/publish --no-restore diff --git a/README.md b/README.md index 432e77f..829ad70 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,92 @@ -# CMS Microservice - Network & Club Commission System +# CMS Microservice - Network & Club Commission + Inventory Management System -[![Status](https://img.shields.io/badge/Status-Production%20Ready-success)]() -[![Progress](https://img.shields.io/badge/Progress-85%25-blue)]() -[![MVP](https://img.shields.io/badge/MVP-100%25%20Complete-brightgreen)]() +[![Status](https://img.shields.io/badge/Status-Active%20Development-success)]() +[![Progress](https://img.shields.io/badge/Inventory%20System-Phase%202%20Complete-blue)]() +[![Phase](https://img.shields.io/badge/Next-Business%20Services-orange)]() -## 📊 Project Status (2025-12-01) +## 📊 Project Status (January 2026) -**Overall Progress**: 85% Complete (7/10 phases) -**Production Readiness**: 95% +### 🏪 Inventory Management System - NEW! +**Progress**: Phase 2 Complete (50%) +**Architecture**: Clean Architecture + CQRS + Repository Pattern + +#### ✅ Completed Phases +1. ✅ **Phase 1: Infrastructure & Domain Layer** + - Domain Entities: `InventoryItem`, `StockMovement`, `Warehouse` + - Domain Enums: `StockMovementType` + - EF Core Configurations with proper indexing + - Database migration applied + +2. ✅ **Phase 2: Repository Pattern & CQRS** + - Repository Interfaces & Implementations + - CQRS Commands (17 commands) + - CQRS Queries (35 queries) + - MediatR Handlers (52 handlers) + +#### 🔄 In Progress +3. 🔄 **Phase 3: Business Services Layer** +4. ⏳ **Phase 4: DTOs & AutoMapper** +5. ⏳ **Phase 5: API Controllers** + +--- + +### 💼 Commission System - Production Ready +**Progress**: 85% Complete **MVP Status**: ✅ 100% Complete -### ✅ Completed Phases (7) -1. ✅ Domain Layer (Entities, Enums, Value Objects) -2. ✅ Club Membership System -3. ✅ Binary Network Tree -4. ✅ **Commission Calculation & Background Worker** (MVP) -5. ✅ Protobuf gRPC Services -6. ✅ History & Configuration Management -7. ✅ Database Migration & Seed Data +#### ✅ Completed Features +- ✅ Binary network tree with automatic placement +- ✅ Club membership (Member/Trial) with commission rates +- ✅ Weekly commission calculation (Lesser Leg algorithm) +- ✅ Background worker with Hangfire +- ✅ Email + SMS notifications (MailKit + Kavenegar) +- ✅ Health check endpoints (Kubernetes-ready) -### 🟡 Partially Complete (1) +### 🟡 Partially Complete - Phase 10: Withdrawal & Settlement (40%) - ✅ Commands & Database - ❌ Payment Gateway Integration -### ❌ Not Started (1) +### ❌ Not Started - Phase 9: Club Shop & Product Integration (0%) -### ⏸️ Postponed (1) -- Phase 7: Testing (Unit, Integration, Load tests) - --- -## 🚀 Recent Updates (2025-12-01) +## 🚀 Recent Updates (January 2026) + +### 🏪 Inventory Management System - NEW! ✅ +**Complete CQRS-based inventory management with:** + +#### Domain Layer: +- ✅ `InventoryItem` - Multi-warehouse product tracking with min/max thresholds +- ✅ `StockMovement` - Complete audit trail with 8 movement types +- ✅ `Warehouse` - Multi-location support with default warehouse + +#### Repository Pattern: +- ✅ `IInventoryItemRepository` - 25+ methods for inventory operations +- ✅ `IStockMovementRepository` - Movement tracking & analytics +- ✅ `IWarehouseRepository` - Warehouse management & statistics + +#### CQRS Commands (17 total): +- **Inventory:** Create, Update, Delete, Reserve, Release, Reduce, Increase +- **Movement:** Create, BulkCreate, Delete +- **Warehouse:** Create, Update, Delete, SetDefault, Activate, BulkCreate + +#### CQRS Queries (35 total): +- **Inventory:** GetById, Search, LowStock, OutOfStock, CheckAvailability +- **Movement:** GetHistory, GetByOrder, Search, Analytics, DailyVolume, TopMoving +- **Warehouse:** GetById, Search, GetStats, GetLowStock, GetAllStats + +#### Business Features: +- ✅ Multi-warehouse inventory management +- ✅ Stock reservation system for orders +- ✅ Automatic movement tracking +- ✅ Low stock & out-of-stock alerts +- ✅ Advanced analytics & reporting +- ✅ Bulk operations support +- ✅ Transaction-safe operations + +--- ### Email & SMS Notifications - COMPLETED ✅ - ✅ **MailKit 4.14.1** for Email (SMTP with HTML templates) @@ -63,8 +117,38 @@ **Clean Architecture** with 4 layers: ``` CMSMicroservice.Domain/ # Entities, Enums, Interfaces +├── Entities/ +│ ├── InventoryItem.cs # NEW: Inventory tracking +│ ├── StockMovement.cs # NEW: Movement audit +│ └── Warehouse.cs # NEW: Multi-warehouse +├── Enums/ +│ └── StockMovementType.cs # NEW: Movement types + CMSMicroservice.Application/ # CQRS (Commands, Queries, MediatR) +├── Features/ +│ ├── InventoryItems/ # NEW: Inventory CQRS +│ │ ├── Commands/ +│ │ ├── Queries/ +│ │ └── Handlers/ +│ ├── StockMovements/ # NEW: Movement CQRS +│ │ ├── Commands/ +│ │ ├── Queries/ +│ │ └── Handlers/ +│ └── Warehouses/ # NEW: Warehouse CQRS +│ ├── Commands/ +│ ├── Queries/ +│ └── Handlers/ +└── Common/Interfaces/ + └── Repositories/ # NEW: Repository interfaces + CMSMicroservice.Infrastructure/ # DbContext, Services, Background Jobs +├── Persistence/ +│ ├── Context/ +│ ├── Configurations/ # NEW: EF Core configs +│ ├── Repositories/ # NEW: Repository implementations +│ └── Migrations/ +└── DependencyInjection.cs # NEW: DI setup + CMSMicroservice.WebApi/ # gRPC Services, Controllers CMSMicroservice.Protobuf/ # Protocol Buffers definitions ``` @@ -84,6 +168,7 @@ CMSMicroservice.Protobuf/ # Protocol Buffers definitions ## 📖 Documentation +- **[Development Plan](docs/development-plan.md)** - NEW: Inventory system roadmap - **[Implementation Progress](docs/implementation-progress.md)** - Detailed phase-by-phase progress - **[Email/SMS Configuration Guide](docs/email-sms-configuration-guide.md)** - Production setup instructions - **[Balance Calculation Logic](docs/balance-calculation-carryover-logic.md)** - Commission algorithm details @@ -92,6 +177,74 @@ CMSMicroservice.Protobuf/ # Protocol Buffers definitions --- +## 🏪 Inventory System Usage + +### Create Warehouse +```csharp +await mediator.Send(new CreateWarehouseCommand +{ + Name = "Main Warehouse", + Code = "WH-001", + IsDefault = true, + IsActive = true +}); +``` + +### Create Inventory Item +```csharp +await mediator.Send(new CreateInventoryItemCommand +{ + ProductId = 1, + WarehouseId = 1, + Quantity = 100, + MinQuantity = 10, + MaxQuantity = 1000 +}); +``` + +### Reserve Stock for Order +```csharp +await mediator.Send(new ReserveInventoryCommand +{ + Id = inventoryId, + Quantity = 5, + OrderId = 12345 +}); +``` + +### Check Availability +```csharp +bool available = await mediator.Send( + new CheckInventoryAvailabilityQuery(inventoryId, 10)); +``` + +### Get Low Stock Alerts +```csharp +var lowStock = await mediator.Send(new GetLowStockItemsQuery +{ + WarehouseId = 1, + Count = 50 +}); +``` + +### Get Movement Analytics +```csharp +var summary = await mediator.Send(new GetMovementSummaryQuery +{ + FromDate = DateTime.Now.AddDays(-7), + ToDate = DateTime.Now +}); + +var topProducts = await mediator.Send(new GetTopMovingProductsQuery +{ + FromDate = DateTime.Now.AddDays(-30), + ToDate = DateTime.Now, + Count = 10 +}); +``` + +--- + ## 🚀 Quick Start ### Prerequisites @@ -197,7 +350,25 @@ curl http://localhost:5133/health/live # Liveness probe (K8s) ## 📊 What's Remaining? -### High Priority +### 🏪 Inventory System (Current Focus) +1. **Phase 3: Business Services** (In Progress) + - `IInventoryManagementService` - High-level operations + - `IStockMovementService` - Movement orchestration + - `IWarehouseService` - Warehouse business logic + - `IInventoryReportingService` - Advanced reporting + +2. **Phase 4: DTOs & AutoMapper** (Next) + - Request/Response DTOs + - AutoMapper profiles + - Validation rules + +3. **Phase 5: API Controllers** (Planned) + - `InventoryController` - REST API + - `WarehouseController` - Warehouse management + - `StockMovementController` - Movement tracking + - Swagger documentation + +### 💼 Commission System 1. **Payment Gateway Integration** (Phase 10 - 1 week) - Daya or Bank Mellat API integration - IBAN transfer automation @@ -230,6 +401,7 @@ curl http://localhost:5133/health/live # Liveness probe (K8s) ## 🎯 MVP Features (100% Complete) +### 💼 Commission System: ✅ Binary network tree with automatic placement ✅ Club membership (Member/Trial) with different commission rates ✅ Weekly commission calculation (Lesser Leg algorithm) @@ -244,12 +416,26 @@ curl http://localhost:5133/health/live # Liveness probe (K8s) ✅ Structured logging (AlertService for Sentry/Slack) ✅ JWT authentication context (CurrentUserService) +### 🏪 Inventory System (Phase 2 Complete): +✅ Domain entities (InventoryItem, StockMovement, Warehouse) +✅ Multi-warehouse inventory management +✅ Stock reservation system for orders +✅ 8 movement types with complete audit trail +✅ Repository pattern with 25+ methods per repository +✅ CQRS with 17 commands and 35 queries +✅ 52 MediatR handlers with business logic +✅ Low stock and out-of-stock alerts +✅ Advanced analytics (top products, daily volume) +✅ Bulk operations support +✅ Transaction-safe operations with rollback +✅ DI container configuration + --- ## 👥 Team **Development**: FourSat Team -**Last Updated**: 2025-12-01 +**Last Updated**: January 2026 --- diff --git a/docs/MOVED-TO-TOTALDOC.md b/docs/MOVED-TO-TOTALDOC.md new file mode 100644 index 0000000..8e394a8 --- /dev/null +++ b/docs/MOVED-TO-TOTALDOC.md @@ -0,0 +1 @@ +Docs moved to /totalDoc — see totalDoc/INDEX.md diff --git a/docs/club-feature-management-services.md b/docs/club-feature-management-services.md deleted file mode 100644 index 851ba2d..0000000 --- a/docs/club-feature-management-services.md +++ /dev/null @@ -1,490 +0,0 @@ -# Club Feature Management Services - Implementation Guide - -## Overview -Admin services for managing user club features (enable/disable features per user). - -## Created Files - -### 1. CQRS Layer (Application) - -#### Query: GetUserClubFeatures -**Location:** `/CMS/src/CMSMicroservice.Application/ClubFeatureCQ/Queries/GetUserClubFeatures/` - -**Files:** -- `GetUserClubFeaturesQuery.cs` - Query definition -- `GetUserClubFeaturesQueryHandler.cs` - Query handler -- `UserClubFeatureDto.cs` - Response DTO - -**Purpose:** Get list of all club features for a specific user with their active status. - -**Input:** -```csharp -public record GetUserClubFeaturesQuery : IRequest> -{ - public long UserId { get; init; } -} -``` - -**Output:** -```csharp -public class UserClubFeatureDto -{ - public long Id { get; set; } - public long UserId { get; set; } - public long ClubMembershipId { get; set; } - public long ClubFeatureId { get; set; } - public string FeatureTitle { get; set; } - public string? FeatureDescription { get; set; } - public bool IsActive { get; set; } - public DateTime GrantedAt { get; set; } - public string? Notes { get; set; } -} -``` - -**Logic:** -- Joins `UserClubFeatures` with `ClubFeature` table -- Filters by `UserId` and `!IsDeleted` -- Returns list of features with their active status - ---- - -#### Command: ToggleUserClubFeature -**Location:** `/CMS/src/CMSMicroservice.Application/ClubFeatureCQ/Commands/ToggleUserClubFeature/` - -**Files:** -- `ToggleUserClubFeatureCommand.cs` - Command definition -- `ToggleUserClubFeatureCommandHandler.cs` - Command handler -- `ToggleUserClubFeatureResponse.cs` - Response DTO - -**Purpose:** Enable or disable a specific club feature for a user. - -**Input:** -```csharp -public record ToggleUserClubFeatureCommand : IRequest -{ - public long UserId { get; init; } - public long ClubFeatureId { get; init; } - public bool IsActive { get; init; } -} -``` - -**Output:** -```csharp -public class ToggleUserClubFeatureResponse -{ - public bool Success { get; set; } - public string Message { get; set; } - public long? UserClubFeatureId { get; set; } - public bool? IsActive { get; set; } -} -``` - -**Validations:** -1. ✅ User exists and not deleted -2. ✅ Club feature exists and not deleted -3. ✅ User has this feature assigned (exists in UserClubFeatures) - -**Logic:** -- Find `UserClubFeature` record by `UserId` + `ClubFeatureId` -- Update `IsActive` field -- Set `LastModified` timestamp -- Save changes - -**Error Messages:** -- "کاربر یافت نشد" - User not found -- "ویژگی باشگاه یافت نشد" - Club feature not found -- "این ویژگی برای کاربر یافت نشد" - User doesn't have this feature - -**Success Messages:** -- "ویژگی با موفقیت فعال شد" - Feature activated successfully -- "ویژگی با موفقیت غیرفعال شد" - Feature deactivated successfully - ---- - -### 2. gRPC Layer (Protobuf + WebApi) - -#### Proto Definition -**File:** `/CMS/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto` - -**Added RPC Methods:** -```protobuf -rpc GetUserClubFeatures(GetUserClubFeaturesRequest) returns (GetUserClubFeaturesResponse){ - option (google.api.http) = { - get: "/ClubFeature/GetUserFeatures" - }; -}; - -rpc ToggleUserClubFeature(ToggleUserClubFeatureRequest) returns (ToggleUserClubFeatureResponse){ - option (google.api.http) = { - post: "/ClubFeature/ToggleFeature" - body: "*" - }; -}; -``` - -**Message Definitions:** -```protobuf -message GetUserClubFeaturesRequest { - int64 user_id = 1; -} - -message GetUserClubFeaturesResponse { - repeated UserClubFeatureModel features = 1; -} - -message UserClubFeatureModel { - int64 id = 1; - int64 user_id = 2; - int64 club_membership_id = 3; - int64 club_feature_id = 4; - string feature_title = 5; - string feature_description = 6; - bool is_active = 7; - google.protobuf.Timestamp granted_at = 8; - string notes = 9; -} - -message ToggleUserClubFeatureRequest { - int64 user_id = 1; - int64 club_feature_id = 2; - bool is_active = 3; -} - -message ToggleUserClubFeatureResponse { - bool success = 1; - string message = 2; - google.protobuf.Int64Value user_club_feature_id = 3; - google.protobuf.BoolValue is_active = 4; -} -``` - ---- - -#### gRPC Service Implementation -**File:** `/CMS/src/CMSMicroservice.WebApi/Services/ClubMembershipService.cs` - -**Added Methods:** -```csharp -public override async Task GetUserClubFeatures( - GetUserClubFeaturesRequest request, - ServerCallContext context) -{ - return await _dispatchRequestToCQRS.Handle< - GetUserClubFeaturesRequest, - GetUserClubFeaturesQuery, - GetUserClubFeaturesResponse>(request, context); -} - -public override async Task - ToggleUserClubFeature( - ToggleUserClubFeatureRequest request, - ServerCallContext context) -{ - return await _dispatchRequestToCQRS.Handle< - ToggleUserClubFeatureRequest, - ToggleUserClubFeatureCommand, - Protobuf.Protos.ClubMembership.ToggleUserClubFeatureResponse>(request, context); -} -``` - ---- - -#### AutoMapper Profile -**File:** `/CMS/src/CMSMicroservice.WebApi/Common/Mappings/ClubFeatureProfile.cs` - -**Mappings:** -1. `GetUserClubFeaturesRequest` → `GetUserClubFeaturesQuery` -2. `UserClubFeatureDto` → `UserClubFeatureModel` (Proto) -3. `List` → `GetUserClubFeaturesResponse` -4. `ToggleUserClubFeatureRequest` → `ToggleUserClubFeatureCommand` -5. `ToggleUserClubFeatureResponse` (App) → `ToggleUserClubFeatureResponse` (Proto) - -**Special Handling:** -- DateTime conversion to `Timestamp` (Protobuf format) -- Null-safe mapping for optional fields -- Fully qualified type names to avoid ambiguity - ---- - -## API Endpoints - -### 1. Get User Club Features -**Method:** GET -**Endpoint:** `/ClubFeature/GetUserFeatures` -**Request:** -```json -{ - "user_id": 123 -} -``` - -**Response:** -```json -{ - "features": [ - { - "id": 1, - "user_id": 123, - "club_membership_id": 456, - "club_feature_id": 1, - "feature_title": "دسترسی به فروشگاه تخفیف", - "feature_description": "امکان خرید از فروشگاه تخفیف", - "is_active": true, - "granted_at": "2025-12-09T18:30:00Z", - "notes": "اعطا شده به‌طور خودکار هنگام فعالسازی" - } - ] -} -``` - ---- - -### 2. Toggle User Club Feature -**Method:** POST -**Endpoint:** `/ClubFeature/ToggleFeature` -**Request:** -```json -{ - "user_id": 123, - "club_feature_id": 1, - "is_active": false -} -``` - -**Response (Success):** -```json -{ - "success": true, - "message": "ویژگی با موفقیت غیرفعال شد", - "user_club_feature_id": 1, - "is_active": false -} -``` - -**Response (Error - User Not Found):** -```json -{ - "success": false, - "message": "کاربر یافت نشد" -} -``` - -**Response (Error - Feature Not Found):** -```json -{ - "success": false, - "message": "ویژگی باشگاه یافت نشد" -} -``` - -**Response (Error - User Doesn't Have Feature):** -```json -{ - "success": false, - "message": "این ویژگی برای کاربر یافت نشد" -} -``` - ---- - -## Database Schema - -### Table: UserClubFeatures -Existing table with newly added `IsActive` field: - -```sql -CREATE TABLE [CMS].[UserClubFeatures] -( - [Id] BIGINT IDENTITY(1,1) PRIMARY KEY, - [UserId] BIGINT NOT NULL, - [ClubMembershipId] BIGINT NOT NULL, - [ClubFeatureId] BIGINT NOT NULL, - [GrantedAt] DATETIME2 NOT NULL, - [IsActive] BIT NOT NULL DEFAULT 1, -- ← NEW FIELD - [Notes] NVARCHAR(MAX) NULL, - [Created] DATETIME2 NOT NULL, - [CreatedBy] NVARCHAR(MAX) NULL, - [LastModified] DATETIME2 NULL, - [LastModifiedBy] NVARCHAR(MAX) NULL, - [IsDeleted] BIT NOT NULL DEFAULT 0, - - CONSTRAINT FK_UserClubFeatures_Users FOREIGN KEY ([UserId]) - REFERENCES [Identity].[Users]([Id]), - CONSTRAINT FK_UserClubFeatures_ClubMembership FOREIGN KEY ([ClubMembershipId]) - REFERENCES [CMS].[ClubMembership]([Id]), - CONSTRAINT FK_UserClubFeatures_ClubFeatures FOREIGN KEY ([ClubFeatureId]) - REFERENCES [CMS].[ClubFeatures]([Id]) -); -``` - ---- - -## Usage Examples - -### Admin Panel Scenario - -#### 1. View User's Club Features -```csharp -// Admin selects user ID: 123 -var request = new GetUserClubFeaturesRequest { UserId = 123 }; -var response = await client.GetUserClubFeaturesAsync(request); - -// Display in grid: -foreach (var feature in response.Features) -{ - Console.WriteLine($"Feature: {feature.FeatureTitle}"); - Console.WriteLine($"Status: {(feature.IsActive ? "فعال" : "غیرفعال")}"); - Console.WriteLine($"Granted: {feature.GrantedAt}"); - Console.WriteLine("---"); -} -``` - -**Output:** -``` -Feature: دسترسی به فروشگاه تخفیف -Status: فعال -Granted: 2025-12-09 18:30:00 ---- -Feature: دسترسی به کمیسیون هفتگی -Status: فعال -Granted: 2025-12-09 18:30:00 ---- -Feature: دسترسی به شارژ شبکه -Status: غیرفعال -Granted: 2025-12-09 18:30:00 ---- -``` - ---- - -#### 2. Disable a Feature -```csharp -// Admin clicks "Disable" on Feature ID: 3 -var request = new ToggleUserClubFeatureRequest -{ - UserId = 123, - ClubFeatureId = 3, - IsActive = false -}; - -var response = await client.ToggleUserClubFeatureAsync(request); - -if (response.Success) -{ - Console.WriteLine(response.Message); - // Output: ویژگی با موفقیت غیرفعال شد -} -``` - ---- - -#### 3. Re-enable a Feature -```csharp -// Admin clicks "Enable" on Feature ID: 3 -var request = new ToggleUserClubFeatureRequest -{ - UserId = 123, - ClubFeatureId = 3, - IsActive = true -}; - -var response = await client.ToggleUserClubFeatureAsync(request); - -if (response.Success) -{ - Console.WriteLine(response.Message); - // Output: ویژگی با موفقیت فعال شد -} -``` - ---- - -## Testing Checklist - -### Unit Tests (Recommended) -- [ ] GetUserClubFeaturesQueryHandler returns correct DTOs -- [ ] ToggleUserClubFeatureCommandHandler validates user exists -- [ ] ToggleUserClubFeatureCommandHandler validates feature exists -- [ ] ToggleUserClubFeatureCommandHandler validates user has feature -- [ ] ToggleUserClubFeatureCommandHandler updates IsActive correctly -- [ ] ToggleUserClubFeatureCommandHandler sets LastModified timestamp - -### Integration Tests -- [ ] gRPC GetUserClubFeatures endpoint returns data -- [ ] gRPC ToggleUserClubFeature endpoint updates database -- [ ] AutoMapper mappings work correctly -- [ ] Proto serialization/deserialization works - -### Manual Testing -1. **Get Features:** - ```bash - grpcurl -d '{"user_id": 123}' \ - -plaintext localhost:5000 \ - clubmembership.ClubMembershipContract/GetUserClubFeatures - ``` - -2. **Disable Feature:** - ```bash - grpcurl -d '{"user_id": 123, "club_feature_id": 1, "is_active": false}' \ - -plaintext localhost:5000 \ - clubmembership.ClubMembershipContract/ToggleUserClubFeature - ``` - -3. **Verify in Database:** - ```sql - SELECT Id, UserId, ClubFeatureId, IsActive, LastModified - FROM CMS.UserClubFeatures - WHERE UserId = 123; - ``` - ---- - -## Build Status -✅ **All projects build successfully** -- CMSMicroservice.Domain: ✅ -- CMSMicroservice.Application: ✅ (0 errors, 274 warnings) -- CMSMicroservice.Protobuf: ✅ -- CMSMicroservice.WebApi: ✅ (0 errors, 17 warnings) - ---- - -## Next Steps (Optional Enhancements) - -1. **Authorization:** - - Add `[Authorize(Roles = "Admin")]` attribute - - Validate admin permissions before toggling - -2. **Audit Logging:** - - Log who changed the feature status - - Track `LastModifiedBy` field - -3. **Bulk Operations:** - - Add endpoint to toggle multiple features at once - - Add endpoint to enable/disable all features for a user - -4. **History Tracking:** - - Create `UserClubFeatureHistory` table - - Log every status change with timestamp and reason - -5. **Notifications:** - - Send notification to user when feature is disabled - - Email/SMS alert for important features - -6. **Business Rules:** - - Add validation: prevent disabling critical features - - Add expiration dates for features - - Add feature dependencies (e.g., Feature B requires Feature A) - ---- - -## Summary -✅ Created CQRS Query + Command for club feature management -✅ Created gRPC Proto definitions and services -✅ Created AutoMapper mappings -✅ All builds successful -✅ Ready for deployment and testing - -**Total Files Created:** 8 -**Total Lines of Code:** ~350 -**Build Errors:** 0 -**Status:** ✅ Complete and ready for use diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommand.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommand.cs new file mode 100644 index 0000000..3cee723 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; + +public class CreateBlogCategoryCommand : IRequest +{ + public string Title { get; set; } = default!; + public string Slug { get; set; } = default!; + public string? Description { get; set; } + public string? IconName { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..87afeda --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandHandler.cs @@ -0,0 +1,33 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; + +public class CreateBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateBlogCategoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateBlogCategoryCommand request, CancellationToken cancellationToken) + { + var entity = new BlogCategory + { + Title = request.Title, + Slug = request.Slug.ToLower(), + Description = request.Description, + IconName = request.IconName, + SortOrder = request.SortOrder, + IsActive = request.IsActive + }; + + _context.BlogCategories.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandValidator.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandValidator.cs new file mode 100644 index 0000000..ab46f3e --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; + +public class CreateBlogCategoryCommandValidator : AbstractValidator +{ + public CreateBlogCategoryCommandValidator() + { + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است") + .MaximumLength(200).WithMessage("عنوان دسته‌بندی حداکثر ۲۰۰ کاراکتر"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("اسلاگ الزامی است") + .MaximumLength(200).WithMessage("اسلاگ حداکثر ۲۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-]+$").WithMessage("اسلاگ فقط شامل حروف کوچک، اعداد و خط تیره"); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("توضیحات حداکثر ۱۰۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommand.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommand.cs new file mode 100644 index 0000000..8446cee --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.DeleteBlogCategory; + +public class DeleteBlogCategoryCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommandHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..63f0545 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommandHandler.cs @@ -0,0 +1,29 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.DeleteBlogCategory; + +public class DeleteBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteBlogCategoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteBlogCategoryCommand request, CancellationToken cancellationToken) + { + var entity = await _context.BlogCategories.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(BlogCategory), request.Id); + + entity.IsDeleted = true; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommand.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommand.cs new file mode 100644 index 0000000..17f8bc0 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; + +public class UpdateBlogCategoryCommand : IRequest +{ + public long Id { get; set; } + public string Title { get; set; } = default!; + public string Slug { get; set; } = default!; + public string? Description { get; set; } + public string? IconName { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..74d7f2b --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; + +public class UpdateBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateBlogCategoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateBlogCategoryCommand request, CancellationToken cancellationToken) + { + var entity = await _context.BlogCategories.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(BlogCategory), request.Id); + + entity.Title = request.Title; + entity.Slug = request.Slug.ToLower(); + entity.Description = request.Description; + entity.IconName = request.IconName; + entity.SortOrder = request.SortOrder; + entity.IsActive = request.IsActive; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandValidator.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandValidator.cs new file mode 100644 index 0000000..2414301 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandValidator.cs @@ -0,0 +1,27 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; + +public class UpdateBlogCategoryCommandValidator : AbstractValidator +{ + public UpdateBlogCategoryCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه دسته‌بندی نامعتبر است"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است") + .MaximumLength(200).WithMessage("عنوان دسته‌بندی حداکثر ۲۰۰ کاراکتر"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("اسلاگ الزامی است") + .MaximumLength(200).WithMessage("اسلاگ حداکثر ۲۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-]+$").WithMessage("اسلاگ فقط شامل حروف کوچک، اعداد و خط تیره"); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("توضیحات حداکثر ۱۰۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetActiveBlogCategories/GetActiveBlogCategoriesQuery.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetActiveBlogCategories/GetActiveBlogCategoriesQuery.cs new file mode 100644 index 0000000..553dbd4 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetActiveBlogCategories/GetActiveBlogCategoriesQuery.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetActiveBlogCategories; + +public class GetActiveBlogCategoriesQuery : IRequest> +{ +} + +public class GetActiveBlogCategoriesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetActiveBlogCategoriesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetActiveBlogCategoriesQuery request, CancellationToken cancellationToken) + { + var categories = await _context.BlogCategories + .Include(x => x.BlogPostCategories) + .Where(x => !x.IsDeleted && x.IsActive) + .OrderBy(x => x.SortOrder) + .ThenBy(x => x.Title) + .Select(x => new BlogCategoryDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Description = x.Description, + IconName = x.IconName, + SortOrder = x.SortOrder, + IsActive = x.IsActive, + PostCount = x.BlogPostCategories.Count, + Created = x.Created, + LastModified = x.LastModified + }) + .ToListAsync(cancellationToken); + + return categories; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQuery.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQuery.cs new file mode 100644 index 0000000..e13bb77 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQuery.cs @@ -0,0 +1,11 @@ +using CMSMicroservice.Application.Common.Models; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; + +public class GetAllBlogCategoriesQuery : IRequest +{ + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 20; + public string? SearchTerm { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQueryHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQueryHandler.cs new file mode 100644 index 0000000..c1d2545 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQueryHandler.cs @@ -0,0 +1,64 @@ +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; + +public class GetAllBlogCategoriesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllBlogCategoriesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllBlogCategoriesQuery request, CancellationToken cancellationToken) + { + var query = _context.BlogCategories + .Include(x => x.BlogPostCategories) + .Where(x => !x.IsDeleted); + + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + var term = request.SearchTerm.ToLower(); + query = query.Where(x => x.Title.ToLower().Contains(term) || x.Slug.ToLower().Contains(term)); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var categories = await query + .OrderBy(x => x.SortOrder) + .ThenBy(x => x.Title) + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new BlogCategoryDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Description = x.Description, + IconName = x.IconName, + SortOrder = x.SortOrder, + IsActive = x.IsActive, + PostCount = x.BlogPostCategories.Count, + Created = x.Created, + LastModified = x.LastModified + }) + .ToListAsync(cancellationToken); + + var metaData = new MetaData + { + TotalCount = totalCount, + PageSize = request.PageSize, + CurrentPage = request.PageNumber, + TotalPage = (int)Math.Ceiling(totalCount / (double)request.PageSize), + HasNext = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize), + HasPrevious = request.PageNumber > 1 + }; + + return new GetAllBlogCategoriesResponseDto { MetaData = metaData, Models = categories }; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesResponseDto.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesResponseDto.cs new file mode 100644 index 0000000..71d1967 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesResponseDto.cs @@ -0,0 +1,10 @@ +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; + +public class GetAllBlogCategoriesResponseDto +{ + public MetaData MetaData { get; set; } = default!; + public List Models { get; set; } = new(); +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/BlogCategoryDto.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/BlogCategoryDto.cs new file mode 100644 index 0000000..e858630 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/BlogCategoryDto.cs @@ -0,0 +1,15 @@ +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; + +public class BlogCategoryDto +{ + public long Id { get; set; } + public string Title { get; set; } = default!; + public string Slug { get; set; } = default!; + public string? Description { get; set; } + public string? IconName { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public int PostCount { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQuery.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQuery.cs new file mode 100644 index 0000000..3cc5ce7 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; + +public class GetBlogCategoryQuery : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQueryHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQueryHandler.cs new file mode 100644 index 0000000..44d3672 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQueryHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; + +public class GetBlogCategoryQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetBlogCategoryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetBlogCategoryQuery request, CancellationToken cancellationToken) + { + var entity = await _context.BlogCategories + .Include(x => x.BlogPostCategories) + .FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken); + + if (entity == null) + throw new NotFoundException(nameof(BlogCategory), request.Id); + + return new BlogCategoryDto + { + Id = entity.Id, + Title = entity.Title, + Slug = entity.Slug, + Description = entity.Description, + IconName = entity.IconName, + SortOrder = entity.SortOrder, + IsActive = entity.IsActive, + PostCount = entity.BlogPostCategories.Count, + Created = entity.Created, + LastModified = entity.LastModified + }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommand.cs new file mode 100644 index 0000000..84364d7 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; + +public class ArchiveBlogPostCommand : IRequest +{ + public long Id { get; set; } +} + +public class ArchiveBlogPostResult +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommandHandler.cs new file mode 100644 index 0000000..d81a33d --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommandHandler.cs @@ -0,0 +1,32 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; + +public class ArchiveBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public ArchiveBlogPostCommandHandler(IApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle(ArchiveBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + post.Status = BlogPostStatus.Archived; + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post archived. Id: {Id}, Title: {Title}", post.Id, post.Title); + + return new ArchiveBlogPostResult { Success = true, Message = "مقاله آرشیو شد" }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommand.cs new file mode 100644 index 0000000..691dcb7 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommand.cs @@ -0,0 +1,25 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; + +/// +/// دستور ایجاد مقاله جدید +/// +public class CreateBlogPostCommand : IRequest +{ + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string HtmlContent { get; set; } = string.Empty; + public string? FeaturedImagePath { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public List CategoryIds { get; set; } = new(); + public List TagIds { get; set; } = new(); + public bool IsFeatured { get; set; } + public int SortOrder { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs new file mode 100644 index 0000000..9bb72e6 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs @@ -0,0 +1,98 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; + +public class CreateBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + private readonly IFileManager _fileManager; + private readonly ILogger _logger; + + public CreateBlogPostCommandHandler( + IApplicationDbContext context, + ICurrentUserService currentUser, + IFileManager fileManager, + ILogger logger) + { + _context = context; + _currentUser = currentUser; + _fileManager = fileManager; + _logger = logger; + } + + public async Task Handle(CreateBlogPostCommand request, CancellationToken cancellationToken) + { + var currentUserId = _currentUser.UserId; + if (string.IsNullOrEmpty(currentUserId) || !long.TryParse(currentUserId, out var authorUserId)) + throw new UnauthorizedAccessException("کاربر احراز هویت نشده است"); + + var post = new BlogPost + { + Title = request.Title.Trim(), + Slug = string.IsNullOrWhiteSpace(request.Slug) + ? $"post-{Guid.NewGuid():N}".Substring(0, 20) + : request.Slug.Trim().ToLower(), + Summary = request.Summary?.Trim(), + HtmlContent = request.HtmlContent, + FeaturedImagePath = request.FeaturedImagePath, + FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath, + Status = BlogPostStatus.Draft, + AuthorUserId = authorUserId, + IsFeatured = request.IsFeatured, + SortOrder = request.SortOrder, + ViewCount = 0 + }; + + // آپلود تصویر شاخص (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/BlogPosts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + post.FeaturedImagePath = result.Main.Path; + post.FeaturedImageThumbnailPath = result.Thumbnail.Path; + } + + _context.BlogPosts.Add(post); + await _context.SaveChangesAsync(cancellationToken); + + // افزودن دسته‌بندی‌ها + foreach (var categoryId in request.CategoryIds) + { + _context.BlogPostCategories.Add(new BlogPostCategory + { + BlogPostId = post.Id, + BlogCategoryId = categoryId + }); + } + + // افزودن تگ‌ها + foreach (var tagId in request.TagIds) + { + _context.BlogPostTags.Add(new BlogPostTag + { + BlogPostId = post.Id, + TagId = tagId + }); + } + + if (request.CategoryIds.Any() || request.TagIds.Any()) + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation( + "Blog post created. Id: {Id}, Title: {Title}, Author: {Author}", + post.Id, post.Title, authorUserId); + + return post.Id; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandValidator.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandValidator.cs new file mode 100644 index 0000000..4ba5dc3 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; + +public class CreateBlogPostCommandValidator : AbstractValidator +{ + public CreateBlogPostCommandValidator() + { + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان مقاله الزامی است") + .MaximumLength(200).WithMessage("عنوان نمی‌تواند بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("نشانی یکتا (slug) الزامی است") + .MaximumLength(200).WithMessage("نشانی نمی‌تواند بیشتر از 200 کاراکتر باشد") + .Matches(@"^[a-z0-9\-]+$").WithMessage("نشانی فقط می‌تواند شامل حروف کوچک انگلیسی، اعداد و خط تیره باشد"); + + RuleFor(x => x.Summary) + .MaximumLength(500).WithMessage("خلاصه نمی‌تواند بیشتر از 500 کاراکتر باشد") + .When(x => !string.IsNullOrEmpty(x.Summary)); + + RuleFor(x => x.HtmlContent) + .NotEmpty().WithMessage("محتوای مقاله الزامی است"); + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommand.cs new file mode 100644 index 0000000..4d4df07 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.DeleteBlogPost; + +public class DeleteBlogPostCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommandHandler.cs new file mode 100644 index 0000000..f646348 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommandHandler.cs @@ -0,0 +1,31 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.DeleteBlogPost; + +public class DeleteBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public DeleteBlogPostCommandHandler(IApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle(DeleteBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + post.IsDeleted = true; + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post soft-deleted. Id: {Id}", post.Id); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommand.cs new file mode 100644 index 0000000..52a56ba --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.IncrementViewCount; + +public class IncrementViewCountCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommandHandler.cs new file mode 100644 index 0000000..a842984 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommandHandler.cs @@ -0,0 +1,27 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.IncrementViewCount; + +public class IncrementViewCountCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public IncrementViewCountCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(IncrementViewCountCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken); + if (post != null) + { + post.ViewCount++; + await _context.SaveChangesAsync(cancellationToken); + } + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommand.cs new file mode 100644 index 0000000..3114b3f --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommand.cs @@ -0,0 +1,15 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; + +public class PublishBlogPostCommand : IRequest +{ + public long Id { get; set; } +} + +public class PublishBlogPostResult +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; + public DateTime? PublishedAt { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommandHandler.cs new file mode 100644 index 0000000..5f19f60 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommandHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; + +public class PublishBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public PublishBlogPostCommandHandler(IApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle(PublishBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + if (post.Status == BlogPostStatus.Published) + return new PublishBlogPostResult { Success = false, Message = "مقاله قبلاً منتشر شده است" }; + + post.Status = BlogPostStatus.Published; + post.PublishedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post published. Id: {Id}, Title: {Title}", post.Id, post.Title); + + return new PublishBlogPostResult + { + Success = true, + Message = "مقاله با موفقیت منتشر شد", + PublishedAt = post.PublishedAt + }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommand.cs new file mode 100644 index 0000000..59121a2 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommand.cs @@ -0,0 +1,23 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; + +public class UpdateBlogPostCommand : IRequest +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string HtmlContent { get; set; } = string.Empty; + public string? FeaturedImagePath { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public List CategoryIds { get; set; } = new(); + public List TagIds { get; set; } = new(); + public bool IsFeatured { get; set; } + public int SortOrder { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs new file mode 100644 index 0000000..8b9c74e --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs @@ -0,0 +1,88 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; + +public class UpdateBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + private readonly ILogger _logger; + + public UpdateBlogPostCommandHandler( + IApplicationDbContext context, + IFileManager fileManager, + ILogger logger) + { + _context = context; + _fileManager = fileManager; + _logger = logger; + } + + public async Task Handle(UpdateBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + post.Title = request.Title.Trim(); + // حفظ slug قبلی اگر مقدار جدید خالی باشد + if (!string.IsNullOrWhiteSpace(request.Slug)) + post.Slug = request.Slug.Trim().ToLower(); + post.Summary = request.Summary?.Trim(); + post.HtmlContent = request.HtmlContent; + post.FeaturedImagePath = request.FeaturedImagePath; + post.FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath; + post.IsFeatured = request.IsFeatured; + post.SortOrder = request.SortOrder; + + // آپلود تصویر شاخص (اگر فایل جدید ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/BlogPosts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + post.FeaturedImagePath = result.Main.Path; + post.FeaturedImageThumbnailPath = result.Thumbnail.Path; + } + + // بروزرسانی دسته‌بندی‌ها + var existingCategories = await _context.BlogPostCategories + .Where(x => x.BlogPostId == post.Id).ToListAsync(cancellationToken); + _context.BlogPostCategories.RemoveRange(existingCategories); + foreach (var categoryId in request.CategoryIds) + { + _context.BlogPostCategories.Add(new BlogPostCategory + { + BlogPostId = post.Id, + BlogCategoryId = categoryId + }); + } + + // بروزرسانی تگ‌ها + var existingTags = await _context.BlogPostTags + .Where(x => x.BlogPostId == post.Id).ToListAsync(cancellationToken); + _context.BlogPostTags.RemoveRange(existingTags); + foreach (var tagId in request.TagIds) + { + _context.BlogPostTags.Add(new BlogPostTag + { + BlogPostId = post.Id, + TagId = tagId + }); + } + + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post updated. Id: {Id}, Title: {Title}", post.Id, post.Title); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandValidator.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandValidator.cs new file mode 100644 index 0000000..7359f03 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; + +public class UpdateBlogPostCommandValidator : AbstractValidator +{ + public UpdateBlogPostCommandValidator() + { + RuleFor(x => x.Id).GreaterThan(0).WithMessage("شناسه مقاله نامعتبر است"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان مقاله الزامی است") + .MaximumLength(200).WithMessage("عنوان نمی‌تواند بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("نشانی یکتا (slug) الزامی است") + .MaximumLength(200).WithMessage("نشانی نمی‌تواند بیشتر از 200 کاراکتر باشد") + .Matches(@"^[a-z0-9\-]+$").WithMessage("نشانی فقط می‌تواند شامل حروف کوچک انگلیسی، اعداد و خط تیره باشد"); + + RuleFor(x => x.HtmlContent) + .NotEmpty().WithMessage("محتوای مقاله الزامی است"); + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQuery.cs new file mode 100644 index 0000000..828db51 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQuery.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; + +public class GetAllBlogPostsQuery : IRequest +{ + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 10; + public string? SortBy { get; set; } + public string? SearchTerm { get; set; } + public BlogPostStatus? Status { get; set; } + public long? CategoryId { get; set; } + public bool? IsFeatured { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQueryHandler.cs new file mode 100644 index 0000000..c6e416e --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQueryHandler.cs @@ -0,0 +1,85 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; + +public class GetAllBlogPostsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllBlogPostsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllBlogPostsQuery request, CancellationToken cancellationToken) + { + var query = _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Where(x => !x.IsDeleted); + + // فیلترها + if (request.Status.HasValue) + query = query.Where(x => x.Status == request.Status.Value); + if (request.CategoryId.HasValue) + query = query.Where(x => x.BlogPostCategories.Any(c => c.BlogCategoryId == request.CategoryId.Value)); + if (request.IsFeatured.HasValue) + query = query.Where(x => x.IsFeatured == request.IsFeatured.Value); + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + var term = request.SearchTerm.ToLower(); + query = query.Where(x => x.Title.ToLower().Contains(term) || (x.Summary != null && x.Summary.ToLower().Contains(term))); + } + + var totalCount = await query.CountAsync(cancellationToken); + + // مرتب‌سازی + query = request.SortBy?.ToLower() switch + { + "title" => query.OrderBy(x => x.Title), + "viewcount" => query.OrderByDescending(x => x.ViewCount), + "publishedat" => query.OrderByDescending(x => x.PublishedAt), + _ => query.OrderByDescending(x => x.Created) + }; + + var posts = await query + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new BlogPostListItemDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Summary = x.Summary, + FeaturedImageThumbnailPath = x.FeaturedImageThumbnailPath, + Status = (int)x.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(x.Status), + PublishedAt = x.PublishedAt, + ViewCount = x.ViewCount, + IsFeatured = x.IsFeatured, + Created = x.Created, + Categories = x.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList() + }) + .ToListAsync(cancellationToken); + + var metaData = new MetaData + { + TotalCount = totalCount, + PageSize = request.PageSize, + CurrentPage = request.PageNumber, + TotalPage = (int)Math.Ceiling(totalCount / (double)request.PageSize), + HasNext = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize), + HasPrevious = request.PageNumber > 1 + }; + + return new GetAllBlogPostsResponseDto { MetaData = metaData, Models = posts }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsResponseDto.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsResponseDto.cs new file mode 100644 index 0000000..f876234 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsResponseDto.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; + +public class GetAllBlogPostsResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Models { get; set; } = new(); +} + +public class BlogPostListItemDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } = string.Empty; + public DateTime? PublishedAt { get; set; } + public int ViewCount { get; set; } + public bool IsFeatured { get; set; } + public DateTime Created { get; set; } + public List Categories { get; set; } = new(); +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/BlogPostDto.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/BlogPostDto.cs new file mode 100644 index 0000000..ae08e01 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/BlogPostDto.cs @@ -0,0 +1,39 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +public class BlogPostDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string HtmlContent { get; set; } = string.Empty; + public string? FeaturedImagePath { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public BlogPostStatus Status { get; set; } + public string StatusName { get; set; } = string.Empty; + public DateTime? PublishedAt { get; set; } + public int ViewCount { get; set; } + public long AuthorUserId { get; set; } + public bool IsFeatured { get; set; } + public int SortOrder { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } + public List Categories { get; set; } = new(); + public List Tags { get; set; } = new(); +} + +public class BlogPostCategoryDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; +} + +public class BlogPostTagDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQuery.cs new file mode 100644 index 0000000..21d01a9 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +public class GetBlogPostQuery : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQueryHandler.cs new file mode 100644 index 0000000..7443984 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQueryHandler.cs @@ -0,0 +1,66 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +public class GetBlogPostQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetBlogPostQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetBlogPostQuery request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Include(x => x.BlogPostTags).ThenInclude(x => x.Tag) + .FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + return new BlogPostDto + { + Id = post.Id, + Title = post.Title, + Slug = post.Slug, + Summary = post.Summary, + HtmlContent = post.HtmlContent, + FeaturedImagePath = post.FeaturedImagePath, + FeaturedImageThumbnailPath = post.FeaturedImageThumbnailPath, + Status = post.Status, + StatusName = GetStatusName(post.Status), + PublishedAt = post.PublishedAt, + ViewCount = post.ViewCount, + AuthorUserId = post.AuthorUserId, + IsFeatured = post.IsFeatured, + SortOrder = post.SortOrder, + Created = post.Created, + LastModified = post.LastModified, + Categories = post.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList(), + Tags = post.BlogPostTags.Select(t => new BlogPostTagDto + { + Id = t.Tag.Id, + Title = t.Tag.Title, + Name = t.Tag.Name + }).ToList() + }; + } + + public static string GetStatusName(BlogPostStatus status) => status switch + { + BlogPostStatus.Draft => "پیش‌نویس", + BlogPostStatus.Published => "منتشرشده", + BlogPostStatus.Scheduled => "زمانبندی‌شده", + BlogPostStatus.Archived => "آرشیو", + _ => "نامشخص" + }; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQuery.cs new file mode 100644 index 0000000..860f737 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPostBySlug; + +public class GetBlogPostBySlugQuery : IRequest +{ + public string Slug { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQueryHandler.cs new file mode 100644 index 0000000..8c020b5 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQueryHandler.cs @@ -0,0 +1,58 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPostBySlug; + +public class GetBlogPostBySlugQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetBlogPostBySlugQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetBlogPostBySlugQuery request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Include(x => x.BlogPostTags).ThenInclude(x => x.Tag) + .FirstOrDefaultAsync(x => x.Slug == request.Slug && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با نشانی '{request.Slug}' یافت نشد"); + + return new BlogPostDto + { + Id = post.Id, + Title = post.Title, + Slug = post.Slug, + Summary = post.Summary, + HtmlContent = post.HtmlContent, + FeaturedImagePath = post.FeaturedImagePath, + FeaturedImageThumbnailPath = post.FeaturedImageThumbnailPath, + Status = post.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(post.Status), + PublishedAt = post.PublishedAt, + ViewCount = post.ViewCount, + AuthorUserId = post.AuthorUserId, + IsFeatured = post.IsFeatured, + SortOrder = post.SortOrder, + Created = post.Created, + LastModified = post.LastModified, + Categories = post.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList(), + Tags = post.BlogPostTags.Select(t => new BlogPostTagDto + { + Id = t.Tag.Id, + Title = t.Tag.Title, + Name = t.Tag.Name + }).ToList() + }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetFeaturedBlogPosts/GetFeaturedBlogPostsQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetFeaturedBlogPosts/GetFeaturedBlogPostsQuery.cs new file mode 100644 index 0000000..3c6e701 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetFeaturedBlogPosts/GetFeaturedBlogPostsQuery.cs @@ -0,0 +1,55 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetFeaturedBlogPosts; + +public class GetFeaturedBlogPostsQuery : IRequest> +{ + public int Count { get; set; } = 5; +} + +public class GetFeaturedBlogPostsQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetFeaturedBlogPostsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetFeaturedBlogPostsQuery request, CancellationToken cancellationToken) + { + var posts = await _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Where(x => !x.IsDeleted && x.Status == BlogPostStatus.Published && x.IsFeatured) + .OrderBy(x => x.SortOrder) + .ThenByDescending(x => x.PublishedAt) + .Take(request.Count) + .Select(x => new GetAllBlogPosts.BlogPostListItemDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Summary = x.Summary, + FeaturedImageThumbnailPath = x.FeaturedImageThumbnailPath, + Status = (int)x.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(x.Status), + PublishedAt = x.PublishedAt, + ViewCount = x.ViewCount, + IsFeatured = x.IsFeatured, + Created = x.Created, + Categories = x.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList() + }) + .ToListAsync(cancellationToken); + + return posts; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetPublishedBlogPosts/GetPublishedBlogPostsQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetPublishedBlogPosts/GetPublishedBlogPostsQuery.cs new file mode 100644 index 0000000..8825b2b --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetPublishedBlogPosts/GetPublishedBlogPostsQuery.cs @@ -0,0 +1,81 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetPublishedBlogPosts; + +public class GetPublishedBlogPostsQuery : IRequest +{ + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 10; + public string? SearchTerm { get; set; } + public long? CategoryId { get; set; } +} + +public class GetPublishedBlogPostsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetPublishedBlogPostsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetPublishedBlogPostsQuery request, CancellationToken cancellationToken) + { + var query = _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Where(x => !x.IsDeleted && x.Status == BlogPostStatus.Published); + + if (request.CategoryId.HasValue) + query = query.Where(x => x.BlogPostCategories.Any(c => c.BlogCategoryId == request.CategoryId.Value)); + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + var term = request.SearchTerm.ToLower(); + query = query.Where(x => x.Title.ToLower().Contains(term) || (x.Summary != null && x.Summary.ToLower().Contains(term))); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var posts = await query + .OrderByDescending(x => x.PublishedAt) + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new GetAllBlogPosts.BlogPostListItemDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Summary = x.Summary, + FeaturedImageThumbnailPath = x.FeaturedImageThumbnailPath, + Status = (int)x.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(x.Status), + PublishedAt = x.PublishedAt, + ViewCount = x.ViewCount, + IsFeatured = x.IsFeatured, + Created = x.Created, + Categories = x.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList() + }) + .ToListAsync(cancellationToken); + + var metaData = new MetaData + { + TotalCount = totalCount, + PageSize = request.PageSize, + CurrentPage = request.PageNumber, + TotalPage = (int)Math.Ceiling(totalCount / (double)request.PageSize), + HasNext = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize), + HasPrevious = request.PageNumber > 1 + }; + + return new GetAllBlogPosts.GetAllBlogPostsResponseDto { MetaData = metaData, Models = posts }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommand.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommand.cs new file mode 100644 index 0000000..8a14516 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; + +public class AddBlogPostImageCommand : IRequest +{ + public long BlogPostId { get; set; } + public string ImagePath { get; set; } = default!; + public string ThumbnailPath { get; set; } = default!; + public string? AltText { get; set; } + public string? Caption { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandHandler.cs new file mode 100644 index 0000000..9b929c0 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandHandler.cs @@ -0,0 +1,39 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; + +public class AddBlogPostImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public AddBlogPostImageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(AddBlogPostImageCommand request, CancellationToken cancellationToken) + { + var blogPost = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.BlogPostId && !x.IsDeleted, cancellationToken); + if (blogPost == null) + throw new NotFoundException(nameof(BlogPost), request.BlogPostId); + + var entity = new BlogPostImage + { + BlogPostId = request.BlogPostId, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + AltText = request.AltText, + Caption = request.Caption, + SortOrder = request.SortOrder + }; + + _context.BlogPostImages.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandValidator.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandValidator.cs new file mode 100644 index 0000000..911cfe1 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; + +public class AddBlogPostImageCommandValidator : AbstractValidator +{ + public AddBlogPostImageCommandValidator() + { + RuleFor(x => x.BlogPostId) + .GreaterThan(0).WithMessage("شناسه پست نامعتبر است"); + + RuleFor(x => x.ImagePath) + .NotEmpty().WithMessage("مسیر تصویر الزامی است"); + + RuleFor(x => x.AltText) + .MaximumLength(500).WithMessage("متن جایگزین حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.Caption) + .MaximumLength(1000).WithMessage("عنوان تصویر حداکثر ۱۰۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommand.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommand.cs new file mode 100644 index 0000000..0990205 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.DeleteBlogPostImage; + +public class DeleteBlogPostImageCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommandHandler.cs new file mode 100644 index 0000000..012c6f0 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommandHandler.cs @@ -0,0 +1,29 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.DeleteBlogPostImage; + +public class DeleteBlogPostImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteBlogPostImageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteBlogPostImageCommand request, CancellationToken cancellationToken) + { + var entity = await _context.BlogPostImages.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(BlogPostImage), request.Id); + + entity.IsDeleted = true; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommand.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommand.cs new file mode 100644 index 0000000..852bcc1 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.ReorderBlogPostImages; + +public class ReorderBlogPostImagesCommand : IRequest +{ + public List Items { get; set; } = new(); +} + +public class ImageSortItem +{ + public long Id { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommandHandler.cs new file mode 100644 index 0000000..282a9d5 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommandHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.ReorderBlogPostImages; + +public class ReorderBlogPostImagesCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReorderBlogPostImagesCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReorderBlogPostImagesCommand request, CancellationToken cancellationToken) + { + var ids = request.Items.Select(x => x.Id).ToList(); + var images = await _context.BlogPostImages + .Where(x => ids.Contains(x.Id) && !x.IsDeleted) + .ToListAsync(cancellationToken); + + foreach (var item in request.Items) + { + var image = images.FirstOrDefault(x => x.Id == item.Id); + if (image != null) + image.SortOrder = item.SortOrder; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQuery.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQuery.cs new file mode 100644 index 0000000..8fd7e19 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQuery.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Queries.GetBlogPostImages; + +public class GetBlogPostImagesQuery : IRequest> +{ + public long BlogPostId { get; set; } +} + +public class BlogPostImageDto +{ + public long Id { get; set; } + public long BlogPostId { get; set; } + public string ImagePath { get; set; } = default!; + public string? ThumbnailPath { get; set; } + public string? AltText { get; set; } + public string? Caption { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQueryHandler.cs new file mode 100644 index 0000000..ecc3101 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQueryHandler.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Queries.GetBlogPostImages; + +public class GetBlogPostImagesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetBlogPostImagesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetBlogPostImagesQuery request, CancellationToken cancellationToken) + { + var images = await _context.BlogPostImages + .Where(x => x.BlogPostId == request.BlogPostId && !x.IsDeleted) + .OrderBy(x => x.SortOrder) + .Select(x => new BlogPostImageDto + { + Id = x.Id, + BlogPostId = x.BlogPostId, + ImagePath = x.ImagePath, + ThumbnailPath = x.ThumbnailPath, + AltText = x.AltText, + Caption = x.Caption, + SortOrder = x.SortOrder + }) + .ToListAsync(cancellationToken); + + return images; + } +} diff --git a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj index dd5c36f..b364983 100644 --- a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj +++ b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj @@ -7,6 +7,8 @@ + + diff --git a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs index 3d30861..83d0fd3 100644 --- a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs +++ b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs @@ -31,7 +31,18 @@ public class GetAllCategoryByFilterQueryHandler : IRequestHandler().ToListAsync(cancellationToken) + .Select(x => new GetAllCategoryByFilterResponseModel + { + Id = x.Id, + Name = x.Name, + Title = x.Title, + Description = x.Description, + ImagePath = x.ImagePath, + ParentId = x.ParentId, + IsActive = x.IsActive, + SortOrder = x.SortOrder, + ProductCount = x.ProductCategories.Count + }).ToListAsync(cancellationToken) }; } } diff --git a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs index 4edaf18..7fd92b2 100644 --- a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs +++ b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs @@ -24,4 +24,6 @@ public class GetAllCategoryByFilterResponseDto public bool IsActive { get; set; } //ترتیب نمایش public int SortOrder { get; set; } + //تعداد محصولات + public int ProductCount { get; set; } } diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs index 07821fd..b135fa0 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs @@ -53,20 +53,28 @@ public class AcceptClubMembershipContractCommandHandler AcceptClubMembershipContractCommand request, CancellationToken cancellationToken) { + // خواندن UserId از JWT (امن‌تر از دریافت از کلاینت) + if (!long.TryParse(_currentUser.UserId, out var userId)) + return new AcceptClubMembershipContractResponseDto + { + Success = false, + Message = "کاربر احراز هویت نشده است" + }; + _logger.LogInformation( "Processing club membership contract for UserId: {UserId}", - request.UserId + userId ); // 1. دریافت کاربر var user = await _context.Users .Include(u => u.ClubMembership) - .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); + .FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); if (user == null) { - _logger.LogWarning("User not found: {UserId}", request.UserId); - throw new NotFoundException(nameof(User), request.UserId); + _logger.LogWarning("User not found: {UserId}", userId); + throw new NotFoundException(nameof(User), userId); } // 2. بررسی خرید پکیج diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs index 8c5f308..ba87684 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs @@ -4,10 +4,6 @@ public class AcceptClubMembershipContractCommandValidator : AbstractValidator x.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر الزامی است"); - RuleFor(x => x.OtpCode) .NotEmpty() .WithMessage("کد تایید الزامی است") diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembership/GetClubMembershipQueryHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembership/GetClubMembershipQueryHandler.cs index 2252d21..d9dd99e 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembership/GetClubMembershipQueryHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembership/GetClubMembershipQueryHandler.cs @@ -3,14 +3,18 @@ namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership public class GetClubMembershipQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly ILogger _logger; - public GetClubMembershipQueryHandler(IApplicationDbContext context) + public GetClubMembershipQueryHandler(IApplicationDbContext context, ILogger logger) { _context = context; + _logger = logger; } public async Task Handle(GetClubMembershipQuery request, CancellationToken cancellationToken) { + _logger.LogInformation("GetClubMembership called for UserId: {UserId}", request.UserId); + var membership = await _context.ClubMemberships .AsNoTracking() .Where(x => x.UserId == request.UserId) @@ -27,6 +31,26 @@ public class GetClubMembershipQueryHandler : IRequestHandler { /// - /// از هفته (فیلتر اختیاری) + /// از هفته — WeekDefinitionId (فیلتر اختیاری) /// - public int? FromWeekOrder { get; init; } + public long? FromWeekDefinitionId { get; init; } /// - /// تا هفته (فیلتر اختیاری) + /// تا هفته — WeekDefinitionId (فیلتر اختیاری) /// - public int? ToWeekOrder { get; init; } + public long? ToWeekDefinitionId { get; init; } /// /// فقط Pool های محاسبه شده diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs index efdbf9e..d49e89f 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs @@ -20,14 +20,14 @@ public class GetAllWeeklyPoolsQueryHandler : IRequestHandler x.WeekDefinition.WeekOrder>=request.FromWeekOrder ); + query = query.Where(x => x.WeekDefinitionId >= request.FromWeekDefinitionId); } - if (request.ToWeekOrder!=null) + if (request.ToWeekDefinitionId != null) { - query = query.Where(x =>x.WeekDefinition.WeekOrder<= request.ToWeekOrder); + query = query.Where(x => x.WeekDefinitionId <= request.ToWeekDefinitionId); } if (request.OnlyCalculated.HasValue && request.OnlyCalculated.Value) diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQuery.cs new file mode 100644 index 0000000..46ec9cc --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQuery.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts; + +/// +/// Query برای دریافت پرداخت‌های کمیسیون کاربر جاری (از JWT) +/// +public record GetMyCommissionPayoutsQuery : IRequest +{ + /// + /// فیلتر وضعیت + /// + public CommissionPayoutStatus? Status { get; init; } + + /// + /// شماره هفته (اختیاری) + /// + public long? WeekDefinitionId { get; init; } + + /// + /// Pagination + /// + public PaginationState? PaginationState { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs new file mode 100644 index 0000000..6011d84 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs @@ -0,0 +1,73 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Extensions; +using CMSMicroservice.Domain.Enums; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts; + +public class GetMyCommissionPayoutsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetMyCommissionPayoutsQueryHandler( + IApplicationDbContext context, + ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetMyCommissionPayoutsQuery request, CancellationToken cancellationToken) + { + // دریافت UserId از JWT (فقط برای Customer API) + if (!long.TryParse(_currentUser.UserId, out var userId)) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + var query = _context.UserCommissionPayouts + .Include(x => x.WeekDefinition) + .Where(x => x.UserId == userId) + .AsNoTracking() + .AsQueryable(); + + // فیلترها + if (request.Status.HasValue) + { + query = query.Where(x => x.Status == request.Status.Value); + } + + if (request.WeekDefinitionId.HasValue) + { + query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId.Value); + } + + // مرتب‌سازی: جدیدترین اول + query = query.OrderByDescending(x => x.Created); + + var meta = await query.GetMetaData(request.PaginationState, cancellationToken); + + var models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .Select(x => new GetMyCommissionPayoutsResponseModel + { + Id = x.Id, + WeekDefinitionId = x.WeekDefinitionId, + WeekDisplayName = x.WeekDefinition != null ? x.WeekDefinition.DisplayName : "", + BalancesEarned = x.BalancesEarned, + TotalAmount = x.TotalAmount, + AmountFormatted = x.TotalAmount.ToString("N0") + " تومان", + Status = x.Status, + CalculatedDate = x.PaidAt ?? (DateTime?)x.Created, + DatePersian = "" + }) + .ToListAsync(cancellationToken); + + return new GetMyCommissionPayoutsResponseDto + { + MetaData = meta, + Models = models + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryValidator.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryValidator.cs new file mode 100644 index 0000000..63b3f81 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts; + +public class GetMyCommissionPayoutsQueryValidator : AbstractValidator +{ + public GetMyCommissionPayoutsQueryValidator() + { + RuleFor(x => x.PaginationState) + .NotNull() + .WithMessage("Pagination state is required"); + + When(x => x.PaginationState != null, () => + { + RuleFor(x => x.PaginationState!.PageNumber) + .GreaterThan(0) + .WithMessage("Page number must be greater than 0"); + + RuleFor(x => x.PaginationState!.PageSize) + .GreaterThan(0) + .LessThanOrEqualTo(100) + .WithMessage("Page size must be between 1 and 100"); + }); + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsResponseDto.cs new file mode 100644 index 0000000..5f85a43 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsResponseDto.cs @@ -0,0 +1,23 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts; + +public class GetMyCommissionPayoutsResponseDto +{ + public MetaData? MetaData { get; set; } + public List Models { get; set; } = new(); +} + +public class GetMyCommissionPayoutsResponseModel +{ + public long Id { get; set; } + public long WeekDefinitionId { get; set; } + public string WeekDisplayName { get; set; } = string.Empty; + public int BalancesEarned { get; set; } + public long TotalAmount { get; set; } + public string AmountFormatted { get; set; } = string.Empty; + public CommissionPayoutStatus Status { get; set; } + public DateTime? CalculatedDate { get; set; } + public string DatePersian { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQuery.cs new file mode 100644 index 0000000..43260b2 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQuery.cs @@ -0,0 +1,24 @@ +using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances; + +/// +/// Query برای دریافت تعادل‌های هفتگی کاربر جاری (از JWT) +/// +public record GetMyWeeklyBalancesQuery : IRequest +{ + /// + /// شناسه تعریف هفته (اختیاری) + /// + public long? WeekDefinitionId { get; init; } + + /// + /// فقط موارد Expired نشده؟ + /// + public bool OnlyActive { get; init; } + + /// + /// Pagination + /// + public PaginationState? PaginationState { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQueryHandler.cs new file mode 100644 index 0000000..ae27b89 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesQueryHandler.cs @@ -0,0 +1,47 @@ +using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances; + +/// +/// Handler برای دریافت تعادل‌های هفتگی کاربر جاری +/// +public class GetMyWeeklyBalancesQueryHandler : IRequestHandler +{ + private readonly ICurrentUserService _currentUserService; + private readonly IMediator _mediator; + private readonly ILogger _logger; + + public GetMyWeeklyBalancesQueryHandler( + ICurrentUserService currentUserService, + IMediator mediator, + ILogger logger) + { + _currentUserService = currentUserService; + _mediator = mediator; + _logger = logger; + } + + public async Task Handle(GetMyWeeklyBalancesQuery request, CancellationToken cancellationToken) + { + // دریافت UserId از JWT + if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) + { + _logger.LogWarning("GetMyWeeklyBalances called without valid user authentication"); + throw new UnauthorizedAccessException("کاربر احراز هویت نشده است"); + } + + _logger.LogInformation("GetMyWeeklyBalances for UserId: {UserId}, WeekDefinitionId: {WeekDefinitionId}", + userId, request.WeekDefinitionId); + + // فراخوانی GetUserWeeklyBalancesQuery با UserId از JWT + var query = new GetUserWeeklyBalancesQuery + { + UserId = userId, + WeekDefinitionId = request.WeekDefinitionId, + OnlyActive = request.OnlyActive, + PaginationState = request.PaginationState + }; + + return await _mediator.Send(query, cancellationToken); + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs index 0183452..8674908 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs @@ -21,10 +21,14 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler 0 → filter by that user + // UserId == 0 or null → show ALL users (admin mode) + // Customer endpoints resolve UserId from JWT before calling this handler + long? userId = request.UserId; + + if (userId.HasValue && userId.Value > 0) { - query = query.Where(x => x.UserId == request.UserId.Value); + query = query.Where(x => x.UserId == userId.Value); } if (request.Status.HasValue) diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs index 20281ab..2ffe742 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs @@ -21,10 +21,14 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler 0 → filter by that user + // UserId == 0 or null → show ALL users (admin mode) + // Customer endpoints resolve UserId from JWT before calling this handler + long? userId = request.UserId; + + if (userId.HasValue && userId.Value > 0) { - query = query.Where(x => x.UserId == request.UserId.Value); + query = query.Where(x => x.UserId == userId.Value); } // فیلتر بر اساس WeekDefinitionId (روش ترجیحی) diff --git a/src/CMSMicroservice.Application/Common/Authorization/IPermissionService.cs b/src/CMSMicroservice.Application/Common/Authorization/IPermissionService.cs new file mode 100644 index 0000000..ea0dce7 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Authorization/IPermissionService.cs @@ -0,0 +1,17 @@ +namespace CMSMicroservice.Application.Common.Authorization; + +/// +/// سرویس بررسی مجوز کاربر بر اساس نقش‌های JWT +/// +public interface IPermissionService +{ + /// + /// دریافت نقش‌های کاربر فعلی از JWT Claims + /// + Task> GetUserRolesAsync(CancellationToken cancellationToken); + + /// + /// بررسی اینکه آیا کاربر فعلی مجوز مشخصی دارد + /// + Task HasPermissionAsync(string permission, CancellationToken cancellationToken); +} diff --git a/src/CMSMicroservice.Application/Common/Authorization/PermissionDefinitions.cs b/src/CMSMicroservice.Application/Common/Authorization/PermissionDefinitions.cs new file mode 100644 index 0000000..95f6991 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Authorization/PermissionDefinitions.cs @@ -0,0 +1,127 @@ +namespace CMSMicroservice.Application.Common.Authorization; + +/// +/// ثوابت نام مجوزها — دسته‌بندی شده بر اساس حوزه +/// +public static class PermissionNames +{ + // Dashboard + public const string DashboardView = "dashboard.view"; + + // Orders + public const string OrdersView = "orders.view"; + public const string OrdersCreate = "orders.create"; + public const string OrdersUpdate = "orders.update"; + public const string OrdersDelete = "orders.delete"; + public const string OrdersCancel = "orders.cancel"; + public const string OrdersApprove = "orders.approve"; + + // Products + public const string ProductsView = "products.view"; + public const string ProductsCreate = "products.create"; + public const string ProductsUpdate = "products.update"; + public const string ProductsDelete = "products.delete"; + + // Users + public const string UsersView = "users.view"; + public const string UsersUpdate = "users.update"; + public const string UsersDelete = "users.delete"; + + // Commission + public const string CommissionView = "commission.view"; + public const string CommissionApproveWithdrawal = "commission.approve_withdrawal"; + + // Public Messages + public const string PublicMessagesView = "publicmessages.view"; + public const string PublicMessagesCreate = "publicmessages.create"; + public const string PublicMessagesUpdate = "publicmessages.update"; + public const string PublicMessagesPublish = "publicmessages.publish"; + + // Manual Payments + public const string ManualPaymentsView = "manualpayments.view"; + public const string ManualPaymentsCreate = "manualpayments.create"; + public const string ManualPaymentsApprove = "manualpayments.approve"; + + // Settings + public const string SettingsView = "settings.view"; + public const string SettingsUpdate = "settings.update"; + public const string SettingsDelete = "settings.delete"; + public const string SettingsManageConfiguration = "settings.manage_configuration"; + public const string SettingsManageVat = "settings.manage_vat"; + + // Reports + public const string ReportsView = "reports.view"; +} + +/// +/// نام نقش‌ها +/// +public static class RoleNames +{ + public const string SuperAdmin = "Administrator"; + public const string Admin = "Admin"; + public const string Inspector = "Inspector"; +} + +/// +/// تنظیمات نقش→مجوز — ماتریس دسترسی +/// +public static class RolePermissionConfig +{ + private static readonly Dictionary> RolePermissions = new(StringComparer.OrdinalIgnoreCase) + { + [RoleNames.SuperAdmin] = new(StringComparer.OrdinalIgnoreCase) { "*" }, // Full access + + [RoleNames.Admin] = new(StringComparer.OrdinalIgnoreCase) + { + PermissionNames.DashboardView, + PermissionNames.OrdersView, + PermissionNames.OrdersCreate, + PermissionNames.OrdersUpdate, + PermissionNames.OrdersCancel, + PermissionNames.ProductsView, + PermissionNames.ProductsCreate, + PermissionNames.ProductsUpdate, + PermissionNames.ProductsDelete, + PermissionNames.UsersView, + PermissionNames.UsersUpdate, + PermissionNames.CommissionView, + PermissionNames.CommissionApproveWithdrawal, + PermissionNames.PublicMessagesView, + PermissionNames.PublicMessagesCreate, + PermissionNames.PublicMessagesUpdate, + PermissionNames.PublicMessagesPublish, + PermissionNames.ManualPaymentsView, + PermissionNames.ManualPaymentsCreate, + PermissionNames.ReportsView + }, + + [RoleNames.Inspector] = new(StringComparer.OrdinalIgnoreCase) + { + PermissionNames.DashboardView, + PermissionNames.OrdersView, + PermissionNames.UsersView, + PermissionNames.CommissionView, + PermissionNames.PublicMessagesView, + PermissionNames.ReportsView + } + }; + + /// + /// بررسی اینکه آیا نقش مشخصی مجوز خاصی دارد + /// + public static bool HasPermission(string role, string permission) + { + if (string.IsNullOrWhiteSpace(role) || string.IsNullOrWhiteSpace(permission)) + return false; + + if (!RolePermissions.TryGetValue(role, out var permissions)) + return false; + + // Wildcard: SuperAdmin has full access + if (permissions.Contains("*")) + return true; + + return permissions.Contains(permission); + } +} diff --git a/src/CMSMicroservice.Application/Common/Authorization/RequiresPermissionAttribute.cs b/src/CMSMicroservice.Application/Common/Authorization/RequiresPermissionAttribute.cs new file mode 100644 index 0000000..8300108 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Authorization/RequiresPermissionAttribute.cs @@ -0,0 +1,15 @@ +namespace CMSMicroservice.Application.Common.Authorization; + +/// +/// Attribute برای مشخص کردن مجوز لازم برای دسترسی به یک متد gRPC +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public sealed class RequiresPermissionAttribute : Attribute +{ + public RequiresPermissionAttribute(string permission) + { + Permission = permission ?? throw new ArgumentNullException(nameof(permission)); + } + + public string Permission { get; } +} diff --git a/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs b/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs index 951bc51..836b657 100644 --- a/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs +++ b/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs @@ -18,7 +18,10 @@ public class LoggingBehaviour : IRequestPreProcessor where T { var requestName = typeof(TRequest).Name; var userId = _currentUserService.UserId ?? string.Empty; - _logger.LogInformation("Request: {Name} {@UserId} {@Request}", - requestName, userId, request); + var safeLog = request?.ToString() ?? ""; + if (safeLog.Length > 2000) + safeLog = safeLog[..2000] + "... [TRUNCATED]"; + _logger.LogInformation("Request: {Name} {UserId} {Request}", + requestName, userId, safeLog); } } diff --git a/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs b/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs index 45cd4e9..6d358ad 100644 --- a/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs +++ b/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs @@ -1,16 +1,20 @@ using System.Diagnostics; +using System.Text.RegularExpressions; using MediatR; using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.Common.Behaviours; -public class PerformanceBehaviour : IPipelineBehavior +public partial class PerformanceBehaviour : IPipelineBehavior where TRequest : IRequest { private readonly Stopwatch _timer; private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; + [GeneratedRegex(@"ImageFile(?:Bytes|Mime|FileName)|ImageFile|File", RegexOptions.None)] + private static partial Regex BinaryPropertyPattern(); + public PerformanceBehaviour(ILogger logger, ICurrentUserService currentUserService) { _timer = new Stopwatch(); @@ -33,11 +37,20 @@ public class PerformanceBehaviour : IPipelineBehavior 2000) + return text[..2000] + "... [TRUNCATED]"; + return text; + } } diff --git a/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs index 12648ad..80754f3 100644 --- a/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs +++ b/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs @@ -22,8 +22,11 @@ public class UnhandledExceptionBehaviour : IPipelineBehavio catch (Exception ex) { var requestName = typeof(TRequest).Name; + var safeLog = request?.ToString() ?? ""; + if (safeLog.Length > 2000) + safeLog = safeLog[..2000] + "... [TRUNCATED]"; - _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {Request}", requestName, safeLog); throw; } diff --git a/src/CMSMicroservice.Application/Common/FileManager/IFileManager.cs b/src/CMSMicroservice.Application/Common/FileManager/IFileManager.cs new file mode 100644 index 0000000..e0afc00 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/FileManager/IFileManager.cs @@ -0,0 +1,67 @@ +namespace CMSMicroservice.Application.Common.FileManager; + +/// +/// سرویس مدیریت فایل — ذخیره روی دیسک، مسیر نسبی در دیتابیس +/// موقع واکشی: خواندن از دیسک و تبدیل به base64 data-URI +/// +public interface IFileManager +{ + /// + /// آپلود یک فایل خام به دیسک + /// + /// نتیجه آپلود شامل مسیر نسبی فایل + /// در صورت خطای آپلود + Task UploadAsync( + string directory, + byte[] fileBytes, + string mime, + string? fileName = null, + CancellationToken ct = default); + + /// + /// آپلود تصویر با بهینه‌سازی خودکار + ساخت بندانگشتی + /// + /// نتیجه آپلود شامل تصویر اصلی و بندانگشتی (مسیرهای نسبی) + /// در صورت خطای آپلود + Task UploadImageAsync( + string directory, + byte[] fileBytes, + string mime, + string? fileName = null, + CancellationToken ct = default); + + /// + /// حذف فایل از دیسک + /// + Task DeleteAsync(long fileId, CancellationToken ct = default); + + /// + /// خواندن فایل از دیسک و تبدیل به base64 data-URI + /// اگر مسیر از قبل data: باشد، همان را برمی‌گرداند + /// اگر فایل وجود نداشته باشد، رشته خالی برمی‌گرداند + /// + string ResolveImageUrl(string? path); +} + +/// +/// نتیجه آپلود فایل +/// +/// شناسه فایل در FMS +/// مسیر فایل ذخیره‌شده (مثلاً /Images/Products/abc.jpg) +public sealed record UploadedFile(long Id, string Path); + +/// +/// نتیجه آپلود تصویر — شامل تصویر اصلی و بندانگشتی +/// +/// تصویر اصلی بهینه‌شده +/// تصویر بندانگشتی +public sealed record UploadedImage(UploadedFile Main, UploadedFile Thumbnail); + +/// +/// خطای آپلود فایل +/// +public class FileUploadException : Exception +{ + public FileUploadException(string message) : base(message) { } + public FileUploadException(string message, Exception inner) : base(message, inner) { } +} diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs index 95da562..938d613 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs @@ -1,3 +1,5 @@ +using CMSMicroservice.Domain.Entities.Blog; +using CMSMicroservice.Domain.Entities.Content; using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Entities.Order; using CMSMicroservice.Domain.Entities.DiscountShop; @@ -32,6 +34,7 @@ public interface IApplicationDbContext DbSet UserWallets { get; } DbSet UserWalletChangeLogs { get; } DbSet ManualPayments { get; } + DbSet PaymentTransactions { get; } DbSet PublicMessages { get; } DbSet ClubMemberships { get; } DbSet ClubMembershipHistories { get; } @@ -51,6 +54,7 @@ public interface IApplicationDbContext DbSet DiscountProducts { get; } DbSet DiscountCategories { get; } DbSet DiscountProductCategories { get; } + DbSet DiscountProductImages { get; } DbSet DiscountShoppingCarts { get; } DbSet DiscountOrders { get; } DbSet DiscountOrderDetails { get; } @@ -60,6 +64,22 @@ public interface IApplicationDbContext DbSet States { get; } DbSet Cities { get; } + // ============= Inventory Management ============= + DbSet Warehouses { get; } + DbSet InventoryItems { get; } + DbSet StockMovements { get; } + + // ============= Blog ============= + DbSet BlogPosts { get; } + DbSet BlogCategories { get; } + DbSet BlogPostCategories { get; } + DbSet BlogPostTags { get; } + DbSet BlogPostImages { get; } + + // ============= Content Management ============= + DbSet SitePages { get; } + DbSet SitePageSections { get; } + /// /// دسترسی به DatabaseFacade برای اجرای raw SQL و Stored Procedures /// diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IInventoryService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IInventoryService.cs new file mode 100644 index 0000000..e4a024c --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Interfaces/IInventoryService.cs @@ -0,0 +1,216 @@ +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.Common.Interfaces; + +/// +/// سرویس مدیریت موجودی - لایه بالاتر برای عملیات business +/// این سرویس مسئول همگام‌سازی موجودی بین InventoryItem و Product.RemainingCount است +/// +public interface IInventoryService +{ + #region Initialization + + /// + /// ایجاد رکورد موجودی برای محصول جدید + /// این متد باید در CreateProductCommandHandler و CreateDiscountProductCommandHandler فراخوانی شود + /// + /// شناسه محصول (Product.Id یا DiscountProduct.Id) + /// نوع محصول (RegularProduct یا DiscountProduct) + /// موجودی اولیه + /// شناسه انبار (پیش‌فرض: انبار اصلی) + /// آستانه هشدار کم‌موجودی + /// CancellationToken + /// شناسه InventoryItem ایجاد شده + Task InitializeInventoryAsync( + long productId, + ProductType productType, + int initialQuantity, + long? warehouseId = null, + int lowStockThreshold = 10, + CancellationToken ct = default); + + #endregion + + #region Query Operations + + /// + /// دریافت موجودی یک محصول + /// + Task GetInventoryAsync( + long productId, + ProductType productType, + long? warehouseId = null, + CancellationToken ct = default); + + /// + /// دریافت موجودی قابل فروش (Quantity - ReservedQuantity) + /// + Task GetAvailableQuantityAsync( + long productId, + ProductType productType, + long? warehouseId = null, + CancellationToken ct = default); + + /// + /// بررسی اینکه آیا موجودی کافی برای فروش وجود دارد + /// + Task CheckAvailabilityAsync( + long productId, + ProductType productType, + int requiredQuantity, + long? warehouseId = null, + CancellationToken ct = default); + + /// + /// دریافت لیست محصولات کم‌موجود + /// + Task> GetLowStockItemsAsync( + ProductType? productType = null, + long? warehouseId = null, + int count = 50, + CancellationToken ct = default); + + /// + /// دریافت تاریخچه حرکات موجودی + /// + Task> GetStockMovementsAsync( + long productId, + ProductType productType, + DateTime? fromDate = null, + DateTime? toDate = null, + CancellationToken ct = default); + + #endregion + + #region Order Flow Operations + + /// + /// رزرو موجودی برای سفارش pending + /// این متد در PlaceOrderCommandHandler فراخوانی می‌شود + /// فقط ReservedQuantity را افزایش می‌دهد، Quantity تغییر نمی‌کند + /// + /// شناسه محصول + /// نوع محصول + /// تعداد رزرو + /// شناسه سفارش (Order.Id یا DiscountOrder.Id) + /// CancellationToken + /// true اگر رزرو موفق بود + Task ReserveStockAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + CancellationToken ct = default); + + /// + /// آزادسازی رزرو (لغو سفارش یا timeout) + /// این متد در CancelOrderCommandHandler فراخوانی می‌شود + /// + Task ReleaseReservationAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + CancellationToken ct = default); + + /// + /// تایید فروش - کسر واقعی موجودی + /// این متد در CompleteOrderPaymentCommandHandler فراخوانی می‌شود + /// ReservedQuantity کاهش می‌یابد، Quantity کاهش می‌یابد، Product.RemainingCount sync می‌شود + /// + Task ConfirmSaleAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + CancellationToken ct = default); + + #endregion + + #region Stock Management Operations + + /// + /// ورود کالا به انبار (Restock) + /// + /// شناسه محصول + /// نوع محصول + /// تعداد ورودی + /// شماره مرجع (مثل شماره فاکتور خرید) + /// یادداشت + /// شناسه کاربر انجام‌دهنده + /// CancellationToken + Task AddStockAsync( + long productId, + ProductType productType, + int quantity, + string? referenceNumber = null, + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default); + + /// + /// تعدیل موجودی (تنظیم به مقدار جدید) + /// + Task AdjustStockAsync( + long productId, + ProductType productType, + int newQuantity, + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default); + + /// + /// ثبت برگشت کالا از مشتری + /// + Task ProcessReturnAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default); + + /// + /// ثبت ضایعات/مفقودی + /// + Task RecordLossAsync( + long productId, + ProductType productType, + int quantity, + StockMovementType lossType, // Damaged or Lost + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default); + + #endregion + + #region Bulk Operations + + /// + /// رزرو موجودی برای چند آیتم (یک سفارش با چند محصول) + /// + Task BulkReserveStockAsync( + IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items, + long? orderId = null, + CancellationToken ct = default); + + /// + /// آزادسازی رزرو برای چند آیتم + /// + Task BulkReleaseReservationAsync( + IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items, + long? orderId = null, + CancellationToken ct = default); + + /// + /// تایید فروش برای چند آیتم + /// + Task BulkConfirmSaleAsync( + IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items, + long? orderId = null, + CancellationToken ct = default); + + #endregion +} diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs index 912cfce..31fa543 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs @@ -27,6 +27,24 @@ public interface IPaymentGatewayService string verificationToken, CancellationToken cancellationToken = default); + /// + /// تأیید پرداخت با مبلغ — برای درگاه‌هایی مثل زرین‌پال که مبلغ را در Verify نیاز دارند + /// + /// شماره مرجع تراکنش (Authority در زرین‌پال) + /// توکن تأیید از درگاه (Status در زرین‌پال) + /// مبلغ تراکنش به تومان + /// + /// وضعیت نهایی تراکنش + Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + // پیش‌فرض: درگاه‌هایی که Amount نمی‌خواهند، از overload بدون amount استفاده کنند + return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + } + /// /// واریز مبلغ به حساب کاربر (برداشت از کیف پول) /// @@ -124,6 +142,21 @@ public class PaymentVerificationResult /// پیام /// public string? Message { get; set; } + + /// + /// شماره کارت ماسک‌شده (مثلاً 6037-****-****-1234) + /// + public string? CardPan { get; set; } + + /// + /// هش کارت بانکی + /// + public string? CardHash { get; set; } + + /// + /// کد وضعیت verify از درگاه (100=موفق، 101=تکراری) + /// + public int? VerificationCode { get; set; } } /// diff --git a/src/CMSMicroservice.Application/Common/Mappings/UserCartsProfile.cs b/src/CMSMicroservice.Application/Common/Mappings/UserCartsProfile.cs deleted file mode 100644 index 5e31d61..0000000 --- a/src/CMSMicroservice.Application/Common/Mappings/UserCartsProfile.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CMSMicroservice.Application.UserCartsCQ.Queries.GetAllUserCartsByFilter; - -namespace CMSMicroservice.Application.Common.Mappings; - -public class UserCartsProfile : IRegister -{ - void IRegister.Register(TypeAdapterConfig config) - { - config.NewConfig() - .Map(dest => dest.Id, src => src.Id) - .Map(dest => dest.Count, src => src.Count) - .Map(dest => dest.ProductId, src => src.ProductId) - .Map(dest => dest.ProductTitle, src => src.Product.Title) - .Map(dest => dest.ProductShortInfomation, src => src.Product.ShortInfomation) - .Map(dest => dest.ProductDiscount, src => src.Product.Discount) - .Map(dest => dest.ProductPrice, src => src.Product.Price) - .Map(dest => dest.ProductThumbnailPath, src => src.Product.ThumbnailPath) - .Map(dest => dest.Created, src => src.Created) - ; - } -} diff --git a/src/CMSMicroservice.Application/Common/Mappings/UserOrderProfile.cs b/src/CMSMicroservice.Application/Common/Mappings/UserOrderProfile.cs deleted file mode 100644 index 3426718..0000000 --- a/src/CMSMicroservice.Application/Common/Mappings/UserOrderProfile.cs +++ /dev/null @@ -1,58 +0,0 @@ -using CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; -using CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder; - -namespace CMSMicroservice.Application.Common.Mappings; - -public class UserOrderProfile : IRegister -{ - void IRegister.Register(TypeAdapterConfig config) - { - config.NewConfig() - .Map(dest => dest.Id, src => src.Id) - .Map(dest => dest.Amount, src => src.Amount) - .Map(dest => dest.PackageId, src => src.PackageId) - .Map(dest => dest.TransactionId, src => src.TransactionId) - .Map(dest => dest.PaymentStatus, src => src.PaymentStatus) - .Map(dest => dest.PaymentDate, src => src.PaymentDate) - .Map(dest => dest.UserId, src => src.UserId) - .Map(dest => dest.UserAddressId, src => src.UserAddressId) - .Map(dest => dest.PaymentMethod, src => src.PaymentMethod) - .Map(dest => dest.UserAddressText, src => src.UserAddress.Address) - .Map(dest => dest.FactorDetails, src => src.FactorDetails.Select(s=>s.Adapt())) - - ; - - config.NewConfig() - .Map(dest => dest.Id, src => src.Id) - .Map(dest => dest.Amount, src => src.Amount) - .Map(dest => dest.PackageId, src => src.PackageId) - .Map(dest => dest.TransactionId, src => src.TransactionId) - .Map(dest => dest.PaymentStatus, src => src.PaymentStatus) - .Map(dest => dest.PaymentDate, src => src.PaymentDate) - .Map(dest => dest.UserId, src => src.UserId) - .Map(dest => dest.UserAddressId, src => src.UserAddressId) - .Map(dest => dest.PaymentMethod, src => src.PaymentMethod) - .Map(dest => dest.UserAddressText, src => src.UserAddress.Address) - .Map(dest => dest.FactorDetails, src => src.FactorDetails.Select(s=>s.Adapt())) - ; - - config.NewConfig() - .Map(dest => dest.ProductId, src => src.ProductId) - .Map(dest => dest.ProductTitle, src => src.Product.Title) - .Map(dest => dest.ProductThumbnailPath, src => src.Product.ThumbnailPath) - .Map(dest => dest.UnitPrice, src => src.Product.Price) - .Map(dest => dest.Count, src => src.Count) - .Map(dest => dest.UnitDiscountPrice, src => src.Product.Price*(src.Product.Discount/100)) - ; - - config.NewConfig() - .Map(dest => dest.ProductId, src => src.ProductId) - .Map(dest => dest.ProductTitle, src => src.Product.Title) - .Map(dest => dest.ProductThumbnailPath, src => src.Product.ThumbnailPath) - .Map(dest => dest.UnitPrice, src => src.Product.Price) - .Map(dest => dest.Count, src => src.Count) - .Map(dest => dest.UnitDiscountPrice, src => src.Product.Price*(src.Product.Discount/100)) - ; - - } -} diff --git a/src/CMSMicroservice.Application/Common/Services/VatCalculator.cs b/src/CMSMicroservice.Application/Common/Services/VatCalculator.cs new file mode 100644 index 0000000..92e2cc9 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Services/VatCalculator.cs @@ -0,0 +1,83 @@ +namespace CMSMicroservice.Application.Common.Services; + +/// +/// سرویس محاسبه مالیات بر ارزش افزوده (VAT) +/// +public static class VatCalculator +{ + /// + /// نرخ VAT ایران - 9 درصد + /// + public const decimal VAT_RATE = 0.09m; + + /// + /// نرخ VAT به صورت درصد (9) + /// + public const int VAT_PERCENT = 9; + + /// + /// محاسبه VAT از مبلغ خالص + /// + /// مبلغ خالص (بدون مالیات) + /// مبلغ VAT + public static long CalculateVat(long netAmount) + { + return (long)(netAmount * VAT_RATE); + } + + /// + /// محاسبه مبلغ ناخالص (شامل VAT) از مبلغ خالص + /// + /// مبلغ خالص + /// مبلغ ناخالص (خالص + VAT) + public static long CalculateGrossAmount(long netAmount) + { + return netAmount + CalculateVat(netAmount); + } + + /// + /// استخراج مبلغ خالص از مبلغ ناخالص + /// + /// مبلغ ناخالص (شامل VAT) + /// مبلغ خالص + public static long ExtractNetAmount(long grossAmount) + { + return (long)(grossAmount / (1 + VAT_RATE)); + } + + /// + /// استخراج VAT از مبلغ ناخالص + /// + /// مبلغ ناخالص (شامل VAT) + /// مبلغ VAT + public static long ExtractVatFromGross(long grossAmount) + { + return grossAmount - ExtractNetAmount(grossAmount); + } + + /// + /// جزئیات محاسبه VAT + /// + public record VatBreakdown( + long NetAmount, + long VatAmount, + long GrossAmount, + decimal VatRate + ); + + /// + /// محاسبه کامل جزئیات VAT + /// + /// مبلغ خالص + /// جزئیات کامل VAT + public static VatBreakdown CalculateBreakdown(long netAmount) + { + var vatAmount = CalculateVat(netAmount); + return new VatBreakdown( + NetAmount: netAmount, + VatAmount: vatAmount, + GrossAmount: netAmount + vatAmount, + VatRate: VAT_RATE + ); + } +} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs index 346dcad..38d8a35 100644 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs @@ -210,7 +210,7 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler +{ + public long DiscountProductId { get; set; } + public string ImagePath { get; set; } = string.Empty; + public string ThumbnailPath { get; set; } = string.Empty; + public string? Title { get; set; } + public string? AltText { get; set; } + + // Image file upload + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommandHandler.cs new file mode 100644 index 0000000..a4db77e --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommandHandler.cs @@ -0,0 +1,67 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.DiscountShop; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddDiscountProductImage; + +public class AddDiscountProductImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public AddDiscountProductImageCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(AddDiscountProductImageCommand request, CancellationToken cancellationToken) + { + // Verify product exists + var productExists = await _context.DiscountProducts + .AnyAsync(p => p.Id == request.DiscountProductId, cancellationToken); + + if (!productExists) + throw new InvalidOperationException($"DiscountProduct with Id {request.DiscountProductId} not found."); + + var imagePath = request.ImagePath; + var thumbnailPath = request.ThumbnailPath; + + // آپلود تصویر (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/DiscountProducts/Gallery", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + imagePath = result.Main.Path; + thumbnailPath = result.Thumbnail.Path; + } + + // Get the max sort order for this product + var maxSortOrder = await _context.DiscountProductImages + .Where(i => i.DiscountProductId == request.DiscountProductId) + .MaxAsync(i => (int?)i.SortOrder, cancellationToken) ?? 0; + + var image = new DiscountProductImage + { + DiscountProductId = request.DiscountProductId, + ImagePath = imagePath, + ThumbnailPath = thumbnailPath, + Title = request.Title, + AltText = request.AltText, + SortOrder = maxSortOrder + 1, + IsActive = true + }; + + _context.DiscountProductImages.Add(image); + await _context.SaveChangesAsync(cancellationToken); + + return image.Id; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddToCustomerCart/AddToCustomerCartCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddToCustomerCart/AddToCustomerCartCommand.cs new file mode 100644 index 0000000..51d10d6 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddToCustomerCart/AddToCustomerCartCommand.cs @@ -0,0 +1,20 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCustomerCart; + +/// +/// Command برای افزودن محصول به سبد خرید کاربر فعلی +/// +public class AddToCustomerCartCommand : IRequest +{ + public long ProductId { get; set; } + public int Count { get; set; } + // UserId from ICurrentUserService +} + +public class AddToCustomerCartCommandResponse +{ + public long Id { get; set; } + public string Message { get; set; } = string.Empty; + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddToCustomerCart/AddToCustomerCartCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddToCustomerCart/AddToCustomerCartCommandHandler.cs new file mode 100644 index 0000000..d9e5972 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddToCustomerCart/AddToCustomerCartCommandHandler.cs @@ -0,0 +1,80 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCustomerCart; + +public class AddToCustomerCartCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public AddToCustomerCartCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(AddToCustomerCartCommand request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Check if product exists and is not deleted + var product = await _context.Products + .FirstOrDefaultAsync(p => p.Id == request.ProductId && !p.IsDeleted, cancellationToken); + + if (product == null) + { + return new AddToCustomerCartCommandResponse + { + Success = false, + Message = "محصول یافت نشد یا حذف شده است" + }; + } + + // Check if item already exists in cart + var existingCartItem = await _context.UserCarts + .FirstOrDefaultAsync(uc => uc.UserId == userId && uc.ProductId == request.ProductId, cancellationToken); + + if (existingCartItem != null) + { + // Update count + existingCartItem.Count += request.Count; + _context.UserCarts.Update(existingCartItem); + + await _context.SaveChangesAsync(cancellationToken); + + return new AddToCustomerCartCommandResponse + { + Id = existingCartItem.Id, + Success = true, + Message = "تعداد محصول در سبد خرید به‌روزرسانی شد" + }; + } + + // Create new cart item + var cartItem = new UserCart + { + UserId = userId, + ProductId = request.ProductId, + Count = request.Count, + Created = DateTime.UtcNow + }; + + _context.UserCarts.Add(cartItem); + await _context.SaveChangesAsync(cancellationToken); + + return new AddToCustomerCartCommandResponse + { + Id = cartItem.Id, + Success = true, + Message = "محصول به سبد خرید اضافه شد" + }; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs index 002f14c..0216a08 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs @@ -8,10 +8,14 @@ namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayme public class CompleteOrderPaymentCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IInventoryService _inventoryService; - public CompleteOrderPaymentCommandHandler(IApplicationDbContext context) + public CompleteOrderPaymentCommandHandler( + IApplicationDbContext context, + IInventoryService inventoryService) { _context = context; + _inventoryService = inventoryService; } public async Task Handle(CompleteOrderPaymentCommand request, CancellationToken cancellationToken) @@ -52,7 +56,7 @@ public class CompleteOrderPaymentCommandHandler : IRequestHandler public int MaxDiscountPercent { get; set; } public string ImagePath { get; set; } public string ThumbnailPath { get; set; } - public int RemainingCount { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; public List CategoryIds { get; set; } = new(); + + // Image file upload + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } + public byte[]? ThumbnailFileBytes { get; set; } + public string? ThumbnailFileMime { get; set; } + public string? ThumbnailFileName { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs index 542952b..b0a7d7b 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs @@ -1,5 +1,7 @@ +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities.DiscountShop; +using CMSMicroservice.Domain.Enums; using MediatR; using Microsoft.EntityFrameworkCore; @@ -8,10 +10,17 @@ namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProd public class CreateDiscountProductCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IInventoryService _inventoryService; + private readonly IFileManager _fileManager; - public CreateDiscountProductCommandHandler(IApplicationDbContext context) + public CreateDiscountProductCommandHandler( + IApplicationDbContext context, + IInventoryService inventoryService, + IFileManager fileManager) { _context = context; + _inventoryService = inventoryService; + _fileManager = fileManager; } public async Task Handle(CreateDiscountProductCommand request, CancellationToken cancellationToken) @@ -23,18 +32,52 @@ public class CreateDiscountProductCommandHandler : IRequestHandler 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/DiscountProducts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + product.ImagePath = result.Main.Path; + product.ThumbnailPath = result.Thumbnail.Path; + } + + // آپلود بندانگشتی جداگانه (اختیاری) + if (request.ThumbnailFileBytes is { Length: > 0 }) + { + var thumbResult = await _fileManager.UploadAsync( + "Images/DiscountProducts/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); + + product.ThumbnailPath = thumbResult.Path; + } + _context.DiscountProducts.Add(product); await _context.SaveChangesAsync(cancellationToken); + // ایجاد رکورد موجودی در سیستم انبارداری با موجودی اولیه صفر + await _inventoryService.InitializeInventoryAsync( + product.Id, + ProductType.DiscountProduct, + 0, // موجودی اولیه صفر - باید از طریق Inventory اضافه شود + ct: cancellationToken); + // Add product categories if (request.CategoryIds.Any()) { diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs index 29908dd..c49726f 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs @@ -11,26 +11,17 @@ public class CreateDiscountProductCommandValidator : AbstractValidator v.ShortInfomation) - .NotEmpty().WithMessage("توضیحات کوتاه الزامی است") - .MaximumLength(500).WithMessage("توضیحات کوتاه نمی‌تواند بیشتر از 500 کاراکتر باشد"); + .MaximumLength(500).WithMessage("توضیحات کوتاه نمی‌تواند بیشتر از 500 کاراکتر باشد") + .When(v => !string.IsNullOrEmpty(v.ShortInfomation)); RuleFor(v => v.FullInformation) - .NotEmpty().WithMessage("توضیحات کامل الزامی است") - .MaximumLength(2000).WithMessage("توضیحات کامل نمی‌تواند بیشتر از 2000 کاراکتر باشد"); + .MaximumLength(10000).WithMessage("توضیحات کامل نمی‌تواند بیشتر از 10000 کاراکتر باشد") + .When(v => !string.IsNullOrEmpty(v.FullInformation)); RuleFor(v => v.Price) .GreaterThan(0).WithMessage("قیمت باید بیشتر از صفر باشد"); RuleFor(v => v.MaxDiscountPercent) .InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد"); - - RuleFor(v => v.RemainingCount) - .GreaterThanOrEqualTo(0).WithMessage("موجودی نمی‌تواند منفی باشد"); - - RuleFor(v => v.ImagePath) - .NotEmpty().WithMessage("تصویر محصول الزامی است"); - - RuleFor(v => v.ThumbnailPath) - .NotEmpty().WithMessage("تصویر بندانگشتی الزامی است"); } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/DeleteDiscountProductImage/DeleteDiscountProductImageCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/DeleteDiscountProductImage/DeleteDiscountProductImageCommand.cs new file mode 100644 index 0000000..a498f36 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/DeleteDiscountProductImage/DeleteDiscountProductImageCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProductImage; + +public class DeleteDiscountProductImageCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/DeleteDiscountProductImage/DeleteDiscountProductImageCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/DeleteDiscountProductImage/DeleteDiscountProductImageCommandHandler.cs new file mode 100644 index 0000000..7e784c5 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/DeleteDiscountProductImage/DeleteDiscountProductImageCommandHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProductImage; + +public class DeleteDiscountProductImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteDiscountProductImageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteDiscountProductImageCommand request, CancellationToken cancellationToken) + { + var image = await _context.DiscountProductImages + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (image == null) + return false; + + _context.DiscountProductImages.Remove(image); + await _context.SaveChangesAsync(cancellationToken); + + // Reorder remaining images for this product + var remainingImages = await _context.DiscountProductImages + .Where(i => i.DiscountProductId == image.DiscountProductId) + .OrderBy(i => i.SortOrder) + .ToListAsync(cancellationToken); + + for (int i = 0; i < remainingImages.Count; i++) + { + remainingImages[i].SortOrder = i + 1; + } + + await _context.SaveChangesAsync(cancellationToken); + return true; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs index b32b074..1f20f3c 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs @@ -18,4 +18,9 @@ public class PlaceOrderResponseDto public long TotalAmount { get; set; } public long DiscountBalanceUsed { get; set; } public long GatewayAmountRequired { get; set; } + + /// + /// URL درگاه پرداخت — اگر null باشد یعنی نیاز به پرداخت آنلاین نیست + /// + public string? PaymentUrl { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs index 7032a20..58bb737 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs @@ -1,19 +1,35 @@ using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Services; using CMSMicroservice.Domain.Entities.DiscountShop; using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Enums; using MediatR; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder; public class PlaceOrderCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IInventoryService _inventoryService; + private readonly IPaymentGatewayService _paymentGateway; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; - public PlaceOrderCommandHandler(IApplicationDbContext context) + public PlaceOrderCommandHandler( + IApplicationDbContext context, + IInventoryService inventoryService, + IPaymentGatewayService paymentGateway, + IConfiguration configuration, + ILogger logger) { _context = context; + _inventoryService = inventoryService; + _paymentGateway = paymentGateway; + _configuration = configuration; + _logger = logger; } public async Task Handle(PlaceOrderCommand request, CancellationToken cancellationToken) @@ -93,25 +109,20 @@ public class PlaceOrderCommandHandler : IRequestHandler ۰ باشد، باید به درگاه پرداخت متصل شویم + string? paymentUrl = null; + + if (finalGatewayAmount > 0) + { + try + { + // آدرس callback — زرین‌پال بعد از پرداخت کاربر را به اینجا هدایت می‌کند + var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846"; + var callbackUrl = $"{cmsBaseUrl}/api/payment/discount-order/callback?orderId={order.Id}"; + + // درخواست به درگاه + var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest + { + Amount = finalGatewayAmount, + UserId = request.UserId, + Description = $"فروشگاه تخفیفی - سفارش #{order.Id}", + CallbackUrl = callbackUrl + }, cancellationToken); + + if (paymentResult.IsSuccess && !string.IsNullOrEmpty(paymentResult.GatewayUrl)) + { + // ذخیره Authority/RefId در تراکنش برای verify بعدی + transaction.RefId = paymentResult.RefId; + + // ثبت PaymentTransaction — جدول جدید با اطلاعات درگاه + var paymentTx = new PaymentTransaction + { + GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", + MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", + Amount = finalGatewayAmount, + CallbackUrl = callbackUrl, + Description = $"فروشگاه تخفیفی - سفارش #{order.Id}", + Mobile = null, + UserId = request.UserId, + RequestStatusCode = 100, + RequestStatusMessage = "Success", + Authority = paymentResult.RefId, + PaymentStatus = false, // هنوز verify نشده + TransactionId = transaction.Id, + OrderId = order.Id.ToString() + }; + _context.PaymentTransactions.Add(paymentTx); + await _context.SaveChangesAsync(cancellationToken); + + paymentUrl = paymentResult.GatewayUrl; + _logger.LogInformation( + "Payment gateway initiated for DiscountOrder #{OrderId}: RefId={RefId}, Url={Url}", + order.Id, paymentResult.RefId, paymentResult.GatewayUrl); + } + else + { + _logger.LogError( + "Payment gateway initiation failed for DiscountOrder #{OrderId}: {Error}", + order.Id, paymentResult.ErrorMessage); + + return new PlaceOrderResponseDto + { + Success = false, + Message = $"خطا در اتصال به درگاه پرداخت: {paymentResult.ErrorMessage}", + OrderId = order.Id + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Payment gateway exception for DiscountOrder #{OrderId}", order.Id); + return new PlaceOrderResponseDto + { + Success = false, + Message = $"خطا در اتصال به درگاه پرداخت: {ex.Message}", + OrderId = order.Id + }; + } + } + else + { + // اگر کل مبلغ از کیف تخفیفی پرداخت شد — مستقیماً تکمیل شود + transaction.PaymentStatus = PaymentStatus.Success; + transaction.PaymentDate = DateTime.Now; + order.PaymentStatus = PaymentStatus.Success; + order.PaymentDate = DateTime.Now; + order.DeliveryStatus = DeliveryStatus.Pending; + + var walletForDeduct = await _context.UserWallets + .FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken); + if (walletForDeduct != null) + walletForDeduct.DiscountBalance -= actualDiscountBalanceUsed; + + foreach (var cartItem in cartItems) + { + await _inventoryService.ConfirmSaleAsync( + cartItem.ProductId, ProductType.DiscountProduct, + cartItem.Count, order.Id, cancellationToken); + cartItem.Product.SaleCount += cartItem.Count; + } + + await _context.SaveChangesAsync(cancellationToken); + _logger.LogInformation( + "DiscountOrder #{OrderId} fully paid via discount balance ({Amount} T)", + order.Id, actualDiscountBalanceUsed); + } + return new PlaceOrderResponseDto { Success = true, - Message = "سفارش ایجاد شد. لطفا پرداخت را تکمیل کنید", + Message = finalGatewayAmount > 0 + ? "سفارش ایجاد شد. در حال انتقال به درگاه پرداخت..." + : "سفارش با موفقیت ثبت و پرداخت شد", OrderId = order.Id, TransactionId = transaction.Id, TotalAmount = totalAmount, DiscountBalanceUsed = actualDiscountBalanceUsed, - GatewayAmountRequired = finalGatewayAmount + GatewayAmountRequired = finalGatewayAmount, + PaymentUrl = paymentUrl }; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/RemoveFromCustomerCart/RemoveFromCustomerCartCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/RemoveFromCustomerCart/RemoveFromCustomerCartCommand.cs new file mode 100644 index 0000000..b0a976f --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/RemoveFromCustomerCart/RemoveFromCustomerCartCommand.cs @@ -0,0 +1,18 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCustomerCart; + +/// +/// Command برای حذف محصول از سبد خرید +/// +public class RemoveFromCustomerCartCommand : IRequest +{ + public long CartItemId { get; set; } + // UserId from ICurrentUserService +} + +public class RemoveFromCustomerCartCommandResponse +{ + public string Message { get; set; } = string.Empty; + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/RemoveFromCustomerCart/RemoveFromCustomerCartCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/RemoveFromCustomerCart/RemoveFromCustomerCartCommandHandler.cs new file mode 100644 index 0000000..dc6c2b5 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/RemoveFromCustomerCart/RemoveFromCustomerCartCommandHandler.cs @@ -0,0 +1,49 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCustomerCart; + +public class RemoveFromCustomerCartCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public RemoveFromCustomerCartCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(RemoveFromCustomerCartCommand request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Find and remove cart item + var cartItem = await _context.UserCarts + .FirstOrDefaultAsync(uc => uc.Id == request.CartItemId && uc.UserId == userId, cancellationToken); + + if (cartItem == null) + { + return new RemoveFromCustomerCartCommandResponse + { + Success = false, + Message = "آیتم سبد خرید یافت نشد" + }; + } + + _context.UserCarts.Remove(cartItem); + await _context.SaveChangesAsync(cancellationToken); + + return new RemoveFromCustomerCartCommandResponse + { + Success = true, + Message = "آیتم از سبد خرید حذف شد" + }; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/ReorderDiscountProductImages/ReorderDiscountProductImagesCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/ReorderDiscountProductImages/ReorderDiscountProductImagesCommand.cs new file mode 100644 index 0000000..d5f37b1 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/ReorderDiscountProductImages/ReorderDiscountProductImagesCommand.cs @@ -0,0 +1,9 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ReorderDiscountProductImages; + +public class ReorderDiscountProductImagesCommand : IRequest +{ + public long DiscountProductId { get; set; } + public List ImageIds { get; set; } = new(); +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/ReorderDiscountProductImages/ReorderDiscountProductImagesCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/ReorderDiscountProductImages/ReorderDiscountProductImagesCommandHandler.cs new file mode 100644 index 0000000..b5d2800 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/ReorderDiscountProductImages/ReorderDiscountProductImagesCommandHandler.cs @@ -0,0 +1,43 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ReorderDiscountProductImages; + +public class ReorderDiscountProductImagesCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReorderDiscountProductImagesCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReorderDiscountProductImagesCommand request, CancellationToken cancellationToken) + { + var images = await _context.DiscountProductImages + .Where(i => i.DiscountProductId == request.DiscountProductId) + .ToListAsync(cancellationToken); + + if (!images.Any()) + return false; + + // Validate all image IDs belong to this product + var imageIdSet = images.Select(i => i.Id).ToHashSet(); + if (!request.ImageIds.All(id => imageIdSet.Contains(id))) + return false; + + // Update sort order based on the new order + for (int i = 0; i < request.ImageIds.Count; i++) + { + var image = images.FirstOrDefault(img => img.Id == request.ImageIds[i]); + if (image != null) + { + image.SortOrder = i + 1; + } + } + + await _context.SaveChangesAsync(cancellationToken); + return true; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateCustomerCartItem/UpdateCustomerCartItemCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateCustomerCartItem/UpdateCustomerCartItemCommand.cs new file mode 100644 index 0000000..10ab908 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateCustomerCartItem/UpdateCustomerCartItemCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCustomerCartItem; + +/// +/// Command برای به‌روزرسانی تعداد محصول در سبد خرید +/// +public class UpdateCustomerCartItemCommand : IRequest +{ + public long CartItemId { get; set; } + public int Count { get; set; } + // UserId from ICurrentUserService +} + +public class UpdateCustomerCartItemCommandResponse +{ + public string Message { get; set; } = string.Empty; + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateCustomerCartItem/UpdateCustomerCartItemCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateCustomerCartItem/UpdateCustomerCartItemCommandHandler.cs new file mode 100644 index 0000000..d2321d4 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateCustomerCartItem/UpdateCustomerCartItemCommandHandler.cs @@ -0,0 +1,64 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCustomerCartItem; + +public class UpdateCustomerCartItemCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public UpdateCustomerCartItemCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(UpdateCustomerCartItemCommand request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Find cart item + var cartItem = await _context.UserCarts + .FirstOrDefaultAsync(uc => uc.Id == request.CartItemId && uc.UserId == userId, cancellationToken); + + if (cartItem == null) + { + return new UpdateCustomerCartItemCommandResponse + { + Success = false, + Message = "آیتم سبد خرید یافت نشد" + }; + } + + // Update count + if (request.Count <= 0) + { + // Remove item if count is 0 or negative + _context.UserCarts.Remove(cartItem); + await _context.SaveChangesAsync(cancellationToken); + + return new UpdateCustomerCartItemCommandResponse + { + Success = true, + Message = "آیتم از سبد خرید حذف شد" + }; + } + + cartItem.Count = request.Count; + _context.UserCarts.Update(cartItem); + await _context.SaveChangesAsync(cancellationToken); + + return new UpdateCustomerCartItemCommandResponse + { + Success = true, + Message = "تعداد آیتم به‌روزرسانی شد" + }; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs index a071314..80bd1e0 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs @@ -12,7 +12,16 @@ public class UpdateDiscountProductCommand : IRequest public int MaxDiscountPercent { get; set; } public string ImagePath { get; set; } public string ThumbnailPath { get; set; } + public int SortOrder { get; set; } public int RemainingCount { get; set; } public bool IsActive { get; set; } public List CategoryIds { get; set; } = new(); + + // Image file upload + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } + public byte[]? ThumbnailFileBytes { get; set; } + public string? ThumbnailFileMime { get; set; } + public string? ThumbnailFileName { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs index 19252ad..426913b 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities.DiscountShop; using MediatR; @@ -8,10 +9,12 @@ namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProd public class UpdateDiscountProductCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; - public UpdateDiscountProductCommandHandler(IApplicationDbContext context) + public UpdateDiscountProductCommandHandler(IApplicationDbContext context, IFileManager fileManager) { _context = context; + _fileManager = fileManager; } public async Task Handle(UpdateDiscountProductCommand request, CancellationToken cancellationToken) @@ -27,11 +30,44 @@ public class UpdateDiscountProductCommandHandler : IRequestHandler 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/DiscountProducts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + product.ImagePath = result.Main.Path; + product.ThumbnailPath = result.Thumbnail.Path; + } + else if (!string.IsNullOrEmpty(request.ImagePath)) + { + product.ImagePath = request.ImagePath; + } + + // آپلود بندانگشتی جداگانه (اختیاری) + if (request.ThumbnailFileBytes is { Length: > 0 }) + { + var thumbResult = await _fileManager.UploadAsync( + "Images/DiscountProducts/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); + + product.ThumbnailPath = thumbResult.Path; + } + else if (!string.IsNullOrEmpty(request.ThumbnailPath)) + { + product.ThumbnailPath = request.ThumbnailPath; + } + // Update categories var existingCategories = await _context.DiscountProductCategories .Where(pc => pc.ProductId == request.ProductId) diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs index 6d3f818..a71b9ac 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs @@ -14,12 +14,12 @@ public class UpdateDiscountProductCommandValidator : AbstractValidator x.ShortInfomation) - .NotEmpty().WithMessage("توضیحات کوتاه الزامی است") - .MaximumLength(500).WithMessage("توضیحات کوتاه نباید بیشتر از 500 کاراکتر باشد"); + .MaximumLength(500).WithMessage("توضیحات کوتاه نباید بیشتر از 500 کاراکتر باشد") + .When(x => !string.IsNullOrEmpty(x.ShortInfomation)); RuleFor(x => x.FullInformation) - .NotEmpty().WithMessage("توضیحات کامل الزامی است") - .MaximumLength(5000).WithMessage("توضیحات کامل نباید بیشتر از 5000 کاراکتر باشد"); + .MaximumLength(10000).WithMessage("توضیحات کامل نباید بیشتر از 10000 کاراکتر باشد") + .When(x => !string.IsNullOrEmpty(x.FullInformation)); RuleFor(x => x.Price) .GreaterThan(0).WithMessage("قیمت باید بزرگتر از صفر باشد"); @@ -27,14 +27,6 @@ public class UpdateDiscountProductCommandValidator : AbstractValidator x.MaxDiscountPercent) .InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد"); - RuleFor(x => x.ImagePath) - .NotEmpty().WithMessage("مسیر تصویر اصلی الزامی است") - .MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد"); - - RuleFor(x => x.ThumbnailPath) - .NotEmpty().WithMessage("مسیر تصویر بندانگشتی الزامی است") - .MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد"); - RuleFor(x => x.RemainingCount) .GreaterThanOrEqualTo(0).WithMessage("موجودی نمی‌تواند منفی باشد"); diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProductImage/UpdateDiscountProductImageCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProductImage/UpdateDiscountProductImageCommand.cs new file mode 100644 index 0000000..7efb3bc --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProductImage/UpdateDiscountProductImageCommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProductImage; + +public class UpdateDiscountProductImageCommand : IRequest +{ + public long Id { get; set; } + public string ImagePath { get; set; } = string.Empty; + public string ThumbnailPath { get; set; } = string.Empty; + public string? Title { get; set; } + public string? AltText { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProductImage/UpdateDiscountProductImageCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProductImage/UpdateDiscountProductImageCommandHandler.cs new file mode 100644 index 0000000..09d931e --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProductImage/UpdateDiscountProductImageCommandHandler.cs @@ -0,0 +1,33 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProductImage; + +public class UpdateDiscountProductImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateDiscountProductImageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateDiscountProductImageCommand request, CancellationToken cancellationToken) + { + var image = await _context.DiscountProductImages + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (image == null) + return false; + + image.ImagePath = request.ImagePath; + image.ThumbnailPath = request.ThumbnailPath; + image.Title = request.Title; + image.AltText = request.AltText; + image.IsActive = request.IsActive; + + await _context.SaveChangesAsync(cancellationToken); + return true; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetAllDiscountOrders/GetAllDiscountOrdersQuery.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetAllDiscountOrders/GetAllDiscountOrdersQuery.cs new file mode 100644 index 0000000..4b447f8 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetAllDiscountOrders/GetAllDiscountOrdersQuery.cs @@ -0,0 +1,102 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders; + +/// +/// کوئری دریافت همه سفارشات فروشگاه تخفیفی برای ادمین +/// +public class GetAllDiscountOrdersQuery : IRequest +{ + public PaginationState? PaginationQuery { get; set; } + + /// + /// فیلتر بر اساس شناسه کاربر + /// + public long? UserId { get; set; } + + /// + /// فیلتر بر اساس وضعیت پرداخت + /// + public PaymentStatus? PaymentStatus { get; set; } + + /// + /// فیلتر بر اساس وضعیت ارسال + /// + public DeliveryStatus? DeliveryStatus { get; set; } + + /// + /// جستجو بر اساس موبایل کاربر + /// + public string? UserMobile { get; set; } + + /// + /// جستجو بر اساس کد رهگیری + /// + public string? TrackingCode { get; set; } + + /// + /// فیلتر از تاریخ + /// + public DateTime? FromDate { get; set; } + + /// + /// فیلتر تا تاریخ + /// + public DateTime? ToDate { get; set; } + + /// + /// حداقل مبلغ سفارش + /// + public long? MinAmount { get; set; } + + /// + /// حداکثر مبلغ سفارش + /// + public long? MaxAmount { get; set; } +} + +public class GetAllDiscountOrdersResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Models { get; set; } = new(); +} + +public class AdminOrderDto +{ + public long Id { get; set; } + + // اطلاعات کاربر + public long UserId { get; set; } + public string UserFullName { get; set; } = string.Empty; + public string UserMobile { get; set; } = string.Empty; + + // مبالغ + public long TotalAmount { get; set; } + public long DiscountBalanceUsed { get; set; } + public long GatewayAmountPaid { get; set; } + public long VatAmount { get; set; } + + // وضعیت ها + public PaymentStatus PaymentStatus { get; set; } + public DateTime? PaymentDate { get; set; } + public DeliveryStatus DeliveryStatus { get; set; } + public DateTime? DeliveryDate { get; set; } + + // آدرس تحویل + public string? ShippingAddress { get; set; } + public string? ReceiverName { get; set; } + public string? ReceiverMobile { get; set; } + + // رهگیری + public string? TrackingCode { get; set; } + public string? AdminNote { get; set; } + + // تاریخ ها + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } + + // تعداد آیتم ها + public int ItemsCount { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetAllDiscountOrders/GetAllDiscountOrdersQueryHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetAllDiscountOrders/GetAllDiscountOrdersQueryHandler.cs new file mode 100644 index 0000000..fac1fde --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetAllDiscountOrders/GetAllDiscountOrdersQueryHandler.cs @@ -0,0 +1,124 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders; + +public class GetAllDiscountOrdersQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllDiscountOrdersQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllDiscountOrdersQuery request, CancellationToken cancellationToken) + { + var query = _context.DiscountOrders + .Include(o => o.User) + .Include(o => o.UserAddress) + .AsQueryable(); + + // فیلتر بر اساس شناسه کاربر + if (request.UserId.HasValue) + { + query = query.Where(o => o.UserId == request.UserId.Value); + } + + // فیلتر بر اساس وضعیت پرداخت + if (request.PaymentStatus.HasValue) + { + query = query.Where(o => o.PaymentStatus == request.PaymentStatus.Value); + } + + // فیلتر بر اساس وضعیت ارسال + if (request.DeliveryStatus.HasValue) + { + query = query.Where(o => o.DeliveryStatus == request.DeliveryStatus.Value); + } + + // جستجو بر اساس موبایل کاربر + if (!string.IsNullOrWhiteSpace(request.UserMobile)) + { + query = query.Where(o => o.User.Mobile.Contains(request.UserMobile)); + } + + // جستجو بر اساس کد رهگیری + if (!string.IsNullOrWhiteSpace(request.TrackingCode)) + { + query = query.Where(o => o.TrackingCode != null && o.TrackingCode.Contains(request.TrackingCode)); + } + + // فیلتر از تاریخ + if (request.FromDate.HasValue) + { + query = query.Where(o => o.Created >= request.FromDate.Value); + } + + // فیلتر تا تاریخ + if (request.ToDate.HasValue) + { + query = query.Where(o => o.Created <= request.ToDate.Value); + } + + // فیلتر حداقل مبلغ + if (request.MinAmount.HasValue) + { + query = query.Where(o => o.TotalAmount >= request.MinAmount.Value); + } + + // فیلتر حداکثر مبلغ + if (request.MaxAmount.HasValue) + { + query = query.Where(o => o.TotalAmount <= request.MaxAmount.Value); + } + + var totalCount = await query.CountAsync(cancellationToken); + + // Apply pagination + var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 20 }; + + var orders = await query + .OrderByDescending(o => o.Created) + .Skip((pagination.PageNumber - 1) * pagination.PageSize) + .Take(pagination.PageSize) + .Select(o => new AdminOrderDto + { + Id = o.Id, + UserId = o.UserId, + UserFullName = (o.User.FirstName ?? "") + " " + (o.User.LastName ?? ""), + UserMobile = o.User.Mobile, + TotalAmount = o.TotalAmount, + DiscountBalanceUsed = o.DiscountBalanceUsed, + GatewayAmountPaid = o.GatewayAmountPaid, + VatAmount = o.VatAmount, + PaymentStatus = o.PaymentStatus, + PaymentDate = o.PaymentDate, + DeliveryStatus = o.DeliveryStatus, + DeliveryDate = null, // TODO: Add DeliveryDate to DiscountOrder if needed + ShippingAddress = o.UserAddress.Address, + ReceiverName = o.UserAddress.Title, + ReceiverMobile = o.User.Mobile, + TrackingCode = o.TrackingCode, + AdminNote = o.DeliveryDescription, + Created = o.Created, + LastModified = o.LastModified, + ItemsCount = o.OrderDetails.Count + }) + .ToListAsync(cancellationToken); + + return new GetAllDiscountOrdersResponseDto + { + MetaData = new MetaData + { + TotalCount = totalCount, + PageSize = pagination.PageSize, + CurrentPage = pagination.PageNumber, + TotalPage = (int)Math.Ceiling(totalCount / (double)pagination.PageSize) + }, + Models = orders + }; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQuery.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQuery.cs new file mode 100644 index 0000000..3216df3 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQuery.cs @@ -0,0 +1,11 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart; + +/// +/// Query برای دریافت سبد خرید کاربر فعلی +/// +public class GetCustomerCartQuery : IRequest +{ + // UserId from ICurrentUserService +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQueryHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQueryHandler.cs new file mode 100644 index 0000000..42fc49b --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQueryHandler.cs @@ -0,0 +1,68 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart; + +public class GetCustomerCartQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerCartQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerCartQuery request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Get all cart items for the current user + var cartItems = await _context.UserCarts + .Include(uc => uc.Product) + .Where(uc => uc.UserId == userId) + .ToListAsync(cancellationToken); + + var response = new GetCustomerCartQueryResponse + { + TotalItemsCount = cartItems.Sum(c => c.Count), + Message = cartItems.Count > 0 ? "سبد خرید با موفقیت بازیابی شد" : "سبد خرید خالی است" + }; + + foreach (var item in cartItems) + { + // Use Product.ThumbnailPath directly + var thumbnailPath = item.Product?.ThumbnailPath ?? string.Empty; + var itemPrice = item.Product?.Price ?? 0; + var itemDiscount = item.Product?.Discount ?? 0; + var finalPrice = itemPrice * (100 - itemDiscount) / 100; + var totalItemPrice = finalPrice * item.Count; + + response.Items.Add(new CustomerCartItemModel + { + Id = item.Id, + ProductId = item.ProductId, + ProductTitle = item.Product?.Title ?? string.Empty, + ProductShortInformation = item.Product?.ShortInfomation ?? string.Empty, // Typo in DB: ShortInfomation + ProductPrice = itemPrice, + ProductDiscount = itemDiscount, + ProductThumbnailPath = thumbnailPath, + Count = item.Count, + TotalItemPrice = totalItemPrice, + Created = item.Created + }); + + response.TotalPrice += totalItemPrice; + } + + return response; + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQueryResponse.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQueryResponse.cs new file mode 100644 index 0000000..6feb345 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetCustomerCart/GetCustomerCartQueryResponse.cs @@ -0,0 +1,23 @@ +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart; + +public class GetCustomerCartQueryResponse +{ + public List Items { get; set; } = new(); + public long TotalPrice { get; set; } + public int TotalItemsCount { get; set; } + public string Message { get; set; } = string.Empty; +} + +public class CustomerCartItemModel +{ + public long Id { get; set; } + public long ProductId { get; set; } + public string ProductTitle { get; set; } = string.Empty; + public string ProductShortInformation { get; set; } = string.Empty; + public long ProductPrice { get; set; } + public int ProductDiscount { get; set; } + public string ProductThumbnailPath { get; set; } = string.Empty; + public int Count { get; set; } + public long TotalItemPrice { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProductImages/GetDiscountProductImagesQuery.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProductImages/GetDiscountProductImagesQuery.cs new file mode 100644 index 0000000..4dccbfb --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProductImages/GetDiscountProductImagesQuery.cs @@ -0,0 +1,21 @@ +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductImages; + +public class GetDiscountProductImagesQuery : IRequest> +{ + public long DiscountProductId { get; set; } + public bool OnlyActive { get; set; } = true; +} + +public class DiscountProductImageDto +{ + public long Id { get; set; } + public long DiscountProductId { get; set; } + public string ImagePath { get; set; } = string.Empty; + public string ThumbnailPath { get; set; } = string.Empty; + public string? Title { get; set; } + public string? AltText { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProductImages/GetDiscountProductImagesQueryHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProductImages/GetDiscountProductImagesQueryHandler.cs new file mode 100644 index 0000000..5a6399d --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProductImages/GetDiscountProductImagesQueryHandler.cs @@ -0,0 +1,39 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductImages; + +public class GetDiscountProductImagesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetDiscountProductImagesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetDiscountProductImagesQuery request, CancellationToken cancellationToken) + { + var query = _context.DiscountProductImages + .Where(i => i.DiscountProductId == request.DiscountProductId); + + if (request.OnlyActive) + query = query.Where(i => i.IsActive); + + return await query + .OrderBy(i => i.SortOrder) + .Select(i => new DiscountProductImageDto + { + Id = i.Id, + DiscountProductId = i.DiscountProductId, + ImagePath = i.ImagePath, + ThumbnailPath = i.ThumbnailPath, + Title = i.Title, + AltText = i.AltText, + SortOrder = i.SortOrder, + IsActive = i.IsActive + }) + .ToListAsync(cancellationToken); + } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountSalesReport/GetDiscountSalesReportQuery.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountSalesReport/GetDiscountSalesReportQuery.cs new file mode 100644 index 0000000..ba322a9 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountSalesReport/GetDiscountSalesReportQuery.cs @@ -0,0 +1,166 @@ +using CMSMicroservice.Application.Common.Models; +using MediatR; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport; + +/// +/// کوئری گزارش فروش فروشگاه تخفیفی +/// +public class GetDiscountSalesReportQuery : IRequest +{ + /// + /// از تاریخ + /// + public DateTime? FromDate { get; set; } + + /// + /// تا تاریخ + /// + public DateTime? ToDate { get; set; } + + /// + /// نوع گزارش + /// + public SalesReportType ReportType { get; set; } = SalesReportType.Summary; +} + +public enum SalesReportType +{ + /// + /// خلاصه کلی + /// + Summary, + + /// + /// روزانه + /// + Daily, + + /// + /// هفتگی + /// + Weekly, + + /// + /// ماهانه + /// + Monthly +} + +public class DiscountSalesReportDto +{ + // خلاصه کلی + public SalesSummary Summary { get; set; } = new(); + + // جزئیات زمانی (برای گزارش‌های روزانه، هفتگی، ماهانه) + public List Periods { get; set; } = new(); + + // پرفروش‌ترین محصولات + public List TopProducts { get; set; } = new(); +} + +public class SalesSummary +{ + /// + /// تعداد کل سفارشات + /// + public int TotalOrders { get; set; } + + /// + /// تعداد سفارشات موفق (پرداخت شده) + /// + public int CompletedOrders { get; set; } + + /// + /// تعداد سفارشات در انتظار پرداخت + /// + public int PendingOrders { get; set; } + + /// + /// تعداد سفارشات لغو شده + /// + public int CancelledOrders { get; set; } + + /// + /// جمع کل فروش (TotalAmount) + /// + public long TotalSalesAmount { get; set; } + + /// + /// جمع تخفیف استفاده شده (DiscountBalanceUsed) + /// + public long TotalDiscountUsed { get; set; } + + /// + /// جمع پرداخت از درگاه (GatewayAmountPaid) + /// + public long TotalGatewayPaid { get; set; } + + /// + /// جمع VAT + /// + public long TotalVatAmount { get; set; } + + /// + /// میانگین ارزش سفارش + /// + public long AverageOrderValue { get; set; } + + /// + /// تعداد کاربران یکتا + /// + public int UniqueCustomers { get; set; } + + /// + /// تعداد کل محصولات فروخته شده + /// + public int TotalProductsSold { get; set; } +} + +public class SalesPeriodDto +{ + /// + /// نام دوره (مثل: 1403/10/11 یا هفته 41) + /// + public string PeriodLabel { get; set; } = string.Empty; + + /// + /// تاریخ شروع دوره + /// + public DateTime PeriodStart { get; set; } + + /// + /// تاریخ پایان دوره + /// + public DateTime PeriodEnd { get; set; } + + /// + /// تعداد سفارشات + /// + public int OrdersCount { get; set; } + + /// + /// جمع فروش + /// + public long TotalAmount { get; set; } + + /// + /// جمع تخفیف + /// + public long DiscountUsed { get; set; } + + /// + /// جمع پرداخت درگاه + /// + public long GatewayPaid { get; set; } +} + +public class TopSellingProductDto +{ + public long ProductId { get; set; } + public string ProductTitle { get; set; } = string.Empty; + public string? ImagePath { get; set; } + public int QuantitySold { get; set; } + public long TotalRevenue { get; set; } + public int OrdersCount { get; set; } +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountSalesReport/GetDiscountSalesReportQueryHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountSalesReport/GetDiscountSalesReportQueryHandler.cs new file mode 100644 index 0000000..4c319a7 --- /dev/null +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountSalesReport/GetDiscountSalesReportQueryHandler.cs @@ -0,0 +1,193 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; +using System.Globalization; + +namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport; + +public class GetDiscountSalesReportQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private static readonly PersianCalendar PersianCalendar = new(); + + public GetDiscountSalesReportQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetDiscountSalesReportQuery request, CancellationToken cancellationToken) + { + var result = new DiscountSalesReportDto(); + + // Base query for orders + var ordersQuery = _context.DiscountOrders.AsQueryable(); + + // Apply date filters + if (request.FromDate.HasValue) + { + ordersQuery = ordersQuery.Where(o => o.Created >= request.FromDate.Value); + } + + if (request.ToDate.HasValue) + { + ordersQuery = ordersQuery.Where(o => o.Created <= request.ToDate.Value); + } + + // Get all matching orders for summary + var orders = await ordersQuery.ToListAsync(cancellationToken); + var completedOrders = orders.Where(o => o.PaymentStatus == PaymentStatus.Success).ToList(); + + // Calculate summary + result.Summary = new SalesSummary + { + TotalOrders = orders.Count, + CompletedOrders = completedOrders.Count, + PendingOrders = orders.Count(o => o.PaymentStatus == PaymentStatus.Pending), + CancelledOrders = orders.Count(o => o.PaymentStatus == PaymentStatus.Reject), + TotalSalesAmount = completedOrders.Sum(o => o.TotalAmount), + TotalDiscountUsed = completedOrders.Sum(o => o.DiscountBalanceUsed), + TotalGatewayPaid = completedOrders.Sum(o => o.GatewayAmountPaid), + TotalVatAmount = completedOrders.Sum(o => o.VatAmount), + AverageOrderValue = completedOrders.Any() ? (long)completedOrders.Average(o => o.TotalAmount) : 0, + UniqueCustomers = orders.Select(o => o.UserId).Distinct().Count() + }; + + // Get total products sold + var orderIds = completedOrders.Select(o => o.Id).ToList(); + result.Summary.TotalProductsSold = await _context.DiscountOrderDetails + .Where(d => orderIds.Contains(d.DiscountOrderId)) + .SumAsync(d => d.Count, cancellationToken); + + // Generate period-based reports + if (request.ReportType != SalesReportType.Summary && completedOrders.Any()) + { + result.Periods = GeneratePeriodReport(completedOrders, request.ReportType); + } + + // Get top selling products + result.TopProducts = await GetTopSellingProducts(orderIds, cancellationToken); + + return result; + } + + private List GeneratePeriodReport(List orders, SalesReportType reportType) + { + var periods = new List(); + + var groupedOrders = reportType switch + { + SalesReportType.Daily => orders.GroupBy(o => o.Created.Date), + SalesReportType.Weekly => orders.GroupBy(o => GetStartOfWeek(o.Created)), + SalesReportType.Monthly => orders.GroupBy(o => new DateTime(o.Created.Year, o.Created.Month, 1)), + _ => orders.GroupBy(o => o.Created.Date) + }; + + foreach (var group in groupedOrders.OrderBy(g => g.Key)) + { + var periodStart = group.Key; + var periodEnd = reportType switch + { + SalesReportType.Daily => periodStart.AddDays(1).AddSeconds(-1), + SalesReportType.Weekly => periodStart.AddDays(7).AddSeconds(-1), + SalesReportType.Monthly => periodStart.AddMonths(1).AddSeconds(-1), + _ => periodStart.AddDays(1).AddSeconds(-1) + }; + + periods.Add(new SalesPeriodDto + { + PeriodLabel = GetPersianPeriodLabel(periodStart, reportType), + PeriodStart = periodStart, + PeriodEnd = periodEnd, + OrdersCount = group.Count(), + TotalAmount = group.Sum(o => o.TotalAmount), + DiscountUsed = group.Sum(o => o.DiscountBalanceUsed), + GatewayPaid = group.Sum(o => o.GatewayAmountPaid) + }); + } + + return periods; + } + + private async Task> GetTopSellingProducts(List orderIds, CancellationToken cancellationToken) + { + if (!orderIds.Any()) + return new List(); + + var topProducts = await _context.DiscountOrderDetails + .Where(d => orderIds.Contains(d.DiscountOrderId)) + .GroupBy(d => d.ProductId) + .Select(g => new + { + ProductId = g.Key, + QuantitySold = g.Sum(d => d.Count), + TotalRevenue = g.Sum(d => d.FinalPrice), + OrdersCount = g.Select(d => d.DiscountOrderId).Distinct().Count() + }) + .OrderByDescending(x => x.QuantitySold) + .Take(10) + .ToListAsync(cancellationToken); + + var productIds = topProducts.Select(p => p.ProductId).ToList(); + var products = await _context.DiscountProducts + .Where(p => productIds.Contains(p.Id)) + .Select(p => new { p.Id, p.Title, p.ThumbnailPath }) + .ToListAsync(cancellationToken); + + return topProducts.Select(tp => new TopSellingProductDto + { + ProductId = tp.ProductId, + ProductTitle = products.FirstOrDefault(p => p.Id == tp.ProductId)?.Title ?? "نامشخص", + ImagePath = products.FirstOrDefault(p => p.Id == tp.ProductId)?.ThumbnailPath, + QuantitySold = tp.QuantitySold, + TotalRevenue = tp.TotalRevenue, + OrdersCount = tp.OrdersCount + }).ToList(); + } + + private static DateTime GetStartOfWeek(DateTime date) + { + // شروع هفته از شنبه (Saturday = 6 in DayOfWeek) + var diff = ((int)date.DayOfWeek + 1) % 7; // Saturday = 0 + return date.AddDays(-diff).Date; + } + + private static string GetPersianPeriodLabel(DateTime date, SalesReportType reportType) + { + var persianYear = PersianCalendar.GetYear(date); + var persianMonth = PersianCalendar.GetMonth(date); + var persianDay = PersianCalendar.GetDayOfMonth(date); + + return reportType switch + { + SalesReportType.Daily => $"{persianYear}/{persianMonth:D2}/{persianDay:D2}", + SalesReportType.Weekly => $"هفته {GetPersianWeekOfYear(date)} - {persianYear}", + SalesReportType.Monthly => $"{GetPersianMonthName(persianMonth)} {persianYear}", + _ => $"{persianYear}/{persianMonth:D2}/{persianDay:D2}" + }; + } + + private static int GetPersianWeekOfYear(DateTime date) + { + var firstDayOfYear = PersianCalendar.ToDateTime(PersianCalendar.GetYear(date), 1, 1, 0, 0, 0, 0); + var daysSinceStart = (date - firstDayOfYear).Days; + return (daysSinceStart / 7) + 1; + } + + private static string GetPersianMonthName(int month) => month switch + { + 1 => "فروردین", + 2 => "اردیبهشت", + 3 => "خرداد", + 4 => "تیر", + 5 => "مرداد", + 6 => "شهریور", + 7 => "مهر", + 8 => "آبان", + 9 => "آذر", + 10 => "دی", + 11 => "بهمن", + 12 => "اسفند", + _ => "نامشخص" + }; +} diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs index ead7df8..46e1e86 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs @@ -43,4 +43,6 @@ public class OrderItemDto public int DiscountPercentUsed { get; set; } public long DiscountAmount { get; set; } public long FinalPrice { get; set; } + public string ImagePath { get; set; } + public string ThumbnailPath { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs index a5e7292..1e55328 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs @@ -53,7 +53,9 @@ public class GetOrderByIdQueryHandler : IRequestHandler +{ +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/HealthCQ/Queries/GetSystemHealth/GetSystemHealthQueryHandler.cs b/src/CMSMicroservice.Application/HealthCQ/Queries/GetSystemHealth/GetSystemHealthQueryHandler.cs new file mode 100644 index 0000000..c5efea2 --- /dev/null +++ b/src/CMSMicroservice.Application/HealthCQ/Queries/GetSystemHealth/GetSystemHealthQueryHandler.cs @@ -0,0 +1,112 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +namespace CMSMicroservice.Application.HealthCQ.Queries.GetSystemHealth; + +public class GetSystemHealthQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IConfiguration _configuration; + + public GetSystemHealthQueryHandler(IApplicationDbContext context, IConfiguration configuration) + { + _context = context; + _configuration = configuration; + } + + public async Task Handle(GetSystemHealthQuery request, CancellationToken cancellationToken) + { + var services = new List(); + var overallHealthy = true; + + // Database Health Check + var dbHealth = await CheckDatabaseHealth(cancellationToken); + services.Add(dbHealth); + if (dbHealth.Status != HealthStatusDto.Healthy) overallHealthy = false; + + // Memory Health Check + var memoryHealth = CheckMemoryHealth(); + services.Add(memoryHealth); + if (memoryHealth.Status != HealthStatusDto.Healthy) overallHealthy = false; + + // External Services Health (if any) + // TODO: Add external service health checks + + return new GetSystemHealthResponseDto + { + OverallHealthy = overallHealthy, + Services = services, + CheckedAt = DateTime.UtcNow, + Version = GetApplicationVersion(), + Environment = _configuration["Environment"] ?? "Unknown" + }; + } + + private async Task CheckDatabaseHealth(CancellationToken cancellationToken) + { + var startTime = DateTime.UtcNow; + try + { + // Simple database connectivity check + var canConnect = await _context.Users.AnyAsync(cancellationToken); + var responseTime = (DateTime.UtcNow - startTime).TotalMilliseconds; + + return new ServiceHealthDto + { + ServiceName = "Database", + Status = HealthStatusDto.Healthy, + Description = "Database connection is healthy", + ResponseTimeMs = (long)responseTime, + LastCheck = DateTime.UtcNow, + Details = new List + { + new() { Key = "ConnectionString", Value = "Connected", Status = HealthStatusDto.Healthy }, + new() { Key = "ResponseTime", Value = $"{responseTime:F2}ms", Status = responseTime < 1000 ? HealthStatusDto.Healthy : HealthStatusDto.Degraded } + } + }; + } + catch (Exception ex) + { + var responseTime = (DateTime.UtcNow - startTime).TotalMilliseconds; + return new ServiceHealthDto + { + ServiceName = "Database", + Status = HealthStatusDto.Unhealthy, + Description = $"Database connection failed: {ex.Message}", + ResponseTimeMs = (long)responseTime, + LastCheck = DateTime.UtcNow, + Details = new List + { + new() { Key = "Error", Value = ex.Message, Status = HealthStatusDto.Unhealthy } + } + }; + } + } + + private ServiceHealthDto CheckMemoryHealth() + { + var process = System.Diagnostics.Process.GetCurrentProcess(); + var workingSetMB = process.WorkingSet64 / 1024 / 1024; + var status = workingSetMB < 500 ? HealthStatusDto.Healthy : + workingSetMB < 1000 ? HealthStatusDto.Degraded : HealthStatusDto.Unhealthy; + + return new ServiceHealthDto + { + ServiceName = "Memory", + Status = status, + Description = $"Current memory usage: {workingSetMB}MB", + ResponseTimeMs = 0, + LastCheck = DateTime.UtcNow, + Details = new List + { + new() { Key = "WorkingSet", Value = $"{workingSetMB}MB", Status = status }, + new() { Key = "ProcessName", Value = process.ProcessName, Status = HealthStatusDto.Healthy } + } + }; + } + + private string GetApplicationVersion() + { + return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "Unknown"; + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/HealthCQ/Queries/GetSystemHealth/GetSystemHealthResponseDto.cs b/src/CMSMicroservice.Application/HealthCQ/Queries/GetSystemHealth/GetSystemHealthResponseDto.cs new file mode 100644 index 0000000..f2320f8 --- /dev/null +++ b/src/CMSMicroservice.Application/HealthCQ/Queries/GetSystemHealth/GetSystemHealthResponseDto.cs @@ -0,0 +1,35 @@ +namespace CMSMicroservice.Application.HealthCQ.Queries.GetSystemHealth; + +public class GetSystemHealthResponseDto +{ + public bool OverallHealthy { get; set; } + public List Services { get; set; } = new(); + public DateTime CheckedAt { get; set; } + public string Version { get; set; } = string.Empty; + public string Environment { get; set; } = string.Empty; +} + +public class ServiceHealthDto +{ + public string ServiceName { get; set; } = string.Empty; + public HealthStatusDto Status { get; set; } + public string Description { get; set; } = string.Empty; + public long ResponseTimeMs { get; set; } + public DateTime LastCheck { get; set; } + public List Details { get; set; } = new(); +} + +public class HealthDetailDto +{ + public string Key { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; + public HealthStatusDto Status { get; set; } +} + +public enum HealthStatusDto +{ + Unknown = 0, + Healthy = 1, + Degraded = 2, + Unhealthy = 3 +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommand.cs new file mode 100644 index 0000000..5415b3e --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommand.cs @@ -0,0 +1,24 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem; + +/// +/// Command برای ایجاد آیتم موجودی جدید +/// +public record CreateInventoryItemCommand : IRequest +{ + /// شناسه محصول عادی + public long? ProductId { get; init; } + /// شناسه محصول تخفیفی + public long? DiscountProductId { get; init; } + /// شناسه انبار + public long WarehouseId { get; init; } + /// تعداد موجودی + public int Quantity { get; init; } + /// حداقل موجودی (هشدار) + public int MinQuantity { get; init; } + /// حداکثر موجودی + public int MaxQuantity { get; init; } + /// فعال؟ + public bool IsActive { get; init; } = true; +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommandHandler.cs new file mode 100644 index 0000000..9ddf10b --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommandHandler.cs @@ -0,0 +1,84 @@ +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem; + +public class CreateInventoryItemCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateInventoryItemCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateInventoryItemCommand request, CancellationToken cancellationToken) + { + // بررسی اینکه حداقل یکی از Product یا DiscountProduct تعریف شده باشد + if (request.ProductId == null && request.DiscountProductId == null) + { + throw new ArgumentException("Either ProductId or DiscountProductId must be provided"); + } + + // بررسی اینکه هر دو ProductId و DiscountProductId تعریف نشده باشند + if (request.ProductId != null && request.DiscountProductId != null) + { + throw new ArgumentException("Only one of ProductId or DiscountProductId can be provided"); + } + + // بررسی وجود آیتم موجودی قبلی برای همین محصول در همین انبار + bool existingItem; + if (request.ProductId.HasValue) + { + existingItem = await _context.InventoryItems + .AnyAsync(i => i.ProductId == request.ProductId.Value && i.WarehouseId == request.WarehouseId, cancellationToken); + } + else + { + existingItem = await _context.InventoryItems + .AnyAsync(i => i.DiscountProductId == request.DiscountProductId!.Value && i.WarehouseId == request.WarehouseId, cancellationToken); + } + + if (existingItem) + { + throw new InvalidOperationException("Inventory item already exists for this product in this warehouse"); + } + + var productType = request.ProductId.HasValue ? ProductType.RegularProduct : ProductType.DiscountProduct; + + var entity = new InventoryItem + { + ProductId = request.ProductId, + DiscountProductId = request.DiscountProductId, + ProductType = productType, + WarehouseId = request.WarehouseId, + Quantity = request.Quantity, + LowStockThreshold = request.MinQuantity, + MaxStockLevel = request.MaxQuantity, + ReservedQuantity = 0 + }; + + await _context.InventoryItems.AddAsync(entity, cancellationToken); + + // ثبت حرکت موجودی اولیه اگر موجودی اولیه بیشتر از صفر باشد + if (request.Quantity > 0) + { + var stockMovement = new StockMovement + { + InventoryItemId = entity.Id, + MovementType = StockMovementType.InitialStock, + Quantity = request.Quantity, + QuantityBefore = 0, + QuantityAfter = request.Quantity, + Note = "Initial stock creation", + ReferenceNumber = $"INIT-{entity.Id}" + }; + + await _context.StockMovements.AddAsync(stockMovement, cancellationToken); + } + + await _context.SaveChangesAsync(cancellationToken); + + return new CreateInventoryItemResponseDto { Id = entity.Id }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommandValidator.cs new file mode 100644 index 0000000..5d53d31 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemCommandValidator.cs @@ -0,0 +1,27 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem; + +public class CreateInventoryItemCommandValidator : AbstractValidator +{ + public CreateInventoryItemCommandValidator() + { + RuleFor(x => x.WarehouseId) + .GreaterThan(0).WithMessage("شناسه انبار معتبر نیست"); + + RuleFor(x => x.Quantity) + .GreaterThanOrEqualTo(0).WithMessage("تعداد موجودی نمی‌تواند منفی باشد"); + + RuleFor(x => x.MinQuantity) + .GreaterThanOrEqualTo(0).WithMessage("حداقل موجودی نمی‌تواند منفی باشد"); + + RuleFor(x => x.MaxQuantity) + .GreaterThanOrEqualTo(0).WithMessage("حداکثر موجودی نمی‌تواند منفی باشد"); + + RuleFor(x => x) + .Must(x => x.ProductId.HasValue || x.DiscountProductId.HasValue) + .WithMessage("حداقل یکی از شناسه محصول یا شناسه محصول تخفیفی باید مشخص شود"); + + RuleFor(x => x) + .Must(x => !(x.ProductId.HasValue && x.DiscountProductId.HasValue)) + .WithMessage("فقط یکی از شناسه محصول یا شناسه محصول تخفیفی باید مشخص شود"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemResponseDto.cs new file mode 100644 index 0000000..95ddf5d --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/CreateInventoryItem/CreateInventoryItemResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.CreateInventoryItem; + +public class CreateInventoryItemResponseDto +{ + /// شناسه آیتم موجودی ایجاد شده + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommand.cs new file mode 100644 index 0000000..92178b8 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommand.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem; + +/// +/// Command برای حذف آیتم موجودی +/// +public record DeleteInventoryItemCommand(long Id) : IRequest; diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommandHandler.cs new file mode 100644 index 0000000..c5ada77 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommandHandler.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Application.Common.Exceptions; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem; + +public class DeleteInventoryItemCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteInventoryItemCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteInventoryItemCommand request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (item == null) + { + throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id); + } + + // بررسی اینکه موجودی رزرو نداشته باشد + if (item.ReservedQuantity > 0) + { + throw new InvalidOperationException("Cannot delete inventory item with reserved quantity"); + } + + _context.InventoryItems.Remove(item); + await _context.SaveChangesAsync(cancellationToken); + + return new DeleteInventoryItemResponseDto { Success = true }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommandValidator.cs new file mode 100644 index 0000000..9a18044 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemCommandValidator.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem; + +public class DeleteInventoryItemCommandValidator : AbstractValidator +{ + public DeleteInventoryItemCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemResponseDto.cs new file mode 100644 index 0000000..a649c59 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/DeleteInventoryItem/DeleteInventoryItemResponseDto.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.DeleteInventoryItem; + +public class DeleteInventoryItemResponseDto +{ + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommand.cs new file mode 100644 index 0000000..aaad0d1 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommand.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory; + +/// +/// Command برای اضافه کردن موجودی (خرید) +/// +public record IncreaseInventoryCommand : IRequest +{ + /// شناسه آیتم موجودی + public long Id { get; init; } + /// تعداد افزایش + public int Quantity { get; init; } + /// شماره مرجع + public string? ReferenceNumber { get; init; } + /// شناسه کاربر انجام‌دهنده + public long? PerformedByUserId { get; init; } + /// یادداشت + public string? Note { get; init; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommandHandler.cs new file mode 100644 index 0000000..aead7ac --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommandHandler.cs @@ -0,0 +1,72 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory; + +public class IncreaseInventoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public IncreaseInventoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(IncreaseInventoryCommand request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (item == null) + { + throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id); + } + + var previousQuantity = item.Quantity; + item.Quantity += request.Quantity; + item.LastRestockedAt = DateTime.UtcNow; + + // ثبت حرکت موجودی + var stockMovement = new StockMovement + { + InventoryItemId = item.Id, + MovementType = StockMovementType.Restock, + Quantity = request.Quantity, + QuantityBefore = previousQuantity, + QuantityAfter = item.Quantity, + Note = request.Note ?? "Stock increased", + ReferenceNumber = request.ReferenceNumber ?? $"ADD-{DateTime.UtcNow:yyyyMMddHHmmss}", + PerformedByUserId = request.PerformedByUserId + }; + + await _context.StockMovements.AddAsync(stockMovement, cancellationToken); + + // همگام‌سازی با Product.RemainingCount یا DiscountProduct.RemainingCount + if (item.ProductType == ProductType.RegularProduct && item.ProductId.HasValue) + { + var product = await _context.Products.FindAsync(new object[] { item.ProductId.Value }, cancellationToken); + if (product != null) + { + product.RemainingCount = item.Quantity; + } + } + else if (item.ProductType == ProductType.DiscountProduct && item.DiscountProductId.HasValue) + { + var discountProduct = await _context.DiscountProducts.FindAsync( + new object[] { item.DiscountProductId.Value }, cancellationToken); + if (discountProduct != null) + { + discountProduct.RemainingCount = item.Quantity; + } + } + + await _context.SaveChangesAsync(cancellationToken); + + return new IncreaseInventoryResponseDto + { + Success = true, + NewQuantity = item.Quantity + }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommandValidator.cs new file mode 100644 index 0000000..44b8ac2 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryCommandValidator.cs @@ -0,0 +1,13 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory; + +public class IncreaseInventoryCommandValidator : AbstractValidator +{ + public IncreaseInventoryCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + + RuleFor(x => x.Quantity) + .GreaterThan(0).WithMessage("تعداد افزایش باید بیشتر از صفر باشد"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryResponseDto.cs new file mode 100644 index 0000000..7dc9d52 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/IncreaseInventory/IncreaseInventoryResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory; + +public class IncreaseInventoryResponseDto +{ + public bool Success { get; set; } + public int NewQuantity { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommand.cs new file mode 100644 index 0000000..88a40b9 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommand.cs @@ -0,0 +1,22 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory; + +/// +/// Command برای کم کردن موجودی (فروش) +/// +public record ReduceInventoryCommand : IRequest +{ + /// شناسه آیتم موجودی + public long Id { get; init; } + /// تعداد کاهش + public int Quantity { get; init; } + /// شناسه سفارش عادی + public long? OrderId { get; init; } + /// شناسه سفارش تخفیفی + public long? DiscountOrderId { get; init; } + /// شماره مرجع + public string? ReferenceNumber { get; init; } + /// شناسه کاربر انجام‌دهنده + public long? PerformedByUserId { get; init; } + /// آیا از موجودی رزرو شده کم شود؟ + public bool FromReserved { get; init; } = true; +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommandHandler.cs new file mode 100644 index 0000000..a4587e8 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommandHandler.cs @@ -0,0 +1,92 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory; + +public class ReduceInventoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReduceInventoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReduceInventoryCommand request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (item == null) + { + throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id); + } + + var previousQuantity = item.Quantity; + + if (request.FromReserved) + { + // کم کردن از موجودی رزرو شده + if (item.ReservedQuantity < request.Quantity) + { + throw new InvalidOperationException($"Insufficient reserved stock. Reserved: {item.ReservedQuantity}, Requested: {request.Quantity}"); + } + + item.ReservedQuantity -= request.Quantity; + item.Quantity -= request.Quantity; + } + else + { + // کم کردن مستقیم از موجودی + if (item.AvailableQuantity < request.Quantity) + { + throw new InvalidOperationException($"Insufficient available stock. Available: {item.AvailableQuantity}, Requested: {request.Quantity}"); + } + + item.Quantity -= request.Quantity; + } + + item.LastSoldAt = DateTime.UtcNow; + + // ثبت حرکت موجودی + var stockMovement = new StockMovement + { + InventoryItemId = item.Id, + MovementType = StockMovementType.Sale, + Quantity = request.Quantity, + QuantityBefore = previousQuantity, + QuantityAfter = item.Quantity, + Note = "Sale confirmed", + ReferenceNumber = request.ReferenceNumber ?? $"SALE-{DateTime.UtcNow:yyyyMMddHHmmss}", + OrderId = request.OrderId, + DiscountOrderId = request.DiscountOrderId, + PerformedByUserId = request.PerformedByUserId + }; + + await _context.StockMovements.AddAsync(stockMovement, cancellationToken); + + // همگام‌سازی با Product.RemainingCount یا DiscountProduct.RemainingCount + if (item.ProductType == ProductType.RegularProduct && item.ProductId.HasValue) + { + var product = await _context.Products.FindAsync(new object[] { item.ProductId.Value }, cancellationToken); + if (product != null) + { + product.RemainingCount = item.Quantity; + } + } + else if (item.ProductType == ProductType.DiscountProduct && item.DiscountProductId.HasValue) + { + var discountProduct = await _context.DiscountProducts.FindAsync( + new object[] { item.DiscountProductId.Value }, cancellationToken); + if (discountProduct != null) + { + discountProduct.RemainingCount = item.Quantity; + } + } + + await _context.SaveChangesAsync(cancellationToken); + + return new ReduceInventoryResponseDto { Success = true }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommandValidator.cs new file mode 100644 index 0000000..9148983 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryCommandValidator.cs @@ -0,0 +1,13 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory; + +public class ReduceInventoryCommandValidator : AbstractValidator +{ + public ReduceInventoryCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + + RuleFor(x => x.Quantity) + .GreaterThan(0).WithMessage("تعداد کاهش باید بیشتر از صفر باشد"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryResponseDto.cs new file mode 100644 index 0000000..4ee8d63 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReduceInventory/ReduceInventoryResponseDto.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory; + +public class ReduceInventoryResponseDto +{ + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommand.cs new file mode 100644 index 0000000..73b437f --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommand.cs @@ -0,0 +1,20 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory; + +/// +/// Command برای آزاد کردن موجودی رزرو شده +/// +public record ReleaseReservedInventoryCommand : IRequest +{ + /// شناسه آیتم موجودی + public long Id { get; init; } + /// تعداد آزادسازی + public int Quantity { get; init; } + /// شناسه سفارش عادی + public long? OrderId { get; init; } + /// شناسه سفارش تخفیفی + public long? DiscountOrderId { get; init; } + /// شماره مرجع + public string? ReferenceNumber { get; init; } + /// شناسه کاربر انجام‌دهنده + public long? PerformedByUserId { get; init; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommandHandler.cs new file mode 100644 index 0000000..d933abd --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommandHandler.cs @@ -0,0 +1,53 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory; + +public class ReleaseReservedInventoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReleaseReservedInventoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReleaseReservedInventoryCommand request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (item == null) + { + throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id); + } + + if (item.ReservedQuantity < request.Quantity) + { + throw new InvalidOperationException($"Cannot release more than reserved. Reserved: {item.ReservedQuantity}, Requested: {request.Quantity}"); + } + + item.ReservedQuantity -= request.Quantity; + + // ثبت حرکت موجودی + var stockMovement = new StockMovement + { + InventoryItemId = item.Id, + MovementType = StockMovementType.Released, + Quantity = request.Quantity, + QuantityBefore = item.Quantity, + QuantityAfter = item.Quantity, + Note = "Reservation released", + ReferenceNumber = request.ReferenceNumber ?? $"REL-{DateTime.UtcNow:yyyyMMddHHmmss}", + OrderId = request.OrderId, + DiscountOrderId = request.DiscountOrderId, + PerformedByUserId = request.PerformedByUserId + }; + + await _context.StockMovements.AddAsync(stockMovement, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + return new ReleaseReservedInventoryResponseDto { Success = true }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommandValidator.cs new file mode 100644 index 0000000..ac854cc --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryCommandValidator.cs @@ -0,0 +1,13 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory; + +public class ReleaseReservedInventoryCommandValidator : AbstractValidator +{ + public ReleaseReservedInventoryCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + + RuleFor(x => x.Quantity) + .GreaterThan(0).WithMessage("تعداد آزادسازی باید بیشتر از صفر باشد"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryResponseDto.cs new file mode 100644 index 0000000..3c418fb --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReleaseReservedInventory/ReleaseReservedInventoryResponseDto.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory; + +public class ReleaseReservedInventoryResponseDto +{ + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommand.cs new file mode 100644 index 0000000..2bbf204 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommand.cs @@ -0,0 +1,20 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory; + +/// +/// Command برای رزرو کردن موجودی +/// +public record ReserveInventoryCommand : IRequest +{ + /// شناسه آیتم موجودی + public long Id { get; init; } + /// تعداد رزرو + public int Quantity { get; init; } + /// شناسه سفارش عادی + public long? OrderId { get; init; } + /// شناسه سفارش تخفیفی + public long? DiscountOrderId { get; init; } + /// شماره مرجع + public string? ReferenceNumber { get; init; } + /// شناسه کاربر انجام‌دهنده + public long? PerformedByUserId { get; init; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommandHandler.cs new file mode 100644 index 0000000..999399c --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommandHandler.cs @@ -0,0 +1,65 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory; + +public class ReserveInventoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReserveInventoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReserveInventoryCommand request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (item == null) + { + throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id); + } + + var availableQuantity = item.AvailableQuantity; + + if (availableQuantity < request.Quantity) + { + return new ReserveInventoryResponseDto + { + Success = false, + Message = $"Insufficient stock. Available: {availableQuantity}, Requested: {request.Quantity}", + AvailableQuantity = availableQuantity + }; + } + + item.ReservedQuantity += request.Quantity; + + // ثبت حرکت موجودی + var stockMovement = new StockMovement + { + InventoryItemId = item.Id, + MovementType = StockMovementType.Reserved, + Quantity = request.Quantity, + QuantityBefore = item.Quantity, + QuantityAfter = item.Quantity, // موجودی اصلی تغییر نمی‌کند، فقط رزرو می‌شود + Note = "Stock reserved", + ReferenceNumber = request.ReferenceNumber ?? $"RSV-{DateTime.UtcNow:yyyyMMddHHmmss}", + OrderId = request.OrderId, + DiscountOrderId = request.DiscountOrderId, + PerformedByUserId = request.PerformedByUserId + }; + + await _context.StockMovements.AddAsync(stockMovement, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + return new ReserveInventoryResponseDto + { + Success = true, + Message = "Stock reserved successfully", + AvailableQuantity = item.AvailableQuantity + }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommandValidator.cs new file mode 100644 index 0000000..bb37a28 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryCommandValidator.cs @@ -0,0 +1,13 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory; + +public class ReserveInventoryCommandValidator : AbstractValidator +{ + public ReserveInventoryCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + + RuleFor(x => x.Quantity) + .GreaterThan(0).WithMessage("تعداد رزرو باید بیشتر از صفر باشد"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryResponseDto.cs new file mode 100644 index 0000000..e567202 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/ReserveInventory/ReserveInventoryResponseDto.cs @@ -0,0 +1,8 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory; + +public class ReserveInventoryResponseDto +{ + public bool Success { get; set; } + public string? Message { get; set; } + public int AvailableQuantity { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommand.cs new file mode 100644 index 0000000..ab2c107 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommand.cs @@ -0,0 +1,16 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem; + +/// +/// Command برای آپدیت آیتم موجودی +/// +public record UpdateInventoryItemCommand : IRequest +{ + /// شناسه آیتم موجودی + public long Id { get; init; } + /// حداقل موجودی (LowStockThreshold) + public int? MinimumStock { get; init; } + /// حداکثر موجودی (MaxStockLevel) + public int? MaximumStock { get; init; } + /// نقطه سفارش مجدد + public int? ReorderPoint { get; init; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommandHandler.cs new file mode 100644 index 0000000..f2d9606 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommandHandler.cs @@ -0,0 +1,54 @@ +using CMSMicroservice.Application.Common.Exceptions; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem; + +public class UpdateInventoryItemCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateInventoryItemCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateInventoryItemCommand request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (item == null) + { + throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id); + } + + if (request.MinimumStock.HasValue) + { + item.LowStockThreshold = request.MinimumStock.Value; + } + + if (request.MaximumStock.HasValue) + { + item.MaxStockLevel = request.MaximumStock.Value; + } + + if (request.ReorderPoint.HasValue) + { + item.ReorderPoint = request.ReorderPoint.Value; + } + + await _context.SaveChangesAsync(cancellationToken); + + return new UpdateInventoryItemResponseDto + { + Id = item.Id, + ProductId = item.ProductId ?? item.DiscountProductId ?? 0, + WarehouseId = item.WarehouseId, + Quantity = item.Quantity, + ReservedQuantity = item.ReservedQuantity, + AvailableQuantity = item.AvailableQuantity, + MinimumStock = item.LowStockThreshold, + MaximumStock = item.MaxStockLevel, + ReorderPoint = item.ReorderPoint + }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommandValidator.cs new file mode 100644 index 0000000..b16d663 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemCommandValidator.cs @@ -0,0 +1,22 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem; + +public class UpdateInventoryItemCommandValidator : AbstractValidator +{ + public UpdateInventoryItemCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + + RuleFor(x => x.MinimumStock) + .GreaterThanOrEqualTo(0).When(x => x.MinimumStock.HasValue) + .WithMessage("حداقل موجودی نمی‌تواند منفی باشد"); + + RuleFor(x => x.MaximumStock) + .GreaterThanOrEqualTo(0).When(x => x.MaximumStock.HasValue) + .WithMessage("حداکثر موجودی نمی‌تواند منفی باشد"); + + RuleFor(x => x.ReorderPoint) + .GreaterThanOrEqualTo(0).When(x => x.ReorderPoint.HasValue) + .WithMessage("نقطه سفارش مجدد نمی‌تواند منفی باشد"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemResponseDto.cs new file mode 100644 index 0000000..8661db3 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryItem/UpdateInventoryItemResponseDto.cs @@ -0,0 +1,14 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem; + +public class UpdateInventoryItemResponseDto +{ + public long Id { get; set; } + public long ProductId { get; set; } + public long WarehouseId { get; set; } + public int Quantity { get; set; } + public int ReservedQuantity { get; set; } + public int AvailableQuantity { get; set; } + public int MinimumStock { get; set; } + public int MaximumStock { get; set; } + public int ReorderPoint { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommand.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommand.cs new file mode 100644 index 0000000..1a889a9 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommand.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity; + +/// +/// Command برای آپدیت کردن موجودی یک آیتم +/// +public record UpdateInventoryQuantityCommand : IRequest +{ + /// شناسه آیتم موجودی + public long Id { get; init; } + /// تعداد جدید موجودی + public int NewQuantity { get; init; } + /// شماره مرجع + public string? ReferenceNumber { get; init; } + /// شناسه کاربر انجام‌دهنده + public long? PerformedByUserId { get; init; } + /// یادداشت + public string? Note { get; init; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommandHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommandHandler.cs new file mode 100644 index 0000000..9a83fa9 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommandHandler.cs @@ -0,0 +1,56 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity; + +public class UpdateInventoryQuantityCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateInventoryQuantityCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateInventoryQuantityCommand request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken); + + if (item == null) + { + throw new NotFoundException(nameof(Domain.Entities.InventoryItem), request.Id); + } + + var previousQuantity = item.Quantity; + var difference = request.NewQuantity - previousQuantity; + + item.Quantity = request.NewQuantity; + + // ثبت حرکت موجودی + var movementType = difference > 0 ? StockMovementType.AdjustmentPlus : StockMovementType.AdjustmentMinus; + + var stockMovement = new StockMovement + { + InventoryItemId = item.Id, + MovementType = movementType, + Quantity = Math.Abs(difference), + QuantityBefore = previousQuantity, + QuantityAfter = request.NewQuantity, + Note = request.Note ?? "Manual quantity adjustment", + ReferenceNumber = request.ReferenceNumber ?? $"ADJ-{DateTime.UtcNow:yyyyMMddHHmmss}", + PerformedByUserId = request.PerformedByUserId + }; + + await _context.StockMovements.AddAsync(stockMovement, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + return new UpdateInventoryQuantityResponseDto + { + Success = true, + PreviousQuantity = previousQuantity, + NewQuantity = request.NewQuantity + }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommandValidator.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommandValidator.cs new file mode 100644 index 0000000..b687e07 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityCommandValidator.cs @@ -0,0 +1,13 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity; + +public class UpdateInventoryQuantityCommandValidator : AbstractValidator +{ + public UpdateInventoryQuantityCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + + RuleFor(x => x.NewQuantity) + .GreaterThanOrEqualTo(0).WithMessage("تعداد موجودی نمی‌تواند منفی باشد"); + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityResponseDto.cs new file mode 100644 index 0000000..5ef94a7 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Commands/UpdateInventoryQuantity/UpdateInventoryQuantityResponseDto.cs @@ -0,0 +1,8 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity; + +public class UpdateInventoryQuantityResponseDto +{ + public bool Success { get; set; } + public int PreviousQuantity { get; set; } + public int NewQuantity { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsQuery.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsQuery.cs new file mode 100644 index 0000000..93159f9 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsQuery.cs @@ -0,0 +1,20 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems; + +/// +/// Query برای جستجوی آیتم های موجودی +/// +public record GetAllInventoryItemsQuery : IRequest +{ + public long? WarehouseId { get; init; } + public long? ProductId { get; init; } + public long? DiscountProductId { get; init; } + public ProductType? ProductType { get; init; } + public string? SearchTerm { get; init; } + public bool? IsActive { get; init; } + public bool? IsLowStock { get; init; } + public bool? IsOutOfStock { get; init; } + public int Skip { get; init; } = 0; + public int Take { get; init; } = 50; +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsQueryHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsQueryHandler.cs new file mode 100644 index 0000000..e7028e3 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsQueryHandler.cs @@ -0,0 +1,97 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems; + +public class GetAllInventoryItemsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllInventoryItemsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllInventoryItemsQuery request, CancellationToken cancellationToken) + { + var query = _context.InventoryItems + .AsNoTracking() + .Include(i => i.Warehouse) + .Include(i => i.Product) + .Include(i => i.DiscountProduct) + .AsQueryable(); + + // فیلترها + if (request.WarehouseId.HasValue) + { + query = query.Where(i => i.WarehouseId == request.WarehouseId.Value); + } + + if (request.ProductId.HasValue) + { + query = query.Where(i => i.ProductId == request.ProductId.Value); + } + + if (request.DiscountProductId.HasValue) + { + query = query.Where(i => i.DiscountProductId == request.DiscountProductId.Value); + } + + if (request.ProductType.HasValue) + { + query = query.Where(i => i.ProductType == request.ProductType.Value); + } + + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + query = query.Where(i => + (i.Product != null && i.Product.Title.Contains(request.SearchTerm)) || + (i.DiscountProduct != null && i.DiscountProduct.Title.Contains(request.SearchTerm))); + } + + if (request.IsActive.HasValue) + { + // فعلاً بدون فیلتر IsActive چون entity این فیلد رو نداره + } + + if (request.IsLowStock == true) + { + query = query.Where(i => i.Quantity <= i.LowStockThreshold); + } + + if (request.IsOutOfStock == true) + { + query = query.Where(i => i.AvailableQuantity <= 0); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var items = await query + .OrderByDescending(i => i.Created) + .Skip(request.Skip) + .Take(request.Take) + .Select(i => new InventoryItemListDto + { + Id = i.Id, + ProductId = i.ProductId, + DiscountProductId = i.DiscountProductId, + ProductType = i.ProductType, + WarehouseId = i.WarehouseId, + WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null, + ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null), + ProductPrice = i.Product != null ? i.Product.Price : (i.DiscountProduct != null ? i.DiscountProduct.Price : 0), + Quantity = i.Quantity, + ReservedQuantity = i.ReservedQuantity, + AvailableQuantity = i.AvailableQuantity, + LowStockThreshold = i.LowStockThreshold, + MaxStockLevel = i.MaxStockLevel, + LastRestockedAt = i.LastRestockedAt, + LastSoldAt = i.LastSoldAt, + Created = i.Created + }) + .ToListAsync(cancellationToken); + + return new GetAllInventoryItemsResponseDto + { + Items = items, + TotalCount = totalCount + }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsResponseDto.cs new file mode 100644 index 0000000..e80c61a --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetAllInventoryItems/GetAllInventoryItemsResponseDto.cs @@ -0,0 +1,30 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems; + +public class GetAllInventoryItemsResponseDto +{ + public List Items { get; set; } = new(); + public int TotalCount { get; set; } +} + +public class InventoryItemListDto +{ + public long Id { get; set; } + public long? ProductId { get; set; } + public long? DiscountProductId { get; set; } + public ProductType ProductType { get; set; } + public long WarehouseId { get; set; } + public string? WarehouseName { get; set; } + public string? ProductTitle { get; set; } + public decimal? ProductPrice { get; set; } + public int Quantity { get; set; } + public int ReservedQuantity { get; set; } + public int AvailableQuantity { get; set; } + public int LowStockThreshold { get; set; } + public int MaxStockLevel { get; set; } + public DateTime? LastRestockedAt { get; set; } + public DateTime? LastSoldAt { get; set; } + public bool IsActive { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductQuery.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductQuery.cs new file mode 100644 index 0000000..49a2ec6 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductQuery.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct; + +/// +/// Query برای دریافت آیتم موجودی با ProductId یا DiscountProductId +/// +public record GetInventoryByProductQuery : IRequest +{ + /// شناسه محصول + public long ProductId { get; init; } + /// نوع محصول + public ProductType ProductType { get; init; } + /// شناسه انبار (اختیاری) + public long? WarehouseId { get; init; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductQueryHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductQueryHandler.cs new file mode 100644 index 0000000..3d833db --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductQueryHandler.cs @@ -0,0 +1,63 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct; + +public class GetInventoryByProductQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetInventoryByProductQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetInventoryByProductQuery request, CancellationToken cancellationToken) + { + var query = _context.InventoryItems + .AsNoTracking() + .Include(i => i.Warehouse) + .Include(i => i.Product) + .Include(i => i.DiscountProduct) + .AsQueryable(); + + // فیلتر بر اساس نوع محصول + if (request.ProductType == ProductType.RegularProduct) + { + query = query.Where(i => i.ProductId == request.ProductId); + } + else + { + query = query.Where(i => i.DiscountProductId == request.ProductId); + } + + // فیلتر بر اساس انبار (اختیاری) + if (request.WarehouseId.HasValue) + { + query = query.Where(i => i.WarehouseId == request.WarehouseId.Value); + } + + var item = await query + .Select(i => new GetInventoryByProductResponseDto + { + Id = i.Id, + ProductId = i.ProductId, + DiscountProductId = i.DiscountProductId, + ProductType = i.ProductType, + WarehouseId = i.WarehouseId, + WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null, + ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null), + ProductPrice = i.Product != null ? i.Product.Price : (i.DiscountProduct != null ? i.DiscountProduct.Price : 0), + Quantity = i.Quantity, + ReservedQuantity = i.ReservedQuantity, + AvailableQuantity = i.AvailableQuantity, + LowStockThreshold = i.LowStockThreshold, + MaxStockLevel = i.MaxStockLevel, + LastRestockedAt = i.LastRestockedAt, + LastSoldAt = i.LastSoldAt, + Created = i.Created + }) + .FirstOrDefaultAsync(cancellationToken); + + return item; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductResponseDto.cs new file mode 100644 index 0000000..8f228e7 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryByProduct/GetInventoryByProductResponseDto.cs @@ -0,0 +1,23 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct; + +public class GetInventoryByProductResponseDto +{ + public long Id { get; set; } + public long? ProductId { get; set; } + public long? DiscountProductId { get; set; } + public ProductType ProductType { get; set; } + public long WarehouseId { get; set; } + public string? WarehouseName { get; set; } + public string? ProductTitle { get; set; } + public decimal? ProductPrice { get; set; } + public int Quantity { get; set; } + public int ReservedQuantity { get; set; } + public int AvailableQuantity { get; set; } + public int LowStockThreshold { get; set; } + public int MaxStockLevel { get; set; } + public DateTime? LastRestockedAt { get; set; } + public DateTime? LastSoldAt { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemQuery.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemQuery.cs new file mode 100644 index 0000000..055127e --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem; + +/// +/// Query برای دریافت آیتم موجودی با شناسه +/// +public record GetInventoryItemQuery(long Id) : IRequest; diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemQueryHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemQueryHandler.cs new file mode 100644 index 0000000..0d51599 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemQueryHandler.cs @@ -0,0 +1,43 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem; + +public class GetInventoryItemQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetInventoryItemQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetInventoryItemQuery request, CancellationToken cancellationToken) + { + var item = await _context.InventoryItems + .AsNoTracking() + .Include(i => i.Warehouse) + .Include(i => i.Product) + .Include(i => i.DiscountProduct) + .Where(i => i.Id == request.Id) + .Select(i => new GetInventoryItemResponseDto + { + Id = i.Id, + ProductId = i.ProductId, + DiscountProductId = i.DiscountProductId, + ProductType = i.ProductType, + WarehouseId = i.WarehouseId, + WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null, + ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null), + ProductPrice = i.Product != null ? i.Product.Price : (i.DiscountProduct != null ? i.DiscountProduct.Price : 0), + Quantity = i.Quantity, + ReservedQuantity = i.ReservedQuantity, + AvailableQuantity = i.AvailableQuantity, + LowStockThreshold = i.LowStockThreshold, + MaxStockLevel = i.MaxStockLevel, + LastRestockedAt = i.LastRestockedAt, + LastSoldAt = i.LastSoldAt, + Created = i.Created + }) + .FirstOrDefaultAsync(cancellationToken); + + return item; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemResponseDto.cs new file mode 100644 index 0000000..16b9f6d --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetInventoryItem/GetInventoryItemResponseDto.cs @@ -0,0 +1,23 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem; + +public class GetInventoryItemResponseDto +{ + public long Id { get; set; } + public long? ProductId { get; set; } + public long? DiscountProductId { get; set; } + public ProductType ProductType { get; set; } + public long WarehouseId { get; set; } + public string? WarehouseName { get; set; } + public string? ProductTitle { get; set; } + public decimal? ProductPrice { get; set; } + public int Quantity { get; set; } + public int ReservedQuantity { get; set; } + public int AvailableQuantity { get; set; } + public int LowStockThreshold { get; set; } + public int MaxStockLevel { get; set; } + public DateTime? LastRestockedAt { get; set; } + public DateTime? LastSoldAt { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsQuery.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsQuery.cs new file mode 100644 index 0000000..de2d135 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsQuery.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems; + +/// +/// Query برای دریافت آیتم های کم موجود +/// +public record GetLowStockItemsQuery : IRequest +{ + public long? WarehouseId { get; init; } + public int Count { get; init; } = 50; +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsQueryHandler.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsQueryHandler.cs new file mode 100644 index 0000000..531f9a2 --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsQueryHandler.cs @@ -0,0 +1,53 @@ +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems; + +public class GetLowStockItemsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetLowStockItemsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetLowStockItemsQuery request, CancellationToken cancellationToken) + { + var query = _context.InventoryItems + .AsNoTracking() + .Include(i => i.Warehouse) + .Include(i => i.Product) + .Include(i => i.DiscountProduct) + .Where(i => i.Quantity <= i.LowStockThreshold); + + if (request.WarehouseId.HasValue) + { + query = query.Where(i => i.WarehouseId == request.WarehouseId.Value); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var items = await query + .OrderBy(i => i.Quantity - i.ReservedQuantity) + .Take(request.Count) + .Select(i => new LowStockItemDto + { + Id = i.Id, + ProductId = i.ProductId, + DiscountProductId = i.DiscountProductId, + ProductType = i.ProductType, + WarehouseId = i.WarehouseId, + WarehouseName = i.Warehouse != null ? i.Warehouse.Name : null, + ProductTitle = i.Product != null ? i.Product.Title : (i.DiscountProduct != null ? i.DiscountProduct.Title : null), + Quantity = i.Quantity, + ReservedQuantity = i.ReservedQuantity, + AvailableQuantity = i.Quantity - i.ReservedQuantity, + LowStockThreshold = i.LowStockThreshold + }) + .ToListAsync(cancellationToken); + + return new GetLowStockItemsResponseDto + { + Items = items, + TotalCount = totalCount + }; + } +} diff --git a/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsResponseDto.cs b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsResponseDto.cs new file mode 100644 index 0000000..ec8b4da --- /dev/null +++ b/src/CMSMicroservice.Application/InventoryItemCQ/Queries/GetLowStockItems/GetLowStockItemsResponseDto.cs @@ -0,0 +1,24 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems; + +public class GetLowStockItemsResponseDto +{ + public List Items { get; set; } = new(); + public int TotalCount { get; set; } +} + +public class LowStockItemDto +{ + public long Id { get; set; } + public long? ProductId { get; set; } + public long? DiscountProductId { get; set; } + public ProductType ProductType { get; set; } + public long WarehouseId { get; set; } + public string? WarehouseName { get; set; } + public string? ProductTitle { get; set; } + public int Quantity { get; set; } + public int ReservedQuantity { get; set; } + public int AvailableQuantity { get; set; } + public int LowStockThreshold { get; set; } +} diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs index 7bbc290..2229ee3 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs @@ -32,4 +32,14 @@ public class CreateManualPaymentCommand : IRequest /// شماره مرجع یا شماره فیش (اختیاری) /// public string? ReferenceNumber { get; set; } + + /// + /// مسیر تصویر فیش واریزی (اختیاری) + /// + public string? ImagePath { get; set; } + + /// + /// شناسه سند در FMS (اختیاری) + /// + public long? ImageDocumentId { get; set; } } diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs index 2f353de..2b6a7ae 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs @@ -1,5 +1,6 @@ using CMSMicroservice.Application.Common.Exceptions; using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Common; using CMSMicroservice.Domain.Entities; using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Enums; @@ -32,13 +33,24 @@ public class CreateManualPaymentCommandHandler : IRequestHandler u.Id == request.UserId, cancellationToken); @@ -48,47 +60,103 @@ public class CreateManualPaymentCommandHandler : IRequestHandler w.UserId == request.UserId, cancellationToken); + + if (wallet == null) { - throw new UnauthorizedAccessException("کاربر احراز هویت نشده است"); + _logger.LogError("Wallet not found for UserId: {UserId}", request.UserId); + throw new NotFoundException($"کیف پول کاربر {request.UserId} یافت نشد"); } - if (!long.TryParse(currentUserId, out var requestedById)) - { - throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است"); - } + // 4. محاسبه مبالغ + var balanceAmount = SystemConstants.BasePackageAmount; // 56M + var discountBalanceAmount = SystemConstants.BasePackageAmount * 2; // 112M + - // 3. ایجاد ManualPayment + // 5. ثبت تراکنش + var transaction = new Transaction + { + Amount = balanceAmount, + Description = $"عضویت دستی باشگاه مشتریان - {request.Description} - مرجع: {request.ReferenceNumber}", + PaymentStatus = PaymentStatus.Success, + PaymentDate = DateTime.Now, + RefId = request.ReferenceNumber, + Type = TransactionType.DepositExternal1 + }; + + _context.Transactions.Add(transaction); + await _context.SaveChangesAsync(cancellationToken); + + // 6. ایجاد ManualPayment با وضعیت Approved (بدون نیاز به تایید دو مرحله‌ای) var manualPayment = new ManualPayment { UserId = request.UserId, - Amount = request.Amount, + Amount = balanceAmount, Type = request.Type, Description = request.Description, ReferenceNumber = request.ReferenceNumber, - Status = ManualPaymentStatus.Pending, - RequestedBy = requestedById + ImagePath = request.ImagePath, + ImageDocumentId = request.ImageDocumentId, + Status = ManualPaymentStatus.Approved, + RequestedBy = adminUserId, + ApprovedBy = adminUserId, + ApprovedAt = DateTime.Now, + TransactionId = transaction.Id }; _context.ManualPayments.Add(manualPayment); + + // 7. اعمال تغییرات بر کیف پول + var oldBalance = wallet.Balance; + var oldDiscountBalance = wallet.DiscountBalance; + + wallet.Balance += balanceAmount; // +56M + wallet.DiscountBalance += discountBalanceAmount; // +112M + + // 8. ثبت لاگ کیف پول + var walletLog = new UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = 0, + ChangeValue = balanceAmount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance =0, + ChangeDiscountValue = discountBalanceAmount, + IsIncrease = true, + RefrenceId = transaction.Id + }; + + await _context.UserWalletChangeLogs.AddAsync(walletLog, cancellationToken); + + // 9. تنظیم روش خرید پکیج + user.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase; + + // 10. ذخیره همه تغییرات await _context.SaveChangesAsync(cancellationToken); _logger.LogInformation( - "Manual payment created successfully. Id: {Id}, UserId: {UserId}, RequestedBy: {RequestedBy}", + "Manual membership payment created successfully. " + + "ManualPaymentId: {Id}, UserId: {UserId}, TransactionId: {TransactionId}, " + + "Balance: {OldBalance} -> {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}", manualPayment.Id, request.UserId, - requestedById + transaction.Id, + oldBalance, + wallet.Balance, + oldDiscountBalance, + wallet.DiscountBalance ); return manualPayment.Id; } - catch (Exception ex) + catch (Exception ex) when (ex is not NotFoundException && ex is not UnauthorizedAccessException) { _logger.LogError( ex, - "Error creating manual payment for UserId: {UserId}", + "Error creating manual membership payment for UserId: {UserId}", request.UserId ); throw; diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs index e36f50a..60ebda5 100644 --- a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs @@ -118,58 +118,19 @@ public class ProcessManualMembershipPaymentCommandHandler : IRequestHandler a.UserId == request.UserId) - .OrderByDescending(a => a.IsDefault) - .ThenBy(a => a.Id) - .FirstOrDefaultAsync(cancellationToken); - - if (userAddress == null) - { - userAddress = new UserAddress - { - UserId = request.UserId, - Title = "آدرس پیشفرض", - Address = "پرداخت دستی عضویت - آدرس موقت", - PostalCode = "0000000000", - IsDefault = true, - CityId = 1 - }; - await _context.UserAddresses.AddAsync(userAddress, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - } - - // 12. ثبت سفارش - var order = new UserOrder - { - UserId = request.UserId, - Amount = request.Amount, - TransactionId = transaction.Id, - PaymentStatus = PaymentStatus.Success, - PaymentDate = DateTime.Now, - PaymentMethod = PaymentMethod.Deposit, - DeliveryStatus = DeliveryStatus.None, - UserAddressId = userAddress.Id, - DeliveryDescription = $"پرداخت دستی عضویت - مرجع: {request.ReferenceNumber}" - }; - - _context.UserOrders.Add(order); - await _context.SaveChangesAsync(cancellationToken); _logger.LogInformation( - "Manual membership payment processed successfully. UserId: {UserId}, Amount: {Amount}, ManualPaymentId: {ManualPaymentId}, TransactionId: {TransactionId}, OrderId: {OrderId}, AdminUserId: {AdminUserId}", - request.UserId, request.Amount, manualPayment.Id, transaction.Id, order.Id, adminUserId); + "Manual membership payment processed successfully. UserId: {UserId}, Amount: {Amount}, ManualPaymentId: {ManualPaymentId}, TransactionId: {TransactionId}, AdminUserId: {AdminUserId}", + request.UserId, request.Amount, manualPayment.Id, transaction.Id, adminUserId); return new ProcessManualMembershipPaymentResponseDto { TransactionId = transaction.Id, - OrderId = order.Id, NewWalletBalance = wallet.Balance, Message = "پرداخت دستی با موفقیت ثبت شد" }; diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQuery.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQuery.cs new file mode 100644 index 0000000..0862b03 --- /dev/null +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQuery.cs @@ -0,0 +1,15 @@ +using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree; + +namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree; + +/// +/// Query برای دریافت درخت شبکه کاربر جاری (Customer-facing) +/// از ICurrentUserService برای دریافت UserId استفاده می‌کند +/// +public record GetMyNetworkTreeQuery : IRequest +{ + /// + /// تعداد سطوح (Depth) که می‌خواهیم نمایش دهیم (پیش‌فرض: 3) + /// + public int MaxDepth { get; init; } = 3; +} diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQueryHandler.cs new file mode 100644 index 0000000..5262b42 --- /dev/null +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQueryHandler.cs @@ -0,0 +1,40 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree; + +namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree; + +/// +/// Handler برای دریافت درخت شبکه کاربر جاری +/// +public class GetMyNetworkTreeQueryHandler : IRequestHandler +{ + private readonly ICurrentUserService _currentUser; + private readonly ISender _sender; + + public GetMyNetworkTreeQueryHandler( + ICurrentUserService currentUser, + ISender sender) + { + _currentUser = currentUser; + _sender = sender; + } + + public async Task Handle(GetMyNetworkTreeQuery request, CancellationToken cancellationToken) + { + // دریافت UserId از JWT + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // استفاده از GetNetworkTreeQuery موجود با UserId از JWT + var query = new GetNetworkTreeQuery + { + UserId = userId, + MaxDepth = request.MaxDepth > 0 ? request.MaxDepth : 3 + }; + + return await _sender.Send(query, cancellationToken); + } +} diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQueryValidator.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQueryValidator.cs new file mode 100644 index 0000000..a34fc46 --- /dev/null +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQueryValidator.cs @@ -0,0 +1,24 @@ +namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree; + +public class GetMyNetworkTreeQueryValidator : AbstractValidator +{ + public GetMyNetworkTreeQueryValidator() + { + RuleFor(x => x.MaxDepth) + .InclusiveBetween(1, 100) + .WithMessage("عمق درخت باید بین 1 تا 100 باشد"); + } + + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (GetMyNetworkTreeQuery)model, + x => x.IncludeProperties(propertyName))); + + if (result.IsValid) + return Array.Empty(); + + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs index 6dcf1c5..da5b186 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs @@ -2,5 +2,8 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStat public class GetNetworkStatisticsQuery : IRequest { - // No parameters - returns overall statistics + /// + /// شناسه کاربر برای محاسبه آمار شبکه او - 0 یعنی آمار کل شبکه (root user) + /// + public long UserId { get; set; } } diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs index 82e6919..903a370 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs @@ -6,60 +6,81 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler Handle(GetNetworkStatisticsQuery request, CancellationToken cancellationToken) { - // Basic statistics - using Users table with NetworkParentId - var totalMembers = await _context.Users - .Where(x => x.NetworkParentId != null) - .CountAsync(cancellationToken); - - var activeMembers = await _context.Users - .Where(x => x.NetworkParentId != null) - .CountAsync(cancellationToken); + // UserId > 0 → stats for that user's network + // UserId == 0 → global stats (find root user of the network) + var userId = request.UserId; - var leftLegCount = await _context.Users - .Where(x => x.LegPosition == NetworkLeg.Left) - .CountAsync(cancellationToken); + // Get all users (needed for descendant calculation anyway) + var allUsers = await _context.Users.ToListAsync(cancellationToken); - var rightLegCount = await _context.Users - .Where(x => x.LegPosition == NetworkLeg.Right) - .CountAsync(cancellationToken); + if (userId == 0) + { + // Find root user (user with no NetworkParentId) + var rootUser = allUsers.FirstOrDefault(x => x.NetworkParentId == null || x.NetworkParentId == 0); + if (rootUser != null) + userId = rootUser.Id; + else if (allUsers.Count > 0) + userId = allUsers.First().Id; + else + throw new InvalidOperationException("No users found in the system"); + } + var allDescendants = GetAllDescendants(userId, allUsers); + + // Statistics for the user's network (all descendants) + var totalMembers = allDescendants.Count; + var activeMembers = allDescendants.Count(x => !x.IsDeleted); + + // Get direct left and right children + var leftChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left); + var rightChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right); + + // Count all descendants in left and right subtrees + var leftLegCount = leftChild != null ? GetAllDescendants(leftChild.Id, allUsers).Count + 1 : 0; // +1 for leftChild itself + var rightLegCount = rightChild != null ? GetAllDescendants(rightChild.Id, allUsers).Count + 1 : 0; // +1 for rightChild itself double leftPercentage = totalMembers > 0 ? (leftLegCount / (double)totalMembers) * 100 : 0; double rightPercentage = totalMembers > 0 ? (rightLegCount / (double)totalMembers) * 100 : 0; - // Calculate depth based on network parent relationships - // For simplicity, we'll estimate average depth as 3-5 levels - double averageDepth = 4.5; // Estimated average - int maxDepth = 10; // Estimated max depth - - // Level distribution - simplified estimation based on growth pattern - var levelDistribution = new List(); - if (totalMembers > 0) + // Calculate actual depth + int maxDepth = 0; + double totalDepthSum = 0; + var userDepths = new Dictionary(); + CalculateDepths(userId, allUsers, 0, userDepths, ref maxDepth); + + if (allDescendants.Count > 0) { - // Approximate distribution: Level 1 (10%), Level 2 (20%), Level 3 (30%), Level 4 (20%), Level 5+ (20%) - levelDistribution = new List - { - new() { Level = 1, Count = (int)(totalMembers * 0.1) }, - new() { Level = 2, Count = (int)(totalMembers * 0.2) }, - new() { Level = 3, Count = (int)(totalMembers * 0.3) }, - new() { Level = 4, Count = (int)(totalMembers * 0.2) }, - new() { Level = 5, Count = (int)(totalMembers * 0.15) }, - new() { Level = 6, Count = totalMembers - (int)(totalMembers * 0.95) } - }; + totalDepthSum = allDescendants.Sum(d => userDepths.ContainsKey(d.Id) ? userDepths[d.Id] : 0); + } + double averageDepth = allDescendants.Count > 0 ? totalDepthSum / allDescendants.Count : 0; + + // Level distribution - calculate from depths + var levelDistribution = new List(); + if (allDescendants.Count > 0) + { + var levelCounts = allDescendants + .Where(d => userDepths.ContainsKey(d.Id)) + .GroupBy(d => userDepths[d.Id]) + .OrderBy(g => g.Key) + .Select(g => new LevelDistributionModel { Level = g.Key, Count = g.Count() }) + .ToList(); + + levelDistribution = levelCounts; } - // Monthly growth (last 6 months) - using Created date + // Monthly growth (last 6 months) - using descendants Created date var sixMonthsAgo = DateTime.Now.AddMonths(-6); - var monthlyGrowthRaw = await _context.Users - .Where(x => x.NetworkParentId != null && x.Created >= sixMonthsAgo) + var monthlyGrowthRaw = allDescendants + .Where(x => x.Created >= sixMonthsAgo) .Select(x => new { x.Created.Year, x.Created.Month }) - .ToListAsync(cancellationToken); + .ToList(); var monthlyGrowth = monthlyGrowthRaw .GroupBy(x => new { x.Year, x.Month }) @@ -71,27 +92,34 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler x.Month) .ToList(); - // Top users by total children count - var topUsers = await _context.Users - .Where(x => x.NetworkParentId != null) + // Top users by total descendants count + var userDescendantCounts = new Dictionary(); + foreach (var user in allDescendants) + { + var descendants = GetAllDescendants(user.Id, allUsers); + userDescendantCounts[user.Id] = descendants.Count; + } + + var topUserData = allDescendants + .Where(x => x.Id != userId && userDescendantCounts[x.Id] > 0) .Select(x => new { x.Id, UserName = (x.FirstName + " " + x.LastName).Trim(), - LeftCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left), - RightCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right) + DescendantCount = userDescendantCounts[x.Id], + LeftCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left), + RightCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right) }) - .Where(x => x.LeftCount + x.RightCount > 0) - .OrderByDescending(x => x.LeftCount + x.RightCount) + .OrderByDescending(x => x.DescendantCount) .Take(10) - .ToListAsync(cancellationToken); + .ToList(); - var topUserModels = topUsers.Select((x, index) => new TopNetworkUserModel + var topUserModels = topUserData.Select((x, index) => new TopNetworkUserModel { Rank = index + 1, UserId = x.Id, UserName = x.UserName, - TotalChildren = x.LeftCount + x.RightCount, + TotalChildren = x.DescendantCount, LeftCount = x.LeftCount, RightCount = x.RightCount }).ToList(); @@ -111,4 +139,40 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler + /// Recursively get all descendants of a user + /// + private List GetAllDescendants(long userId, List allUsers) + { + var descendants = new List(); + var directChildren = allUsers.Where(x => x.NetworkParentId == userId).ToList(); + + foreach (var child in directChildren) + { + descendants.Add(child); + descendants.AddRange(GetAllDescendants(child.Id, allUsers)); + } + + return descendants; + } + + /// + /// Calculate depth for all descendants recursively + /// + private void CalculateDepths(long userId, List allUsers, int currentDepth, Dictionary depths, ref int maxDepth) + { + var children = allUsers.Where(x => x.NetworkParentId == userId).ToList(); + + foreach (var child in children) + { + var childDepth = currentDepth + 1; + depths[child.Id] = childDepth; + + if (childDepth > maxDepth) + maxDepth = childDepth; + + CalculateDepths(child.Id, allUsers, childDepth, depths, ref maxDepth); + } + } } diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs index 23e00ea..98236a8 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs @@ -19,10 +19,21 @@ public class GetNetworkTreeQueryHandler : IRequestHandler Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken) { + // UserId must be provided - customer endpoints resolve from JWT before calling + var userId = request.UserId; + + if (userId == 0) + { + throw new ArgumentException("UserId is required for network tree query"); + } + + // Create a new request with the resolved userId + var resolvedRequest = request with { UserId = userId }; + try { // دریافت نتایج flat از Stored Procedure - var flatNodes = await ExecuteStoredProcedureAsync(request, cancellationToken); + var flatNodes = await ExecuteStoredProcedureAsync(resolvedRequest, cancellationToken); if (flatNodes == null || !flatNodes.Any()) { diff --git a/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommand.cs b/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommand.cs index dc2181b..97cdbf3 100644 --- a/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommand.cs +++ b/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommand.cs @@ -5,5 +5,8 @@ public record CreateNewOtpTokenCommand : IRequest public string Mobile { get; init; } //مقصود public string Purpose { get; init; } - + /// + /// شناسه GUID قرارداد (فقط برای purpose=signcontract/signclubcontract) + /// + public string? SignGuid { get; init; } } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs b/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs index fc4314f..745790b 100644 --- a/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs +++ b/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs @@ -57,7 +57,7 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler { private readonly ILogger _logger; + private readonly IKavenegarService _kavenegarService; - public CreateNewOtpTokenEventHandler(ILogger logger) + public CreateNewOtpTokenEventHandler( + ILogger logger, + IKavenegarService kavenegarService) { _logger = logger; + _kavenegarService = kavenegarService; } - public Task Handle(CreateNewOtpTokenEvent notification, CancellationToken cancellationToken) + public async Task Handle(CreateNewOtpTokenEvent notification, CancellationToken cancellationToken) { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); + _logger.LogInformation("Domain Event: {DomainEvent} for mobile {Mobile}", + notification.GetType().Name, notification.Item.Mobile); - return Task.CompletedTask; + try + { + var purpose = notification.Item.Purpose?.ToLowerInvariant(); + + // برای امضای قرارداد، پیامک ساده با GUID ارسال شود + if ((purpose == "signcontract" || purpose == "signclubcontract") + && !string.IsNullOrEmpty(notification.SignGuid)) + { + var message = $"کد تایید امضای قرارداد: {notification.PlainCode}\nشناسه قرارداد: {notification.SignGuid}"; + await _kavenegarService.SendAsync(notification.Item.Mobile, message); + } + else + { + await _kavenegarService.VerifyLookupAsync(notification.Item.Mobile, notification.PlainCode); + } + + _logger.LogInformation("OTP SMS sent successfully to {Mobile}", notification.Item.Mobile); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send OTP SMS to {Mobile}", notification.Item.Mobile); + } } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs index 2f7bd0e..3427031 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchasePackage/PurchasePackageCommandHandler.cs @@ -5,6 +5,7 @@ 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; @@ -15,15 +16,18 @@ public class PurchasePackageCommandHandler { 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; } @@ -116,7 +120,7 @@ public class PurchasePackageCommandHandler Amount = order.Amount, UserId = user.Id, Mobile = user.Mobile ?? "", - CallbackUrl = $"https://yourdomain.com/api/package/verify-package", + CallbackUrl = $"{_configuration["CmsBaseUrl"] ?? "https://localhost:32846"}/api/package/verify-package", Description = $"خرید پکیج - سفارش #{order.Id}" }; diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQuery.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQuery.cs new file mode 100644 index 0000000..27b9e97 --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails; + +public class GetCustomerPackageDetailsQuery : IRequest +{ + public long PackageId { get; set; } +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs new file mode 100644 index 0000000..b4122ea --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs @@ -0,0 +1,68 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using Mapster; + +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails; + +public class GetCustomerPackageDetailsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetCustomerPackageDetailsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetCustomerPackageDetailsQuery request, CancellationToken cancellationToken) + { + var package = await _context.Packages + .AsNoTracking() + .Where(x => x.Id == request.PackageId) + .ProjectToType() + .FirstOrDefaultAsync(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 + { + 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 + } + }; + + // Set purchase requirements + package.Requirements = new PurchaseRequirementsDto + { + RequiresMembership = false, + MinimumWalletBalance = package.Price / 10, // 10% minimum + Restrictions = new List + { + "باید حداقل 18 سال سن داشته باشید", + "تایید هویت الزامی است" + } + }; + + return package; + } +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsResponseDto.cs new file mode 100644 index 0000000..53f3b84 --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsResponseDto.cs @@ -0,0 +1,27 @@ +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails; + +public class GetCustomerPackageDetailsResponseDto +{ + public long Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public long Price { get; set; } + public string ImagePath { get; set; } + public List Features { get; set; } = new(); + public PurchaseRequirementsDto Requirements { get; set; } +} + +public class PackageFeatureDto +{ + public string Title { get; set; } + public string Description { get; set; } + public string Icon { get; set; } + public bool IsHighlighted { get; set; } +} + +public class PurchaseRequirementsDto +{ + public bool RequiresMembership { get; set; } + public long MinimumWalletBalance { get; set; } + public List Restrictions { get; set; } = new(); +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQuery.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQuery.cs new file mode 100644 index 0000000..5fa8fff --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQuery.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages; + +public class GetCustomerPackagesQuery : IRequest> +{ + public bool IncludeInactive { get; set; } + public int? PackageTypeFilter { get; set; } +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs new file mode 100644 index 0000000..6374498 --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using Mapster; + +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages; + +public class GetCustomerPackagesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetCustomerPackagesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetCustomerPackagesQuery request, CancellationToken cancellationToken) + { + var query = _context.Packages + .AsNoTracking() + .AsQueryable(); + + // Filter by PackageType if specified + if (request.PackageTypeFilter.HasValue) + { + // 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; + } +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs new file mode 100644 index 0000000..dfbf55d --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages; + +public class GetCustomerPackagesResponseDto +{ + public long Id { get; set; } + public string Name { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public long Price { get; set; } + public string Currency { get; set; } = "IRR"; + public int PackageType { get; set; } + public bool IsAvailable { get; set; } = true; + public string ImageUrl { get; set; } + public string ImagePath { get; set; } + public int ValidityDays { get; set; } + public bool IsPopular { get; set; } + public string ShortDescription { get; set; } +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQuery.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQuery.cs new file mode 100644 index 0000000..148fb79 --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQuery.cs @@ -0,0 +1,12 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory; + +public class GetCustomerPurchaseHistoryQuery : IRequest +{ + public long UserId { get; set; } + public PaginationState PaginationState { get; set; } + public int? PackageTypeFilter { get; set; } + public DateTime? FromDate { get; set; } + public DateTime? ToDate { get; set; } +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs new file mode 100644 index 0000000..c923293 --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs @@ -0,0 +1,90 @@ +using CMSMicroservice.Application.Common.Extensions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; +using Mapster; + +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory; + +public class GetCustomerPurchaseHistoryQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerPurchaseHistoryQueryHandler( + IApplicationDbContext context, + ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerPurchaseHistoryQuery request, CancellationToken cancellationToken) + { + // Resolve UserId from JWT if not specified + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found"); + + var query = _context.UserOrders + .AsNoTracking() + .Where(x => x.UserId == userId && x.PackageId != null) + .Include(x => x.Package) + .AsQueryable(); + + // Apply date filters if specified + if (request.FromDate.HasValue) + query = query.Where(x => x.Created >= request.FromDate.Value); + + if (request.ToDate.HasValue) + query = query.Where(x => x.Created <= request.ToDate.Value); + + // Apply PackageType filter if needed (Package entity doesn't have Type enum currently) + // This would require Package entity to have a PackageType field + + // Order by most recent first + query = query.OrderByDescending(x => x.Created); + + // Get metadata + var metaData = await query.GetMetaData(request.PaginationState, cancellationToken); + + // Get paginated results + var orders = await query + .PaginatedListAsync(request.PaginationState) + .ToListAsync(cancellationToken); + + var purchases = orders.Select(order => new PackagePurchaseHistoryDto + { + Id = order.Id, + PackageId = order.PackageId ?? 0, + PackageName = order.Package?.Title ?? "نامشخص", + Amount = order.Amount, + PackageType = 0, // Default, needs Package.PackageType field + PurchaseDate = order.Created, + ExpiryDate = order.PaymentDate?.AddDays(365), // Assuming 1 year validity + Status = order.PaymentStatus, + StatusMessage = GetStatusMessage(order.PaymentStatus), + ReferenceCode = order.Transaction?.RefId ?? order.Id.ToString() + }).ToList(); + + return new GetCustomerPurchaseHistoryResponseDto + { + MetaData = metaData, + Purchases = purchases + }; + } + + private string GetStatusMessage(PaymentStatus status) + { + return status switch + { + PaymentStatus.Pending => "در انتظار پرداخت", + PaymentStatus.Success => "فعال", + PaymentStatus.Reject => "رد شده", + _ => "نامشخص" + }; + } +} diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryResponseDto.cs new file mode 100644 index 0000000..f89f82e --- /dev/null +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryResponseDto.cs @@ -0,0 +1,24 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory; + +public class GetCustomerPurchaseHistoryResponseDto +{ + public MetaData MetaData { get; set; } + public List Purchases { get; set; } = new(); +} + +public class PackagePurchaseHistoryDto +{ + public long Id { get; set; } + public long PackageId { get; set; } + public string PackageName { get; set; } + public long Amount { get; set; } + public int PackageType { get; set; } + public DateTime PurchaseDate { get; set; } + public DateTime? ExpiryDate { get; set; } + public PaymentStatus Status { get; set; } + public string StatusMessage { get; set; } + public string ReferenceCode { get; set; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommand.cs new file mode 100644 index 0000000..eb366c0 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommand.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.ProductsCQ.Commands.AddProductImage; + +public record AddProductImageCommand : IRequest +{ + public long ProductId { get; init; } + public string Title { get; init; } = string.Empty; + public byte[]? ImageFileBytes { get; init; } + public string? ImageFileMime { get; init; } + public string? ImageFileName { get; init; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommandHandler.cs new file mode 100644 index 0000000..093cbbd --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommandHandler.cs @@ -0,0 +1,75 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.ProductsCQ.Commands.AddProductImage; + +public class AddProductImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + private readonly ILogger _logger; + + public AddProductImageCommandHandler( + IApplicationDbContext context, + IFileManager fileManager, + ILogger logger) + { + _context = context; + _fileManager = fileManager; + _logger = logger; + } + + public async Task Handle(AddProductImageCommand request, + CancellationToken cancellationToken) + { + // بررسی وجود محصول + var productExists = await _context.Products + .AnyAsync(p => p.Id == request.ProductId, cancellationToken); + if (!productExists) + throw new NotFoundException(nameof(Product), request.ProductId); + + // بدون فایل تصویر، کاری انجام نمی‌شود + if (request.ImageFileBytes is not { Length: > 0 }) + throw new FileUploadException("فایل تصویر ارسال نشده است"); + + // آپلود تصویر به FMS (اگر خطا بخوره، exception پرتاب می‌شه و entity ذخیره نمیشه) + var uploaded = await _fileManager.UploadImageAsync( + "Images/Products/Gallery", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + // ساخت رکورد تصویر + var productImage = new ProductImage + { + Title = request.Title, + ImagePath = uploaded.Main.Path, + ImageThumbnailPath = uploaded.Thumbnail.Path + }; + + await _context.ProductImages.AddAsync(productImage, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + // اتصال تصویر به محصول + var productGallery = new ProductGallery + { + ProductId = request.ProductId, + ProductImageId = productImage.Id + }; + + await _context.ProductGalleries.AddAsync(productGallery, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + return new AddProductImageResponseDto + { + ProductGalleryId = productGallery.Id, + ProductImageId = productImage.Id, + Title = productImage.Title, + ImagePath = productImage.ImagePath, + ImageThumbnailPath = productImage.ImageThumbnailPath + }; + } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageResponseDto.cs new file mode 100644 index 0000000..db6245b --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/AddProductImage/AddProductImageResponseDto.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.ProductsCQ.Commands.AddProductImage; + +public class AddProductImageResponseDto +{ + public long ProductGalleryId { get; set; } + public long ProductImageId { get; set; } + public string Title { get; set; } = string.Empty; + public string ImagePath { get; set; } = string.Empty; + public string ImageThumbnailPath { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommand.cs deleted file mode 100644 index 0ba6b4f..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommand.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices; - -/// -/// به‌روزرسانی دسته‌ای قیمت محصولات -/// -public record BulkUpdateProductPricesCommand : IRequest -{ - /// - /// لیست محصولات و قیمت‌های جدید - /// - public List Products { get; init; } = new(); -} - -/// -/// مدل به‌روزرسانی قیمت یک محصول -/// -public class ProductPriceUpdate -{ - /// - /// شناسه محصول - /// - public long ProductId { get; set; } - - /// - /// قیمت جدید (ریال) - /// - public long NewPrice { get; set; } - - /// - /// درصد تخفیف جدید (اختیاری) - /// - public int? NewDiscount { get; set; } - - /// - /// درصد تخفیف باشگاه جدید (اختیاری) - /// - public int? NewClubDiscountPercent { get; set; } -} - -/// -/// پاسخ به‌روزرسانی دسته‌ای قیمت -/// -public class BulkUpdateProductPricesResponseDto -{ - /// - /// تعداد محصولات به‌روزرسانی شده - /// - public int UpdatedCount { get; set; } - - /// - /// تعداد محصولات ناموفق - /// - public int FailedCount { get; set; } - - /// - /// جزئیات خطاها - /// - public List Errors { get; set; } = new(); - - /// - /// آیا همه موفق بودند - /// - public bool IsSuccess => FailedCount == 0; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandHandler.cs deleted file mode 100644 index 46527cf..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandHandler.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices; - -public class BulkUpdateProductPricesCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public BulkUpdateProductPricesCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(BulkUpdateProductPricesCommand request, CancellationToken cancellationToken) - { - var response = new BulkUpdateProductPricesResponseDto(); - var productIds = request.Products.Select(p => p.ProductId).ToList(); - - // دریافت محصولات از دیتابیس - var products = await _context.Products - .Where(p => productIds.Contains(p.Id)) - .ToListAsync(cancellationToken); - - var productDict = products.ToDictionary(p => p.Id); - - foreach (var update in request.Products) - { - try - { - if (!productDict.TryGetValue(update.ProductId, out var product)) - { - response.FailedCount++; - response.Errors.Add($"محصول با شناسه {update.ProductId} یافت نشد"); - continue; - } - - // به‌روزرسانی قیمت - product.Price = update.NewPrice; - - // به‌روزرسانی تخفیف (اگر ارسال شده باشد) - if (update.NewDiscount.HasValue) - { - product.Discount = update.NewDiscount.Value; - } - - // به‌روزرسانی تخفیف باشگاه (اگر ارسال شده باشد) - if (update.NewClubDiscountPercent.HasValue) - { - product.ClubDiscountPercent = update.NewClubDiscountPercent.Value; - } - - response.UpdatedCount++; - - _logger.LogInformation( - "Product {ProductId} price updated to {NewPrice} (Discount: {Discount}%, ClubDiscount: {ClubDiscount}%)", - product.Id, product.Price, product.Discount, product.ClubDiscountPercent); - } - catch (Exception ex) - { - response.FailedCount++; - response.Errors.Add($"خطا در به‌روزرسانی محصول {update.ProductId}: {ex.Message}"); - _logger.LogError(ex, "Error updating product {ProductId} price", update.ProductId); - } - } - - if (response.UpdatedCount > 0) - { - await _context.SaveChangesAsync(cancellationToken); - _logger.LogInformation("Bulk price update completed: {UpdatedCount} succeeded, {FailedCount} failed", - response.UpdatedCount, response.FailedCount); - } - - return response; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandValidator.cs deleted file mode 100644 index 2bb5728..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandValidator.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices; - -public class BulkUpdateProductPricesCommandValidator : AbstractValidator -{ - public BulkUpdateProductPricesCommandValidator() - { - RuleFor(x => x.Products) - .NotEmpty().WithMessage("لیست محصولات نمی‌تواند خالی باشد") - .Must(x => x.Count <= 100).WithMessage("حداکثر 100 محصول در هر بار قابل به‌روزرسانی است"); - - RuleForEach(x => x.Products).ChildRules(product => - { - product.RuleFor(p => p.ProductId) - .GreaterThan(0).WithMessage("شناسه محصول باید بزرگتر از 0 باشد"); - - product.RuleFor(p => p.NewPrice) - .GreaterThanOrEqualTo(0).WithMessage("قیمت نمی‌تواند منفی باشد"); - - product.RuleFor(p => p.NewDiscount) - .InclusiveBetween(0, 100) - .When(p => p.NewDiscount.HasValue) - .WithMessage("درصد تخفیف باید بین 0 تا 100 باشد"); - - product.RuleFor(p => p.NewClubDiscountPercent) - .InclusiveBetween(0, 100) - .When(p => p.NewClubDiscountPercent.HasValue) - .WithMessage("درصد تخفیف باشگاه باید بین 0 تا 100 باشد"); - }); - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommand.cs deleted file mode 100644 index 9219808..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommand.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock; - -/// -/// به‌روزرسانی دسته‌ای موجودی محصولات -/// -public record BulkUpdateProductStockCommand : IRequest -{ - /// - /// لیست محصولات و موجودی‌های جدید - /// - public List Products { get; init; } = new(); - - /// - /// نوع به‌روزرسانی - /// - public StockUpdateType UpdateType { get; init; } = StockUpdateType.Set; -} - -/// -/// نوع به‌روزرسانی موجودی -/// -public enum StockUpdateType -{ - /// - /// تنظیم مقدار مطلق - /// - Set = 1, - - /// - /// اضافه کردن به موجودی فعلی - /// - Add = 2, - - /// - /// کم کردن از موجودی فعلی - /// - Subtract = 3 -} - -/// -/// مدل به‌روزرسانی موجودی یک محصول -/// -public class ProductStockUpdate -{ - /// - /// شناسه محصول - /// - public long ProductId { get; set; } - - /// - /// مقدار جدید/تغییر موجودی - /// - public int Quantity { get; set; } -} - -/// -/// پاسخ به‌روزرسانی دسته‌ای موجودی -/// -public class BulkUpdateProductStockResponseDto -{ - /// - /// تعداد محصولات به‌روزرسانی شده - /// - public int UpdatedCount { get; set; } - - /// - /// تعداد محصولات ناموفق - /// - public int FailedCount { get; set; } - - /// - /// جزئیات خطاها - /// - public List Errors { get; set; } = new(); - - /// - /// آیا همه موفق بودند - /// - public bool IsSuccess => FailedCount == 0; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandHandler.cs deleted file mode 100644 index 123b760..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandHandler.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock; - -public class BulkUpdateProductStockCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public BulkUpdateProductStockCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(BulkUpdateProductStockCommand request, CancellationToken cancellationToken) - { - var response = new BulkUpdateProductStockResponseDto(); - var productIds = request.Products.Select(p => p.ProductId).ToList(); - - // دریافت محصولات از دیتابیس - var products = await _context.Products - .Where(p => productIds.Contains(p.Id)) - .ToListAsync(cancellationToken); - - var productDict = products.ToDictionary(p => p.Id); - - foreach (var update in request.Products) - { - try - { - if (!productDict.TryGetValue(update.ProductId, out var product)) - { - response.FailedCount++; - response.Errors.Add($"محصول با شناسه {update.ProductId} یافت نشد"); - continue; - } - - var oldStock = product.RemainingCount; - - // به‌روزرسانی موجودی بر اساس نوع - switch (request.UpdateType) - { - case StockUpdateType.Set: - product.RemainingCount = update.Quantity; - break; - - case StockUpdateType.Add: - product.RemainingCount += update.Quantity; - break; - - case StockUpdateType.Subtract: - product.RemainingCount -= update.Quantity; - // جلوگیری از موجودی منفی - if (product.RemainingCount < 0) - { - response.FailedCount++; - response.Errors.Add($"محصول {update.ProductId}: موجودی منفی شد (موجودی فعلی: {oldStock}, کم کردن: {update.Quantity})"); - product.RemainingCount = oldStock; // بازگرداندن مقدار قبلی - continue; - } - break; - } - - response.UpdatedCount++; - - _logger.LogInformation( - "Product {ProductId} stock updated from {OldStock} to {NewStock} (Type: {UpdateType})", - product.Id, oldStock, product.RemainingCount, request.UpdateType); - } - catch (Exception ex) - { - response.FailedCount++; - response.Errors.Add($"خطا در به‌روزرسانی محصول {update.ProductId}: {ex.Message}"); - _logger.LogError(ex, "Error updating product {ProductId} stock", update.ProductId); - } - } - - if (response.UpdatedCount > 0) - { - await _context.SaveChangesAsync(cancellationToken); - _logger.LogInformation("Bulk stock update completed: {UpdatedCount} succeeded, {FailedCount} failed", - response.UpdatedCount, response.FailedCount); - } - - return response; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandValidator.cs deleted file mode 100644 index 18e28bb..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandValidator.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock; - -public class BulkUpdateProductStockCommandValidator : AbstractValidator -{ - public BulkUpdateProductStockCommandValidator() - { - RuleFor(x => x.Products) - .NotEmpty().WithMessage("لیست محصولات نمی‌تواند خالی باشد") - .Must(x => x.Count <= 100).WithMessage("حداکثر 100 محصول در هر بار قابل به‌روزرسانی است"); - - RuleForEach(x => x.Products).ChildRules(product => - { - product.RuleFor(p => p.ProductId) - .GreaterThan(0).WithMessage("شناسه محصول باید بزرگتر از 0 باشد"); - - // برای Set mode، مقدار نمی‌تواند منفی باشد (چک در Handler انجام می‌شود) - product.RuleFor(p => p.Quantity) - .GreaterThanOrEqualTo(-10000) - .WithMessage("مقدار موجودی نامعتبر است"); - }); - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommand.cs index 4f7ec10..7cf656a 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommand.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommand.cs @@ -1,31 +1,26 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.CreateNewProducts; + public record CreateNewProductsCommand : IRequest { - // - public string Title { get; init; } - // - public string Description { get; init; } - // - public string ShortInfomation { get; init; } - // - public string FullInformation { get; init; } - // + public string Title { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public string ShortInfomation { get; init; } = string.Empty; + public string FullInformation { get; init; } = string.Empty; public long Price { get; init; } - // public int Discount { get; init; } - // public int Rate { get; init; } - // - public string ImagePath { get; init; } - // - public string ThumbnailPath { get; init; } - // + public string? ImagePath { get; init; } + public string? ThumbnailPath { get; init; } public int SaleCount { get; init; } - // public int ViewCount { get; init; } - // public int RemainingCount { get; init; } - // لیست شناسه دسته‌بندی‌های محصول - public ICollection? CategoryIds { get; init; } - + public List CategoryIds { get; init; } = new(); + + // File upload fields (raw bytes from client) + public byte[]? ImageFileBytes { get; init; } + public string? ImageFileMime { get; init; } + public string? ImageFileName { get; init; } + public byte[]? ThumbnailFileBytes { get; init; } + public string? ThumbnailFileMime { get; init; } + public string? ThumbnailFileName { get; init; } } diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs index 4e38c2e..8bd4783 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs @@ -1,44 +1,95 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities; -using CMSMicroservice.Domain.Events; +using Microsoft.Extensions.Logging; + namespace CMSMicroservice.Application.ProductsCQ.Commands.CreateNewProducts; + public class CreateNewProductsCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + private readonly ILogger _logger; - public CreateNewProductsCommandHandler(IApplicationDbContext context) + public CreateNewProductsCommandHandler( + IApplicationDbContext context, + IFileManager fileManager, + ILogger logger) { _context = context; + _fileManager = fileManager; + _logger = logger; } public async Task Handle(CreateNewProductsCommand request, CancellationToken cancellationToken) { - var entity = request.Adapt(); + var entity = new Product + { + Title = request.Title, + Description = request.Description, + ShortInfomation = request.ShortInfomation, + FullInformation = request.FullInformation, + Price = request.Price, + Discount = request.Discount, + Rate = request.Rate, + ImagePath = string.Empty, + ThumbnailPath = string.Empty, + SaleCount = request.SaleCount, + ViewCount = request.ViewCount, + RemainingCount = request.RemainingCount + }; + + // آپلود تصویر اصلی (اگر ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/Products", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.ImagePath = result.Main.Path; + entity.ThumbnailPath = result.Thumbnail.Path; + } + + // آپلود بندانگشتی جداگانه (اختیاری — جایگزین بندانگشتی خودکار) + if (request.ThumbnailFileBytes is { Length: > 0 }) + { + var thumbResult = await _fileManager.UploadAsync( + "Images/Products/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); + + entity.ThumbnailPath = thumbResult.Path; + } + await _context.Products.AddAsync(entity, cancellationToken); await _context.SaveChangesAsync(cancellationToken); - // ثبت دسته‌بندی‌های محصول (در صورت ارسال) + // Handle category assignments if (request.CategoryIds is { Count: > 0 }) { - var distinctCategoryIds = request.CategoryIds - .Where(id => id > 0) - .Distinct() - .ToList(); - - foreach (var categoryId in distinctCategoryIds) + foreach (var categoryId in request.CategoryIds) { - var rel = new ProductCategory + var categoryExists = await _context.Categories + .AnyAsync(c => c.Id == categoryId, cancellationToken); + + if (categoryExists) { - ProductId = entity.Id, - CategoryId = categoryId - }; - await _context.ProductCategories.AddAsync(rel, cancellationToken); + await _context.ProductCategories.AddAsync(new ProductCategory + { + ProductId = entity.Id, + CategoryId = categoryId + }, cancellationToken); + } } - await _context.SaveChangesAsync(cancellationToken); } - entity.AddDomainEvent(new CreateNewProductsEvent(entity)); - return entity.Adapt(); + return new CreateNewProductsResponseDto { Id = entity.Id }; } } diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandValidator.cs index d4b6957..6ab40b8 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandValidator.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandValidator.cs @@ -1,36 +1,24 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.CreateNewProducts; + public class CreateNewProductsCommandValidator : AbstractValidator { public CreateNewProductsCommandValidator() { RuleFor(model => model.Title) - .NotEmpty(); - RuleFor(model => model.Description) - .NotEmpty(); - RuleFor(model => model.ShortInfomation) - .NotEmpty(); - RuleFor(model => model.FullInformation) - .NotEmpty(); + .NotEmpty().WithMessage("عنوان محصول الزامی است"); + RuleFor(model => model.Price) - .NotNull(); + .GreaterThanOrEqualTo(0).WithMessage("قیمت نمی‌تواند منفی باشد"); + RuleFor(model => model.Discount) - .NotNull(); - RuleFor(model => model.Rate) - .NotNull(); - RuleFor(model => model.ImagePath) - .NotEmpty(); - RuleFor(model => model.ThumbnailPath) - .NotEmpty(); - RuleFor(model => model.SaleCount) - .NotNull(); - RuleFor(model => model.ViewCount) - .NotNull(); - RuleFor(model => model.RemainingCount) - .NotNull(); + .InclusiveBetween(0, 100).WithMessage("تخفیف باید بین 0 تا 100 باشد"); } + public Func>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewProductsCommand)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (CreateNewProductsCommand)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsResponseDto.cs index bfaa965..6c8227c 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsResponseDto.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsResponseDto.cs @@ -1,7 +1,6 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.CreateNewProducts; + public class CreateNewProductsResponseDto { - // public long Id { get; set; } - -} \ No newline at end of file +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommand.cs index 1435676..fe7a46a 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommand.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommand.cs @@ -1,7 +1,6 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.DeleteProducts; + public record DeleteProductsCommand : IRequest { - // public long Id { get; init; } - -} \ No newline at end of file +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommandHandler.cs index bc1a515..28bb98c 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommandHandler.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommandHandler.cs @@ -1,5 +1,8 @@ -using CMSMicroservice.Domain.Events; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; + namespace CMSMicroservice.Application.ProductsCQ.Commands.DeleteProducts; + public class DeleteProductsCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; @@ -12,11 +15,24 @@ public class DeleteProductsCommandHandler : IRequestHandler Handle(DeleteProductsCommand request, CancellationToken cancellationToken) { var entity = await _context.Products - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(Product), request.Id); - entity.IsDeleted = true; - _context.Products.Update(entity); - entity.AddDomainEvent(new DeleteProductsEvent(entity)); + .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) + ?? throw new NotFoundException(nameof(Product), request.Id); + + // Remove category associations + var productCategories = await _context.ProductCategories + .Where(pc => pc.ProductId == entity.Id) + .ToListAsync(cancellationToken); + _context.ProductCategories.RemoveRange(productCategories); + + // Remove gallery associations + var productGalleries = await _context.ProductGalleries + .Where(pg => pg.ProductId == entity.Id) + .ToListAsync(cancellationToken); + _context.ProductGalleries.RemoveRange(productGalleries); + + _context.Products.Remove(entity); await _context.SaveChangesAsync(cancellationToken); + return Unit.Value; } } diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommandValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommandValidator.cs deleted file mode 100644 index 0d171f4..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/DeleteProducts/DeleteProductsCommandValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.DeleteProducts; -public class DeleteProductsCommandValidator : AbstractValidator -{ - public DeleteProductsCommandValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteProductsCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/RemoveProductImage/RemoveProductImageCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/RemoveProductImage/RemoveProductImageCommand.cs new file mode 100644 index 0000000..422b408 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/RemoveProductImage/RemoveProductImageCommand.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.ProductsCQ.Commands.RemoveProductImage; + +public record RemoveProductImageCommand : IRequest +{ + public long ProductGalleryId { get; init; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/RemoveProductImage/RemoveProductImageCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/RemoveProductImage/RemoveProductImageCommandHandler.cs new file mode 100644 index 0000000..019ba1d --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/RemoveProductImage/RemoveProductImageCommandHandler.cs @@ -0,0 +1,40 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.ProductsCQ.Commands.RemoveProductImage; + +public class RemoveProductImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public RemoveProductImageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(RemoveProductImageCommand request, CancellationToken cancellationToken) + { + var gallery = await _context.ProductGalleries + .Include(pg => pg.ProductImage) + .FirstOrDefaultAsync(pg => pg.Id == request.ProductGalleryId, cancellationToken) + ?? throw new NotFoundException(nameof(ProductGallery), request.ProductGalleryId); + + // Remove gallery entry + _context.ProductGalleries.Remove(gallery); + + // Remove the product image if it exists and is not referenced by other galleries + if (gallery.ProductImage != null) + { + var otherReferences = await _context.ProductGalleries + .AnyAsync(pg => pg.ProductImageId == gallery.ProductImageId && pg.Id != gallery.Id, cancellationToken); + + if (!otherReferences) + { + _context.ProductImages.Remove(gallery.ProductImage); + } + } + + await _context.SaveChangesAsync(cancellationToken); + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommand.cs deleted file mode 100644 index bceadd7..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommand.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.ToggleProductStatus; - -/// -/// فعال/غیرفعال کردن دسته‌ای محصولات -/// (با تنظیم موجودی به 0 برای غیرفعال کردن) -/// -public record ToggleProductStatusCommand : IRequest -{ - /// - /// لیست شناسه محصولات - /// - public List ProductIds { get; init; } = new(); - - /// - /// فعال کردن یا غیرفعال کردن - /// - public bool Enable { get; init; } - - /// - /// موجودی پیش‌فرض برای فعال‌سازی (پیش‌فرض: 1) - /// - public int DefaultStock { get; init; } = 1; -} - -/// -/// پاسخ فعال/غیرفعال کردن دسته‌ای -/// -public class ToggleProductStatusResponseDto -{ - /// - /// تعداد محصولات به‌روزرسانی شده - /// - public int UpdatedCount { get; set; } - - /// - /// تعداد محصولات ناموفق - /// - public int FailedCount { get; set; } - - /// - /// جزئیات خطاها - /// - public List Errors { get; set; } = new(); - - /// - /// آیا همه موفق بودند - /// - public bool IsSuccess => FailedCount == 0; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandHandler.cs deleted file mode 100644 index 1b5b5cd..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandHandler.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.ToggleProductStatus; - -public class ToggleProductStatusCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public ToggleProductStatusCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(ToggleProductStatusCommand request, CancellationToken cancellationToken) - { - var response = new ToggleProductStatusResponseDto(); - - // دریافت محصولات از دیتابیس - var products = await _context.Products - .Where(p => request.ProductIds.Contains(p.Id)) - .ToListAsync(cancellationToken); - - if (products.Count == 0) - { - response.Errors.Add("هیچ محصولی با شناسه‌های داده شده یافت نشد"); - return response; - } - - foreach (var product in products) - { - try - { - if (request.Enable) - { - // فعال‌سازی: اگر موجودی 0 است، آن را به مقدار پیش‌فرض تنظیم کن - if (product.RemainingCount == 0) - { - product.RemainingCount = request.DefaultStock; - _logger.LogInformation( - "Product {ProductId} enabled with stock {Stock}", - product.Id, request.DefaultStock); - } - else - { - _logger.LogInformation( - "Product {ProductId} already has stock {Stock}, no change needed", - product.Id, product.RemainingCount); - } - } - else - { - // غیرفعال‌سازی: موجودی را به 0 تنظیم کن - var oldStock = product.RemainingCount; - product.RemainingCount = 0; - _logger.LogInformation( - "Product {ProductId} disabled (stock changed from {OldStock} to 0)", - product.Id, oldStock); - } - - response.UpdatedCount++; - } - catch (Exception ex) - { - response.FailedCount++; - response.Errors.Add($"خطا در به‌روزرسانی محصول {product.Id}: {ex.Message}"); - _logger.LogError(ex, "Error toggling product {ProductId} status", product.Id); - } - } - - if (response.UpdatedCount > 0) - { - await _context.SaveChangesAsync(cancellationToken); - _logger.LogInformation( - "Toggle product status completed: {UpdatedCount} succeeded, {FailedCount} failed (Enable: {Enable})", - response.UpdatedCount, response.FailedCount, request.Enable); - } - - return response; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandValidator.cs deleted file mode 100644 index 22f6c2b..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.ToggleProductStatus; - -public class ToggleProductStatusCommandValidator : AbstractValidator -{ - public ToggleProductStatusCommandValidator() - { - RuleFor(x => x.ProductIds) - .NotEmpty().WithMessage("لیست محصولات نمی‌تواند خالی باشد") - .Must(x => x.Count <= 100).WithMessage("حداکثر 100 محصول در هر بار قابل به‌روزرسانی است"); - - RuleFor(x => x.DefaultStock) - .GreaterThanOrEqualTo(0) - .When(x => x.Enable) - .WithMessage("موجودی پیش‌فرض نمی‌تواند منفی باشد"); - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommand.cs deleted file mode 100644 index 69c55e0..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommand.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MediatR; - -namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProductBulk; - -/// -/// دستور به‌روزرسانی گروهی محصولات -/// Admin می‌تواند چندین محصول را همزمان ویرایش کند -/// -public class UpdateProductBulkCommand : IRequest -{ - /// - /// لیست شناسه محصولات برای به‌روزرسانی - /// - public List ProductIds { get; set; } = new(); - - /// - /// قیمت جدید (اختیاری - اگر null باشد تغییر نمی‌کند) - /// - public long? NewPrice { get; set; } - - /// - /// درصد افزایش/کاهش قیمت (اختیاری) - /// مثلاً: 10 = افزایش 10%، -15 = کاهش 15% - /// - public decimal? PriceChangePercent { get; set; } - - /// - /// موجودی (اختیاری) - /// - public int? Stock { get; set; } - - /// - /// افزودن مقدار به موجودی (اختیاری) - /// - public int? StockIncrement { get; set; } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommandHandler.cs deleted file mode 100644 index f5539fd..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommandHandler.cs +++ /dev/null @@ -1,92 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProductBulk; - -public class UpdateProductBulkCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public UpdateProductBulkCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(UpdateProductBulkCommand request, CancellationToken cancellationToken) - { - var response = new UpdateProductBulkResponseDto - { - TotalRequested = request.ProductIds.Count - }; - - var products = await _context.Products - .Where(x => request.ProductIds.Contains(x.Id) && !x.IsDeleted) - .ToListAsync(cancellationToken); - - if (products.Count == 0) - { - response.Errors.Add("هیچ محصولی با شناسه‌های داده شده یافت نشد"); - return response; - } - - foreach (var product in products) - { - try - { - // تغییر قیمت - if (request.NewPrice.HasValue) - { - product.Price = request.NewPrice.Value; - } - else if (request.PriceChangePercent.HasValue) - { - var changeAmount = (long)(product.Price * (request.PriceChangePercent.Value / 100)); - product.Price += changeAmount; - - // اطمینان از مثبت بودن قیمت - if (product.Price < 0) - product.Price = 0; - } - - // تغییر موجودی - if (request.Stock.HasValue) - { - product.RemainingCount = request.Stock.Value; - } - else if (request.StockIncrement.HasValue) - { - product.RemainingCount += request.StockIncrement.Value; - - // اطمینان از غیرمنفی بودن موجودی - if (product.RemainingCount < 0) - product.RemainingCount = 0; - } - - response.UpdatedProductIds.Add(product.Id); - response.SuccessCount++; - } - catch (Exception ex) - { - response.Errors.Add($"خطا در به‌روزرسانی محصول {product.Id}: {ex.Message}"); - response.FailedCount++; - _logger.LogError(ex, "Error updating product {ProductId}", product.Id); - } - } - - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Bulk update completed. Success: {Success}, Failed: {Failed}", - response.SuccessCount, - response.FailedCount - ); - - return response; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommandValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommandValidator.cs deleted file mode 100644 index 0186da0..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkCommandValidator.cs +++ /dev/null @@ -1,40 +0,0 @@ -using FluentValidation; - -namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProductBulk; - -public class UpdateProductBulkCommandValidator : AbstractValidator -{ - public UpdateProductBulkCommandValidator() - { - RuleFor(x => x.ProductIds) - .NotEmpty().WithMessage("حداقل یک محصول باید انتخاب شود") - .Must(x => x.Count <= 100).WithMessage("حداکثر 100 محصول را می‌توان همزمان به‌روزرسانی کرد"); - - RuleFor(x => x.NewPrice) - .GreaterThan(0).WithMessage("قیمت باید بزرگتر از صفر باشد") - .LessThanOrEqualTo(1_000_000_000).WithMessage("قیمت نامعتبر است") - .When(x => x.NewPrice.HasValue); - - RuleFor(x => x.PriceChangePercent) - .GreaterThanOrEqualTo(-100).WithMessage("درصد تخفیف نمی‌تواند بیشتر از 100% باشد") - .LessThanOrEqualTo(1000).WithMessage("درصد افزایش نامعتبر است") - .When(x => x.PriceChangePercent.HasValue); - - RuleFor(x => x.Stock) - .GreaterThanOrEqualTo(0).WithMessage("موجودی نمی‌تواند منفی باشد") - .When(x => x.Stock.HasValue); - - RuleFor(x => x) - .Must(x => x.NewPrice.HasValue || x.PriceChangePercent.HasValue || - x.Stock.HasValue || x.StockIncrement.HasValue) - .WithMessage("حداقل یک فیلد برای به‌روزرسانی باید مشخص شود"); - - RuleFor(x => x) - .Must(x => !(x.NewPrice.HasValue && x.PriceChangePercent.HasValue)) - .WithMessage("نمی‌توان همزمان قیمت جدید و درصد تغییر قیمت را مشخص کرد"); - - RuleFor(x => x) - .Must(x => !(x.Stock.HasValue && x.StockIncrement.HasValue)) - .WithMessage("نمی‌توان همزمان موجودی جدید و افزایش موجودی را مشخص کرد"); - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkResponseDto.cs deleted file mode 100644 index 73c5bed..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProductBulk/UpdateProductBulkResponseDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProductBulk; - -public class UpdateProductBulkResponseDto -{ - public int TotalRequested { get; set; } - public int SuccessCount { get; set; } - public int FailedCount { get; set; } - public List UpdatedProductIds { get; set; } = new(); - public List Errors { get; set; } = new(); -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommand.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommand.cs index a562980..1a31995 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommand.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommand.cs @@ -1,33 +1,27 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProducts; + public record UpdateProductsCommand : IRequest { - // public long Id { get; init; } - // - public string Title { get; init; } - // - public string Description { get; init; } - // - public string ShortInfomation { get; init; } - // - public string FullInformation { get; init; } - // + public string Title { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public string ShortInfomation { get; init; } = string.Empty; + public string FullInformation { get; init; } = string.Empty; public long Price { get; init; } - // public int Discount { get; init; } - // public int Rate { get; init; } - // - public string ImagePath { get; init; } - // - public string ThumbnailPath { get; init; } - // + public string? ImagePath { get; init; } + public string? ThumbnailPath { get; init; } public int SaleCount { get; init; } - // public int ViewCount { get; init; } - // public int RemainingCount { get; init; } - // لیست شناسه دسته‌بندی‌های محصول - public ICollection? CategoryIds { get; init; } - + public List CategoryIds { get; init; } = new(); + + // File upload fields (raw bytes from client) + public byte[]? ImageFileBytes { get; init; } + public string? ImageFileMime { get; init; } + public string? ImageFileName { get; init; } + public byte[]? ThumbnailFileBytes { get; init; } + public string? ThumbnailFileMime { get; init; } + public string? ThumbnailFileName { get; init; } } diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs index ddac8ee..da72bfc 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs @@ -1,64 +1,102 @@ -using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities; -using CMSMicroservice.Domain.Events; -using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProducts; + public class UpdateProductsCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + private readonly ILogger _logger; - public UpdateProductsCommandHandler(IApplicationDbContext context) + public UpdateProductsCommandHandler( + IApplicationDbContext context, + IFileManager fileManager, + ILogger logger) { _context = context; + _fileManager = fileManager; + _logger = logger; } public async Task Handle(UpdateProductsCommand request, CancellationToken cancellationToken) { var entity = await _context.Products .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) - ?? throw new NotFoundException(nameof(Product), request.Id); + ?? throw new NotFoundException(nameof(Product), request.Id); - request.Adapt(entity); - _context.Products.Update(entity); + // Update basic properties + entity.Title = request.Title; + entity.Description = request.Description; + entity.ShortInfomation = request.ShortInfomation; + entity.FullInformation = request.FullInformation; + entity.Price = request.Price; + entity.Discount = request.Discount; + entity.Rate = request.Rate; + entity.SaleCount = request.SaleCount; + entity.ViewCount = request.ViewCount; + entity.RemainingCount = request.RemainingCount; - // به‌روزرسانی دسته‌بندی‌های محصول در صورت ارسال CategoryIds - if (request.CategoryIds is not null) + // آپلود تصویر اصلی جدید (اگر ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) { - var targetIds = (request.CategoryIds ?? Array.Empty()) - .Where(id => id > 0) - .Distinct() - .ToHashSet(); + var result = await _fileManager.UploadImageAsync( + "Images/Products", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); - var existingRelations = await _context.ProductCategories - .Where(x => x.ProductId == entity.Id) - .ToListAsync(cancellationToken); - - var existingIds = existingRelations - .Select(x => x.CategoryId) - .ToHashSet(); - - var toAdd = targetIds.Except(existingIds).ToList(); - var toRemove = existingRelations.Where(x => !targetIds.Contains(x.CategoryId)).ToList(); - - foreach (var categoryId in toAdd) - { - var rel = new ProductCategory - { - ProductId = entity.Id, - CategoryId = categoryId - }; - await _context.ProductCategories.AddAsync(rel, cancellationToken); - } - - if (toRemove.Count > 0) - { - _context.ProductCategories.RemoveRange(toRemove); - } + entity.ImagePath = result.Main.Path; + entity.ThumbnailPath = result.Thumbnail.Path; } - entity.AddDomainEvent(new UpdateProductsEvent(entity)); + // آپلود بندانگشتی جداگانه (اختیاری — جایگزین بندانگشتی خودکار) + if (request.ThumbnailFileBytes is { Length: > 0 }) + { + var thumbResult = await _fileManager.UploadAsync( + "Images/Products/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); + + entity.ThumbnailPath = thumbResult.Path; + } + + _context.Products.Update(entity); await _context.SaveChangesAsync(cancellationToken); + + // Update category assignments + if (request.CategoryIds != null) + { + // Remove existing categories + var existingCategories = await _context.ProductCategories + .Where(pc => pc.ProductId == entity.Id) + .ToListAsync(cancellationToken); + + _context.ProductCategories.RemoveRange(existingCategories); + + // Add new categories + foreach (var categoryId in request.CategoryIds) + { + var categoryExists = await _context.Categories + .AnyAsync(c => c.Id == categoryId, cancellationToken); + + if (categoryExists) + { + await _context.ProductCategories.AddAsync(new ProductCategory + { + ProductId = entity.Id, + CategoryId = categoryId + }, cancellationToken); + } + } + await _context.SaveChangesAsync(cancellationToken); + } + return Unit.Value; } } diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandValidator.cs index ef07175..12a9ec7 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandValidator.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandValidator.cs @@ -1,38 +1,27 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProducts; + public class UpdateProductsCommandValidator : AbstractValidator { public UpdateProductsCommandValidator() { RuleFor(model => model.Id) - .NotNull(); + .NotNull().WithMessage("شناسه محصول الزامی است"); + RuleFor(model => model.Title) - .NotEmpty(); - RuleFor(model => model.Description) - .NotEmpty(); - RuleFor(model => model.ShortInfomation) - .NotEmpty(); - RuleFor(model => model.FullInformation) - .NotEmpty(); + .NotEmpty().WithMessage("عنوان محصول الزامی است"); + RuleFor(model => model.Price) - .NotNull(); + .GreaterThanOrEqualTo(0).WithMessage("قیمت نمی‌تواند منفی باشد"); + RuleFor(model => model.Discount) - .NotNull(); - RuleFor(model => model.Rate) - .NotNull(); - RuleFor(model => model.ImagePath) - .NotEmpty(); - RuleFor(model => model.ThumbnailPath) - .NotEmpty(); - RuleFor(model => model.SaleCount) - .NotNull(); - RuleFor(model => model.ViewCount) - .NotNull(); - RuleFor(model => model.RemainingCount) - .NotNull(); + .InclusiveBetween(0, 100).WithMessage("تخفیف باید بین 0 تا 100 باشد"); } + public Func>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateProductsCommand)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync( + ValidationContext.CreateWithOptions( + (UpdateProductsCommand)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/CreateNewProductsEventHandlers/CreateNewProductsEventHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/CreateNewProductsEventHandlers/CreateNewProductsEventHandler.cs deleted file mode 100644 index b9c5a3b..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/CreateNewProductsEventHandlers/CreateNewProductsEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.ProductsCQ.EventHandlers; - -public class CreateNewProductsEventHandler : INotificationHandler -{ - private readonly ILogger< - CreateNewProductsEventHandler> _logger; - - public CreateNewProductsEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(CreateNewProductsEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/DeleteProductsEventHandlers/DeleteProductsEventHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/DeleteProductsEventHandlers/DeleteProductsEventHandler.cs deleted file mode 100644 index 1ded1d3..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/DeleteProductsEventHandlers/DeleteProductsEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.ProductsCQ.EventHandlers; - -public class DeleteProductsEventHandler : INotificationHandler -{ - private readonly ILogger< - DeleteProductsEventHandler> _logger; - - public DeleteProductsEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(DeleteProductsEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/UpdateProductsEventHandlers/UpdateProductsEventHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/UpdateProductsEventHandlers/UpdateProductsEventHandler.cs deleted file mode 100644 index 0158a6d..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/EventHandlers/UpdateProductsEventHandlers/UpdateProductsEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.ProductsCQ.EventHandlers; - -public class UpdateProductsEventHandler : INotificationHandler -{ - private readonly ILogger< - UpdateProductsEventHandler> _logger; - - public UpdateProductsEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(UpdateProductsEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQuery.cs deleted file mode 100644 index 8c1573b..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQuery.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetAllProductsByFilter; -public record GetAllProductsByFilterQuery : IRequest -{ - //موقعیت صفحه بندی - public PaginationState? PaginationState { get; init; } - //مرتب سازی بر اساس - public string? SortBy { get; init; } - //فیلتر - public GetAllProductsByFilterFilter? Filter { get; init; } - -}public class GetAllProductsByFilterFilter -{ - // - public long? Id { get; set; } - // - public string? Title { get; set; } - // - public string? Description { get; set; } - // - public string? ShortInfomation { get; set; } - // - public string? FullInformation { get; set; } - // - public long? Price { get; set; } - // - public int? Discount { get; set; } - // - public int? Rate { get; set; } - // - public long? CategoryId { get; set; } - // - public string? ImagePath { get; set; } - // - public string? ThumbnailPath { get; set; } - // - public int? SaleCount { get; set; } - // - public int? ViewCount { get; set; } - // - public int? RemainingCount { get; set; } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQueryHandler.cs deleted file mode 100644 index e45a7fb..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQueryHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetAllProductsByFilter; -public class GetAllProductsByFilterQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public GetAllProductsByFilterQueryHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(GetAllProductsByFilterQuery request, CancellationToken cancellationToken) - { - var query = _context.Products - .ApplyOrder(sortBy: request.SortBy) - .AsNoTracking() - .AsQueryable(); - if (request.Filter is not null) - { - query = query - .Where(x => request.Filter.Id == null || x.Id == request.Filter.Id) - .Where(x => request.Filter.Title == null || x.Title.Contains(request.Filter.Title)) - .Where(x => request.Filter.Description == null || x.Description.Contains(request.Filter.Description)) - .Where(x => request.Filter.ShortInfomation == null || x.ShortInfomation.Contains(request.Filter.ShortInfomation)) - .Where(x => request.Filter.FullInformation == null || x.FullInformation.Contains(request.Filter.FullInformation)) - .Where(x => request.Filter.Price == null || x.Price == request.Filter.Price) - .Where(x => request.Filter.Discount == null || x.Discount == request.Filter.Discount) - .Where(x => request.Filter.Rate == null || x.Rate == request.Filter.Rate) - .Where(x => request.Filter.CategoryId == null || x.ProductCategories.Any(pc => pc.CategoryId == request.Filter.CategoryId)) - .Where(x => request.Filter.ImagePath == null || x.ImagePath.Contains(request.Filter.ImagePath)) - .Where(x => request.Filter.ThumbnailPath == null || x.ThumbnailPath.Contains(request.Filter.ThumbnailPath)) - .Where(x => request.Filter.SaleCount == null || x.SaleCount == request.Filter.SaleCount) - .Where(x => request.Filter.ViewCount == null || x.ViewCount == request.Filter.ViewCount) - .Where(x => request.Filter.RemainingCount == null || x.RemainingCount == request.Filter.RemainingCount) -; - } - var meta = await query.GetMetaData(request.PaginationState, cancellationToken); - - var models = await query - .PaginatedListAsync(paginationState: request.PaginationState) - .Select(x => new GetAllProductsByFilterResponseModel - { - Id = x.Id, - Title = x.Title, - Description = x.Description, - ShortInfomation = x.ShortInfomation, - FullInformation = x.FullInformation, - Price = x.Price, - Discount = x.Discount, - Rate = x.Rate, - ImagePath = x.ImagePath, - ThumbnailPath = x.ThumbnailPath, - SaleCount = x.SaleCount, - ViewCount = x.ViewCount, - RemainingCount = x.RemainingCount, - CategoryIds = x.ProductCategories - .Select(pc => pc.CategoryId) - .ToList() - }) - .ToListAsync(cancellationToken); - - return new GetAllProductsByFilterResponseDto - { - MetaData = meta, - Models = models - }; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQueryValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQueryValidator.cs deleted file mode 100644 index 613f24e..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterQueryValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetAllProductsByFilter; -public class GetAllProductsByFilterQueryValidator : AbstractValidator -{ - public GetAllProductsByFilterQueryValidator() - { - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllProductsByFilterQuery)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterResponseDto.cs deleted file mode 100644 index 78a7b57..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetAllProductsByFilter/GetAllProductsByFilterResponseDto.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetAllProductsByFilter; -public class GetAllProductsByFilterResponseDto -{ - //متادیتا - public MetaData MetaData { get; set; } - //مدل خروجی - public List? Models { get; set; } - -} - -public class GetAllProductsByFilterResponseModel -{ - // - public long Id { get; set; } - // - public string Title { get; set; } - // - public string Description { get; set; } - // - public string ShortInfomation { get; set; } - // - public string FullInformation { get; set; } - // - public long Price { get; set; } - // - public int Discount { get; set; } - // - public int Rate { get; set; } - // - public string ImagePath { get; set; } - // - public string ThumbnailPath { get; set; } - // - public int SaleCount { get; set; } - // - public int ViewCount { get; set; } - // - public int RemainingCount { get; set; } - // لیست شناسه دسته‌بندی‌های محصول - public List CategoryIds { get; set; } = new(); -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsQuery.cs new file mode 100644 index 0000000..c020b58 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts; + +public class GetCustomerProductsQuery : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsQueryHandler.cs new file mode 100644 index 0000000..6dfe69a --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsQueryHandler.cs @@ -0,0 +1,91 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts; + +public class GetCustomerProductsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetCustomerProductsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetCustomerProductsQuery request, CancellationToken cancellationToken) + { + var product = await _context.Products + .AsNoTracking() + .Where(x => x.Id == request.Id) + .Include(x => x.ProductGalleries) + .ThenInclude(pg => pg.ProductImage) + .Include(x => x.ProductCategories) + .ThenInclude(pc => pc.Category) + .FirstOrDefaultAsync(cancellationToken); + + if (product == null) + throw new NotFoundException(nameof(Product), request.Id); + + var response = new GetCustomerProductsResponseDto + { + Id = product.Id, + Title = product.Title, + Description = product.Description, + ShortInfomation = product.ShortInfomation, + FullInformation = product.FullInformation, + Price = product.Price, + Discount = product.Discount, + Rate = product.Rate, + ImagePath = product.ImagePath, + ThumbnailPath = product.ThumbnailPath, + SaleCount = product.SaleCount, + ViewCount = product.ViewCount, + RemainingCount = product.RemainingCount, + Gallery = product.ProductGalleries?.Select(pg => new ProductGalleryModel + { + ProductGalleryId = pg.Id, + ProductImageId = pg.ProductImageId, + Title = pg.ProductImage?.Title ?? string.Empty, + ImagePath = pg.ProductImage?.ImagePath ?? string.Empty, + ImageThumbnailPath = pg.ProductImage?.ImageThumbnailPath ?? string.Empty + }).ToList() ?? new List(), + Categories = product.ProductCategories?.Select(pc => new ProductCategoryModel + { + CategoryId = pc.CategoryId, + Title = pc.Category?.Title ?? string.Empty, + Path = BuildCategoryPath(pc.Category) + }).ToList() ?? new List() + }; + + return response; + } + + private List BuildCategoryPath(Category? category) + { + var path = new List(); + + while (category != null) + { + path.Insert(0, new CategoryNodeModel + { + Id = category.Id, + Title = category.Title, + ParentId = category.ParentId + }); + + // Move to parent + if (category.ParentId.HasValue) + { + category = _context.Categories + .AsNoTracking() + .FirstOrDefault(c => c.Id == category.ParentId.Value); + } + else + { + category = null; + } + } + + return path; + } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsResponseDto.cs new file mode 100644 index 0000000..736ef5f --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProducts/GetCustomerProductsResponseDto.cs @@ -0,0 +1,43 @@ +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts; + +public class GetCustomerProductsResponseDto +{ + public long Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string ShortInfomation { get; set; } + public string FullInformation { get; set; } + public long Price { get; set; } + public int Discount { get; set; } + public int Rate { get; set; } + public string ImagePath { get; set; } + public string ThumbnailPath { get; set; } + public int SaleCount { get; set; } + public int ViewCount { get; set; } + public int RemainingCount { get; set; } + public List Gallery { get; set; } + public List Categories { get; set; } +} + +public class ProductGalleryModel +{ + public long ProductGalleryId { get; set; } + public long ProductImageId { get; set; } + public string Title { get; set; } + public string ImagePath { get; set; } + public string ImageThumbnailPath { get; set; } +} + +public class ProductCategoryModel +{ + public long CategoryId { get; set; } + public string Title { get; set; } + public List Path { get; set; } +} + +public class CategoryNodeModel +{ + public long Id { get; set; } + public string Title { get; set; } + public long? ParentId { get; set; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQuery.cs new file mode 100644 index 0000000..ed3763f --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQuery.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter; + +public class GetCustomerProductsByFilterQuery : IRequest +{ + public PaginationState? PaginationState { get; set; } + public string? SortBy { get; set; } + + // Filters + public long? Id { get; set; } + public string? Title { get; set; } + public string? Description { get; set; } + public string? ShortInfomation { get; set; } + public string? FullInformation { get; set; } + public long? Price { get; set; } + public int? Discount { get; set; } + public int? Rate { get; set; } + public string? ImagePath { get; set; } + public string? ThumbnailPath { get; set; } + public int? SaleCount { get; set; } + public int? ViewCount { get; set; } + public int? RemainingCount { get; set; } + public bool? IsActive { get; set; } + public List? CategoryIds { get; set; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs new file mode 100644 index 0000000..9a1e371 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs @@ -0,0 +1,149 @@ +using CMSMicroservice.Application.Common.Extensions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter; + +public class GetCustomerProductsByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetCustomerProductsByFilterQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetCustomerProductsByFilterQuery request, CancellationToken cancellationToken) + { + var query = _context.Products + .AsNoTracking() + .Include(x => x.ProductCategories) + .ThenInclude(pc => pc.Category) + .AsQueryable(); + + // Apply filters + if (request.Id.HasValue) + query = query.Where(x => x.Id == request.Id.Value); + + if (!string.IsNullOrEmpty(request.Title)) + query = query.Where(x => x.Title.Contains(request.Title)); + + if (!string.IsNullOrEmpty(request.Description)) + query = query.Where(x => x.Description.Contains(request.Description)); + + if (!string.IsNullOrEmpty(request.ShortInfomation)) + query = query.Where(x => x.ShortInfomation.Contains(request.ShortInfomation)); + + if (!string.IsNullOrEmpty(request.FullInformation)) + query = query.Where(x => x.FullInformation.Contains(request.FullInformation)); + + if (request.Price.HasValue) + query = query.Where(x => x.Price == request.Price.Value); + + if (request.Discount.HasValue) + query = query.Where(x => x.Discount == request.Discount.Value); + + if (request.Rate.HasValue) + query = query.Where(x => x.Rate == request.Rate.Value); + + if (request.SaleCount.HasValue) + query = query.Where(x => x.SaleCount == request.SaleCount.Value); + + if (request.ViewCount.HasValue) + query = query.Where(x => x.ViewCount == request.ViewCount.Value); + + if (request.RemainingCount.HasValue) + query = query.Where(x => x.RemainingCount == request.RemainingCount.Value); + + if (request.CategoryIds != null && request.CategoryIds.Any()) + query = query.Where(x => x.ProductCategories.Any(pc => request.CategoryIds.Contains(pc.CategoryId))); + + if (request.IsActive.HasValue) + query = query.Where(x => x.IsDeleted != request.IsActive.Value); + + // Apply sorting + if (!string.IsNullOrEmpty(request.SortBy)) + query = query.ApplyOrder(request.SortBy); + else + query = query.OrderByDescending(x => x.Created); + + // Pagination + var totalCount = await query.CountAsync(cancellationToken); + + var paginationState = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 10 }; + var products = await query + .Skip((paginationState.PageNumber - 1) * paginationState.PageSize) + .Take(paginationState.PageSize) + .ToListAsync(cancellationToken); + + var metaData = new MetaData + { + TotalCount = totalCount, + CurrentPage = paginationState.PageNumber, + PageSize = paginationState.PageSize, + TotalPage = (int)Math.Ceiling((double)totalCount / paginationState.PageSize), + HasPrevious = paginationState.PageNumber > 1, + HasNext = paginationState.PageNumber < (int)Math.Ceiling((double)totalCount / paginationState.PageSize) + }; + + var models = products.Select(p => new CustomerProductModel + { + Id = p.Id, + Title = p.Title, + Description = p.Description, + ShortInfomation = p.ShortInfomation, + FullInformation = p.FullInformation, + Price = p.Price, + Discount = p.Discount, + Rate = p.Rate, + ImagePath = p.ImagePath, + ThumbnailPath = p.ThumbnailPath, + SaleCount = p.SaleCount, + ViewCount = p.ViewCount, + RemainingCount = p.RemainingCount, + IsActive = !p.IsDeleted, + Categories = p.ProductCategories?.Select(pc => new ProductCategoryPathModel + { + CategoryId = pc.CategoryId, + Title = pc.Category?.Title ?? string.Empty, + Path = BuildCategoryPath(pc.Category) + }).ToList() ?? new List() + }).ToList(); + + return new GetCustomerProductsByFilterResponseDto + { + MetaData = metaData, + Models = models + }; + } + + private List BuildCategoryPath(Category? category) + { + var path = new List(); + + while (category != null) + { + path.Insert(0, new CategoryNodeItemModel + { + Id = category.Id, + Title = category.Title, + ParentId = category.ParentId + }); + + // Move to parent + if (category.ParentId.HasValue) + { + category = _context.Categories + .AsNoTracking() + .FirstOrDefault(c => c.Id == category.ParentId.Value); + } + else + { + category = null; + } + } + + return path; + } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterResponseDto.cs new file mode 100644 index 0000000..bccbcb8 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterResponseDto.cs @@ -0,0 +1,42 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter; + +public class GetCustomerProductsByFilterResponseDto +{ + public MetaData MetaData { get; set; } + public List Models { get; set; } +} + +public class CustomerProductModel +{ + public long Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string ShortInfomation { get; set; } + public string FullInformation { get; set; } + public long Price { get; set; } + public int Discount { get; set; } + public int Rate { get; set; } + public string ImagePath { get; set; } + public string ThumbnailPath { get; set; } + public int SaleCount { get; set; } + public int ViewCount { get; set; } + public int RemainingCount { get; set; } + public bool IsActive { get; set; } + public List Categories { get; set; } +} + +public class ProductCategoryPathModel +{ + public long CategoryId { get; set; } + public string Title { get; set; } + public List Path { get; set; } +} + +public class CategoryNodeItemModel +{ + public long Id { get; set; } + public string Title { get; set; } + public long? ParentId { get; set; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQuery.cs deleted file mode 100644 index 1b3a08e..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQuery.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetLowStockProducts; - -/// -/// دریافت محصولات کم موجودی -/// -public record GetLowStockProductsQuery : IRequest -{ - /// - /// آستانه موجودی (پیش‌فرض: 10) - /// - public int Threshold { get; init; } = 10; - - /// - /// شماره صفحه (پیش‌فرض: 1) - /// - public int PageIndex { get; init; } = 1; - - /// - /// تعداد در هر صفحه (پیش‌فرض: 20) - /// - public int PageSize { get; init; } = 20; - - /// - /// فقط محصولات انحصاری باشگاه (اختیاری) - /// - public bool? IsClubExclusive { get; init; } -} - -/// -/// پاسخ لیست محصولات کم موجودی -/// -public class GetLowStockProductsResponseDto -{ - public MetaData MetaData { get; set; } = new(); - public List Products { get; set; } = new(); -} - -/// -/// اطلاعات محصول کم موجودی -/// -public class LowStockProductDto -{ - public long Id { get; set; } - public string Title { get; set; } = string.Empty; - public long Price { get; set; } - public int Discount { get; set; } - public int RemainingCount { get; set; } - public int SaleCount { get; set; } - public bool IsClubExclusive { get; set; } - public string ImagePath { get; set; } = string.Empty; - public string ThumbnailPath { get; set; } = string.Empty; - public DateTime Created { get; set; } - public DateTime? LastModified { get; set; } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryHandler.cs deleted file mode 100644 index 3109c71..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryHandler.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetLowStockProducts; - -public class GetLowStockProductsQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public GetLowStockProductsQueryHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(GetLowStockProductsQuery request, CancellationToken cancellationToken) - { - // Query اصلی: محصولاتی که موجودی کمتر یا مساوی آستانه دارند - var query = _context.Products - .Where(p => p.RemainingCount <= request.Threshold); - - // فیلتر محصولات انحصاری باشگاه (اگر مشخص شده باشد) - if (request.IsClubExclusive.HasValue) - { - query = query.Where(p => p.IsClubExclusive == request.IsClubExclusive.Value); - } - - // مرتب‌سازی بر اساس موجودی (کمترین موجودی اول) - query = query.OrderBy(p => p.RemainingCount) - .ThenByDescending(p => p.SaleCount); // محصولات پرفروش اولویت بیشتری دارند - - // شمارش کل - var totalCount = await query.CountAsync(cancellationToken); - - // Pagination - var products = await query - .Skip((request.PageIndex - 1) * request.PageSize) - .Take(request.PageSize) - .Select(p => new LowStockProductDto - { - Id = p.Id, - Title = p.Title, - Price = p.Price, - Discount = p.Discount, - RemainingCount = p.RemainingCount, - SaleCount = p.SaleCount, - IsClubExclusive = p.IsClubExclusive, - ImagePath = p.ImagePath, - ThumbnailPath = p.ThumbnailPath, - Created = p.Created, - LastModified = p.LastModified - }) - .ToListAsync(cancellationToken); - - _logger.LogInformation( - "Found {Count} low stock products (threshold: {Threshold}, page: {Page})", - totalCount, request.Threshold, request.PageIndex); - - var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize); - - return new GetLowStockProductsResponseDto - { - MetaData = new MetaData - { - CurrentPage = request.PageIndex, - TotalPage = totalPages, - PageSize = request.PageSize, - TotalCount = totalCount, - HasNext = request.PageIndex < totalPages, - HasPrevious = request.PageIndex > 1 - }, - Products = products - }; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryValidator.cs deleted file mode 100644 index d1e3b12..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetLowStockProducts; - -public class GetLowStockProductsQueryValidator : AbstractValidator -{ - public GetLowStockProductsQueryValidator() - { - RuleFor(x => x.Threshold) - .GreaterThanOrEqualTo(0).WithMessage("آستانه موجودی نمی‌تواند منفی باشد"); - - RuleFor(x => x.PageIndex) - .GreaterThan(0).WithMessage("شماره صفحه باید بزرگتر از 0 باشد"); - - RuleFor(x => x.PageSize) - .InclusiveBetween(1, 100).WithMessage("تعداد در هر صفحه باید بین 1 تا 100 باشد"); - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryQuery.cs new file mode 100644 index 0000000..b355023 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductGallery; + +public class GetProductGalleryQuery : IRequest +{ + public long ProductId { get; set; } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryQueryHandler.cs new file mode 100644 index 0000000..61acd97 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryQueryHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Interfaces; + +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductGallery; + +public class GetProductGalleryQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetProductGalleryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetProductGalleryQuery request, CancellationToken cancellationToken) + { + var galleries = await _context.ProductGalleries + .AsNoTracking() + .Where(pg => pg.ProductId == request.ProductId) + .Include(pg => pg.ProductImage) + .ToListAsync(cancellationToken); + + return new GetProductGalleryResponseDto + { + Items = galleries.Select(pg => new ProductGalleryItemDto + { + ProductGalleryId = pg.Id, + ProductImageId = pg.ProductImageId, + Title = pg.ProductImage?.Title ?? string.Empty, + ImagePath = pg.ProductImage?.ImagePath ?? string.Empty, + ImageThumbnailPath = pg.ProductImage?.ImageThumbnailPath ?? string.Empty + }).ToList() + }; + } +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryResponseDto.cs new file mode 100644 index 0000000..0c8b7f2 --- /dev/null +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductGallery/GetProductGalleryResponseDto.cs @@ -0,0 +1,15 @@ +namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductGallery; + +public class GetProductGalleryResponseDto +{ + public List Items { get; set; } = new(); +} + +public class ProductGalleryItemDto +{ + public long ProductGalleryId { get; set; } + public long ProductImageId { get; set; } + public string Title { get; set; } = string.Empty; + public string ImagePath { get; set; } = string.Empty; + public string ImageThumbnailPath { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQuery.cs deleted file mode 100644 index 76c3d45..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQuery.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProducts; -public record GetProductsQuery : IRequest -{ - // - public long Id { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQueryHandler.cs deleted file mode 100644 index c936a0e..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQueryHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProducts; -public class GetProductsQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public GetProductsQueryHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(GetProductsQuery request, - CancellationToken cancellationToken) - { - var response = await _context.Products - .AsNoTracking() - .Where(x => x.Id == request.Id) - .Select(x => new GetProductsResponseDto - { - Id = x.Id, - Title = x.Title, - Description = x.Description, - ShortInfomation = x.ShortInfomation, - FullInformation = x.FullInformation, - Price = x.Price, - Discount = x.Discount, - Rate = x.Rate, - ImagePath = x.ImagePath, - ThumbnailPath = x.ThumbnailPath, - SaleCount = x.SaleCount, - ViewCount = x.ViewCount, - RemainingCount = x.RemainingCount, - CategoryIds = x.ProductCategories - .Select(pc => pc.CategoryId) - .ToList() - }) - .FirstOrDefaultAsync(cancellationToken); - - return response ?? throw new NotFoundException(nameof(Product), request.Id); - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQueryValidator.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQueryValidator.cs deleted file mode 100644 index e577eb0..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsQueryValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProducts; -public class GetProductsQueryValidator : AbstractValidator -{ - public GetProductsQueryValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetProductsQuery)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsResponseDto.cs deleted file mode 100644 index ea5ac1c..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProducts/GetProductsResponseDto.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProducts; -public class GetProductsResponseDto -{ - // - public long Id { get; set; } - // - public string Title { get; set; } - // - public string Description { get; set; } - // - public string ShortInfomation { get; set; } - // - public string FullInformation { get; set; } - // - public long Price { get; set; } - // - public int Discount { get; set; } - // - public int Rate { get; set; } - // - public string ImagePath { get; set; } - // - public string ThumbnailPath { get; set; } - // - public int SaleCount { get; set; } - // - public int ViewCount { get; set; } - // - public int RemainingCount { get; set; } - // لیست شناسه دسته‌بندی‌های محصول - public List CategoryIds { get; set; } = new(); - -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryQuery.cs deleted file mode 100644 index 7e14827..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryQuery.cs +++ /dev/null @@ -1,16 +0,0 @@ -using CMSMicroservice.Application.Common.Models; -using MediatR; - -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByCategory; - -/// -/// کوئری دریافت محصولات بر اساس دسته‌بندی -/// -public class GetProductsByCategoryQuery : IRequest -{ - public long CategoryId { get; set; } - public int PageNumber { get; set; } = 1; - public int PageSize { get; set; } = 20; - public bool OnlyActive { get; set; } = true; - public bool OnlyInStock { get; set; } = false; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryQueryHandler.cs deleted file mode 100644 index 35744ab..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryQueryHandler.cs +++ /dev/null @@ -1,74 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Application.Common.Models; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByCategory; - -public class GetProductsByCategoryQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public GetProductsByCategoryQueryHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(GetProductsByCategoryQuery request, CancellationToken cancellationToken) - { - var query = _context.Products - .Where(x => !x.IsDeleted) - .Where(x => x.ProductCategories.Any(pc => pc.CategoryId == request.CategoryId && !pc.IsDeleted)); - - if (request.OnlyInStock) - { - query = query.Where(x => x.RemainingCount > 0); - } - - var totalCount = await query.CountAsync(cancellationToken); - - var products = await query - .OrderByDescending(x => x.Created) - .Skip((request.PageNumber - 1) * request.PageSize) - .Take(request.PageSize) - .Select(x => new ProductListDto - { - Id = x.Id, - Name = x.Title, - Description = x.Description, - Price = x.Price, - Stock = x.RemainingCount, - IsActive = !x.IsDeleted, - ImageUrl = x.ImagePath, - Created = x.Created - }) - .ToListAsync(cancellationToken); - - var metaData = new MetaData - { - TotalCount = totalCount, - PageSize = request.PageSize, - CurrentPage = request.PageNumber, - TotalPage = (int)Math.Ceiling(totalCount / (double)request.PageSize), - HasNext = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize), - HasPrevious = request.PageNumber > 1 - }; - - _logger.LogInformation( - "Retrieved {Count} products for category {CategoryId}", - products.Count, - request.CategoryId - ); - - return new GetProductsByCategoryResponseDto - { - MetaData = metaData, - Products = products - }; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryResponseDto.cs deleted file mode 100644 index d346fda..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByCategory/GetProductsByCategoryResponseDto.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CMSMicroservice.Application.Common.Models; - -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByCategory; - -public class GetProductsByCategoryResponseDto -{ - public MetaData MetaData { get; set; } = new(); - public List Products { get; set; } = new(); -} - -public class ProductListDto -{ - public long Id { get; set; } - public string Name { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public long Price { get; set; } - public int Stock { get; set; } - public bool IsActive { get; set; } - public string? ImageUrl { get; set; } - public DateTime Created { get; set; } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagQuery.cs deleted file mode 100644 index 50368db..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagQuery.cs +++ /dev/null @@ -1,16 +0,0 @@ -using CMSMicroservice.Application.Common.Models; -using MediatR; - -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByTag; - -/// -/// کوئری دریافت محصولات بر اساس تگ -/// -public class GetProductsByTagQuery : IRequest -{ - public long TagId { get; set; } - public int PageNumber { get; set; } = 1; - public int PageSize { get; set; } = 20; - public bool OnlyActive { get; set; } = true; - public bool OnlyInStock { get; set; } = false; -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagQueryHandler.cs deleted file mode 100644 index acda699..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagQueryHandler.cs +++ /dev/null @@ -1,75 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Application.Common.Models; -using CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByCategory; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByTag; - -public class GetProductsByTagQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public GetProductsByTagQueryHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(GetProductsByTagQuery request, CancellationToken cancellationToken) - { - var query = _context.Products - .Where(x => !x.IsDeleted) - .Where(x => x.ProductTags.Any(pt => pt.TagId == request.TagId && !pt.IsDeleted)); - - if (request.OnlyInStock) - { - query = query.Where(x => x.RemainingCount > 0); - } - - var totalCount = await query.CountAsync(cancellationToken); - - var products = await query - .OrderByDescending(x => x.Created) - .Skip((request.PageNumber - 1) * request.PageSize) - .Take(request.PageSize) - .Select(x => new ProductListDto - { - Id = x.Id, - Name = x.Title, - Description = x.Description, - Price = x.Price, - Stock = x.RemainingCount, - IsActive = !x.IsDeleted, - ImageUrl = x.ImagePath, - Created = x.Created - }) - .ToListAsync(cancellationToken); - - var metaData = new MetaData - { - TotalCount = totalCount, - PageSize = request.PageSize, - CurrentPage = request.PageNumber, - TotalPage = (int)Math.Ceiling(totalCount / (double)request.PageSize), - HasNext = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize), - HasPrevious = request.PageNumber > 1 - }; - - _logger.LogInformation( - "Retrieved {Count} products for tag {TagId}", - products.Count, - request.TagId - ); - - return new GetProductsByTagResponseDto - { - MetaData = metaData, - Products = products - }; - } -} diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagResponseDto.cs deleted file mode 100644 index c2f7633..0000000 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetProductsByTag/GetProductsByTagResponseDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -using CMSMicroservice.Application.Common.Models; -using CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByCategory; - -namespace CMSMicroservice.Application.ProductsCQ.Queries.GetProductsByTag; - -public class GetProductsByTagResponseDto -{ - public MetaData MetaData { get; set; } = new(); - public List Products { get; set; } = new(); -} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommand.cs new file mode 100644 index 0000000..1239233 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommand.cs @@ -0,0 +1,18 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePage; + +public class CreateSitePageCommand : IRequest +{ + public string PageKey { get; set; } = default!; + public string Title { get; set; } = default!; + public string? MetaDescription { get; set; } + public string? HeroTitle { get; set; } + public string? HeroSubtitle { get; set; } + public bool IsActive { get; set; } = true; + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommandHandler.cs new file mode 100644 index 0000000..ed50d10 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommandHandler.cs @@ -0,0 +1,49 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePage; + +public class CreateSitePageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public CreateSitePageCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(CreateSitePageCommand request, CancellationToken cancellationToken) + { + var entity = new SitePage + { + PageKey = request.PageKey, + Title = request.Title, + MetaDescription = request.MetaDescription, + HeroTitle = request.HeroTitle, + HeroSubtitle = request.HeroSubtitle, + IsActive = request.IsActive + }; + + // آپلود تصویر هیرو (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePages", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.HeroImagePath = result.Main.Path; + } + + _context.SitePages.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommand.cs new file mode 100644 index 0000000..77f79dc --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommand.cs @@ -0,0 +1,21 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; + +public class CreateSitePageSectionCommand : IRequest +{ + public long SitePageId { get; set; } + public string SectionKey { get; set; } = default!; + public string? Title { get; set; } + public string? Subtitle { get; set; } + public string? HtmlContent { get; set; } + public string? IconName { get; set; } + public string? ImagePath { get; set; } + public bool IsActive { get; set; } = true; + public string? ExtraData { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs new file mode 100644 index 0000000..2f79325 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs @@ -0,0 +1,68 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; + +public class CreateSitePageSectionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public CreateSitePageSectionCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(CreateSitePageSectionCommand request, CancellationToken cancellationToken) + { + var page = await _context.SitePages.FirstOrDefaultAsync(x => x.Id == request.SitePageId && !x.IsDeleted, cancellationToken); + if (page == null) + throw new NotFoundException(nameof(SitePage), request.SitePageId); + + var maxSortOrder = await _context.SitePageSections + .Where(x => x.SitePageId == request.SitePageId && !x.IsDeleted) + .MaxAsync(x => (int?)x.SortOrder, cancellationToken) ?? 0; + + var sectionKey = string.IsNullOrWhiteSpace(request.SectionKey) + ? $"section-{Guid.NewGuid():N}"[..20] + : request.SectionKey.Trim().ToLower(); + + var entity = new SitePageSection + { + SitePageId = request.SitePageId, + SectionKey = sectionKey, + Title = request.Title, + Subtitle = request.Subtitle, + HtmlContent = request.HtmlContent, + IconName = request.IconName, + ImagePath = request.ImagePath, + SortOrder = maxSortOrder + 1, + IsActive = request.IsActive, + ExtraData = request.ExtraData + }; + + // آپلود تصویر بخش (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePageSections", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.ImagePath = result.Main.Path; + entity.ImageThumbnailPath = result.Thumbnail.Path; + } + + _context.SitePageSections.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs new file mode 100644 index 0000000..e8fe36c --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; + +public class CreateSitePageSectionCommandValidator : AbstractValidator +{ + public CreateSitePageSectionCommandValidator() + { + RuleFor(x => x.SitePageId) + .GreaterThan(0).WithMessage("شناسه صفحه نامعتبر است"); + + RuleFor(x => x.SectionKey) + .MaximumLength(100).WithMessage("کلید بخش حداکثر ۱۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-_]*$").WithMessage("کلید بخش فقط شامل حروف کوچک، اعداد، خط تیره و زیرخط") + .When(x => !string.IsNullOrWhiteSpace(x.SectionKey)); + + RuleFor(x => x.Title) + .MaximumLength(300).WithMessage("عنوان بخش حداکثر ۳۰۰ کاراکتر"); + + RuleFor(x => x.Subtitle) + .MaximumLength(500).WithMessage("زیرعنوان بخش حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommand.cs new file mode 100644 index 0000000..1b447da --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePage; + +public class DeleteSitePageCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommandHandler.cs new file mode 100644 index 0000000..75cdc0e --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommandHandler.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePage; + +public class DeleteSitePageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteSitePageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteSitePageCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePage), request.Id); + + entity.IsDeleted = true; + + // حذف نرم بخش‌های وابسته + foreach (var section in entity.Sections.Where(s => !s.IsDeleted)) + { + section.IsDeleted = true; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommand.cs new file mode 100644 index 0000000..9b16a0d --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePageSection; + +public class DeleteSitePageSectionCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommandHandler.cs new file mode 100644 index 0000000..aa5f08f --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommandHandler.cs @@ -0,0 +1,29 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePageSection; + +public class DeleteSitePageSectionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteSitePageSectionCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteSitePageSectionCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePageSections.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePageSection), request.Id); + + entity.IsDeleted = true; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommand.cs new file mode 100644 index 0000000..3231038 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.ReorderSitePageSections; + +public class ReorderSitePageSectionsCommand : IRequest +{ + public List Items { get; set; } = new(); +} + +public class SectionSortItem +{ + public long Id { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommandHandler.cs new file mode 100644 index 0000000..7d7844a --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommandHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.ReorderSitePageSections; + +public class ReorderSitePageSectionsCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReorderSitePageSectionsCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReorderSitePageSectionsCommand request, CancellationToken cancellationToken) + { + var ids = request.Items.Select(x => x.Id).ToList(); + var sections = await _context.SitePageSections + .Where(x => ids.Contains(x.Id) && !x.IsDeleted) + .ToListAsync(cancellationToken); + + foreach (var item in request.Items) + { + var section = sections.FirstOrDefault(x => x.Id == item.Id); + if (section != null) + section.SortOrder = item.SortOrder; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommand.cs new file mode 100644 index 0000000..b9dc42e --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; + +public class UpdateSitePageCommand : IRequest +{ + public long Id { get; set; } + public string Title { get; set; } = default!; + public string? MetaDescription { get; set; } + public string? HeroTitle { get; set; } + public string? HeroSubtitle { get; set; } + public string? HeroImagePath { get; set; } + public bool IsActive { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandHandler.cs new file mode 100644 index 0000000..289b600 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandHandler.cs @@ -0,0 +1,50 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; + +public class UpdateSitePageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public UpdateSitePageCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(UpdateSitePageCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePage), request.Id); + + entity.Title = request.Title; + entity.MetaDescription = request.MetaDescription; + entity.HeroTitle = request.HeroTitle; + entity.HeroSubtitle = request.HeroSubtitle; + entity.HeroImagePath = request.HeroImagePath; + entity.IsActive = request.IsActive; + + // آپلود تصویر هیرو (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePages", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.HeroImagePath = result.Main.Path; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandValidator.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandValidator.cs new file mode 100644 index 0000000..32cc616 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; + +public class UpdateSitePageCommandValidator : AbstractValidator +{ + public UpdateSitePageCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه صفحه نامعتبر است"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان صفحه الزامی است") + .MaximumLength(200).WithMessage("عنوان صفحه حداکثر ۲۰۰ کاراکتر"); + + RuleFor(x => x.MetaDescription) + .MaximumLength(500).WithMessage("توضیحات متا حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.HeroTitle) + .MaximumLength(300).WithMessage("عنوان هیرو حداکثر ۳۰۰ کاراکتر"); + + RuleFor(x => x.HeroSubtitle) + .MaximumLength(500).WithMessage("زیرعنوان هیرو حداکثر ۵۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommand.cs new file mode 100644 index 0000000..be6f6c7 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommand.cs @@ -0,0 +1,22 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; + +public class UpdateSitePageSectionCommand : IRequest +{ + public long Id { get; set; } + public string SectionKey { get; set; } = default!; + public string? Title { get; set; } + public string? Subtitle { get; set; } + public string? HtmlContent { get; set; } + public string? IconName { get; set; } + public string? ImagePath { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public string? ExtraData { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandHandler.cs new file mode 100644 index 0000000..ad58697 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandHandler.cs @@ -0,0 +1,54 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; + +public class UpdateSitePageSectionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public UpdateSitePageSectionCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(UpdateSitePageSectionCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePageSections.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePageSection), request.Id); + + entity.SectionKey = request.SectionKey; + entity.Title = request.Title; + entity.Subtitle = request.Subtitle; + entity.HtmlContent = request.HtmlContent; + entity.IconName = request.IconName; + entity.ImagePath = request.ImagePath; + entity.SortOrder = request.SortOrder; + entity.IsActive = request.IsActive; + entity.ExtraData = request.ExtraData; + + // آپلود تصویر بخش (اگر فایل جدید ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePageSections", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.ImagePath = result.Main.Path; + entity.ImageThumbnailPath = result.Thumbnail.Path; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandValidator.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandValidator.cs new file mode 100644 index 0000000..2a5b665 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; + +public class UpdateSitePageSectionCommandValidator : AbstractValidator +{ + public UpdateSitePageSectionCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه بخش نامعتبر است"); + + RuleFor(x => x.SectionKey) + .NotEmpty().WithMessage("کلید بخش الزامی است") + .MaximumLength(100).WithMessage("کلید بخش حداکثر ۱۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-_]+$").WithMessage("کلید بخش فقط شامل حروف کوچک، اعداد، خط تیره و زیرخط"); + + RuleFor(x => x.Title) + .MaximumLength(300).WithMessage("عنوان بخش حداکثر ۳۰۰ کاراکتر"); + + RuleFor(x => x.Subtitle) + .MaximumLength(500).WithMessage("زیرعنوان بخش حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetAllSitePages/GetAllSitePagesQuery.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetAllSitePages/GetAllSitePagesQuery.cs new file mode 100644 index 0000000..7f4d4ba --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetAllSitePages/GetAllSitePagesQuery.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetAllSitePages; + +public class GetAllSitePagesQuery : IRequest> +{ +} + +public class SitePageListItemDto +{ + public long Id { get; set; } + public string PageKey { get; set; } = default!; + public string Title { get; set; } = default!; + public bool IsActive { get; set; } + public int SectionCount { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } +} + +public class GetAllSitePagesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetAllSitePagesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetAllSitePagesQuery request, CancellationToken cancellationToken) + { + var pages = await _context.SitePages + .Include(x => x.Sections) + .Where(x => !x.IsDeleted) + .OrderBy(x => x.PageKey) + .Select(x => new SitePageListItemDto + { + Id = x.Id, + PageKey = x.PageKey, + Title = x.Title, + IsActive = x.IsActive, + SectionCount = x.Sections.Count(s => !s.IsDeleted), + Created = x.Created, + LastModified = x.LastModified + }) + .ToListAsync(cancellationToken); + + return pages; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQuery.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQuery.cs new file mode 100644 index 0000000..a83dc25 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; + +public class GetSitePageQuery : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQueryHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQueryHandler.cs new file mode 100644 index 0000000..f01ca01 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQueryHandler.cs @@ -0,0 +1,59 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; + +public class GetSitePageQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetSitePageQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetSitePageQuery request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages + .Include(x => x.Sections.Where(s => !s.IsDeleted).OrderBy(s => s.SortOrder)) + .FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken); + + if (entity == null) + throw new NotFoundException(nameof(SitePage), request.Id); + + return MapToDto(entity); + } + + internal static SitePageDto MapToDto(SitePage entity) + { + return new SitePageDto + { + Id = entity.Id, + PageKey = entity.PageKey, + Title = entity.Title, + MetaDescription = entity.MetaDescription, + HeroTitle = entity.HeroTitle, + HeroSubtitle = entity.HeroSubtitle, + HeroImagePath = entity.HeroImagePath, + IsActive = entity.IsActive, + Created = entity.Created, + LastModified = entity.LastModified, + Sections = entity.Sections.Select(s => new SitePageSectionDto + { + Id = s.Id, + SectionKey = s.SectionKey, + Title = s.Title, + Subtitle = s.Subtitle, + HtmlContent = s.HtmlContent, + IconName = s.IconName, + ImagePath = s.ImagePath, + SortOrder = s.SortOrder, + IsActive = s.IsActive, + ExtraData = s.ExtraData + }).ToList() + }; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/SitePageDto.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/SitePageDto.cs new file mode 100644 index 0000000..2aa46c7 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/SitePageDto.cs @@ -0,0 +1,30 @@ +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; + +public class SitePageDto +{ + public long Id { get; set; } + public string PageKey { get; set; } = default!; + public string Title { get; set; } = default!; + public string? MetaDescription { get; set; } + public string? HeroTitle { get; set; } + public string? HeroSubtitle { get; set; } + public string? HeroImagePath { get; set; } + public bool IsActive { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } + public List Sections { get; set; } = new(); +} + +public class SitePageSectionDto +{ + public long Id { get; set; } + public string SectionKey { get; set; } = default!; + public string? Title { get; set; } + public string? Subtitle { get; set; } + public string? HtmlContent { get; set; } + public string? IconName { get; set; } + public string? ImagePath { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public string? ExtraData { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePageByKey/GetSitePageByKeyQuery.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePageByKey/GetSitePageByKeyQuery.cs new file mode 100644 index 0000000..ffd890b --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePageByKey/GetSitePageByKeyQuery.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePageByKey; + +public class GetSitePageByKeyQuery : IRequest +{ + public string PageKey { get; set; } = default!; +} + +public class GetSitePageByKeyQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetSitePageByKeyQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetSitePageByKeyQuery request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages + .Include(x => x.Sections.Where(s => !s.IsDeleted).OrderBy(s => s.SortOrder)) + .FirstOrDefaultAsync(x => x.PageKey == request.PageKey && !x.IsDeleted, cancellationToken); + + if (entity == null) + throw new NotFoundException(nameof(SitePage), request.PageKey); + + return GetSitePage.GetSitePageQueryHandler.MapToDto(entity); + } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommand.cs b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommand.cs new file mode 100644 index 0000000..fbef3c1 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommand.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement; + +/// +/// Command برای ایجاد حرکت موجودی جدید +/// +public record CreateStockMovementCommand : IRequest +{ + /// شناسه آیتم موجودی + public long InventoryItemId { get; init; } + /// نوع حرکت + public StockMovementType MovementType { get; init; } + /// تعداد + public int Quantity { get; init; } + /// یادداشت + public string? Note { get; init; } + /// شماره مرجع + public string? ReferenceNumber { get; init; } + /// شناسه سفارش عادی + public long? OrderId { get; init; } + /// شناسه سفارش تخفیفی + public long? DiscountOrderId { get; init; } + /// شناسه کاربر انجام‌دهنده + public long? PerformedByUserId { get; init; } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandHandler.cs b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandHandler.cs new file mode 100644 index 0000000..8fac09f --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandHandler.cs @@ -0,0 +1,95 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement; + +public class CreateStockMovementCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateStockMovementCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateStockMovementCommand request, CancellationToken cancellationToken) + { + var inventoryItem = await _context.InventoryItems + .FirstOrDefaultAsync(i => i.Id == request.InventoryItemId, cancellationToken); + + if (inventoryItem == null) + { + throw new NotFoundException(nameof(InventoryItem), request.InventoryItemId); + } + + var quantityBefore = inventoryItem.Quantity; + var quantityAfter = quantityBefore; + + // اعمال تغییرات موجودی بر اساس نوع حرکت + switch (request.MovementType) + { + case StockMovementType.InitialStock: + case StockMovementType.Restock: + case StockMovementType.Return: + case StockMovementType.AdjustmentPlus: + case StockMovementType.TransferIn: + quantityAfter = quantityBefore + request.Quantity; + inventoryItem.Quantity = quantityAfter; + inventoryItem.LastRestockedAt = DateTime.UtcNow; + break; + + case StockMovementType.Sale: + case StockMovementType.AdjustmentMinus: + case StockMovementType.Lost: + case StockMovementType.Damaged: + case StockMovementType.TransferOut: + if (inventoryItem.Quantity < request.Quantity) + { + throw new InvalidOperationException($"Insufficient stock. Available: {inventoryItem.Quantity}, Requested: {request.Quantity}"); + } + quantityAfter = quantityBefore - request.Quantity; + inventoryItem.Quantity = quantityAfter; + if (request.MovementType == StockMovementType.Sale) + { + inventoryItem.LastSoldAt = DateTime.UtcNow; + } + break; + + case StockMovementType.Reserved: + if (inventoryItem.AvailableQuantity < request.Quantity) + { + throw new InvalidOperationException($"Insufficient available stock. Available: {inventoryItem.AvailableQuantity}, Requested: {request.Quantity}"); + } + inventoryItem.ReservedQuantity += request.Quantity; + break; + + case StockMovementType.Released: + if (inventoryItem.ReservedQuantity < request.Quantity) + { + throw new InvalidOperationException($"Cannot release more than reserved. Reserved: {inventoryItem.ReservedQuantity}, Requested: {request.Quantity}"); + } + inventoryItem.ReservedQuantity -= request.Quantity; + break; + } + + var entity = new StockMovement + { + InventoryItemId = request.InventoryItemId, + MovementType = request.MovementType, + Quantity = request.Quantity, + QuantityBefore = quantityBefore, + QuantityAfter = quantityAfter, + Note = request.Note, + ReferenceNumber = request.ReferenceNumber ?? $"MVT-{DateTime.UtcNow:yyyyMMddHHmmss}", + OrderId = request.OrderId, + DiscountOrderId = request.DiscountOrderId, + PerformedByUserId = request.PerformedByUserId + }; + + await _context.StockMovements.AddAsync(entity, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + return new CreateStockMovementResponseDto { Id = entity.Id }; + } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandValidator.cs b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandValidator.cs new file mode 100644 index 0000000..8388116 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementCommandValidator.cs @@ -0,0 +1,16 @@ +namespace CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement; + +public class CreateStockMovementCommandValidator : AbstractValidator +{ + public CreateStockMovementCommandValidator() + { + RuleFor(x => x.InventoryItemId) + .GreaterThan(0).WithMessage("شناسه آیتم موجودی معتبر نیست"); + + RuleFor(x => x.Quantity) + .GreaterThan(0).WithMessage("تعداد باید بیشتر از صفر باشد"); + + RuleFor(x => x.MovementType) + .IsInEnum().WithMessage("نوع حرکت معتبر نیست"); + } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementResponseDto.cs b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementResponseDto.cs new file mode 100644 index 0000000..a3d3144 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Commands/CreateStockMovement/CreateStockMovementResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement; + +public class CreateStockMovementResponseDto +{ + /// شناسه حرکت موجودی ایجاد شده + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsQuery.cs b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsQuery.cs new file mode 100644 index 0000000..8a82038 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsQuery.cs @@ -0,0 +1,18 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements; + +/// +/// Query برای جستجوی حرکات موجودی +/// +public record GetStockMovementsQuery : IRequest +{ + public long? InventoryItemId { get; init; } + public long? ProductId { get; init; } + public ProductType? ProductType { get; init; } + public StockMovementType? MovementType { get; init; } + public DateTime? StartDate { get; init; } + public DateTime? EndDate { get; init; } + public int Skip { get; init; } = 0; + public int Take { get; init; } = 50; +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsQueryHandler.cs b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsQueryHandler.cs new file mode 100644 index 0000000..6beadf9 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsQueryHandler.cs @@ -0,0 +1,92 @@ +namespace CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements; + +public class GetStockMovementsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetStockMovementsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetStockMovementsQuery request, CancellationToken cancellationToken) + { + var query = _context.StockMovements + .AsNoTracking() + .Include(s => s.InventoryItem) + .ThenInclude(i => i!.Product) + .Include(s => s.InventoryItem) + .ThenInclude(i => i!.DiscountProduct) + .AsQueryable(); + + // فیلترها + if (request.InventoryItemId.HasValue) + { + query = query.Where(s => s.InventoryItemId == request.InventoryItemId.Value); + } + + if (request.ProductId.HasValue) + { + query = query.Where(s => + s.InventoryItem != null && + (s.InventoryItem.ProductId == request.ProductId.Value || + s.InventoryItem.DiscountProductId == request.ProductId.Value)); + } + + if (request.ProductType.HasValue) + { + query = query.Where(s => + s.InventoryItem != null && + s.InventoryItem.ProductType == request.ProductType.Value); + } + + if (request.MovementType.HasValue) + { + query = query.Where(s => s.MovementType == request.MovementType.Value); + } + + if (request.StartDate.HasValue) + { + query = query.Where(s => s.Created >= request.StartDate.Value); + } + + if (request.EndDate.HasValue) + { + query = query.Where(s => s.Created <= request.EndDate.Value); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var movements = await query + .OrderByDescending(s => s.Created) + .Skip(request.Skip) + .Take(request.Take) + .Select(s => new StockMovementListDto + { + Id = s.Id, + InventoryItemId = s.InventoryItemId, + MovementType = s.MovementType, + Quantity = s.Quantity, + QuantityBefore = s.QuantityBefore, + QuantityAfter = s.QuantityAfter, + Note = s.Note, + ReferenceNumber = s.ReferenceNumber, + OrderId = s.OrderId, + DiscountOrderId = s.DiscountOrderId, + PerformedByUserId = s.PerformedByUserId, + ProductTitle = s.InventoryItem != null && s.InventoryItem.Product != null + ? s.InventoryItem.Product.Title + : (s.InventoryItem != null && s.InventoryItem.DiscountProduct != null + ? s.InventoryItem.DiscountProduct.Title + : null), + Created = s.Created + }) + .ToListAsync(cancellationToken); + + return new GetStockMovementsResponseDto + { + Movements = movements, + TotalCount = totalCount + }; + } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsResponseDto.cs b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsResponseDto.cs new file mode 100644 index 0000000..f8ffb39 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovements/GetStockMovementsResponseDto.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements; + +public class GetStockMovementsResponseDto +{ + public List Movements { get; set; } = new(); + public int TotalCount { get; set; } +} + +public class StockMovementListDto +{ + public long Id { get; set; } + public long InventoryItemId { get; set; } + public StockMovementType MovementType { get; set; } + public int Quantity { get; set; } + public int QuantityBefore { get; set; } + public int QuantityAfter { get; set; } + public string? Note { get; set; } + public string? ReferenceNumber { get; set; } + public long? OrderId { get; set; } + public long? DiscountOrderId { get; set; } + public long? PerformedByUserId { get; set; } + public string? ProductTitle { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemQuery.cs b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemQuery.cs new file mode 100644 index 0000000..aa4c28c --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemQuery.cs @@ -0,0 +1,11 @@ +namespace CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem; + +/// +/// Query برای دریافت تاریخچه حرکات موجودی یک آیتم +/// +public record GetStockMovementsByInventoryItemQuery : IRequest +{ + public long InventoryItemId { get; init; } + public int Skip { get; init; } = 0; + public int Take { get; init; } = 50; +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemQueryHandler.cs b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemQueryHandler.cs new file mode 100644 index 0000000..d9cb4e3 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemQueryHandler.cs @@ -0,0 +1,46 @@ +namespace CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem; + +public class GetStockMovementsByInventoryItemQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetStockMovementsByInventoryItemQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetStockMovementsByInventoryItemQuery request, CancellationToken cancellationToken) + { + var query = _context.StockMovements + .AsNoTracking() + .Where(s => s.InventoryItemId == request.InventoryItemId); + + var totalCount = await query.CountAsync(cancellationToken); + + var movements = await query + .OrderByDescending(s => s.Created) + .Skip(request.Skip) + .Take(request.Take) + .Select(s => new StockMovementHistoryDto + { + Id = s.Id, + MovementType = s.MovementType, + Quantity = s.Quantity, + QuantityBefore = s.QuantityBefore, + QuantityAfter = s.QuantityAfter, + Note = s.Note, + ReferenceNumber = s.ReferenceNumber, + OrderId = s.OrderId, + DiscountOrderId = s.DiscountOrderId, + PerformedByUserId = s.PerformedByUserId, + Created = s.Created + }) + .ToListAsync(cancellationToken); + + return new GetStockMovementsByInventoryItemResponseDto + { + Movements = movements, + TotalCount = totalCount + }; + } +} diff --git a/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemResponseDto.cs b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemResponseDto.cs new file mode 100644 index 0000000..79c7102 --- /dev/null +++ b/src/CMSMicroservice.Application/StockMovementCQ/Queries/GetStockMovementsByInventoryItem/GetStockMovementsByInventoryItemResponseDto.cs @@ -0,0 +1,24 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem; + +public class GetStockMovementsByInventoryItemResponseDto +{ + public List Movements { get; set; } = new(); + public int TotalCount { get; set; } +} + +public class StockMovementHistoryDto +{ + public long Id { get; set; } + public StockMovementType MovementType { get; set; } + public int Quantity { get; set; } + public int QuantityBefore { get; set; } + public int QuantityAfter { get; set; } + public string? Note { get; set; } + public string? ReferenceNumber { get; set; } + public long? OrderId { get; set; } + public long? DiscountOrderId { get; set; } + public long? PerformedByUserId { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionQuery.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionQuery.cs new file mode 100644 index 0000000..4e63137 --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionQuery.cs @@ -0,0 +1,8 @@ +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction; + +public class GetCustomerTransactionQuery : IRequest +{ + public long? Id { get; set; } + public string Authority { get; set; } + public long UserId { get; set; } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionQueryHandler.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionQueryHandler.cs new file mode 100644 index 0000000..cfeb590 --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionQueryHandler.cs @@ -0,0 +1,52 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction; + +public class GetCustomerTransactionQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerTransactionQueryHandler( + IApplicationDbContext context, + ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerTransactionQuery request, CancellationToken cancellationToken) + { + // Resolve UserId from JWT if not specified + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found"); + + // Transaction entity doesn't have UserId, so we need to find it through UserOrders + var transaction = await _context.Transactions + .AsNoTracking() + .Where(x => request.Id.HasValue ? x.Id == request.Id.Value : true) + .Include(x => x.UserOrders) + .Where(x => x.UserOrders.Any(o => o.UserId == userId)) + .FirstOrDefaultAsync(cancellationToken); + + if (transaction == null) + throw new NotFoundException(nameof(Transaction), request.Id ?? 0); + + return new GetCustomerTransactionResponseDto + { + Id = transaction.Id, + Amount = transaction.Amount, + Description = transaction.Description ?? "", + PaymentStatus = transaction.PaymentStatus, + PaymentDate = transaction.PaymentDate, + RefId = transaction.RefId ?? "", + Type = transaction.Type + }; + } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionResponseDto.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionResponseDto.cs new file mode 100644 index 0000000..e23e6d0 --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransaction/GetCustomerTransactionResponseDto.cs @@ -0,0 +1,14 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction; + +public class GetCustomerTransactionResponseDto +{ + public long Id { get; set; } + public long Amount { get; set; } + public string Description { get; set; } + public PaymentStatus PaymentStatus { get; set; } + public DateTime? PaymentDate { get; set; } + public string RefId { get; set; } + public TransactionType Type { get; set; } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterQuery.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterQuery.cs new file mode 100644 index 0000000..3cc3afd --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterQuery.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter; + +public class GetCustomerTransactionsByFilterQuery : IRequest +{ + public long UserId { get; set; } + public PaginationState PaginationState { get; set; } + public string SortBy { get; set; } + public long? IdFilter { get; set; } + public long? AmountFilter { get; set; } + public string DescriptionFilter { get; set; } + public bool? PaymentStatusFilter { get; set; } + public string RefIdFilter { get; set; } + public int? TypeFilter { get; set; } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterQueryHandler.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterQueryHandler.cs new file mode 100644 index 0000000..6ce8d8b --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterQueryHandler.cs @@ -0,0 +1,92 @@ +using CMSMicroservice.Application.Common.Extensions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter; + +public class GetCustomerTransactionsByFilterQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerTransactionsByFilterQueryHandler( + IApplicationDbContext context, + ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerTransactionsByFilterQuery request, CancellationToken cancellationToken) + { + // Resolve UserId from JWT if not specified + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found"); + + // Transaction doesn't have UserId, find through UserOrders + var query = _context.Transactions + .AsNoTracking() + .Include(x => x.UserOrders) + .Where(x => x.UserOrders.Any(o => o.UserId == userId)) + .AsQueryable(); + + // Apply filters + if (request.IdFilter.HasValue) + query = query.Where(x => x.Id == request.IdFilter.Value); + + if (request.AmountFilter.HasValue) + query = query.Where(x => x.Amount == request.AmountFilter.Value); + + if (!string.IsNullOrEmpty(request.DescriptionFilter)) + query = query.Where(x => x.Description.Contains(request.DescriptionFilter)); + + if (request.PaymentStatusFilter.HasValue) + { + var status = request.PaymentStatusFilter.Value + ? Domain.Enums.PaymentStatus.Success + : Domain.Enums.PaymentStatus.Reject; + query = query.Where(x => x.PaymentStatus == status); + } + + if (!string.IsNullOrEmpty(request.RefIdFilter)) + query = query.Where(x => x.RefId == request.RefIdFilter); + + if (request.TypeFilter.HasValue) + query = query.Where(x => (int)x.Type == request.TypeFilter.Value); + + // Apply sorting + if (!string.IsNullOrEmpty(request.SortBy)) + query = query.ApplyOrder(request.SortBy); + else + query = query.OrderByDescending(x => x.Created); + + // Get metadata + var metaData = await query.GetMetaData(request.PaginationState, cancellationToken); + + // Get paginated results + var transactions = await query + .PaginatedListAsync(request.PaginationState) + .ToListAsync(cancellationToken); + + var models = transactions.Select(t => new CustomerTransactionModel + { + Id = t.Id, + Amount = t.Amount, + Description = t.Description ?? "", + PaymentStatus = t.PaymentStatus, + PaymentDate = t.PaymentDate, + RefId = t.RefId ?? "", + Type = t.Type + }).ToList(); + + return new GetCustomerTransactionsByFilterResponseDto + { + MetaData = metaData, + Models = models + }; + } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterResponseDto.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterResponseDto.cs new file mode 100644 index 0000000..2fca6f8 --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetCustomerTransactionsByFilter/GetCustomerTransactionsByFilterResponseDto.cs @@ -0,0 +1,21 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter; + +public class GetCustomerTransactionsByFilterResponseDto +{ + public MetaData MetaData { get; set; } + public List Models { get; set; } = new(); +} + +public class CustomerTransactionModel +{ + public long Id { get; set; } + public long Amount { get; set; } + public string Description { get; set; } + public PaymentStatus PaymentStatus { get; set; } + public DateTime? PaymentDate { get; set; } + public string RefId { get; set; } + public TransactionType Type { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/CreateCustomerAddress/CreateCustomerAddressCommand.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/CreateCustomerAddress/CreateCustomerAddressCommand.cs new file mode 100644 index 0000000..10e6639 --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/CreateCustomerAddress/CreateCustomerAddressCommand.cs @@ -0,0 +1,22 @@ +using MediatR; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.CreateCustomerAddress; + +/// +/// Command برای ایجاد آدرس جدید برای کاربر فعلی +/// +public class CreateCustomerAddressCommand : IRequest +{ + public string Title { get; set; } = string.Empty; + public string Address { get; set; } = string.Empty; + public string PostalCode { get; set; } = string.Empty; + public bool IsDefault { get; set; } + public long CityId { get; set; } + // UserId from ICurrentUserService +} + +public class CreateCustomerAddressCommandResponse +{ + public long Id { get; set; } + public string Message { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/CreateCustomerAddress/CreateCustomerAddressCommandHandler.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/CreateCustomerAddress/CreateCustomerAddressCommandHandler.cs new file mode 100644 index 0000000..b6f3e4d --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/CreateCustomerAddress/CreateCustomerAddressCommandHandler.cs @@ -0,0 +1,62 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.CreateCustomerAddress; + +public class CreateCustomerAddressCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public CreateCustomerAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(CreateCustomerAddressCommand request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // If this address is set as default, unset other defaults + if (request.IsDefault) + { + var existingDefaults = await _context.UserAddresses + .Where(ua => ua.UserId == userId && ua.IsDefault && !ua.IsDeleted) + .ToListAsync(cancellationToken); + + foreach (var addr in existingDefaults) + { + addr.IsDefault = false; + } + } + + // Create new address + var address = new UserAddress + { + UserId = userId, + Title = request.Title, + Address = request.Address, + PostalCode = request.PostalCode, + IsDefault = request.IsDefault, + CityId = request.CityId, + Created = DateTime.UtcNow + }; + + _context.UserAddresses.Add(address); + await _context.SaveChangesAsync(cancellationToken); + + return new CreateCustomerAddressCommandResponse + { + Id = address.Id, + Message = "آدرس با موفقیت ایجاد شد" + }; + } +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/DeleteCustomerAddress/DeleteCustomerAddressCommand.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/DeleteCustomerAddress/DeleteCustomerAddressCommand.cs new file mode 100644 index 0000000..a0d22ed --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/DeleteCustomerAddress/DeleteCustomerAddressCommand.cs @@ -0,0 +1,12 @@ +using MediatR; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.DeleteCustomerAddress; + +/// +/// Command برای حذف آدرس کاربر فعلی +/// +public class DeleteCustomerAddressCommand : IRequest +{ + public long Id { get; set; } + // UserId from ICurrentUserService +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/DeleteCustomerAddress/DeleteCustomerAddressCommandHandler.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/DeleteCustomerAddress/DeleteCustomerAddressCommandHandler.cs new file mode 100644 index 0000000..ccaa56b --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/DeleteCustomerAddress/DeleteCustomerAddressCommandHandler.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.DeleteCustomerAddress; + +public class DeleteCustomerAddressCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public DeleteCustomerAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(DeleteCustomerAddressCommand request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Find address and verify ownership + var address = await _context.UserAddresses + .FirstOrDefaultAsync(ua => ua.Id == request.Id && ua.UserId == userId && !ua.IsDeleted, cancellationToken); + + if (address == null) + { + throw new Exception("آدرس یافت نشد یا به شما تعلق ندارد"); + } + + // Soft delete + address.IsDeleted = true; + address.LastModified = DateTime.UtcNow; + + _context.UserAddresses.Update(address); + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/SetCustomerDefaultAddress/SetCustomerDefaultAddressCommand.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/SetCustomerDefaultAddress/SetCustomerDefaultAddressCommand.cs new file mode 100644 index 0000000..dc46d30 --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/SetCustomerDefaultAddress/SetCustomerDefaultAddressCommand.cs @@ -0,0 +1,12 @@ +using MediatR; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.SetCustomerDefaultAddress; + +/// +/// Command برای تنظیم آدرس پیش‌فرض کاربر فعلی +/// +public class SetCustomerDefaultAddressCommand : IRequest +{ + public long Id { get; set; } + // UserId from ICurrentUserService +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/SetCustomerDefaultAddress/SetCustomerDefaultAddressCommandHandler.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/SetCustomerDefaultAddress/SetCustomerDefaultAddressCommandHandler.cs new file mode 100644 index 0000000..7ea2a0e --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/SetCustomerDefaultAddress/SetCustomerDefaultAddressCommandHandler.cs @@ -0,0 +1,55 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.SetCustomerDefaultAddress; + +public class SetCustomerDefaultAddressCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public SetCustomerDefaultAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(SetCustomerDefaultAddressCommand request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Find address and verify ownership + var address = await _context.UserAddresses + .FirstOrDefaultAsync(ua => ua.Id == request.Id && ua.UserId == userId && !ua.IsDeleted, cancellationToken); + + if (address == null) + { + throw new Exception("آدرس یافت نشد یا به شما تعلق ندارد"); + } + + // Unset all other defaults for this user + var existingDefaults = await _context.UserAddresses + .Where(ua => ua.UserId == userId && ua.IsDefault && ua.Id != request.Id && !ua.IsDeleted) + .ToListAsync(cancellationToken); + + foreach (var addr in existingDefaults) + { + addr.IsDefault = false; + } + + // Set this address as default + address.IsDefault = true; + address.LastModified = DateTime.UtcNow; + + _context.UserAddresses.Update(address); + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/UpdateCustomerAddress/UpdateCustomerAddressCommand.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/UpdateCustomerAddress/UpdateCustomerAddressCommand.cs new file mode 100644 index 0000000..88da490 --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/UpdateCustomerAddress/UpdateCustomerAddressCommand.cs @@ -0,0 +1,17 @@ +using MediatR; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.UpdateCustomerAddress; + +/// +/// Command برای به‌روزرسانی آدرس کاربر فعلی +/// +public class UpdateCustomerAddressCommand : IRequest +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Address { get; set; } = string.Empty; + public string PostalCode { get; set; } = string.Empty; + public bool IsDefault { get; set; } + public long CityId { get; set; } + // UserId from ICurrentUserService +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Commands/UpdateCustomerAddress/UpdateCustomerAddressCommandHandler.cs b/src/CMSMicroservice.Application/UserAddressCQ/Commands/UpdateCustomerAddress/UpdateCustomerAddressCommandHandler.cs new file mode 100644 index 0000000..a1e5f06 --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Commands/UpdateCustomerAddress/UpdateCustomerAddressCommandHandler.cs @@ -0,0 +1,62 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserAddressCQ.Commands.UpdateCustomerAddress; + +public class UpdateCustomerAddressCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public UpdateCustomerAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(UpdateCustomerAddressCommand request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Find address and verify ownership + var address = await _context.UserAddresses + .FirstOrDefaultAsync(ua => ua.Id == request.Id && ua.UserId == userId && !ua.IsDeleted, cancellationToken); + + if (address == null) + { + throw new Exception("آدرس یافت نشد یا به شما تعلق ندارد"); + } + + // If setting as default, unset other defaults + if (request.IsDefault && !address.IsDefault) + { + var existingDefaults = await _context.UserAddresses + .Where(ua => ua.UserId == userId && ua.IsDefault && ua.Id != request.Id && !ua.IsDeleted) + .ToListAsync(cancellationToken); + + foreach (var addr in existingDefaults) + { + addr.IsDefault = false; + } + } + + // Update address + address.Title = request.Title; + address.Address = request.Address; + address.PostalCode = request.PostalCode; + address.IsDefault = request.IsDefault; + address.CityId = request.CityId; + address.LastModified = DateTime.UtcNow; + + _context.UserAddresses.Update(address); + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQuery.cs b/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQuery.cs new file mode 100644 index 0000000..6095648 --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQuery.cs @@ -0,0 +1,11 @@ +using MediatR; + +namespace CMSMicroservice.Application.UserAddressCQ.Queries.GetCustomerAddresses; + +/// +/// Query برای دریافت لیست آدرس‌های کاربر فعلی +/// +public class GetCustomerAddressesQuery : IRequest +{ + // UserId from ICurrentUserService +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQueryHandler.cs b/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQueryHandler.cs new file mode 100644 index 0000000..f20c670 --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQueryHandler.cs @@ -0,0 +1,53 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserAddressCQ.Queries.GetCustomerAddresses; + +public class GetCustomerAddressesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerAddressesQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerAddressesQuery request, CancellationToken cancellationToken) + { + // Extract UserId from JWT token + var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0; + if (userId == 0) + { + throw new UnauthorizedAccessException("User not authenticated"); + } + + // Get all addresses for the current user + var addresses = await _context.UserAddresses + .Where(ua => ua.UserId == userId && !ua.IsDeleted) + .OrderByDescending(ua => ua.IsDefault) + .ThenByDescending(ua => ua.Created) + .ToListAsync(cancellationToken); + + var response = new GetCustomerAddressesQueryResponse(); + + foreach (var addr in addresses) + { + response.Addresses.Add(new CustomerAddressModel + { + Id = addr.Id, + Title = addr.Title, + Address = addr.Address, + PostalCode = addr.PostalCode, + IsDefault = addr.IsDefault, + CityId = addr.CityId, + CityName = string.Empty, // Will be populated by FrontOffice from City service + ProvinceName = string.Empty // Will be populated by FrontOffice from City service + }); + } + + return response; + } +} diff --git a/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQueryResponse.cs b/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQueryResponse.cs new file mode 100644 index 0000000..d988ce7 --- /dev/null +++ b/src/CMSMicroservice.Application/UserAddressCQ/Queries/GetCustomerAddresses/GetCustomerAddressesQueryResponse.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.UserAddressCQ.Queries.GetCustomerAddresses; + +public class GetCustomerAddressesQueryResponse +{ + public List Addresses { get; set; } = new(); +} + +public class CustomerAddressModel +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Address { get; set; } = string.Empty; + public string PostalCode { get; set; } = string.Empty; + public bool IsDefault { get; set; } + public long CityId { get; set; } + public string CityName { get; set; } = string.Empty; + public string ProvinceName { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommand.cs b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommand.cs new file mode 100644 index 0000000..b800cef --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommand.cs @@ -0,0 +1,11 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.AcceptContract; +public record AcceptContractCommand : IRequest +{ + //کد otp + public string Code { get; init; } + //فایل قرارداد + public string ContractHtml { get; init; } + //شناسه یکتای امضا + public string SignGuid { get; init; } + +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs new file mode 100644 index 0000000..ccd1322 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs @@ -0,0 +1,89 @@ +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.UserCQ.Commands.AcceptContract; +public class AcceptContractCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUserService; + private readonly IHashService _hashService; + private readonly IGenerateJwtToken _generateJwt; + private readonly IConfiguration _cfg; + + public AcceptContractCommandHandler(IApplicationDbContext context, ICurrentUserService currentUserService, + IHashService hashService, IGenerateJwtToken generateJwt, IConfiguration cfg) + { + _context = context; + _currentUserService = currentUserService; + _hashService = hashService; + _generateJwt = generateJwt; + _cfg = cfg; + } + + public async Task Handle(AcceptContractCommand request, CancellationToken cancellationToken) + { + // پیدا کردن کاربر بر اساس UserId از توکن JWT + if (!long.TryParse(_currentUserService.UserId, out var userId)) + return new AcceptContractResponseDto { IsSuccess = false, Message = "کاربر احراز هویت نشده است" }; + + var user = await _context.Users + .Include(u => u.UserContracts) + .ThenInclude(uc => uc.Contract) + .Include(u => u.UserRoles) + .ThenInclude(ur => ur.Role) + .Include(u => u.ClubMembership) + .Where(x => x.Id == userId) + .FirstOrDefaultAsync(cancellationToken); + + if (user == null) + return new AcceptContractResponseDto { IsSuccess = false, Message = "کاربر یافت نشد" }; + + // جستجوی OTP بر اساس شماره موبایل کاربر (نه Username) + var otpToken = await _context.OtpTokens + .Where(x => x.Mobile == user.Mobile && x.Purpose == "signContract" && !x.IsUsed) + .OrderByDescending(x => x.Id) + .FirstOrDefaultAsync(cancellationToken); + + var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); + if (otpToken == null || !otpToken.IsValid() || !_hashService.VerifyHmacSha256Hex(request.Code, otpToken.CodeHash, secret)) + return new AcceptContractResponseDto { IsSuccess = false, Message = "کد تایید نامعتبر است" }; + + // Create user contract + var userContract = new UserContract + { + UserId = user.Id, + ContractId = 1, // Default contract + SignGuid = request.SignGuid, + SignedPdfFile = request.ContractHtml + }; + + _context.UserContracts.Add(userContract); + + // Mark OTP as used + otpToken.IsUsed = true; + + await _context.SaveChangesAsync(cancellationToken); + + // بارگذاری مجدد کاربر با تمام روابط برای تولید JWT صحیح + user = await _context.Users + .Include(u => u.UserContracts) + .ThenInclude(uc => uc.Contract) + .Include(u => u.UserRoles) + .ThenInclude(ur => ur.Role) + .Include(u => u.ClubMembership) + .Where(x => x.Id == userId) + .FirstAsync(cancellationToken); + + // Generate JWT token with updated contract status + var token = await _generateJwt.GenerateJwtToken(user); + + return new AcceptContractResponseDto + { + IsSuccess = true, + Message = "قرارداد با موفقیت تایید شد", + Token = token + }; + } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandValidator.cs b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandValidator.cs new file mode 100644 index 0000000..763f488 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandValidator.cs @@ -0,0 +1,20 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.AcceptContract; +public class AcceptContractCommandValidator : AbstractValidator +{ + public AcceptContractCommandValidator() + { + RuleFor(model => model.Code) + .NotEmpty(); + RuleFor(model => model.ContractHtml) + .NotEmpty(); + RuleFor(model => model.SignGuid) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((AcceptContractCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractResponseDto.cs b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractResponseDto.cs new file mode 100644 index 0000000..63363fe --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractResponseDto.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.AcceptContract; +public class AcceptContractResponseDto +{ + //موفق؟ + public bool IsSuccess { get; set; } + //پیام + public string? Message { get; set; } + //توکن + public string? Token { get; set; } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommand.cs b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommand.cs new file mode 100644 index 0000000..06b0eb2 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommand.cs @@ -0,0 +1,11 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken; +public record CreateNewOtpTokenCommand : IRequest +{ + //موبایل مقصد + public string Mobile { get; init; } + //مقصود + public string Purpose { get; init; } + //شناسه امضا + public string? SignGuid { get; init; } + +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs new file mode 100644 index 0000000..c9d9603 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs @@ -0,0 +1,109 @@ +using System.Security.Cryptography; +using System.Text; +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken; + +public class CreateNewOtpTokenCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IKavenegarService _kavenegarService; + private readonly IHashService _hashService; + private readonly IConfiguration _cfg; + + const int CodeLength = 6; + const int MaxAttempts = 5; + static readonly TimeSpan Ttl = TimeSpan.FromMinutes(2); + static readonly TimeSpan Cooldown = TimeSpan.FromSeconds(60); + + public CreateNewOtpTokenCommandHandler( + IApplicationDbContext context, + IKavenegarService kavenegarService, + IHashService hashService, + IConfiguration cfg) + { + _context = context; + _kavenegarService = kavenegarService; + _hashService = hashService; + _cfg = cfg; + } + + public async Task Handle(CreateNewOtpTokenCommand request, + CancellationToken cancellationToken) + { + var mobile = request.Mobile.NormalizeIranMobile(); + var purpose = request.Purpose?.ToLowerInvariant() ?? "login"; + var now = DateTime.Now; + + // ریت‌لیمیت: اگر هنوز کدی فعال و تازه داریم، اجازه نده + var lastActive = await _context.OtpTokens + .Where(o => o.Mobile == mobile && o.Purpose == purpose && !o.IsUsed && o.ExpiresAt > now) + .OrderByDescending(o => o.Created) + .FirstOrDefaultAsync(cancellationToken); + + if (lastActive is not null && (now - lastActive.Created) < Cooldown) + return new CreateNewOtpTokenResponseDto + { + Success = false, + Message = "لطفاً کمی بعد دوباره تلاش کنید." + }; + + // تولید کد ۶ رقمی امن + var code = GenerateNumericCode(CodeLength); + var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); + var codeHash = _hashService.ComputeHmacSha256Hex(code, secret); + + // Create new OTP token + var otpToken = new OtpToken + { + Mobile = mobile, + Purpose = purpose, + CodeHash = codeHash, + IsUsed = false, + Attempts = 0, + ExpiresAt = now.Add(Ttl) + }; + + _context.OtpTokens.Add(otpToken); + await _context.SaveChangesAsync(cancellationToken); + + try + { + // برای امضای قرارداد، شناسه GUID هم در پیامک ارسال شود + if ((purpose == "signcontract" || purpose == "signclubcontract") && !string.IsNullOrEmpty(request.SignGuid)) + { + var message = $"کد تایید امضای قرارداد: {code}\nشناسه قرارداد: {request.SignGuid}"; + await _kavenegarService.SendAsync(mobile, message); + } + else + { + await _kavenegarService.VerifyLookupAsync(mobile, code); + } + } + catch (Exception) + { + // Log error but don't fail the request + } + + return new CreateNewOtpTokenResponseDto + { + Success = true, + Message = "کد تایید با موفقیت ارسال شد", + RemainingAttempts = MaxAttempts, + RemainingSeconds = (int)Ttl.TotalSeconds, + ExpiresAt = otpToken.ExpiresAt + }; + } + + private static string GenerateNumericCode(int len) + { + var bytes = new byte[len]; + RandomNumberGenerator.Fill(bytes); + var sb = new StringBuilder(len); + foreach (var b in bytes) sb.Append((b % 10).ToString()); + return sb.ToString(); + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandValidator.cs b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandValidator.cs new file mode 100644 index 0000000..a7b42ba --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandValidator.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken; +public class CreateNewOtpTokenCommandValidator : AbstractValidator +{ + public CreateNewOtpTokenCommandValidator() + { + RuleFor(model => model.Mobile) + .NotEmpty(); + RuleFor(model => model.Purpose) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewOtpTokenCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenResponseDto.cs b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenResponseDto.cs new file mode 100644 index 0000000..3ae5d41 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenResponseDto.cs @@ -0,0 +1,14 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken; +public class CreateNewOtpTokenResponseDto +{ + //موفق؟ + public bool Success { get; set; } + //پیام + public string Message { get; set; } + //تلاش باقی مانده + public int RemainingAttempts { get; set; } + //ثانیه باقی مانده + public int RemainingSeconds { get; set; } + //زمان انقضاء + public DateTime? ExpiresAt { get; set; } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommand.cs b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommand.cs new file mode 100644 index 0000000..594d105 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommand.cs @@ -0,0 +1,13 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; +public record VerifyOtpTokenCommand : IRequest +{ + //موبایل مقصد + public string Mobile { get; init; } + //مقصود + public string Purpose { get; init; } + //کد + public string Code { get; init; } + //کد معرف والد + public string? ParentReferralCode { get; init; } + +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs new file mode 100644 index 0000000..01cc350 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs @@ -0,0 +1,165 @@ +using CMSMicroservice.Domain.Events; +using Microsoft.Extensions.Configuration; + +namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; +public class VerifyOtpTokenCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IGenerateJwtToken _generateJwt; + private readonly IHashService _hashService; + private readonly IConfiguration _cfg; + + public VerifyOtpTokenCommandHandler(IApplicationDbContext context, IGenerateJwtToken generateJwt, + IHashService hashService, IConfiguration cfg) + { + _context = context; + _generateJwt = generateJwt; + _hashService = hashService; + _cfg = cfg; + } + + const int MaxAttempts = 5; + + public async Task Handle(VerifyOtpTokenCommand request, CancellationToken cancellationToken) + { + var mobile = request.Mobile.NormalizeIranMobile(); + var purpose = request.Purpose?.ToLowerInvariant() ?? "login"; + var now = DateTime.Now; + + var otpToken = await _context.OtpTokens + .Where(x => x.Mobile == mobile && x.Purpose == purpose && !x.IsUsed && x.ExpiresAt > now) + .OrderByDescending(x => x.Id) + .FirstOrDefaultAsync(cancellationToken); + + if (otpToken == null) + return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید منقضی شده یا وجود ندارد. لطفاً کد جدید دریافت کنید." }; + + // بررسی تعداد تلاش + if (otpToken.Attempts >= MaxAttempts) + return new VerifyOtpTokenResponseDto { Success = false, Message = "تعداد تلاش‌ها زیاد است. لطفاً کد جدید دریافت کنید." }; + + otpToken.Attempts++; + + // Verify using HMAC-SHA256 + var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); + if (!_hashService.VerifyHmacSha256Hex(request.Code, otpToken.CodeHash, secret)) + { + await _context.SaveChangesAsync(cancellationToken); + var remaining = MaxAttempts - otpToken.Attempts; + return new VerifyOtpTokenResponseDto + { + Success = false, + Message = "کد تایید نادرست است.", + RemainingAttempts = remaining + }; + } + + // ── جستجوی کاربر ── + var user = await _context.Users + .Include(u => u.UserContracts) + .ThenInclude(uc => uc.Contract) + .Include(u => u.UserRoles) + .ThenInclude(ur => ur.Role) + .Include(u => u.ClubMembership) + .Where(x => x.Mobile == mobile) + .FirstOrDefaultAsync(cancellationToken); + + // ── کاربر وجود ندارد → ثبت‌نام جدید ── + if (user == null) + { + // کد معرف الزامی است + if (string.IsNullOrWhiteSpace(request.ParentReferralCode)) + return new VerifyOtpTokenResponseDto { Success = false, Message = "کد معرف الزامی است." }; + + // بررسی وجود معرف و فعال بودن عضویت باشگاه + var parent = await _context.Users + .Include(u => u.ClubMembership) + .FirstOrDefaultAsync(u => u.ReferralCode == request.ParentReferralCode, cancellationToken); + + if (parent == null) + return new VerifyOtpTokenResponseDto { Success = false, Message = "معرف وجود ندارد." }; + + if (parent.ClubMembership == null || !parent.ClubMembership.IsActive) + return new VerifyOtpTokenResponseDto + { + Success = false, + Message = "لینک دعوت معرف فعال نیست. لطفاً از کد دعوت معتبر دیگری استفاده کنید." + }; + + // بررسی ظرفیت معرف (حداکثر ۲ زیرمجموعه مستقیم) + var existingChildren = await _context.Users + .Where(x => x.NetworkParentId == parent.Id && !x.IsDeleted) + .Select(x => x.LegPosition) + .ToListAsync(cancellationToken); + + if (existingChildren.Count > 1) + return new VerifyOtpTokenResponseDto { Success = false, Message = "ظرفیت معرف تکمیل است!!" }; + + // تعیین موقعیت شاخه (چپ اول، بعد راست) + NetworkLeg newUserLegPosition; + if (!existingChildren.Any(x => x == NetworkLeg.Left)) + newUserLegPosition = NetworkLeg.Left; + else if (!existingChildren.Any(x => x == NetworkLeg.Right)) + newUserLegPosition = NetworkLeg.Right; + else + return new VerifyOtpTokenResponseDto { Success = false, Message = "ظرفیت معرف تکمیل است!!" }; + + // ایجاد کاربر جدید + user = new User + { + Mobile = mobile, + ReferralCode = UtilExtensions.Generate(digits: 10, firstDigitNonZero: true), + IsMobileVerified = true, + MobileVerifiedAt = now, + IsRulesAccepted = true, + RulesAcceptedAt = now, + NetworkParentId = parent.Id, + LegPosition = newUserLegPosition + }; + await _context.Users.AddAsync(user, cancellationToken); + user.AddDomainEvent(new CreateNewUserEvent(user)); + await _context.SaveChangesAsync(cancellationToken); + + // ایجاد نقش کاربری + var userRole = new UserRole { UserId = user.Id, RoleId = 1 }; + await _context.UserRoles.AddAsync(userRole, cancellationToken); + user.AddDomainEvent(new CreateNewUserRoleEvent(userRole)); + + // ایجاد کیف پول + var userWallet = new UserWallet { UserId = user.Id, Balance = 0, NetworkBalance = 0 }; + await _context.UserWallets.AddAsync(userWallet, cancellationToken); + user.AddDomainEvent(new CreateNewUserWalletEvent(userWallet)); + await _context.SaveChangesAsync(cancellationToken); + + // بارگذاری مجدد کاربر با روابط کامل (برای تولید توکن) + user = await _context.Users + .Include(u => u.UserContracts) + .ThenInclude(uc => uc.Contract) + .Include(u => u.UserRoles) + .ThenInclude(ur => ur.Role) + .Include(u => u.ClubMembership) + .FirstAsync(x => x.Id == user.Id, cancellationToken); + } + else + { + // کاربر موجود — به‌روزرسانی وضعیت تایید موبایل + user.IsMobileVerified = true; + user.MobileVerifiedAt ??= now; + } + + // Mark OTP as used + otpToken.IsUsed = true; + await _context.SaveChangesAsync(cancellationToken); + + // Generate JWT token + var token = await _generateJwt.GenerateJwtToken(user); + + return new VerifyOtpTokenResponseDto + { + Success = true, + Message = "کد تایید با موفقیت تایید شد", + Token = token, + RemainingAttempts = MaxAttempts + }; + } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandValidator.cs b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandValidator.cs new file mode 100644 index 0000000..7e4f20f --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandValidator.cs @@ -0,0 +1,20 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; +public class VerifyOtpTokenCommandValidator : AbstractValidator +{ + public VerifyOtpTokenCommandValidator() + { + RuleFor(model => model.Mobile) + .NotEmpty(); + RuleFor(model => model.Purpose) + .NotEmpty(); + RuleFor(model => model.Code) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((VerifyOtpTokenCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenResponseDto.cs b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenResponseDto.cs new file mode 100644 index 0000000..0b3aa63 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenResponseDto.cs @@ -0,0 +1,15 @@ +namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; +public class VerifyOtpTokenResponseDto +{ + //موفق؟ + public bool Success { get; set; } + //پیام + public string Message { get; set; } + //توکن + public string? Token { get; set; } + //تلاش باقی مانده + public int RemainingAttempts { get; set; } + //ثانیه باقی مانده + public int RemainingSeconds { get; set; } + +} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileQuery.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileQuery.cs new file mode 100644 index 0000000..20c6667 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile; + +public class GetCustomerProfileQuery : IRequest +{ + public long UserId { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileQueryHandler.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileQueryHandler.cs new file mode 100644 index 0000000..7c139a0 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileQueryHandler.cs @@ -0,0 +1,77 @@ +using CMSMicroservice.Application.Common.Interfaces; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile; + +public class GetCustomerProfileQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerProfileQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerProfileQuery request, CancellationToken cancellationToken) + { + // Get userId from ICurrentUserService if not provided + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var id) ? id : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found in JWT token"); + + var user = await _context.Users + .AsNoTracking() + .Where(x => x.Id == userId) + .FirstOrDefaultAsync(cancellationToken); + + if (user == null) + throw new NotFoundException(nameof(User), userId); + + var fullName = $"{user.FirstName} {user.LastName}".Trim(); + var profileCompletionPercentage = CalculateProfileCompletion(user); + + return new GetCustomerProfileResponseDto + { + Id = user.Id, + FirstName = user.FirstName, + LastName = user.LastName, + Mobile = user.Mobile, + Email = user.Email, + NationalCode = user.NationalCode, + AvatarPath = user.AvatarPath, + ParentId = user.NetworkParentId, + ReferralCode = user.ReferralCode, + IsMobileVerified = user.IsMobileVerified, + MobileVerifiedAt = user.MobileVerifiedAt, + EmailNotifications = user.EmailNotifications, + SmsNotifications = user.SmsNotifications, + PushNotifications = user.PushNotifications, + BirthDate = user.BirthDate, + FullName = fullName, + ProfileCompletionPercentage = profileCompletionPercentage + }; + } + + private int CalculateProfileCompletion(Domain.Entities.User user) + { + var totalFields = 10; + var completedFields = 0; + + if (!string.IsNullOrEmpty(user.FirstName)) completedFields++; + if (!string.IsNullOrEmpty(user.LastName)) completedFields++; + if (!string.IsNullOrEmpty(user.Mobile)) completedFields++; + if (!string.IsNullOrEmpty(user.Email)) completedFields++; + if (!string.IsNullOrEmpty(user.NationalCode)) completedFields++; + if (!string.IsNullOrEmpty(user.AvatarPath)) completedFields++; + if (user.BirthDate.HasValue) completedFields++; + if (user.IsMobileVerified) completedFields++; + if (user.NetworkParentId.HasValue) completedFields++; + if (!string.IsNullOrEmpty(user.ReferralCode)) completedFields++; + + return (int)((double)completedFields / totalFields * 100); + } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileResponseDto.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileResponseDto.cs new file mode 100644 index 0000000..3e50184 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerProfile/GetCustomerProfileResponseDto.cs @@ -0,0 +1,22 @@ +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile; + +public class GetCustomerProfileResponseDto +{ + public long Id { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string Mobile { get; set; } + public string? Email { get; set; } + public string? NationalCode { get; set; } + public string? AvatarPath { get; set; } + public long? ParentId { get; set; } + public string ReferralCode { get; set; } + public bool IsMobileVerified { get; set; } + public DateTime? MobileVerifiedAt { get; set; } + public bool EmailNotifications { get; set; } + public bool SmsNotifications { get; set; } + public bool PushNotifications { get; set; } + public DateTime? BirthDate { get; set; } + public string FullName { get; set; } + public int ProfileCompletionPercentage { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsQuery.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsQuery.cs new file mode 100644 index 0000000..b0b9c34 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsQuery.cs @@ -0,0 +1,10 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals; + +public class GetCustomerReferralsQuery : IRequest +{ + public long UserId { get; set; } + public PaginationState? PaginationState { get; set; } + public string? StatusFilter { get; set; } // ACTIVE, INACTIVE, ALL +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsQueryHandler.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsQueryHandler.cs new file mode 100644 index 0000000..b72f560 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsQueryHandler.cs @@ -0,0 +1,114 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals; + +public class GetCustomerReferralsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerReferralsQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerReferralsQuery request, CancellationToken cancellationToken) + { + // Get userId from ICurrentUserService if not provided + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var id) ? id : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found in JWT token"); + + // Get referrals (children in network) + var query = _context.Users + .AsNoTracking() + .Where(x => x.NetworkParentId == userId); + + // Apply status filter + if (!string.IsNullOrEmpty(request.StatusFilter)) + { + if (request.StatusFilter.ToUpper() == "ACTIVE") + query = query.Where(x => x.IsMobileVerified); + else if (request.StatusFilter.ToUpper() == "INACTIVE") + query = query.Where(x => !x.IsMobileVerified); + // ALL - no additional filter + } + + query = query.OrderByDescending(x => x.Created); + + // Calculate stats + var allReferrals = await _context.Users + .AsNoTracking() + .Where(x => x.NetworkParentId == userId) + .ToListAsync(cancellationToken); + + var totalReferrals = allReferrals.Count; + var activeReferrals = allReferrals.Count(x => x.IsMobileVerified); + + // Calculate commission for current user + var userWallet = await _context.UserWallets + .AsNoTracking() + .Where(x => x.UserId == userId) + .FirstOrDefaultAsync(cancellationToken); + + var totalCommission = userWallet?.NetworkBalance ?? 0; + + // Calculate this month's commission from wallet changelog + var startOfMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); + var thisMonthCommission = await _context.UserWalletChangeLogs + .AsNoTracking() + .Include(x => x.Wallet) + .Where(x => x.Wallet.UserId == userId && x.Created >= startOfMonth) + .SumAsync(x => x.ChangeNerworkValue, cancellationToken); + + // Pagination + var totalCount = await query.CountAsync(cancellationToken); + + var paginationState = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 10 }; + var referrals = await query + .Skip((paginationState.PageNumber - 1) * paginationState.PageSize) + .Take(paginationState.PageSize) + .ToListAsync(cancellationToken); + + var metaData = new MetaData + { + TotalCount = totalCount, + CurrentPage = paginationState.PageNumber, + PageSize = paginationState.PageSize, + TotalPage = (int)Math.Ceiling((double)totalCount / paginationState.PageSize), + HasPrevious = paginationState.PageNumber > 1, + HasNext = paginationState.PageNumber < (int)Math.Ceiling((double)totalCount / paginationState.PageSize) + }; + + var referralModels = referrals.Select(r => new CustomerReferralModel + { + Id = r.Id, + FirstName = r.FirstName, + LastName = r.LastName, + Mobile = r.Mobile, + JoinDate = r.Created, + IsActive = r.IsMobileVerified, + StatusMessage = r.IsMobileVerified ? "Active" : "Inactive", + Level = 1, // Direct referral + TotalCommission = 0 // Not tracking per-referral commission + }).ToList(); + + return new GetCustomerReferralsResponseDto + { + MetaData = metaData, + Referrals = referralModels, + Stats = new CustomerReferralStats + { + TotalReferrals = totalReferrals, + ActiveReferrals = activeReferrals, + TotalCommissionEarned = totalCommission, + ThisMonthCommission = thisMonthCommission + } + }; + } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsResponseDto.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsResponseDto.cs new file mode 100644 index 0000000..eab707f --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerReferrals/GetCustomerReferralsResponseDto.cs @@ -0,0 +1,31 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals; + +public class GetCustomerReferralsResponseDto +{ + public MetaData MetaData { get; set; } + public List Referrals { get; set; } + public CustomerReferralStats Stats { get; set; } +} + +public class CustomerReferralModel +{ + public long Id { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string Mobile { get; set; } + public DateTime JoinDate { get; set; } + public bool IsActive { get; set; } + public string StatusMessage { get; set; } + public int Level { get; set; } + public long TotalCommission { get; set; } +} + +public class CustomerReferralStats +{ + public int TotalReferrals { get; set; } + public int ActiveReferrals { get; set; } + public long TotalCommissionEarned { get; set; } + public long ThisMonthCommission { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsQuery.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsQuery.cs new file mode 100644 index 0000000..9d1fda3 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings; + +public class GetCustomerSettingsQuery : IRequest +{ + public long UserId { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsQueryHandler.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsQueryHandler.cs new file mode 100644 index 0000000..2e20de0 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsQueryHandler.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Application.Common.Interfaces; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings; + +public class GetCustomerSettingsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerSettingsQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerSettingsQuery request, CancellationToken cancellationToken) + { + // Get userId from ICurrentUserService if not provided + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var id) ? id : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found in JWT token"); + + var user = await _context.Users + .AsNoTracking() + .Where(x => x.Id == userId) + .FirstOrDefaultAsync(cancellationToken); + + if (user == null) + throw new NotFoundException(nameof(User), userId); + + return new GetCustomerSettingsResponseDto + { + EmailNotifications = user.EmailNotifications, + SmsNotifications = user.SmsNotifications, + PushNotifications = user.PushNotifications, + MarketingNotifications = false, // Not in User entity, default to false + PreferredLanguage = "fa", // Default Persian + TimeZone = "Asia/Tehran", // Default Iran timezone + TwoFactorAuthEnabled = false // Not implemented yet + }; + } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsResponseDto.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsResponseDto.cs new file mode 100644 index 0000000..ebf5afe --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetCustomerSettings/GetCustomerSettingsResponseDto.cs @@ -0,0 +1,12 @@ +namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings; + +public class GetCustomerSettingsResponseDto +{ + public bool EmailNotifications { get; set; } + public bool SmsNotifications { get; set; } + public bool PushNotifications { get; set; } + public bool MarketingNotifications { get; set; } + public string PreferredLanguage { get; set; } + public string TimeZone { get; set; } + public bool TwoFactorAuthEnabled { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetJwtToken/GetJwtTokenQueryHandler.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetJwtToken/GetJwtTokenQueryHandler.cs index 0de8bcf..ba3baa1 100644 --- a/src/CMSMicroservice.Application/UserCQ/Queries/GetJwtToken/GetJwtTokenQueryHandler.cs +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetJwtToken/GetJwtTokenQueryHandler.cs @@ -14,9 +14,9 @@ public class GetJwtTokenQueryHandler : IRequestHandler u.UserContracts) - .ThenInclude(u => u.Contract) + .ThenInclude(uc => uc.Contract) .Include(u => u.UserRoles) - .ThenInclude(ur => ur.Role) + .ThenInclude(ur => ur.Role) .Include(u => u.ClubMembership) .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(User), request.Id); return new GetJwtTokenResponseDto() diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetUser/GetUserQueryHandler.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetUser/GetUserQueryHandler.cs index 2e895b6..016517e 100644 --- a/src/CMSMicroservice.Application/UserCQ/Queries/GetUser/GetUserQueryHandler.cs +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetUser/GetUserQueryHandler.cs @@ -11,12 +11,17 @@ public class GetUserQueryHandler : IRequestHandler Handle(GetUserQuery request, CancellationToken cancellationToken) { + // UserId must be provided - customer endpoints resolve from JWT before calling + var userId = request.Id; + if (userId == 0) + throw new ArgumentException("UserId is required"); + var response = await _context.Users .AsNoTracking() - .Where(x => x.Id == request.Id) + .Where(x => x.Id == userId) .ProjectToType() .FirstOrDefaultAsync(cancellationToken); - return response ?? throw new NotFoundException(nameof(User), request.Id); + return response ?? throw new NotFoundException(nameof(User), userId); } } diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommand.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommand.cs deleted file mode 100644 index 00895ea..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommand.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.ClearCart; - -/// -/// Command برای پاک کردن تمام سبد خرید کاربر -/// -public record ClearCartCommand : IRequest -{ - /// - /// شناسه کاربر - /// - public long UserId { get; init; } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommandHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommandHandler.cs deleted file mode 100644 index 36aecaa..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommandHandler.cs +++ /dev/null @@ -1,52 +0,0 @@ -using CMSMicroservice.Domain.Events; - -namespace CMSMicroservice.Application.UserCartsCQ.Commands.ClearCart; - -public class ClearCartCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public ClearCartCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(ClearCartCommand request, CancellationToken cancellationToken) - { - // پیدا کردن تمام آیتم‌های سبد خرید کاربر - var cartItems = await _context.UserCarts - .Where(c => c.UserId == request.UserId) - .ToListAsync(cancellationToken); - - if (!cartItems.Any()) - { - return new ClearCartResponseDto - { - UserId = request.UserId, - RemovedItemsCount = 0, - Message = "سبد خرید خالی است" - }; - } - - var itemsCount = cartItems.Count; - - // حذف تمام آیتم‌ها - _context.UserCarts.RemoveRange(cartItems); - - // ثبت Event - // می‌تونیم یک Event برای هر آیتم یا یک Event کلی بفرستیم - foreach (var item in cartItems) - { - item.AddDomainEvent(new ClearCartEvent(item)); - } - - await _context.SaveChangesAsync(cancellationToken); - - return new ClearCartResponseDto - { - UserId = request.UserId, - RemovedItemsCount = itemsCount, - Message = $"{itemsCount} آیتم از سبد خرید حذف شد" - }; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommandValidator.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommandValidator.cs deleted file mode 100644 index fe9f488..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartCommandValidator.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.ClearCart; - -public class ClearCartCommandValidator : AbstractValidator -{ - public ClearCartCommandValidator() - { - RuleFor(v => v.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد"); - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartResponseDto.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartResponseDto.cs deleted file mode 100644 index a7e7278..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/ClearCart/ClearCartResponseDto.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.ClearCart; - -public class ClearCartResponseDto -{ - public long UserId { get; set; } - public int RemovedItemsCount { get; set; } - public string Message { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommand.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommand.cs deleted file mode 100644 index 0b4f500..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.CreateNewUserCarts; -public record CreateNewUserCartsCommand : IRequest -{ - // - public long ProductId { get; init; } - // - public long UserId { get; init; } - // - public int Count { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommandHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommandHandler.cs deleted file mode 100644 index 593612d..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommandHandler.cs +++ /dev/null @@ -1,31 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserCartsCQ.Commands.CreateNewUserCarts; -public class CreateNewUserCartsCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public CreateNewUserCartsCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(CreateNewUserCartsCommand request, - CancellationToken cancellationToken) - { - var entity = request.Adapt(); - var existingUserCart = await _context.UserCarts - .FirstOrDefaultAsync(x => x.UserId == entity.UserId && x.ProductId == entity.ProductId && !x.IsDeleted, cancellationToken); - if (existingUserCart != null) - { - existingUserCart.Count += entity.Count; - _context.UserCarts.Update(existingUserCart); - existingUserCart.AddDomainEvent(new UpdateUserCartsEvent(existingUserCart)); - await _context.SaveChangesAsync(cancellationToken); - return existingUserCart.Adapt(); - } - await _context.UserCarts.AddAsync(entity, cancellationToken); - entity.AddDomainEvent(new CreateNewUserCartsEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return entity.Adapt(); - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommandValidator.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommandValidator.cs deleted file mode 100644 index 389bb35..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsCommandValidator.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.CreateNewUserCarts; -public class CreateNewUserCartsCommandValidator : AbstractValidator -{ - public CreateNewUserCartsCommandValidator() - { - RuleFor(model => model.ProductId) - .NotNull(); - RuleFor(model => model.UserId) - .NotNull(); - RuleFor(model => model.Count) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserCartsCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsResponseDto.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsResponseDto.cs deleted file mode 100644 index a0c8e22..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/CreateNewUserCarts/CreateNewUserCartsResponseDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.CreateNewUserCarts; -public class CreateNewUserCartsResponseDto -{ - // - public long Id { get; set; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommand.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommand.cs deleted file mode 100644 index b8009cf..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.DeleteUserCarts; -public record DeleteUserCartsCommand : IRequest -{ - // - public long Id { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommandHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommandHandler.cs deleted file mode 100644 index 83b3f7c..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommandHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserCartsCQ.Commands.DeleteUserCarts; -public class DeleteUserCartsCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public DeleteUserCartsCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(DeleteUserCartsCommand request, CancellationToken cancellationToken) - { - var entity = await _context.UserCarts - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserCart), request.Id); - entity.IsDeleted = true; - _context.UserCarts.Update(entity); - entity.AddDomainEvent(new DeleteUserCartsEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return Unit.Value; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommandValidator.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommandValidator.cs deleted file mode 100644 index b59a80e..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/DeleteUserCarts/DeleteUserCartsCommandValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.DeleteUserCarts; -public class DeleteUserCartsCommandValidator : AbstractValidator -{ - public DeleteUserCartsCommandValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserCartsCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommand.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommand.cs deleted file mode 100644 index fdac4d0..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommand.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.MergeCart; - -/// -/// Command برای ادغام سبد خرید مهمان با سبد خرید کاربر بعد از ورود -/// -public record MergeCartCommand : IRequest -{ - /// - /// شناسه کاربر (بعد از Login) - /// - public long UserId { get; init; } - - /// - /// لیست محصولات سبد مهمان - /// - public List GuestCartItems { get; init; } = new(); -} - -public class GuestCartItem -{ - public long ProductId { get; set; } - public int Count { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommandHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommandHandler.cs deleted file mode 100644 index 7acf2ef..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommandHandler.cs +++ /dev/null @@ -1,97 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Domain.Entities; -using Microsoft.EntityFrameworkCore; - -namespace CMSMicroservice.Application.UserCartsCQ.Commands.MergeCart; - -public class MergeCartCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public MergeCartCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(MergeCartCommand request, CancellationToken cancellationToken) - { - // بررسی وجود کاربر - var user = await _context.Users - .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); - - if (user == null) - { - return new MergeCartResponseDto - { - Success = false, - Message = "کاربر یافت نشد" - }; - } - - // دریافت سبد فعلی کاربر - var existingCartItems = await _context.UserCarts - .Where(c => c.UserId == request.UserId && !c.IsDeleted) - .ToListAsync(cancellationToken); - - int mergedCount = 0; - - // ادغام آیتم‌های مهمان با سبد کاربر - foreach (var guestItem in request.GuestCartItems) - { - // بررسی موجود بودن محصول - var product = await _context.Products - .FirstOrDefaultAsync(p => p.Id == guestItem.ProductId && !p.IsDeleted, cancellationToken); - - if (product == null) - continue; // محصول پیدا نشد یا حذف شده - - // بررسی موجودی - if (product.RemainingCount < guestItem.Count) - continue; // موجودی کافی نیست - - // چک کردن آیا این محصول قبلاً در سبد کاربر هست - var existingItem = existingCartItems.FirstOrDefault(c => c.ProductId == guestItem.ProductId); - - if (existingItem != null) - { - // آیتم موجود است → افزایش تعداد - existingItem.Count += guestItem.Count; - - // محدود کردن به موجودی - if (existingItem.Count > product.RemainingCount) - existingItem.Count = product.RemainingCount; - - _context.UserCarts.Update(existingItem); - } - else - { - // آیتم جدید → اضافه کردن به سبد - var newCartItem = new UserCart - { - UserId = request.UserId, - ProductId = guestItem.ProductId, - Count = Math.Min(guestItem.Count, product.RemainingCount) - }; - - await _context.UserCarts.AddAsync(newCartItem, cancellationToken); - } - - mergedCount++; - } - - await _context.SaveChangesAsync(cancellationToken); - - // محاسبه تعداد کل آیتم‌های سبد بعد از ادغام - var totalItems = await _context.UserCarts - .Where(c => c.UserId == request.UserId && !c.IsDeleted) - .CountAsync(cancellationToken); - - return new MergeCartResponseDto - { - Success = true, - Message = $"{mergedCount} محصول با موفقیت به سبد خرید اضافه شد", - MergedItemsCount = mergedCount, - TotalCartItems = totalItems - }; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommandValidator.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommandValidator.cs deleted file mode 100644 index cb2c2a9..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartCommandValidator.cs +++ /dev/null @@ -1,31 +0,0 @@ -using FluentValidation; - -namespace CMSMicroservice.Application.UserCartsCQ.Commands.MergeCart; - -public class MergeCartCommandValidator : AbstractValidator -{ - public MergeCartCommandValidator() - { - RuleFor(x => x.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر نامعتبر است"); - - RuleFor(x => x.GuestCartItems) - .NotNull() - .WithMessage("لیست آیتم‌های سبد خرید نباید خالی باشد"); - - RuleForEach(x => x.GuestCartItems) - .ChildRules(item => - { - item.RuleFor(i => i.ProductId) - .GreaterThan(0) - .WithMessage("شناسه محصول نامعتبر است"); - - item.RuleFor(i => i.Count) - .GreaterThan(0) - .WithMessage("تعداد باید بیشتر از صفر باشد") - .LessThanOrEqualTo(100) - .WithMessage("حداکثر تعداد مجاز 100 عدد است"); - }); - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartResponseDto.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartResponseDto.cs deleted file mode 100644 index e81b19e..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/MergeCart/MergeCartResponseDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.MergeCart; - -public class MergeCartResponseDto -{ - public bool Success { get; set; } - public string Message { get; set; } - public int MergedItemsCount { get; set; } - public int TotalCartItems { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommand.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommand.cs deleted file mode 100644 index 9c9fbf9..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommand.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.UpdateUserCarts; -public record UpdateUserCartsCommand : IRequest -{ - // - public long Id { get; init; } - // - public int Count { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommandHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommandHandler.cs deleted file mode 100644 index f754d59..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommandHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using CMSMicroservice.Application.UserCartsCQ.Commands.DeleteUserCarts; -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserCartsCQ.Commands.UpdateUserCarts; -public class UpdateUserCartsCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ISender _sender; - - public UpdateUserCartsCommandHandler(IApplicationDbContext context, ISender sender) - { - _context = context; - _sender = sender; - } - - public async Task Handle(UpdateUserCartsCommand request, CancellationToken cancellationToken) - { - if (request.Count<=0) - { - await _sender.Send(request.Adapt(), cancellationToken); - } - var entity = await _context.UserCarts - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserCart), request.Id); - request.Adapt(entity); - _context.UserCarts.Update(entity); - entity.AddDomainEvent(new UpdateUserCartsEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return Unit.Value; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommandValidator.cs b/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommandValidator.cs deleted file mode 100644 index c7034e6..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Commands/UpdateUserCarts/UpdateUserCartsCommandValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Commands.UpdateUserCarts; -public class UpdateUserCartsCommandValidator : AbstractValidator -{ - public UpdateUserCartsCommandValidator() - { - RuleFor(model => model.Id) - .NotNull(); - RuleFor(model => model.Count) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserCartsCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/ClearCartEventHandlers/ClearCartEventHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/ClearCartEventHandlers/ClearCartEventHandler.cs deleted file mode 100644 index f593912..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/ClearCartEventHandlers/ClearCartEventHandler.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.Extensions.Logging; -using CMSMicroservice.Domain.Events; - -namespace CMSMicroservice.Application.UserCartsCQ.EventHandlers.ClearCartEventHandlers; - -public class ClearCartEventHandler : INotificationHandler -{ - private readonly ILogger _logger; - - public ClearCartEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(ClearCartEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Cart item {CartId} removed for user {UserId}", - notification.Item.Id, - notification.Item.UserId); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/CreateNewUserCartsEventHandlers/CreateNewUserCartsEventHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/CreateNewUserCartsEventHandlers/CreateNewUserCartsEventHandler.cs deleted file mode 100644 index 6812e9e..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/CreateNewUserCartsEventHandlers/CreateNewUserCartsEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserCartsCQ.EventHandlers; - -public class CreateNewUserCartsEventHandler : INotificationHandler -{ - private readonly ILogger< - CreateNewUserCartsEventHandler> _logger; - - public CreateNewUserCartsEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(CreateNewUserCartsEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/DeleteUserCartsEventHandlers/DeleteUserCartsEventHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/DeleteUserCartsEventHandlers/DeleteUserCartsEventHandler.cs deleted file mode 100644 index 813ca8e..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/DeleteUserCartsEventHandlers/DeleteUserCartsEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserCartsCQ.EventHandlers; - -public class DeleteUserCartsEventHandler : INotificationHandler -{ - private readonly ILogger< - DeleteUserCartsEventHandler> _logger; - - public DeleteUserCartsEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(DeleteUserCartsEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/UpdateUserCartsEventHandlers/UpdateUserCartsEventHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/UpdateUserCartsEventHandlers/UpdateUserCartsEventHandler.cs deleted file mode 100644 index 2b1855d..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/EventHandlers/UpdateUserCartsEventHandlers/UpdateUserCartsEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserCartsCQ.EventHandlers; - -public class UpdateUserCartsEventHandler : INotificationHandler -{ - private readonly ILogger< - UpdateUserCartsEventHandler> _logger; - - public UpdateUserCartsEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(UpdateUserCartsEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQuery.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQuery.cs deleted file mode 100644 index ef5a37f..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQuery.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetAllUserCartsByFilter; -public record GetAllUserCartsByFilterQuery : IRequest -{ - //موقعیت صفحه بندی - public PaginationState? PaginationState { get; init; } - //مرتب سازی بر اساس - public string? SortBy { get; init; } - //فیلتر - public GetAllUserCartsByFilterFilter? Filter { get; init; } - -}public class GetAllUserCartsByFilterFilter -{ - // - public long? Id { get; set; } - // - public long? ProductId { get; set; } - // - public long? UserId { get; set; } - // - public int? Count { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQueryHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQueryHandler.cs deleted file mode 100644 index 2fe5778..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQueryHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetAllUserCartsByFilter; -public class GetAllUserCartsByFilterQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public GetAllUserCartsByFilterQueryHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(GetAllUserCartsByFilterQuery request, CancellationToken cancellationToken) - { - var query = _context.UserCarts.Include(i=>i.Product) - .ApplyOrder(sortBy: request.SortBy) - .AsNoTracking() - .AsQueryable(); - if (request.Filter is not null) - { - query = query - .Where(x => request.Filter.Id == null || x.Id == request.Filter.Id) - .Where(x => request.Filter.ProductId == null || x.ProductId == request.Filter.ProductId) - .Where(x => request.Filter.UserId == null || x.UserId == request.Filter.UserId) - .Where(x => request.Filter.Count == null || x.Count == request.Filter.Count) -; - } - return new GetAllUserCartsByFilterResponseDto - { - MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), - Models = await query.PaginatedListAsync(paginationState: request.PaginationState) - .ProjectToType().ToListAsync(cancellationToken) - }; - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQueryValidator.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQueryValidator.cs deleted file mode 100644 index 075a051..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterQueryValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetAllUserCartsByFilter; -public class GetAllUserCartsByFilterQueryValidator : AbstractValidator -{ - public GetAllUserCartsByFilterQueryValidator() - { - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserCartsByFilterQuery)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterResponseDto.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterResponseDto.cs deleted file mode 100644 index 6af8e0a..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetAllUserCartsByFilter/GetAllUserCartsByFilterResponseDto.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetAllUserCartsByFilter; -public class GetAllUserCartsByFilterResponseDto -{ - //متادیتا - public MetaData MetaData { get; set; } - //مدل خروجی - public List? Models { get; set; } - -}public class GetAllUserCartsByFilterResponseModel -{ - // - public long Id { get; set; } - // - public long ProductId { get; set; } - // - public long UserId { get; set; } - // - public int Count { get; set; } - // - public string ProductTitle { get; set; } - // - public string ProductShortInfomation { get; set; } - // - public long ProductPrice { get; set; } - // - public int ProductDiscount { get; set; } - // - public string ProductThumbnailPath { get; set; } - // - public DateTime Created { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQuery.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQuery.cs deleted file mode 100644 index 9257618..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQuery.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetUserCarts; -public record GetUserCartsQuery : IRequest -{ - // - public long Id { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQueryHandler.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQueryHandler.cs deleted file mode 100644 index 27473f2..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQueryHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetUserCarts; -public class GetUserCartsQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public GetUserCartsQueryHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(GetUserCartsQuery request, - CancellationToken cancellationToken) - { - var response = await _context.UserCarts - .AsNoTracking() - .Where(x => x.Id == request.Id) - .ProjectToType() - .FirstOrDefaultAsync(cancellationToken); - - return response ?? throw new NotFoundException(nameof(UserCart), request.Id); - } -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQueryValidator.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQueryValidator.cs deleted file mode 100644 index 5a8677e..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsQueryValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetUserCarts; -public class GetUserCartsQueryValidator : AbstractValidator -{ - public GetUserCartsQueryValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserCartsQuery)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsResponseDto.cs b/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsResponseDto.cs deleted file mode 100644 index 109829b..0000000 --- a/src/CMSMicroservice.Application/UserCartsCQ/Queries/GetUserCarts/GetUserCartsResponseDto.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace CMSMicroservice.Application.UserCartsCQ.Queries.GetUserCarts; -public class GetUserCartsResponseDto -{ - // - public long Id { get; set; } - // - public long ProductId { get; set; } - // - public long UserId { get; set; } - // - public int Count { get; set; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommand.cs deleted file mode 100644 index 61d04f8..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder; - -/// -/// اعمال تخفیف به سفارش -/// -public record ApplyDiscountToOrderCommand : IRequest -{ - /// - /// شناسه سفارش - /// - public long OrderId { get; init; } - - /// - /// مبلغ تخفیف (ریال) - /// - public long DiscountAmount { get; init; } - - /// - /// دلیل تخفیف - /// - public string Reason { get; init; } = string.Empty; - - /// - /// کد تخفیف (اختیاری) - /// - public string? DiscountCode { get; init; } -} - -/// -/// پاسخ اعمال تخفیف -/// -public class ApplyDiscountToOrderResponseDto -{ - public bool Success { get; set; } - public string Message { get; set; } = string.Empty; - public long OriginalAmount { get; set; } - public long DiscountAmount { get; set; } - public long FinalAmount { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandHandler.cs deleted file mode 100644 index 13f2018..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandHandler.cs +++ /dev/null @@ -1,74 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder; - -public class ApplyDiscountToOrderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public ApplyDiscountToOrderCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(ApplyDiscountToOrderCommand request, CancellationToken cancellationToken) - { - // TODO: پیاده‌سازی اعمال تخفیف به سفارش - // 1. پیدا کردن سفارش: - // - var order = await _context.UserOrders.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken) - // - بررسی null و پرتاب NotFoundException - // - // 2. بررسی شرایط اعمال تخفیف: - // - سفارش نباید Delivered یا Cancelled باشد - // - مبلغ تخفیف نباید بیشتر از Amount باشد - // - if (order.DeliveryStatus == DeliveryStatus.Delivered || order.DeliveryStatus == DeliveryStatus.Cancelled) - // throw new InvalidOperationException("نمی‌توان به این سفارش تخفیف اعمال کرد") - // - if (request.DiscountAmount > order.Amount) - // throw new InvalidOperationException("مبلغ تخفیف نمی‌تواند بیشتر از مبلغ سفارش باشد") - // - // 3. محاسبه مبلغ نهایی: - // - var originalAmount = order.Amount - // - var newDiscountedPrice = order.Amount - request.DiscountAmount - // - مطمئن شوید که منفی نشود: newDiscountedPrice = Math.Max(0, newDiscountedPrice) - // - // 4. به‌روزرسانی سفارش: - // - order.DiscountedPrice = newDiscountedPrice - // - اگر فیلد OrderDiscountAmount وجود دارد، آن را هم به‌روز کنید - // - order.OrderDiscountAmount = request.DiscountAmount - // - اضافه کردن به توضیحات: - // order.DeliveryDescription = (order.DeliveryDescription ?? "") + - // $"\nتخفیف اعمال شده: {request.DiscountAmount} ریال - دلیل: {request.Reason}" - // - // 5. ذخیره Log تخفیف (اختیاری - اگر جدول OrderDiscountLog دارید): - // - var discountLog = new OrderDiscountLog { - // OrderId = order.Id, - // DiscountAmount = request.DiscountAmount, - // Reason = request.Reason, - // DiscountCode = request.DiscountCode, - // AppliedAt = DateTime.Now - // } - // - await _context.OrderDiscountLogs.AddAsync(discountLog, cancellationToken) - // - // 6. ذخیره و Log: - // - await _context.SaveChangesAsync(cancellationToken) - // - _logger.LogInformation("Discount {Amount} applied to order {OrderId}: {Reason}", - // request.DiscountAmount, request.OrderId, request.Reason) - // - // 7. برگشت Response: - // - return new ApplyDiscountToOrderResponseDto { - // Success = true, - // Message = "تخفیف با موفقیت اعمال شد", - // OriginalAmount = originalAmount, - // DiscountAmount = request.DiscountAmount, - // FinalAmount = newDiscountedPrice - // } - // - // نکته: این تخفیف برای تخفیفات دستی Admin است و جدا از تخفیف‌های محصول - - throw new NotImplementedException("ApplyDiscountToOrder needs implementation"); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandValidator.cs deleted file mode 100644 index 6c0fcfb..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandValidator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder; - -public class ApplyDiscountToOrderCommandValidator : AbstractValidator -{ - public ApplyDiscountToOrderCommandValidator() - { - RuleFor(x => x.OrderId) - .GreaterThan(0) - .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد"); - - RuleFor(x => x.DiscountAmount) - .GreaterThan(0) - .WithMessage("مبلغ تخفیف باید بزرگتر از 0 باشد"); - - RuleFor(x => x.Reason) - .NotEmpty() - .WithMessage("دلیل تخفیف الزامی است") - .MaximumLength(500) - .WithMessage("دلیل تخفیف نمی‌تواند بیشتر از 500 کاراکتر باشد"); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommand.cs deleted file mode 100644 index b728ee7..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommand.cs +++ /dev/null @@ -1,24 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder; - -/// -/// Command برای لغو سفارش -/// -public record CancelOrderCommand : IRequest -{ - /// - /// شناسه سفارش - /// - public long OrderId { get; init; } - - /// - /// دلیل لغو سفارش - /// - public string CancelReason { get; init; } - - /// - /// آیا مبلغ باید بازگردانده شود؟ - /// - public bool RefundPayment { get; init; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommandHandler.cs deleted file mode 100644 index 1f51d09..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommandHandler.cs +++ /dev/null @@ -1,74 +0,0 @@ -using CMSMicroservice.Domain.Events; -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder; - -public class CancelOrderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public CancelOrderCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(CancelOrderCommand request, CancellationToken cancellationToken) - { - // پیدا کردن سفارش - var order = await _context.UserOrders - .Include(o => o.Transaction) - .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken); - - if (order == null) - { - throw new NotFoundException(nameof(UserOrder), request.OrderId); - } - - // چک کردن که سفارش قابل لغو باشد - if (order.DeliveryStatus == DeliveryStatus.Delivered) - { - throw new InvalidOperationException("سفارش تحویل داده شده قابل لغو نیست"); - } - - if (order.DeliveryStatus == DeliveryStatus.Cancelled) - { - throw new InvalidOperationException("این سفارش قبلاً لغو شده است"); - } - - // تغییر وضعیت سفارش - order.DeliveryStatus = DeliveryStatus.Cancelled; - order.DeliveryDescription = $"لغو شده: {request.CancelReason}"; - - // اگر درخواست بازگشت پول داریم و پرداخت موفق بوده - if (request.RefundPayment && - order.Transaction != null && - order.Transaction.PaymentStatus == PaymentStatus.Success) - { - // ایجاد تراکنش استرداد - var refundTransaction = new Transaction - { - Amount = -order.Amount, - Description = $"بازگشت وجه سفارش {request.OrderId}: {request.CancelReason}", - PaymentStatus = PaymentStatus.Success, - PaymentDate = DateTime.Now, - RefId = $"REFUND-ORDER-{order.Id}", - Type = TransactionType.Buy - }; - - await _context.Transactions.AddAsync(refundTransaction, cancellationToken); - } - - // ثبت Event - order.AddDomainEvent(new CancelOrderEvent(order, request.CancelReason)); - - await _context.SaveChangesAsync(cancellationToken); - - return new CancelOrderResponseDto - { - OrderId = order.Id, - Status = order.DeliveryStatus, - Message = "سفارش با موفقیت لغو شد", - RefundProcessed = request.RefundPayment && order.Transaction != null - }; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommandValidator.cs deleted file mode 100644 index 4f666b7..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderCommandValidator.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder; - -public class CancelOrderCommandValidator : AbstractValidator -{ - public CancelOrderCommandValidator() - { - RuleFor(v => v.OrderId) - .GreaterThan(0) - .WithMessage("شناسه سفارش باید بزرگتر از صفر باشد"); - - RuleFor(v => v.CancelReason) - .NotEmpty() - .WithMessage("دلیل لغو سفارش الزامی است") - .MaximumLength(500) - .WithMessage("دلیل لغو نباید بیش از 500 کاراکتر باشد"); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderResponseDto.cs deleted file mode 100644 index 7d27107..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CancelOrder/CancelOrderResponseDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder; - -public class CancelOrderResponseDto -{ - public long OrderId { get; set; } - public DeliveryStatus Status { get; set; } - public string Message { get; set; } - public bool RefundProcessed { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommand.cs deleted file mode 100644 index ea7a34e..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommand.cs +++ /dev/null @@ -1,23 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder; -public record CreateNewUserOrderCommand : IRequest -{ - //قیمت - public long Amount { get; init; } - //شناسه پکیج - public long PackageId { get; init; } - //شناسه تراکنش - public long? TransactionId { get; init; } - //وضعیت پرداخت - public PaymentStatus PaymentStatus { get; init; } - //تاریخ پرداخت - public DateTime? PaymentDate { get; init; } - //شناسه کاربر - public long UserId { get; init; } - //شناسه آدرس کاربر - public long UserAddressId { get; init; } - // - public PaymentMethod? PaymentMethod { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandHandler.cs deleted file mode 100644 index e13bd11..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder; -public class CreateNewUserOrderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public CreateNewUserOrderCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(CreateNewUserOrderCommand request, - CancellationToken cancellationToken) - { - if (await _context.UserOrders.AnyAsync(x => x.UserId == request.UserId && x.PackageId == request.PackageId, cancellationToken: cancellationToken)) - throw new Exception(message: "duplicate order!!"); - - var package = await _context.Packages - .FirstOrDefaultAsync(x => x.Id == request.PackageId, cancellationToken) ?? throw new NotFoundException(nameof(Package), request.PackageId); - - var entity = request.Adapt(); - entity.Amount = package.Price; - await _context.UserOrders.AddAsync(entity, cancellationToken); - entity.AddDomainEvent(new CreateNewUserOrderEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return entity.Adapt(); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandValidator.cs deleted file mode 100644 index 231bb6d..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderCommandValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder; -public class CreateNewUserOrderCommandValidator : AbstractValidator -{ - public CreateNewUserOrderCommandValidator() - { - RuleFor(model => model.Amount) - .NotNull(); - RuleFor(model => model.PackageId) - .NotNull(); - RuleFor(model => model.PaymentStatus) - .IsInEnum() - .NotNull(); - RuleFor(model => model.UserId) - .NotNull(); - RuleFor(model => model.UserAddressId) - .NotNull(); - RuleFor(model => model.PaymentMethod) - .IsInEnum(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserOrderCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderResponseDto.cs deleted file mode 100644 index 997db65..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/CreateNewUserOrder/CreateNewUserOrderResponseDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder; -public class CreateNewUserOrderResponseDto -{ - //شناسه - public long Id { get; set; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommand.cs deleted file mode 100644 index eea3fa2..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder; -public record DeleteUserOrderCommand : IRequest -{ - //شناسه - public long Id { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandHandler.cs deleted file mode 100644 index d07061b..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder; -public class DeleteUserOrderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public DeleteUserOrderCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(DeleteUserOrderCommand request, CancellationToken cancellationToken) - { - var entity = await _context.UserOrders - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserOrder), request.Id); - entity.IsDeleted = true; - _context.UserOrders.Update(entity); - entity.AddDomainEvent(new DeleteUserOrderEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return Unit.Value; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandValidator.cs deleted file mode 100644 index 0942569..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/DeleteUserOrder/DeleteUserOrderCommandValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder; -public class DeleteUserOrderCommandValidator : AbstractValidator -{ - public DeleteUserOrderCommandValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserOrderCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommand.cs deleted file mode 100644 index 1732438..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommand.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder; -public record SubmitShopBuyOrderCommand : IRequest -{ - //کل مبلغ ورودی - public long TotalAmount { get; init; } - //شناسه کاربر - public long UserId { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommandHandler.cs deleted file mode 100644 index 1cbea16..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommandHandler.cs +++ /dev/null @@ -1,229 +0,0 @@ -using CMSMicroservice.Domain.Common; -using CMSMicroservice.Domain.Enums; -using CMSMicroservice.Domain.Events; -using CMSMicroservice.Domain.Entities.Order; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder; - -public class - SubmitShopBuyOrderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - private float _vatRate; - - public SubmitShopBuyOrderCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(SubmitShopBuyOrderCommand request, - CancellationToken cancellationToken) - { - var user = await _context.Users - .Include(i => i.UserAddresses) - .Include(i => i.UserWallets) - .ThenInclude(i => i.UserWalletChangeLogs) - .Include(i => i.UserCarts) - .ThenInclude(i => i.Product) - .FirstOrDefaultAsync(w => w.Id == request.UserId, cancellationToken: cancellationToken); - if (user.UserCarts.Count == 0) - throw new NotFoundException("UserCart", request.UserId); - - // چک موجودی محصولات - var outOfStockProducts = new List(); - var insufficientStockProducts = new List<(string Title, int Requested, int Available)>(); - - foreach (var cartItem in user.UserCarts) - { - if (cartItem.Product.RemainingCount <= 0) - { - outOfStockProducts.Add(cartItem.Product.Title); - } - else if (cartItem.Product.RemainingCount < cartItem.Count) - { - insufficientStockProducts.Add((cartItem.Product.Title, cartItem.Count, - cartItem.Product.RemainingCount)); - } - } - - if (outOfStockProducts.Any()) - { - throw new Exception($"محصولات زیر ناموجود شده‌اند: {string.Join("، ", outOfStockProducts)}"); - } - - if (insufficientStockProducts.Any()) - { - var messages = insufficientStockProducts.Select(p => - $"«{p.Title}»: درخواست {p.Requested} عدد، موجودی {p.Available} عدد"); - throw new Exception($"موجودی محصولات زیر کافی نیست: {string.Join(" | ", messages)}"); - } - - long finalAmount = 0; - - // استفاده از SystemConstants برای VAT - if (SystemConstants.ShopVATEnabled) - { - _vatRate = (float)SystemConstants.ShopVAT; - finalAmount = AddVAT(user.UserCarts.Sum(s => s.Count * s.Product.Price)); - _logger.LogInformation( - "Calculating final amount with VAT. Base Amount: {BaseAmount}, VAT Rate: {VATRate}, Final Amount: {FinalAmount}", - user.UserCarts.Sum(s => s.Count * s.Product.Price), _vatRate, finalAmount); - } - else - { - finalAmount = user.UserCarts.Sum(s => s.Count * s.Product.Price); - } - - if (finalAmount != request.TotalAmount) - throw new Exception("مبلغ سفارش با مجموع سبد خرید مطابقت ندارد."); - - - var userWallet = user.UserWallets.FirstOrDefault(); - if (userWallet == null) - throw new Exception("کیف پول کاربر یافت نشد."); - - if (userWallet.Balance <= 0 || userWallet.Balance < request.TotalAmount) - throw new Exception("موجودی کیف پول کاربر برای انجام این تراکنش کافی نیست."); - - var newTransaction = new Transaction() - { - Amount = request.TotalAmount, - Description = "خرید از فروشگاه", - PaymentStatus = PaymentStatus.Success, - PaymentDate = DateTime.Now, - Type = TransactionType.Buy, - RefId = "localwallet-" + Guid.NewGuid().ToString() - }; - await _context.Transactions.AddAsync(newTransaction, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - - var newWalletLog = new UserWalletChangeLog() - { - CurrentBalance = userWallet.Balance, - CurrentNetworkBalance = userWallet.NetworkBalance, - WalletId = userWallet.Id, - ChangeValue = -1 * request.TotalAmount, - IsIncrease = false, - RefrenceId = newTransaction.Id, - }; - userWallet.Balance -= request.TotalAmount; - - await _context.UserWalletChangeLogs.AddAsync(newWalletLog, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - - var newOrder = new UserOrder() - { - Amount = request.TotalAmount, - PaymentStatus = PaymentStatus.Success, - PaymentMethod = PaymentMethod.Wallet, - PaymentDate = DateTime.Now, - UserId = request.UserId, - UserAddressId = user.UserAddresses.First(f => f.IsDefault).Id, - TransactionId = newTransaction.Id, - // سفارش فروشگاهی فیزیکی است، پس در ابتدا در انتظار ارسال است - DeliveryStatus = DeliveryStatus.Pending - }; - await _context.UserOrders.AddAsync(newOrder, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - - // محاسبه و ثبت VAT (اگر فعال باشد) - var vatCreated = await CalculateAndSaveVAT(newOrder.Id, request.TotalAmount, cancellationToken); - if (vatCreated) - { - newOrder.HasVAT = true; - await _context.SaveChangesAsync(cancellationToken); - } - - var factorDetailsList = user.UserCarts.Select(s => new FactorDetails() - { - ProductId = s.ProductId, - Count = s.Count, - UnitPrice = SystemConstants.ShopVATEnabled ? AddVAT(s.Product.Price) : s.Product.Price, - OrderId = newOrder.Id - }); - await _context.FactorDetails.AddRangeAsync(factorDetailsList, cancellationToken); - - // کاهش موجودی محصولات و افزایش تعداد فروش - foreach (var cartItem in user.UserCarts) - { - cartItem.Product.RemainingCount -= cartItem.Count; - cartItem.Product.SaleCount += cartItem.Count; - } - - user.UserCarts.Clear(); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation("Order {OrderId} completed. Stock updated for {ProductCount} products.", - newOrder.Id, factorDetailsList.Count()); - - var finalResult = new SubmitShopBuyOrderResponseDto() - { - Id = newOrder.Id, - }; - return finalResult; - } - - public long CalculateVAT(long amount) => (long)(amount * _vatRate); - - /// - /// محاسبه قیمت با احتساب مالیات - /// - public long AddVAT(long amount) => amount + CalculateVAT(amount); - - private async Task CalculateAndSaveVAT(long orderId, long orderAmount, CancellationToken cancellationToken) - { - try - { - // بررسی فعال بودن VAT از SystemConstants - if (!SystemConstants.ShopVATEnabled) - { - _logger.LogInformation("VAT is disabled. Skipping VAT calculation for order {OrderId}", orderId); - return false; - } - - // دریافت نرخ VAT از SystemConstants - var vatRate = SystemConstants.ShopVAT; - - // محاسبه مالیات - var vatAmount = (long)(orderAmount * vatRate); - var totalAmount = orderAmount + vatAmount; - - // ثبت VAT - var orderVAT = new OrderVAT - { - OrderId = orderId, - VATRate = vatRate, - BaseAmount = orderAmount, - VATAmount = vatAmount, - TotalAmount = totalAmount, - IsPaid = true, - PaidAt = DateTime.Now - }; - - await _context.OrderVATs.AddAsync(orderVAT, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "VAT calculated and saved for order {OrderId}. Rate: {Rate}%, Base: {Base}, VAT: {VAT}, Total: {Total}", - orderId, - vatRate * 100, - orderAmount, - vatAmount, - totalAmount - ); - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error calculating VAT for order {OrderId}", orderId); - // عدم محاسبه VAT نباید مانع ثبت سفارش شود - return false; - } - } -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommandValidator.cs deleted file mode 100644 index 106d6e5..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderCommandValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder; -public class SubmitShopBuyOrderCommandValidator : AbstractValidator -{ - public SubmitShopBuyOrderCommandValidator() - { - RuleFor(model => model.TotalAmount) - .NotNull(); - RuleFor(model => model.UserId) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((SubmitShopBuyOrderCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderResponseDto.cs deleted file mode 100644 index 0eaaaf2..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/SubmitShopBuyOrder/SubmitShopBuyOrderResponseDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder; -public class SubmitShopBuyOrderResponseDto -{ - //شناسه - public long Id { get; set; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs deleted file mode 100644 index 5ebc317..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs +++ /dev/null @@ -1,34 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus; - -/// -/// تغییر وضعیت سفارش -/// -public record UpdateOrderStatusCommand : IRequest -{ - /// - /// شناسه سفارش - /// - public long OrderId { get; init; } - - /// - /// وضعیت تحویل جدید - /// - public DeliveryStatus NewStatus { get; init; } - - /// - /// توضیحات (اختیاری) - /// - public string? Description { get; init; } -} - -/// -/// پاسخ تغییر وضعیت سفارش -/// -public class UpdateOrderStatusResponseDto -{ - public bool Success { get; set; } - public string Message { get; set; } = string.Empty; - public DeliveryStatus CurrentStatus { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs deleted file mode 100644 index 5527fd1..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs +++ /dev/null @@ -1,56 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Domain.Enums; -using Microsoft.EntityFrameworkCore; -using ValidationException = FluentValidation.ValidationException; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus; - -public class UpdateOrderStatusCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public UpdateOrderStatusCommandHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken) - { - var order = await _context.UserOrders - .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken); - - if (order == null) - { - throw new NotFoundException(nameof(order), request.OrderId); - } - - var oldStatus = order.DeliveryStatus; - - // قوانین ساده انتقال وضعیت: از Cancelled نمی‌توان خارج شد - if (oldStatus == DeliveryStatus.Cancelled) - { - throw new ValidationException("امکان تغییر وضعیت سفارش لغو شده وجود ندارد"); - } - - order.DeliveryStatus = request.NewStatus; - - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Order {OrderId} status changed from {OldStatus} to {NewStatus}", - request.OrderId, - oldStatus, - request.NewStatus); - - return new UpdateOrderStatusResponseDto - { - Success = true, - Message = "وضعیت سفارش با موفقیت تغییر کرد", - CurrentStatus = order.DeliveryStatus - }; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs deleted file mode 100644 index d4ebd11..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus; - -public class UpdateOrderStatusCommandValidator : AbstractValidator -{ - public UpdateOrderStatusCommandValidator() - { - RuleFor(x => x.OrderId) - .GreaterThan(0) - .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد"); - - RuleFor(x => x.NewStatus) - .IsInEnum() - .WithMessage("وضعیت تحویل نامعتبر است"); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommand.cs deleted file mode 100644 index dd536c6..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommand.cs +++ /dev/null @@ -1,30 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder; -public record UpdateUserOrderCommand : IRequest -{ - //شناسه - public long Id { get; init; } - //قیمت - public long? Amount { get; init; } - //شناسه پکیج - public long? PackageId { get; init; } - //شناسه تراکنش - public long? TransactionId { get; init; } - //وضعیت پرداخت - public PaymentStatus? PaymentStatus { get; init; } - //تاریخ پرداخت - public DateTime? PaymentDate { get; init; } - //شناسه کاربر - public long? UserId { get; init; } - //شناسه آدرس کاربر - public long? UserAddressId { get; init; } - // - public PaymentMethod? PaymentMethod { get; init; } - // وضعیت ارسال سفارش - public DeliveryStatus? DeliveryStatus { get; init; } - // کد رهگیری مرسوله - public string? TrackingCode { get; init; } - // توضیحات ارسال - public string? DeliveryDescription { get; init; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandHandler.cs deleted file mode 100644 index 35407d3..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder; -public class UpdateUserOrderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public UpdateUserOrderCommandHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(UpdateUserOrderCommand request, CancellationToken cancellationToken) - { - var entity = await _context.UserOrders - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserOrder), request.Id); - request.Adapt(entity); - _context.UserOrders.Update(entity); - entity.AddDomainEvent(new UpdateUserOrderEvent(entity)); - await _context.SaveChangesAsync(cancellationToken); - return Unit.Value; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandValidator.cs deleted file mode 100644 index a30be47..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateUserOrder/UpdateUserOrderCommandValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder; -public class UpdateUserOrderCommandValidator : AbstractValidator -{ - public UpdateUserOrderCommandValidator() - { - RuleFor(model => model.Id) - .NotNull(); - // RuleFor(model => model.Amount) - // .NotNull(); - // RuleFor(model => model.PackageId) - // .NotNull(); - // RuleFor(model => model.PaymentStatus) - // .IsInEnum() - // .NotNull(); - // RuleFor(model => model.UserId) - // .NotNull(); - // RuleFor(model => model.UserAddressId) - // .NotNull(); - // RuleFor(model => model.PaymentMethod) - // .IsInEnum(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserOrderCommand)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/CancelOrderEventHandlers/CancelOrderEventHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/CancelOrderEventHandlers/CancelOrderEventHandler.cs deleted file mode 100644 index 8f10952..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/CancelOrderEventHandlers/CancelOrderEventHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.Extensions.Logging; -using CMSMicroservice.Domain.Events; - -namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers.CancelOrderEventHandlers; - -public class CancelOrderEventHandler : INotificationHandler -{ - private readonly ILogger _logger; - - public CancelOrderEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(CancelOrderEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Order {OrderId} cancelled. Reason: {Reason}", - notification.Order.Id, - notification.CancelReason); - - // اینجا می‌تونیم اعلان به کاربر بفرستیم - // یا موجودی محصولات رو بازگردانیم به انبار - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/CreateNewUserOrderEventHandlers/CreateNewUserOrderEventHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/CreateNewUserOrderEventHandlers/CreateNewUserOrderEventHandler.cs deleted file mode 100644 index 0c7d157..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/CreateNewUserOrderEventHandlers/CreateNewUserOrderEventHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers; - -public class CreateNewUserOrderEventHandler : INotificationHandler -{ - private readonly ILogger _logger; - - public CreateNewUserOrderEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(CreateNewUserOrderEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/DeleteUserOrderEventHandlers/DeleteUserOrderEventHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/DeleteUserOrderEventHandlers/DeleteUserOrderEventHandler.cs deleted file mode 100644 index 1e204cb..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/DeleteUserOrderEventHandlers/DeleteUserOrderEventHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers; - -public class DeleteUserOrderEventHandler : INotificationHandler -{ - private readonly ILogger _logger; - - public DeleteUserOrderEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(DeleteUserOrderEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/SubmitShopBuyOrderEventHandlers/SubmitShopBuyOrderEventHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/SubmitShopBuyOrderEventHandlers/SubmitShopBuyOrderEventHandler.cs deleted file mode 100644 index 2cf4931..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/SubmitShopBuyOrderEventHandlers/SubmitShopBuyOrderEventHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers; - -public class SubmitShopBuyOrderEventHandler : INotificationHandler -{ - private readonly ILogger< - SubmitShopBuyOrderEventHandler> _logger; - - public SubmitShopBuyOrderEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(SubmitShopBuyOrderEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/UpdateUserOrderEventHandlers/UpdateUserOrderEventHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/UpdateUserOrderEventHandlers/UpdateUserOrderEventHandler.cs deleted file mode 100644 index f965c31..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/EventHandlers/UpdateUserOrderEventHandlers/UpdateUserOrderEventHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CMSMicroservice.Domain.Events; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers; - -public class UpdateUserOrderEventHandler : INotificationHandler -{ - private readonly ILogger _logger; - - public UpdateUserOrderEventHandler(ILogger logger) - { - _logger = logger; - } - - public Task Handle(UpdateUserOrderEvent notification, CancellationToken cancellationToken) - { - _logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name); - - return Task.CompletedTask; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQuery.cs deleted file mode 100644 index c29b1f8..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQuery.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV; - -/// -/// محاسبه امتیاز PV سفارش -/// -public record CalculateOrderPVQuery : IRequest -{ - /// - /// شناسه سفارش - /// - public long OrderId { get; init; } -} - -/// -/// پاسخ محاسبه PV سفارش -/// -public class CalculateOrderPVResponseDto -{ - /// - /// مجموع امتیاز PV سفارش - /// - public decimal TotalPV { get; set; } - - /// - /// جزئیات PV هر محصول - /// - public List ProductPVs { get; set; } = new(); - - /// - /// مبلغ قابل پرداخت - /// - public long PayableAmount { get; set; } -} - -/// -/// جزئیات PV یک محصول در سفارش -/// -public class ProductPVDto -{ - public long ProductId { get; set; } - public string ProductTitle { get; set; } = string.Empty; - public int Quantity { get; set; } - public decimal UnitPV { get; set; } - public decimal TotalPV { get; set; } - public long UnitPrice { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryHandler.cs deleted file mode 100644 index 8f3ea20..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryHandler.cs +++ /dev/null @@ -1,80 +0,0 @@ -using CMSMicroservice.Application.Common.Exceptions; -using CMSMicroservice.Application.Common.Interfaces; -using Microsoft.EntityFrameworkCore; - -namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV; - -public class CalculateOrderPVQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - // نسبت PV به قیمت بر اساس مثال‌های بیزینسی: - // محصول ۱: قیمت 100,000 → PV = 50 - // محصول ۲: قیمت 200,000 → PV = 100 - // یعنی: PV = Price / 2000 - private const decimal PvPerRial = 1m / 2000m; - - public CalculateOrderPVQueryHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(CalculateOrderPVQuery request, CancellationToken cancellationToken) - { - var order = await _context.UserOrders - .Include(o => o.FactorDetails) - .ThenInclude(fd => fd.Product) - .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken); - - if (order == null) - { - throw new NotFoundException(nameof(order), request.OrderId); - } - - var productPVs = new List(); - decimal totalPV = 0; - - foreach (var detail in order.FactorDetails) - { - if (detail.Product == null) - { - continue; - } - - var unitPrice = detail.Product.Price; - var unitPV = Math.Round(unitPrice * PvPerRial, 2, MidpointRounding.AwayFromZero); - var itemTotalPV = unitPV * detail.Count; - - productPVs.Add(new ProductPVDto - { - ProductId = detail.ProductId, - ProductTitle = detail.Product.Title, - Quantity = detail.Count, - UnitPV = unitPV, - TotalPV = itemTotalPV, - UnitPrice = unitPrice - }); - - totalPV += itemTotalPV; - } - - var response = new CalculateOrderPVResponseDto - { - TotalPV = totalPV, - ProductPVs = productPVs, - // فعلاً مبلغ قابل پرداخت همان Amount است؛ در آینده می‌توان تخفیف را هم اعمال کرد - PayableAmount = order.Amount - }; - - _logger.LogInformation( - "Calculated PV for order {OrderId}: {TotalPV}", - request.OrderId, - totalPV); - - return response; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryValidator.cs deleted file mode 100644 index 671348a..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryValidator.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV; - -public class CalculateOrderPVQueryValidator : AbstractValidator -{ - public CalculateOrderPVQueryValidator() - { - RuleFor(x => x.OrderId) - .GreaterThan(0) - .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد"); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQuery.cs deleted file mode 100644 index 576a47d..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQuery.cs +++ /dev/null @@ -1,35 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; -public record GetAllUserOrderByFilterQuery : IRequest -{ - //موقعیت صفحه بندی - public PaginationState? PaginationState { get; init; } - //مرتب سازی بر اساس - public string? SortBy { get; init; } - //فیلتر - public GetAllUserOrderByFilterFilter? Filter { get; init; } - -}public class GetAllUserOrderByFilterFilter -{ - //شناسه - public long? Id { get; set; } - //قیمت - public long? Amount { get; set; } - //شناسه پکیج - public long? PackageId { get; set; } - //شناسه تراکنش - public long? TransactionId { get; set; } - //وضعیت پرداخت - public PaymentStatus? PaymentStatus { get; set; } - //تاریخ پرداخت - public DateTime? PaymentDate { get; set; } - //شناسه کاربر - public long? UserId { get; set; } - //شناسه آدرس کاربر - public long? UserAddressId { get; set; } - // - public PaymentMethod? PaymentMethod { get; set; } - // وضعیت ارسال - public DeliveryStatus? DeliveryStatus { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryHandler.cs deleted file mode 100644 index 5864f7a..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryHandler.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; -public class GetAllUserOrderByFilterQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public GetAllUserOrderByFilterQueryHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(GetAllUserOrderByFilterQuery request, CancellationToken cancellationToken) - { - var query = _context.UserOrders - .Include(i => i.UserAddress) - .Include(i => i.User) - .Include(i => i.FactorDetails) - .ThenInclude(t => t.Product) - .Include(i => i.OrderVAT) - .ApplyOrder(sortBy: request.SortBy) - .AsNoTracking() - .AsQueryable(); - if (request.Filter is not null) - { - query = query - .Where(x => request.Filter.Id == null || x.Id == request.Filter.Id) - .Where(x => request.Filter.Amount == null || x.Amount == request.Filter.Amount) - .Where(x => request.Filter.PackageId == null || x.PackageId == request.Filter.PackageId) - .Where(x => request.Filter.TransactionId == null || x.TransactionId == request.Filter.TransactionId) - .Where(x => request.Filter.PaymentStatus == null || x.PaymentStatus == request.Filter.PaymentStatus.Value) - .Where(x => request.Filter.PaymentDate == null || x.PaymentDate >= request.Filter.PaymentDate) - .Where(x => request.Filter.UserId == null || x.UserId == request.Filter.UserId) - .Where(x => request.Filter.UserAddressId == null || x.UserAddressId == request.Filter.UserAddressId) - .Where(x => request.Filter.PaymentMethod == null || x.PaymentMethod == request.Filter.PaymentMethod) - .Where(x => request.Filter.DeliveryStatus == null || x.DeliveryStatus== request.Filter.DeliveryStatus); - } - var meta = await query.GetMetaData(request.PaginationState, cancellationToken); - - var models = await query - .PaginatedListAsync(paginationState: request.PaginationState) - .Select(x => new GetAllUserOrderByFilterResponseModel - { - Id = x.Id, - Amount = x.Amount, - PackageId = x.PackageId ?? 0, - TransactionId = x.TransactionId, - PaymentStatus = x.PaymentStatus, - PaymentDate = x.PaymentDate, - UserId = x.UserId, - UserAddressId = x.UserAddressId, - PaymentMethod = x.PaymentMethod, - UserAddressText = x.UserAddress.Address, - FactorDetails = x.FactorDetails.Select(fd => new GetAllUserOrderByFilterResponseModelFactorDetail - { - ProductId = fd.ProductId, - ProductTitle = fd.Product.Title, - ProductThumbnailPath = fd.Product.ThumbnailPath, - UnitPrice = fd.UnitPrice, - Count = fd.Count, - UnitDiscountPrice = fd.UnitDiscountPrice - }).ToList(), - DeliveryStatus = x.DeliveryStatus, - TrackingCode = x.TrackingCode, - DeliveryDescription = x.DeliveryDescription, - UserFullName = (x.User.FirstName ?? string.Empty) + " " + (x.User.LastName ?? string.Empty), - UserNationalCode = x.User.NationalCode, - VatAmount = x.OrderVAT != null ? x.OrderVAT.VATAmount : 0, - VatPercentage = x.OrderVAT != null ? (double)(x.OrderVAT.VATRate * 100) : 0 - }) - .ToListAsync(cancellationToken); - - return new GetAllUserOrderByFilterResponseDto - { - MetaData = meta, - Models = models - }; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryValidator.cs deleted file mode 100644 index e25cf25..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterQueryValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; -public class GetAllUserOrderByFilterQueryValidator : AbstractValidator -{ - public GetAllUserOrderByFilterQueryValidator() - { - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetAllUserOrderByFilterQuery)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterResponseDto.cs deleted file mode 100644 index 0635162..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetAllUserOrderByFilter/GetAllUserOrderByFilterResponseDto.cs +++ /dev/null @@ -1,64 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; -public class GetAllUserOrderByFilterResponseDto -{ - //متادیتا - public MetaData MetaData { get; set; } - //مدل خروجی - public List? Models { get; set; } - -}public class GetAllUserOrderByFilterResponseModel -{ - //شناسه - public long Id { get; set; } - //قیمت - public long Amount { get; set; } - //شناسه پکیج - public long PackageId { get; set; } - //شناسه تراکنش - public long? TransactionId { get; set; } - //وضعیت پرداخت - public PaymentStatus PaymentStatus { get; set; } - //تاریخ پرداخت - public DateTime? PaymentDate { get; set; } - //شناسه کاربر - public long UserId { get; set; } - //شناسه آدرس کاربر - public long UserAddressId { get; set; } - // - public PaymentMethod? PaymentMethod { get; set; } - // - public string? UserAddressText { get; set; } - // - public List? FactorDetails { get; set; } - // وضعیت ارسال سفارش - public DeliveryStatus DeliveryStatus { get; set; } - // کد رهگیری مرسوله - public string? TrackingCode { get; set; } - // توضیحات ارسال - public string? DeliveryDescription { get; set; } - // نام کامل کاربر - public string? UserFullName { get; set; } - // کدملی کاربر - public string? UserNationalCode { get; set; } - // مبلغ مالیات بر ارزش افزوده (ریال) - public long VatAmount { get; set; } - // درصد مالیات بر ارزش افزوده (مثلاً 9 برای 9٪) - public double VatPercentage { get; set; } -} -public class GetAllUserOrderByFilterResponseModelFactorDetail -{ - //شناسه - public long ProductId { get; set; } - // - public string ProductTitle { get; set; } - // - public string? ProductThumbnailPath { get; set; } - // - public long? UnitPrice { get; set; } - // - public int? Count { get; set; } - // - public long? UnitDiscountPrice { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderQuery.cs new file mode 100644 index 0000000..d0732da --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderQuery.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder; + +public class GetCustomerOrderQuery : IRequest +{ + public long OrderId { get; set; } + public long UserId { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderQueryHandler.cs new file mode 100644 index 0000000..0368f24 --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderQueryHandler.cs @@ -0,0 +1,68 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder; + +public class GetCustomerOrderQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetCustomerOrderQueryHandler( + IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetCustomerOrderQuery request, CancellationToken cancellationToken) + { + // UserId > 0 → filter by that user (customer security) + // UserId == 0 → no user filter (admin can view any order by ID) + var userId = request.UserId; + + var order = await _context.UserOrders + .AsNoTracking() + .Where(x => x.Id == request.OrderId && (userId == 0 || x.UserId == userId)) + .Include(x => x.Package) + .Include(x => x.Transaction) + .Include(x => x.UserAddress) + .Include(x => x.User) + .Include(x => x.FactorDetails) + .ThenInclude(f => f.Product) + .Include(x => x.OrderVAT) + .FirstOrDefaultAsync(cancellationToken); + + if (order == null) + throw new NotFoundException(nameof(UserOrder), request.OrderId); + + return new GetCustomerOrderResponseDto + { + Id = order.Id, + Amount = order.Amount, + PackageId = order.PackageId, + TransactionId = order.TransactionId, + PaymentStatus = order.PaymentStatus, + PaymentDate = order.PaymentDate, + UserId = order.UserId, + UserAddressId = order.UserAddressId, + PaymentMethod = order.PaymentMethod, + UserAddressText = order.UserAddress?.Address ?? "", + DeliveryStatus = order.DeliveryStatus, + TrackingCode = order.TrackingCode ?? "", + DeliveryDescription = order.DeliveryDescription ?? "", + UserFullName = $"{order.User?.FirstName ?? ""} {order.User?.LastName ?? ""}".Trim(), + UserNationalCode = order.User?.NationalCode ?? "", + VatAmount = order.OrderVAT?.VATAmount ?? 0, + VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0, + FactorDetails = order.FactorDetails?.Select(fd => new FactorDetailDto + { + ProductId = fd.ProductId, + ProductTitle = fd.Product?.Title ?? "", + ProductThumbnailPath = fd.Product?.ThumbnailPath ?? "", + UnitPrice = fd.UnitPrice, + Count = fd.Count, + UnitDiscountPrice = fd.UnitDiscountPrice + }).ToList() ?? new List() + }; + } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderResponseDto.cs new file mode 100644 index 0000000..36ba239 --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrder/GetCustomerOrderResponseDto.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder; + +public class GetCustomerOrderResponseDto +{ + public long Id { get; set; } + public long Amount { get; set; } + public long? PackageId { get; set; } + public long? TransactionId { get; set; } + public PaymentStatus PaymentStatus { get; set; } + public DateTime? PaymentDate { get; set; } + public long UserId { get; set; } + public long UserAddressId { get; set; } + public PaymentMethod? PaymentMethod { get; set; } + public string UserAddressText { get; set; } + public List FactorDetails { get; set; } = new(); + public DeliveryStatus DeliveryStatus { get; set; } + public string TrackingCode { get; set; } + public string DeliveryDescription { get; set; } + public string UserFullName { get; set; } + public string UserNationalCode { get; set; } + public long VatAmount { get; set; } + public double VatPercentage { get; set; } +} + +public class FactorDetailDto +{ + public long ProductId { get; set; } + public string ProductTitle { get; set; } + public string ProductThumbnailPath { get; set; } + public long? UnitPrice { get; set; } + public int? Count { get; set; } + public long? UnitDiscountPrice { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryQuery.cs new file mode 100644 index 0000000..6a0adbd --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryQuery.cs @@ -0,0 +1,12 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory; + +public class GetCustomerOrderHistoryQuery : IRequest +{ + public long UserId { get; set; } + public PaginationState PaginationState { get; set; } + public int? StatusFilter { get; set; } + public DateTime? FromDate { get; set; } + public DateTime? ToDate { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryQueryHandler.cs new file mode 100644 index 0000000..e261100 --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryQueryHandler.cs @@ -0,0 +1,155 @@ +using CMSMicroservice.Application.Common.Extensions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory; + +public class GetCustomerOrderHistoryQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerOrderHistoryQueryHandler( + IApplicationDbContext context, + ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetCustomerOrderHistoryQuery request, CancellationToken cancellationToken) + { + // Resolve UserId from JWT if not specified + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found"); + + var query = _context.UserOrders + .AsNoTracking() + .Where(x => x.UserId == userId) + .Include(x => x.Package) + .Include(x => x.FactorDetails) + .AsQueryable(); + + // Apply status filter if specified + if (request.StatusFilter.HasValue) + { + // Status filter is based on OrderStatusEnum from Proto + // We need to map to DeliveryStatus enum values + var deliveryStatus = MapProtoStatusToDeliveryStatus(request.StatusFilter.Value); + if (deliveryStatus.HasValue) + query = query.Where(x => x.DeliveryStatus == deliveryStatus.Value); + } + + // Apply date filters + if (request.FromDate.HasValue) + query = query.Where(x => x.Created >= request.FromDate.Value); + + if (request.ToDate.HasValue) + query = query.Where(x => x.Created <= request.ToDate.Value); + + // Order by most recent first + query = query.OrderByDescending(x => x.Created); + + // Get metadata + var metaData = await query.GetMetaData(request.PaginationState, cancellationToken); + + // Get paginated results + var orders = await query + .PaginatedListAsync(request.PaginationState) + .ToListAsync(cancellationToken); + + var orderModels = orders.Select(order => new CustomerOrderHistoryModel + { + Id = order.Id, + Amount = order.Amount, + PackageId = order.PackageId, + PackageName = order.Package?.Title ?? "سفارش محصولات", + Status = MapDeliveryStatusToProtoStatus(order.DeliveryStatus), + StatusMessage = GetStatusMessage(order.DeliveryStatus), + OrderDate = order.Created, + DeliveryDate = order.PaymentDate?.AddDays(GetEstimatedDeliveryDays(order.DeliveryStatus)), + TrackingCode = order.TrackingCode ?? "", + ItemsCount = order.FactorDetails?.Count ?? 0, + CanCancel = CanCancelOrder(order.DeliveryStatus, order.Created), + CanReorder = true // همیشه می‌توان دوباره سفارش داد + }).ToList(); + + return new GetCustomerOrderHistoryResponseDto + { + MetaData = metaData, + Orders = orderModels + }; + } + + private DeliveryStatus? MapProtoStatusToDeliveryStatus(int protoStatus) + { + // OrderStatusEnum from Proto: + // 0=Pending, 1=Confirmed, 2=Processing, 3=Shipped, 4=Delivered, 5=Cancelled, 6=Refunded + return protoStatus switch + { + 0 => DeliveryStatus.Pending, + 1 => DeliveryStatus.Pending, + 2 => DeliveryStatus.Pending, + 3 => DeliveryStatus.InTransit, + 4 => DeliveryStatus.Delivered, + 5 => DeliveryStatus.Cancelled, + 6 => DeliveryStatus.Cancelled, + _ => null + }; + } + + private int MapDeliveryStatusToProtoStatus(DeliveryStatus status) + { + return status switch + { + DeliveryStatus.None => 0, + DeliveryStatus.Pending => 1, + DeliveryStatus.InTransit => 3, + DeliveryStatus.Delivered => 4, + DeliveryStatus.Cancelled => 5, + DeliveryStatus.Returned => 6, + _ => 0 + }; + } + + private string GetStatusMessage(DeliveryStatus status) + { + return status switch + { + DeliveryStatus.None => "ثبت نشده", + DeliveryStatus.Pending => "در انتظار پردازش", + DeliveryStatus.InTransit => "ارسال شده", + DeliveryStatus.Delivered => "تحویل داده شد", + DeliveryStatus.Cancelled => "لغو شده", + DeliveryStatus.Returned => "مرجوع شده", + _ => "نامشخص" + }; + } + + private int GetEstimatedDeliveryDays(DeliveryStatus status) + { + return status switch + { + DeliveryStatus.None => 7, + DeliveryStatus.Pending => 5, + DeliveryStatus.InTransit => 3, + DeliveryStatus.Delivered => 0, + _ => 0 + }; + } + + private bool CanCancelOrder(DeliveryStatus status, DateTime orderDate) + { + // فقط سفارشات Pending یا None که کمتر از 24 ساعت از ثبت آنها گذشته قابل لغو هستند + if (status != DeliveryStatus.Pending && status != DeliveryStatus.None) + return false; + + var hoursSinceOrder = (DateTime.UtcNow - orderDate).TotalHours; + return hoursSinceOrder < 24; + } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryResponseDto.cs new file mode 100644 index 0000000..41d85c3 --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrderHistory/GetCustomerOrderHistoryResponseDto.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory; + +public class GetCustomerOrderHistoryResponseDto +{ + public MetaData MetaData { get; set; } + public List Orders { get; set; } = new(); +} + +public class CustomerOrderHistoryModel +{ + public long Id { get; set; } + public long Amount { get; set; } + public long? PackageId { get; set; } + public string PackageName { get; set; } + public int Status { get; set; } + public string StatusMessage { get; set; } + public DateTime OrderDate { get; set; } + public DateTime? DeliveryDate { get; set; } + public string TrackingCode { get; set; } + public int ItemsCount { get; set; } + public bool CanCancel { get; set; } + public bool CanReorder { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersQuery.cs new file mode 100644 index 0000000..3988c2e --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersQuery.cs @@ -0,0 +1,13 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders; + +public class GetCustomerOrdersQuery : IRequest +{ + public long UserId { get; set; } + public PaginationState PaginationState { get; set; } + public int? PaymentStatusFilter { get; set; } + public int? DeliveryStatusFilter { get; set; } + public DateTime? FromDate { get; set; } + public DateTime? ToDate { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersQueryHandler.cs new file mode 100644 index 0000000..0dba3de --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersQueryHandler.cs @@ -0,0 +1,96 @@ +using CMSMicroservice.Application.Common.Extensions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using Mapster; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders; + +public class GetCustomerOrdersQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetCustomerOrdersQueryHandler( + IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetCustomerOrdersQuery request, CancellationToken cancellationToken) + { + // UserId > 0 → filter by that user + // UserId == 0 → show ALL users (admin mode) + var userId = request.UserId; + + var query = _context.UserOrders + .AsNoTracking() + .Where(x => userId == 0 || x.UserId == userId) + .Include(x => x.Package) + .Include(x => x.Transaction) + .Include(x => x.UserAddress) + .Include(x => x.User) + .Include(x => x.FactorDetails) + .ThenInclude(f => f.Product) + .Include(x => x.OrderVAT) + .AsQueryable(); + + // Apply filters + if (request.PaymentStatusFilter.HasValue) + query = query.Where(x => (int)x.PaymentStatus == request.PaymentStatusFilter.Value); + + if (request.DeliveryStatusFilter.HasValue) + query = query.Where(x => (int)x.DeliveryStatus == request.DeliveryStatusFilter.Value); + + if (request.FromDate.HasValue) + query = query.Where(x => x.Created >= request.FromDate.Value); + + if (request.ToDate.HasValue) + query = query.Where(x => x.Created <= request.ToDate.Value); + + // Order by most recent first + query = query.OrderByDescending(x => x.Created); + + // Get metadata + var metaData = await query.GetMetaData(request.PaginationState, cancellationToken); + + // Get paginated results + var orders = await query + .PaginatedListAsync(request.PaginationState) + .ToListAsync(cancellationToken); + + var models = orders.Select(order => new CustomerOrderModel + { + Id = order.Id, + Amount = order.Amount, + PackageId = order.PackageId, + TransactionId = order.TransactionId, + PaymentStatus = order.PaymentStatus, + PaymentDate = order.PaymentDate, + UserId = order.UserId, + UserAddressId = order.UserAddressId, + PaymentMethod = order.PaymentMethod, + UserAddressText = order.UserAddress?.Address ?? "", + DeliveryStatus = order.DeliveryStatus, + TrackingCode = order.TrackingCode ?? "", + DeliveryDescription = order.DeliveryDescription ?? "", + UserFullName = $"{order.User?.FirstName ?? ""} {order.User?.LastName ?? ""}".Trim(), + UserNationalCode = order.User?.NationalCode ?? "", + VatAmount = order.OrderVAT?.VATAmount ?? 0, + VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0, + FactorDetails = order.FactorDetails?.Select(fd => new FactorDetailModel + { + ProductId = fd.ProductId, + ProductTitle = fd.Product?.Title ?? "", + ProductThumbnailPath = fd.Product?.ThumbnailPath ?? "", + UnitPrice = fd.UnitPrice, + Count = fd.Count, + UnitDiscountPrice = fd.UnitDiscountPrice + }).ToList() ?? new List() + }).ToList(); + + return new GetCustomerOrdersResponseDto + { + MetaData = metaData, + Models = models + }; + } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersResponseDto.cs new file mode 100644 index 0000000..f6ebe7c --- /dev/null +++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetCustomerOrders/GetCustomerOrdersResponseDto.cs @@ -0,0 +1,42 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders; + +public class GetCustomerOrdersResponseDto +{ + public MetaData MetaData { get; set; } + public List Models { get; set; } = new(); +} + +public class CustomerOrderModel +{ + public long Id { get; set; } + public long Amount { get; set; } + public long? PackageId { get; set; } + public long? TransactionId { get; set; } + public PaymentStatus PaymentStatus { get; set; } + public DateTime? PaymentDate { get; set; } + public long UserId { get; set; } + public long UserAddressId { get; set; } + public PaymentMethod? PaymentMethod { get; set; } + public string UserAddressText { get; set; } + public List FactorDetails { get; set; } = new(); + public DeliveryStatus DeliveryStatus { get; set; } + public string TrackingCode { get; set; } + public string DeliveryDescription { get; set; } + public string UserFullName { get; set; } + public string UserNationalCode { get; set; } + public long VatAmount { get; set; } + public double VatPercentage { get; set; } +} + +public class FactorDetailModel +{ + public long ProductId { get; set; } + public string ProductTitle { get; set; } + public string ProductThumbnailPath { get; set; } + public long? UnitPrice { get; set; } + public int? Count { get; set; } + public long? UnitDiscountPrice { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQuery.cs deleted file mode 100644 index 67cba45..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQuery.cs +++ /dev/null @@ -1,66 +0,0 @@ -using CMSMicroservice.Application.Common.Models; -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange; - -/// -/// دریافت سفارشات بر اساس بازه زمانی -/// -public record GetOrdersByDateRangeQuery : IRequest -{ - /// - /// تاریخ شروع (UTC) - /// - public DateTime StartDate { get; init; } - - /// - /// تاریخ پایان (UTC) - /// - public DateTime EndDate { get; init; } - - /// - /// فیلتر وضعیت تحویل (اختیاری) - /// - public DeliveryStatus? Status { get; init; } - - /// - /// شناسه کاربر (اختیاری - برای فیلتر بر اساس کاربر) - /// - public long? UserId { get; init; } - - /// - /// شماره صفحه - /// - public int PageIndex { get; init; } = 1; - - /// - /// تعداد در صفحه - /// - public int PageSize { get; init; } = 20; -} - -/// -/// پاسخ لیست سفارشات -/// -public class GetOrdersByDateRangeResponseDto -{ - public MetaData MetaData { get; set; } = new(); - public List Orders { get; set; } = new(); -} - -/// -/// خلاصه اطلاعات سفارش -/// -public class OrderSummaryDto -{ - public long Id { get; set; } - public long UserId { get; set; } - public string UserFullName { get; set; } = string.Empty; - public long Amount { get; set; } - public long DiscountedPrice { get; set; } - public DeliveryStatus Status { get; set; } - public DateTime Created { get; set; } - public DateTime? ShippedAt { get; set; } - public DateTime? DeliveredAt { get; set; } - public int ItemsCount { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryHandler.cs deleted file mode 100644 index 4fb7d94..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryHandler.cs +++ /dev/null @@ -1,96 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Application.Common.Models; -using Microsoft.EntityFrameworkCore; - -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange; - -public class GetOrdersByDateRangeQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly ILogger _logger; - - public GetOrdersByDateRangeQueryHandler( - IApplicationDbContext context, - ILogger logger) - { - _context = context; - _logger = logger; - } - - public async Task Handle(GetOrdersByDateRangeQuery request, CancellationToken cancellationToken) - { - var query = _context.UserOrders - .AsNoTracking() - .Include(o => o.User) - .Include(o => o.FactorDetails) - .AsQueryable(); - - query = query.Where(o => o.Created >= request.StartDate && o.Created <= request.EndDate); - - if (request.Status.HasValue) - { - query = query.Where(o => o.DeliveryStatus == request.Status.Value); - } - - if (request.UserId.HasValue) - { - query = query.Where(o => o.UserId == request.UserId.Value); - } - - var totalCount = await query.CountAsync(cancellationToken); - - var response = new GetOrdersByDateRangeResponseDto - { - MetaData = new MetaData - { - CurrentPage = request.PageIndex, - TotalPage = totalCount == 0 ? 0 : (int)Math.Ceiling(totalCount / (double)request.PageSize), - PageSize = request.PageSize, - TotalCount = totalCount, - HasNext = totalCount > 0 && request.PageIndex * request.PageSize < totalCount, - HasPrevious = request.PageIndex > 1 - } - }; - - if (totalCount == 0) - { - return response; - } - - var orders = await query - .OrderByDescending(o => o.Created) - .Skip((request.PageIndex - 1) * request.PageSize) - .Take(request.PageSize) - .ToListAsync(cancellationToken); - - response.Orders = orders.Select(o => - { - var firstName = o.User?.FirstName ?? string.Empty; - var lastName = o.User?.LastName ?? string.Empty; - var fullName = $"{firstName} {lastName}".Trim(); - - return new OrderSummaryDto - { - Id = o.Id, - UserId = o.UserId, - UserFullName = fullName, - Amount = o.Amount, - // در حال حاضر فیلد DiscountedPrice در UserOrder وجود ندارد، پس همان Amount برگردانده می‌شود - DiscountedPrice = o.Amount, - Status = o.DeliveryStatus, - Created = o.Created, - ShippedAt = null, - DeliveredAt = null, - ItemsCount = o.FactorDetails?.Count ?? 0 - }; - }).ToList(); - - _logger.LogInformation( - "Retrieved {Count} orders for date range {Start} to {End}", - response.Orders.Count, - request.StartDate, - request.EndDate); - - return response; - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryValidator.cs deleted file mode 100644 index 5e239c2..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryValidator.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange; - -public class GetOrdersByDateRangeQueryValidator : AbstractValidator -{ - public GetOrdersByDateRangeQueryValidator() - { - RuleFor(x => x.StartDate) - .LessThanOrEqualTo(x => x.EndDate) - .WithMessage("تاریخ شروع باید کوچکتر یا مساوی تاریخ پایان باشد"); - - RuleFor(x => x.EndDate) - .LessThanOrEqualTo(DateTime.Now.AddDays(1)) - .WithMessage("تاریخ پایان نمی‌تواند در آینده باشد"); - - RuleFor(x => x.PageIndex) - .GreaterThan(0) - .WithMessage("شماره صفحه باید بزرگتر از 0 باشد"); - - RuleFor(x => x.PageSize) - .InclusiveBetween(1, 100) - .WithMessage("تعداد در صفحه باید بین 1 تا 100 باشد"); - - // بازه زمانی نباید بیش از 1 سال باشد - RuleFor(x => x) - .Must(x => (x.EndDate - x.StartDate).TotalDays <= 365) - .WithMessage("بازه زمانی نمی‌تواند بیش از 1 سال باشد"); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQuery.cs deleted file mode 100644 index e605e34..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQuery.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder; -public record GetUserOrderQuery : IRequest -{ - //شناسه - public long Id { get; init; } - -} \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryHandler.cs deleted file mode 100644 index 6430527..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryHandler.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder; -public class GetUserOrderQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - - public GetUserOrderQueryHandler(IApplicationDbContext context) - { - _context = context; - } - - public async Task Handle(GetUserOrderQuery request, - CancellationToken cancellationToken) - { - var response = await _context.UserOrders - .Include(i => i.UserAddress) - .Include(i => i.User) - .Include(i => i.FactorDetails) - .ThenInclude(t => t.Product) - .Include(i => i.OrderVAT) - .AsNoTracking() - .Where(x => x.Id == request.Id) - .Select(x => new GetUserOrderResponseDto - { - Id = x.Id, - Amount = x.Amount, - PackageId = x.PackageId ?? 0, - TransactionId = x.TransactionId, - PaymentStatus = x.PaymentStatus, - PaymentDate = x.PaymentDate, - UserId = x.UserId, - UserAddressId = x.UserAddressId, - PaymentMethod = x.PaymentMethod, - UserAddressText = x.UserAddress.Address, - FactorDetails = x.FactorDetails.Select(fd => new GetUserOrderResponseFactorDetail - { - ProductId = fd.ProductId, - ProductTitle = fd.Product.Title, - ProductThumbnailPath = fd.Product.ThumbnailPath, - UnitPrice = fd.UnitPrice, - Count = fd.Count, - UnitDiscountPrice = fd.UnitDiscountPrice - }).ToList(), - DeliveryStatus = x.DeliveryStatus, - TrackingCode = x.TrackingCode, - DeliveryDescription = x.DeliveryDescription, - UserFullName = (x.User.FirstName ?? string.Empty) + " " + (x.User.LastName ?? string.Empty), - UserNationalCode = x.User.NationalCode, - VatInfo = x.OrderVAT != null ? new OrderVATInfoDto - { - VatRate = x.OrderVAT.VATRate, - BaseAmount = x.OrderVAT.BaseAmount, - VatAmount = x.OrderVAT.VATAmount, - TotalAmount = x.OrderVAT.TotalAmount, - IsPaid = x.OrderVAT.IsPaid - } : null - }) - .FirstOrDefaultAsync(cancellationToken); - - return response ?? throw new NotFoundException(nameof(UserOrder), request.Id); - } -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryValidator.cs deleted file mode 100644 index 4f6d6f2..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderQueryValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder; -public class GetUserOrderQueryValidator : AbstractValidator -{ - public GetUserOrderQueryValidator() - { - RuleFor(model => model.Id) - .NotNull(); - } - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserOrderQuery)model, x => x.IncludeProperties(propertyName))); - if (result.IsValid) - return Array.Empty(); - return result.Errors.Select(e => e.ErrorMessage); - }; -} diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderResponseDto.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderResponseDto.cs deleted file mode 100644 index d8376a6..0000000 --- a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetUserOrder/GetUserOrderResponseDto.cs +++ /dev/null @@ -1,83 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder; -public class GetUserOrderResponseDto -{ - //شناسه - public long Id { get; set; } - //قیمت - public long Amount { get; set; } - //شناسه پکیج - public long PackageId { get; set; } - //شناسه تراکنش - public long? TransactionId { get; set; } - //وضعیت پرداخت - public PaymentStatus PaymentStatus { get; set; } - //تاریخ پرداخت - public DateTime? PaymentDate { get; set; } - //شناسه کاربر - public long UserId { get; set; } - //شناسه آدرس کاربر - public long UserAddressId { get; set; } - // - public PaymentMethod? PaymentMethod { get; set; } - // - public string? UserAddressText { get; set; } - // - public List? FactorDetails { get; set; } - // وضعیت ارسال سفارش - public DeliveryStatus DeliveryStatus { get; set; } - // کدرهگیری مرسوله - public string? TrackingCode { get; set; } - // توضیحات ارسال - public string? DeliveryDescription { get; set; } - // نام کامل کاربر - public string? UserFullName { get; set; } - // کدملی کاربر - public string? UserNationalCode { get; set; } - // اطلاعات مالیات بر ارزش افزوده - public OrderVATInfoDto? VatInfo { get; set; } -} - -/// -/// اطلاعات مالیات بر ارزش افزوده -/// -public class OrderVATInfoDto -{ - /// - /// نرخ مالیات (مثلاً 0.09 = 9%) - /// - public decimal VatRate { get; set; } - /// - /// مبلغ پایه (قبل از مالیات) - /// - public long BaseAmount { get; set; } - /// - /// مبلغ مالیات - /// - public long VatAmount { get; set; } - /// - /// مبلغ کل (پایه + مالیات) - /// - public long TotalAmount { get; set; } - /// - /// آیا پرداخت شده - /// - public bool IsPaid { get; set; } -} - -public class GetUserOrderResponseFactorDetail -{ - //شناسه - public long ProductId { get; set; } - // - public string ProductTitle { get; set; } - // - public string? ProductThumbnailPath { get; set; } - // - public long? UnitPrice { get; set; } - // - public int? Count { get; set; } - // - public long? UnitDiscountPrice { get; set; } -} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQuery.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQuery.cs new file mode 100644 index 0000000..87a3109 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQuery.cs @@ -0,0 +1,14 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; + +public class GetCustomerWalletChangeLogQuery : IRequest> +{ + /// + /// فیلتر بر اساس شناسه ارجاع (اختیاری) + /// + public long? ReferenceId { get; set; } + + /// + /// فیلتر بر اساس نوع تغییر - افزایشی یا کاهشی (اختیاری) + /// + public bool? IsIncrease { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQueryHandler.cs new file mode 100644 index 0000000..bb09d35 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogQueryHandler.cs @@ -0,0 +1,61 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; + +public class GetCustomerWalletChangeLogQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerWalletChangeLogQueryHandler( + IApplicationDbContext context, + ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task> Handle( + GetCustomerWalletChangeLogQuery request, + CancellationToken cancellationToken) + { + // Get current user's ID from JWT + if (!long.TryParse(_currentUser.UserId, out var currentUserId)) + { + throw new UnauthorizedAccessException("User ID not found in token"); + } + + // Get user's wallet + var userWallet = await _context.UserWallets + .AsNoTracking() + .Where(x => x.UserId == currentUserId) + .FirstOrDefaultAsync(cancellationToken); + + if (userWallet == null) + { + throw new NotFoundException(nameof(UserWallet), currentUserId); + } + + // Build query for wallet change logs + var query = _context.UserWalletChangeLogs + .AsNoTracking() + .Where(x => x.WalletId == userWallet.Id); + + // Apply optional filters + if (request.ReferenceId.HasValue) + { + query = query.Where(x => x.RefrenceId == request.ReferenceId.Value); + } + + if (request.IsIncrease.HasValue) + { + query = query.Where(x => x.IsIncrease == request.IsIncrease.Value); + } + + // Order by newest first + var result = await query + .OrderByDescending(x => x.Created) + .ProjectToType() + .ToListAsync(cancellationToken); + + return result; + } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogResponseDto.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogResponseDto.cs new file mode 100644 index 0000000..0460c89 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWalletChangeLog/GetCustomerWalletChangeLogResponseDto.cs @@ -0,0 +1,49 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; + +public class GetCustomerWalletChangeLogResponseDto +{ + /// + /// موجودی جاری + /// + public long CurrentBalance { get; set; } + + /// + /// مقدار تغییر + /// + public long ChangeValue { get; set; } + + /// + /// موجودی جاری شبکه + /// + public long CurrentNetworkBalance { get; set; } + + /// + /// مقدار تغییر شبکه + /// + public long ChangeNerworkValue { get; set; } + + /// + /// موجودی جاری تخفیف + /// + public long CurrentDiscountBalance { get; set; } + + /// + /// مقدار تغییر تخفیف + /// + public long ChangeDiscountValue { get; set; } + + /// + /// افزایشی است؟ + /// + public bool IsIncrease { get; set; } + + /// + /// شناسه ارجاع + /// + public long? RefrenceId { get; set; } + + /// + /// تاریخ ایجاد + /// + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsQuery.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsQuery.cs new file mode 100644 index 0000000..387c1b6 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings; + +public class GetCustomerWithdrawalSettingsQuery : IRequest +{ + // No parameters needed - returns system-wide settings +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsQueryHandler.cs new file mode 100644 index 0000000..d4d380a --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsQueryHandler.cs @@ -0,0 +1,19 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings; + +public class GetCustomerWithdrawalSettingsQueryHandler : IRequestHandler +{ + // TODO: In future, read from SystemConfiguration table + private const long MIN_WITHDRAWAL_AMOUNT = 50000; // 50,000 Rials + + public Task Handle( + GetCustomerWithdrawalSettingsQuery request, + CancellationToken cancellationToken) + { + var response = new GetCustomerWithdrawalSettingsResponseDto + { + MinWithdrawalAmount = MIN_WITHDRAWAL_AMOUNT + }; + + return Task.FromResult(response); + } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsResponseDto.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsResponseDto.cs new file mode 100644 index 0000000..98695f2 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawalSettings/GetCustomerWithdrawalSettingsResponseDto.cs @@ -0,0 +1,9 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings; + +public class GetCustomerWithdrawalSettingsResponseDto +{ + /// + /// حداقل مبلغ برداشت (ریال) + /// + public long MinWithdrawalAmount { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsQuery.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsQuery.cs new file mode 100644 index 0000000..9263e41 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsQuery.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals; + +public class GetCustomerWithdrawalsQuery : IRequest> +{ + /// + /// فیلتر بر اساس وضعیت (اختیاری) + /// 0: Pending, 1: Paid, 2: WithdrawRequested, 3: Withdrawn, 4: PaymentFailed, 5: Cancelled + /// + public int? Status { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsQueryHandler.cs new file mode 100644 index 0000000..f559a90 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsQueryHandler.cs @@ -0,0 +1,56 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals; + +public class GetCustomerWithdrawalsQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetCustomerWithdrawalsQueryHandler( + IApplicationDbContext context, + ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task> Handle( + GetCustomerWithdrawalsQuery request, + CancellationToken cancellationToken) + { + // Get current user's ID from JWT + if (!long.TryParse(_currentUser.UserId, out var currentUserId)) + { + throw new UnauthorizedAccessException("User ID not found in token"); + } + + // Build query for user's commission payouts (withdrawals) + var query = _context.UserCommissionPayouts + .AsNoTracking() + .Include(x => x.WeekDefinition) + .Where(x => x.UserId == currentUserId); + + // Apply status filter if provided + if (request.Status.HasValue) + { + query = query.Where(x => (int)x.Status == request.Status.Value); + } + + // Order by newest first and map to DTO + var result = await query + .OrderByDescending(x => x.Created) + .Select(x => new GetCustomerWithdrawalsResponseDto + { + Id = x.Id, + WeekDefinitionId = x.WeekDefinitionId, + WeekDisplayName = x.WeekDefinition.DisplayName ?? "", + TotalAmount = x.TotalAmount, + Status = (int)x.Status, + WithdrawalMethod = x.WithdrawalMethod.HasValue ? (int)x.WithdrawalMethod.Value : null, + IbanNumber = x.IbanNumber ?? "", + Created = x.Created + }) + .ToListAsync(cancellationToken); + + return result; + } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsResponseDto.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsResponseDto.cs new file mode 100644 index 0000000..d6f8a58 --- /dev/null +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetCustomerWithdrawals/GetCustomerWithdrawalsResponseDto.cs @@ -0,0 +1,44 @@ +namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals; + +public class GetCustomerWithdrawalsResponseDto +{ + /// + /// شناسه + /// + public long Id { get; set; } + + /// + /// شناسه تعریف هفته + /// + public long WeekDefinitionId { get; set; } + + /// + /// نام نمایشی هفته + /// + public string WeekDisplayName { get; set; } + + /// + /// مبلغ کل + /// + public long TotalAmount { get; set; } + + /// + /// وضعیت + /// + public int Status { get; set; } + + /// + /// روش برداشت + /// + public int? WithdrawalMethod { get; set; } + + /// + /// شماره شبا + /// + public string IbanNumber { get; set; } + + /// + /// تاریخ ایجاد + /// + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletQueryHandler.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletQueryHandler.cs index 18cce89..fd1b17c 100644 --- a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletQueryHandler.cs +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletQueryHandler.cs @@ -11,12 +11,17 @@ public class GetUserWalletQueryHandler : IRequestHandler Handle(GetUserWalletQuery request, CancellationToken cancellationToken) { + // UserId must be provided - customer endpoints resolve from JWT before calling + var userId = request.Id; + if (userId == 0) + throw new ArgumentException("UserId is required"); + var response = await _context.UserWallets .AsNoTracking() - .Where(x => x.Id == request.Id) + .Where(x => x.UserId == userId) // Changed from x.Id to x.UserId .ProjectToType() .FirstOrDefaultAsync(cancellationToken); - return response ?? throw new NotFoundException(nameof(UserWallet), request.Id); + return response ?? throw new NotFoundException(nameof(UserWallet), userId); } } diff --git a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletResponseDto.cs b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletResponseDto.cs index e6d2bb7..5910d73 100644 --- a/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletResponseDto.cs +++ b/src/CMSMicroservice.Application/UserWalletCQ/Queries/GetUserWallet/GetUserWalletResponseDto.cs @@ -9,5 +9,6 @@ public class GetUserWalletResponseDto public long Balance { get; set; } //موجودی شبکه public long NetworkBalance { get; set; } - + //موجودی تخفیف + public long DiscountBalance { get; set; } } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandHandler.cs index 147a904..53494b0 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeDiscountWallet/ChargeDiscountWalletCommandHandler.cs @@ -2,8 +2,10 @@ using CMSMicroservice.Application.Common.Exceptions; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.Common.Models; using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Entities.Payment; using MediatR; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet; @@ -13,15 +15,18 @@ public class ChargeDiscountWalletCommandHandler { private readonly IApplicationDbContext _context; private readonly IPaymentGatewayService _paymentGateway; + private readonly IConfiguration _configuration; private readonly ILogger _logger; public ChargeDiscountWalletCommandHandler( IApplicationDbContext context, IPaymentGatewayService paymentGateway, + IConfiguration configuration, ILogger logger) { _context = context; _paymentGateway = paymentGateway; + _configuration = configuration; _logger = logger; } @@ -58,12 +63,15 @@ public class ChargeDiscountWalletCommandHandler } // 3. ایجاد درخواست پرداخت + var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846"; + var callbackUrl = $"{cmsBaseUrl}/api/wallet/verify-discount-charge"; + var paymentRequest = new PaymentRequest { Amount = request.Amount, UserId = user.Id, Mobile = user.Mobile ?? "", - CallbackUrl = $"https://yourdomain.com/api/wallet/verify-discount-charge", + CallbackUrl = callbackUrl, Description = $"شارژ کیف پول تخفیفی - کاربر {user.Id}" }; @@ -80,10 +88,29 @@ public class ChargeDiscountWalletCommandHandler throw new Exception($"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}"); } + // 4. ثبت PaymentTransaction + var paymentTx = new PaymentTransaction + { + GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", + MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", + Amount = request.Amount, + CallbackUrl = callbackUrl, + Description = $"شارژ کیف پول تخفیفی - کاربر {user.Id}", + Mobile = user.Mobile, + UserId = user.Id, + RequestStatusCode = 100, + RequestStatusMessage = "Success", + Authority = paymentResult.RefId, + PaymentStatus = false + }; + _context.PaymentTransactions.Add(paymentTx); + await _context.SaveChangesAsync(cancellationToken); + _logger.LogInformation( - "Discount wallet charge initiated. UserId: {UserId}, RefId: {RefId}", + "Discount wallet charge initiated. UserId: {UserId}, RefId: {RefId}, PaymentTxId: {PaymentTxId}", user.Id, - paymentResult.RefId + paymentResult.RefId, + paymentTx.Id ); return paymentResult; diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs index 292555d..c1989b7 100644 --- a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs @@ -50,11 +50,25 @@ public class VerifyDiscountWalletChargeCommandHandler } // 2. Verify با درگاه + // Authority = کد مرجع تراکنش، Status = "OK" (تأیید کاربر از درگاه) var verifyResult = await _paymentGateway.VerifyPaymentAsync( request.Authority, - request.Authority // verificationToken - در بعضی درگاه‌ها مثل زرین‌پال همان Authority است + "OK" // وقتی این handler فراخوانی میشه یعنی کاربر از درگاه برگشته — Status باید OK باشه ); + // آپدیت PaymentTransaction با نتیجه verify + var paymentTx = await _context.PaymentTransactions + .FirstOrDefaultAsync(pt => pt.Authority == request.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; + } + if (!verifyResult.IsSuccess) { _logger.LogWarning( @@ -100,6 +114,13 @@ public class VerifyDiscountWalletChargeCommandHandler _context.Transactions.Add(transaction); await _context.SaveChangesAsync(cancellationToken); + // لینک PaymentTransaction به Transaction داخلی + if (paymentTx != null) + { + paymentTx.TransactionId = transaction.Id; + await _context.SaveChangesAsync(cancellationToken); + } + _logger.LogInformation( "Discount wallet charged successfully. UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}", user.Id, diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommand.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommand.cs new file mode 100644 index 0000000..58b95f5 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommand.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse; + +/// +/// Command برای ایجاد انبار جدید +/// +public record CreateWarehouseCommand : IRequest +{ + /// نام انبار + public string Name { get; init; } = string.Empty; + /// کد انبار + public string Code { get; init; } = string.Empty; + /// آدرس انبار + public string? Address { get; init; } + /// فعال؟ + public bool IsActive { get; init; } = true; + /// پیش‌فرض؟ + public bool IsDefault { get; init; } = false; +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommandHandler.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommandHandler.cs new file mode 100644 index 0000000..4fe2801 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommandHandler.cs @@ -0,0 +1,39 @@ +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse; + +public class CreateWarehouseCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateWarehouseCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateWarehouseCommand request, CancellationToken cancellationToken) + { + // بررسی تکراری نبودن کد + var codeExists = await _context.Warehouses + .AnyAsync(w => w.Code == request.Code, cancellationToken); + + if (codeExists) + { + throw new InvalidOperationException($"Warehouse with code '{request.Code}' already exists"); + } + + var entity = new Warehouse + { + Name = request.Name, + Code = request.Code, + Address = request.Address, + IsActive = request.IsActive, + IsDefault = request.IsDefault + }; + + await _context.Warehouses.AddAsync(entity, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + return new CreateWarehouseResponseDto { Id = entity.Id }; + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommandValidator.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommandValidator.cs new file mode 100644 index 0000000..5346713 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseCommandValidator.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse; + +public class CreateWarehouseCommandValidator : AbstractValidator +{ + public CreateWarehouseCommandValidator() + { + RuleFor(x => x.Name) + .NotEmpty().WithMessage("نام انبار الزامی است") + .MaximumLength(100).WithMessage("نام انبار نباید بیش از 100 کاراکتر باشد"); + + RuleFor(x => x.Code) + .NotEmpty().WithMessage("کد انبار الزامی است") + .MaximumLength(50).WithMessage("کد انبار نباید بیش از 50 کاراکتر باشد"); + + RuleFor(x => x.Address) + .MaximumLength(500).WithMessage("آدرس نباید بیش از 500 کاراکتر باشد"); + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseResponseDto.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseResponseDto.cs new file mode 100644 index 0000000..c200ff4 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/CreateWarehouse/CreateWarehouseResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse; + +public class CreateWarehouseResponseDto +{ + /// شناسه انبار ایجاد شده + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommand.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommand.cs new file mode 100644 index 0000000..6b06979 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommand.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse; + +/// +/// Command برای حذف انبار +/// +public record DeleteWarehouseCommand(long Id) : IRequest; diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommandHandler.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommandHandler.cs new file mode 100644 index 0000000..fca8e28 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommandHandler.cs @@ -0,0 +1,38 @@ +using CMSMicroservice.Application.Common.Exceptions; + +namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse; + +public class DeleteWarehouseCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteWarehouseCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteWarehouseCommand request, CancellationToken cancellationToken) + { + var warehouse = await _context.Warehouses + .FirstOrDefaultAsync(w => w.Id == request.Id, cancellationToken); + + if (warehouse == null) + { + throw new NotFoundException(nameof(Domain.Entities.Warehouse), request.Id); + } + + // بررسی اینکه انبار دارای موجودی نباشد + var hasInventory = await _context.InventoryItems + .AnyAsync(i => i.WarehouseId == request.Id, cancellationToken); + + if (hasInventory) + { + throw new InvalidOperationException("Cannot delete warehouse with existing inventory items"); + } + + _context.Warehouses.Remove(warehouse); + await _context.SaveChangesAsync(cancellationToken); + + return new DeleteWarehouseResponseDto { Success = true }; + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommandValidator.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommandValidator.cs new file mode 100644 index 0000000..86e796f --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseCommandValidator.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse; + +public class DeleteWarehouseCommandValidator : AbstractValidator +{ + public DeleteWarehouseCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه انبار معتبر نیست"); + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseResponseDto.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseResponseDto.cs new file mode 100644 index 0000000..c7560dc --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/DeleteWarehouse/DeleteWarehouseResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse; + +public class DeleteWarehouseResponseDto +{ + /// آیا عملیات موفق بود؟ + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommand.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommand.cs new file mode 100644 index 0000000..bb81677 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommand.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse; + +/// +/// Command برای تعیین انبار پیش‌فرض +/// +public record SetDefaultWarehouseCommand(long Id) : IRequest; diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommandHandler.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommandHandler.cs new file mode 100644 index 0000000..3821d08 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommandHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Application.Common.Exceptions; + +namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse; + +public class SetDefaultWarehouseCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public SetDefaultWarehouseCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(SetDefaultWarehouseCommand request, CancellationToken cancellationToken) + { + var warehouse = await _context.Warehouses + .FirstOrDefaultAsync(w => w.Id == request.Id, cancellationToken); + + if (warehouse == null) + { + throw new NotFoundException(nameof(Domain.Entities.Warehouse), request.Id); + } + + // ریست کردن همه انبارهای دیگر + var otherWarehouses = await _context.Warehouses + .Where(w => w.IsDefault && w.Id != request.Id) + .ToListAsync(cancellationToken); + + foreach (var other in otherWarehouses) + { + other.IsDefault = false; + } + + // تنظیم این انبار به عنوان پیش‌فرض + warehouse.IsDefault = true; + + await _context.SaveChangesAsync(cancellationToken); + + return new SetDefaultWarehouseResponseDto { Success = true }; + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommandValidator.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommandValidator.cs new file mode 100644 index 0000000..7e23a7a --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseCommandValidator.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse; + +public class SetDefaultWarehouseCommandValidator : AbstractValidator +{ + public SetDefaultWarehouseCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه انبار معتبر نیست"); + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseResponseDto.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseResponseDto.cs new file mode 100644 index 0000000..a025763 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/SetDefaultWarehouse/SetDefaultWarehouseResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse; + +public class SetDefaultWarehouseResponseDto +{ + /// آیا عملیات موفق بود؟ + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommand.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommand.cs new file mode 100644 index 0000000..815b6ad --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommand.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse; + +/// +/// Command برای آپدیت انبار +/// +public record UpdateWarehouseCommand : IRequest +{ + /// شناسه انبار + public long Id { get; init; } + /// نام انبار + public string? Name { get; init; } + /// کد انبار + public string? Code { get; init; } + /// آدرس انبار + public string? Address { get; init; } + /// فعال؟ + public bool? IsActive { get; init; } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommandHandler.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommandHandler.cs new file mode 100644 index 0000000..e54c512 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommandHandler.cs @@ -0,0 +1,56 @@ +using CMSMicroservice.Application.Common.Exceptions; + +namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse; + +public class UpdateWarehouseCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateWarehouseCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateWarehouseCommand request, CancellationToken cancellationToken) + { + var warehouse = await _context.Warehouses + .FirstOrDefaultAsync(w => w.Id == request.Id, cancellationToken); + + if (warehouse == null) + { + throw new NotFoundException(nameof(Domain.Entities.Warehouse), request.Id); + } + + // بررسی تکراری نبودن کد جدید + if (!string.IsNullOrEmpty(request.Code) && request.Code != warehouse.Code) + { + var codeExists = await _context.Warehouses + .AnyAsync(w => w.Code == request.Code && w.Id != request.Id, cancellationToken); + + if (codeExists) + { + throw new InvalidOperationException($"Warehouse with code '{request.Code}' already exists"); + } + warehouse.Code = request.Code; + } + + if (!string.IsNullOrEmpty(request.Name)) + { + warehouse.Name = request.Name; + } + + if (request.Address != null) + { + warehouse.Address = request.Address; + } + + if (request.IsActive.HasValue) + { + warehouse.IsActive = request.IsActive.Value; + } + + await _context.SaveChangesAsync(cancellationToken); + + return new UpdateWarehouseResponseDto { Success = true }; + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommandValidator.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommandValidator.cs new file mode 100644 index 0000000..87ac658 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseCommandValidator.cs @@ -0,0 +1,19 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse; + +public class UpdateWarehouseCommandValidator : AbstractValidator +{ + public UpdateWarehouseCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه انبار معتبر نیست"); + + RuleFor(x => x.Name) + .MaximumLength(100).WithMessage("نام انبار نباید بیش از 100 کاراکتر باشد"); + + RuleFor(x => x.Code) + .MaximumLength(50).WithMessage("کد انبار نباید بیش از 50 کاراکتر باشد"); + + RuleFor(x => x.Address) + .MaximumLength(500).WithMessage("آدرس نباید بیش از 500 کاراکتر باشد"); + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseResponseDto.cs b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseResponseDto.cs new file mode 100644 index 0000000..b0feee2 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Commands/UpdateWarehouse/UpdateWarehouseResponseDto.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse; + +public class UpdateWarehouseResponseDto +{ + /// آیا عملیات موفق بود؟ + public bool Success { get; set; } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesQuery.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesQuery.cs new file mode 100644 index 0000000..5bbff8c --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses; + +/// +/// Query برای گرفتن لیست تمام انبارها +/// +public record GetAllWarehousesQuery : IRequest; diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesQueryHandler.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesQueryHandler.cs new file mode 100644 index 0000000..14d3099 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesQueryHandler.cs @@ -0,0 +1,34 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses; + +public class GetAllWarehousesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllWarehousesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllWarehousesQuery request, CancellationToken cancellationToken) + { + var warehouses = await _context.Warehouses + .AsNoTracking() + .OrderBy(w => w.Name) + .Select(w => new WarehouseItemDto + { + Id = w.Id, + Name = w.Name, + Code = w.Code, + Address = w.Address, + IsDefault = w.IsDefault, + IsActive = w.IsActive + }) + .ToListAsync(cancellationToken); + + return new GetAllWarehousesResponseDto + { + Warehouses = warehouses, + TotalCount = warehouses.Count + }; + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesResponseDto.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesResponseDto.cs new file mode 100644 index 0000000..3b7fa5f --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetAllWarehouses/GetAllWarehousesResponseDto.cs @@ -0,0 +1,17 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses; + +public class GetAllWarehousesResponseDto +{ + public List Warehouses { get; set; } = new(); + public int TotalCount { get; set; } +} + +public class WarehouseItemDto +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Code { get; set; } = string.Empty; + public string? Address { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseQuery.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseQuery.cs new file mode 100644 index 0000000..48820c3 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse; + +/// +/// Query برای گرفتن انبار با شناسه +/// +public record GetWarehouseQuery(long Id) : IRequest; diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseQueryHandler.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseQueryHandler.cs new file mode 100644 index 0000000..b75e1b5 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseQueryHandler.cs @@ -0,0 +1,30 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse; + +public class GetWarehouseQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetWarehouseQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetWarehouseQuery request, CancellationToken cancellationToken) + { + var warehouse = await _context.Warehouses + .AsNoTracking() + .Where(w => w.Id == request.Id) + .Select(w => new GetWarehouseResponseDto + { + Id = w.Id, + Name = w.Name, + Code = w.Code, + Address = w.Address, + IsDefault = w.IsDefault, + IsActive = w.IsActive + }) + .FirstOrDefaultAsync(cancellationToken); + + return warehouse; + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseResponseDto.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseResponseDto.cs new file mode 100644 index 0000000..a84e514 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/GetWarehouse/GetWarehouseResponseDto.cs @@ -0,0 +1,11 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse; + +public class GetWarehouseResponseDto +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Code { get; set; } = string.Empty; + public string? Address { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesQuery.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesQuery.cs new file mode 100644 index 0000000..8d9f436 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesQuery.cs @@ -0,0 +1,12 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.SearchWarehouses; + +/// +/// Query برای جستجوی انبارها +/// +public record SearchWarehousesQuery : IRequest +{ + public string? SearchTerm { get; init; } + public bool? IsActive { get; init; } + public int Skip { get; init; } = 0; + public int Take { get; init; } = 50; +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesQueryHandler.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesQueryHandler.cs new file mode 100644 index 0000000..3ed25db --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesQueryHandler.cs @@ -0,0 +1,54 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.SearchWarehouses; + +public class SearchWarehousesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public SearchWarehousesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(SearchWarehousesQuery request, CancellationToken cancellationToken) + { + var query = _context.Warehouses.AsNoTracking(); + + // فیلتر بر اساس جستجو + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + query = query.Where(w => + w.Name.Contains(request.SearchTerm) || + w.Code.Contains(request.SearchTerm) || + (w.Address != null && w.Address.Contains(request.SearchTerm))); + } + + // فیلتر بر اساس فعال بودن + if (request.IsActive.HasValue) + { + query = query.Where(w => w.IsActive == request.IsActive.Value); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var warehouses = await query + .OrderBy(w => w.Name) + .Skip(request.Skip) + .Take(request.Take) + .Select(w => new WarehouseSearchItemDto + { + Id = w.Id, + Name = w.Name, + Code = w.Code, + Address = w.Address, + IsDefault = w.IsDefault, + IsActive = w.IsActive + }) + .ToListAsync(cancellationToken); + + return new SearchWarehousesResponseDto + { + Warehouses = warehouses, + TotalCount = totalCount + }; + } +} diff --git a/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesResponseDto.cs b/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesResponseDto.cs new file mode 100644 index 0000000..891a840 --- /dev/null +++ b/src/CMSMicroservice.Application/WarehouseCQ/Queries/SearchWarehouses/SearchWarehousesResponseDto.cs @@ -0,0 +1,17 @@ +namespace CMSMicroservice.Application.WarehouseCQ.Queries.SearchWarehouses; + +public class SearchWarehousesResponseDto +{ + public List Warehouses { get; set; } = new(); + public int TotalCount { get; set; } +} + +public class WarehouseSearchItemDto +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Code { get; set; } = string.Empty; + public string? Address { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/CMSMicroservice.Domain/CMSMicroservice.Domain.csproj b/src/CMSMicroservice.Domain/CMSMicroservice.Domain.csproj index c8d69e7..9beaf01 100644 --- a/src/CMSMicroservice.Domain/CMSMicroservice.Domain.csproj +++ b/src/CMSMicroservice.Domain/CMSMicroservice.Domain.csproj @@ -6,6 +6,7 @@ + diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogCategory.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogCategory.cs new file mode 100644 index 0000000..930cbd6 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogCategory.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// دسته‌بندی مقالات بلاگ +/// +public class BlogCategory : BaseAuditableEntity +{ + //عنوان دسته‌بندی + public string Title { get; set; } = string.Empty; + //نشانی یکتا + public string Slug { get; set; } = string.Empty; + //توضیحات + public string? Description { get; set; } + //نام آیکون Material + public string? IconName { get; set; } + //ترتیب نمایش + public int SortOrder { get; set; } + //فعال؟ + public bool IsActive { get; set; } = true; + + //مقالات + public virtual ICollection BlogPostCategories { get; set; } = new List(); +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPost.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPost.cs new file mode 100644 index 0000000..2a4ddf4 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPost.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Domain.Common; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// مقاله بلاگ +/// شامل عنوان، خلاصه، محتوای HTML، تصویر شاخص، وضعیت انتشار و آمار بازدید +/// +public class BlogPost : BaseAuditableEntity +{ + //عنوان مقاله + public string Title { get; set; } = string.Empty; + //نشانی یکتا (slug) + public string Slug { get; set; } = string.Empty; + //خلاصه مقاله برای نمایش در کارت‌ها + public string? Summary { get; set; } + //محتوای HTML مقاله + public string HtmlContent { get; set; } = string.Empty; + //مسیر تصویر شاخص + public string? FeaturedImagePath { get; set; } + //مسیر تامبنیل تصویر شاخص + public string? FeaturedImageThumbnailPath { get; set; } + //وضعیت مقاله + public BlogPostStatus Status { get; set; } = BlogPostStatus.Draft; + //زمان انتشار + public DateTime? PublishedAt { get; set; } + //زمانبندی انتشار خودکار + public DateTime? ScheduledPublishAt { get; set; } + //تعداد بازدید + public int ViewCount { get; set; } = 0; + //شناسه نویسنده + public long AuthorUserId { get; set; } + //نمایش در صفحه اول + public bool IsFeatured { get; set; } = false; + //ترتیب نمایش + public int SortOrder { get; set; } + + //دسته‌بندی‌ها + public virtual ICollection BlogPostCategories { get; set; } = new List(); + //تگ‌ها + public virtual ICollection BlogPostTags { get; set; } = new List(); + //تصاویر گالری + public virtual ICollection BlogPostImages { get; set; } = new List(); +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPostCategory.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostCategory.cs new file mode 100644 index 0000000..c237e72 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostCategory.cs @@ -0,0 +1,15 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// جدول واسط بین مقاله و دسته‌بندی (چند به چند) +/// +public class BlogPostCategory : BaseEntity +{ + public long BlogPostId { get; set; } + public long BlogCategoryId { get; set; } + + public virtual BlogPost BlogPost { get; set; } = null!; + public virtual BlogCategory BlogCategory { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPostImage.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostImage.cs new file mode 100644 index 0000000..8215454 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostImage.cs @@ -0,0 +1,23 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// تصاویر گالری مقاله +/// +public class BlogPostImage : BaseAuditableEntity +{ + public long BlogPostId { get; set; } + //مسیر تصویر + public string ImagePath { get; set; } = string.Empty; + //مسیر تامبنیل + public string ThumbnailPath { get; set; } = string.Empty; + //متن جایگزین + public string? AltText { get; set; } + //عنوان تصویر + public string? Caption { get; set; } + //ترتیب نمایش + public int SortOrder { get; set; } + + public virtual BlogPost BlogPost { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPostTag.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostTag.cs new file mode 100644 index 0000000..5daa2e7 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostTag.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// جدول واسط بین مقاله و تگ (چند به چند) +/// از Tag موجود استفاده مجدد می‌شود +/// +public class BlogPostTag : BaseEntity +{ + public long BlogPostId { get; set; } + public long TagId { get; set; } + + public virtual BlogPost BlogPost { get; set; } = null!; + public virtual Tag Tag { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Entities/Content/SitePage.cs b/src/CMSMicroservice.Domain/Entities/Content/SitePage.cs new file mode 100644 index 0000000..b2d59e2 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Content/SitePage.cs @@ -0,0 +1,28 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Content; + +/// +/// صفحات دینامیک سایت (درباره ما، تماس با ما و ...) +/// محتوای هر صفحه از طریق پنل ادمین قابل ویرایش است +/// +public class SitePage : BaseAuditableEntity +{ + //کلید یکتا (about, contact, ...) + public string PageKey { get; set; } = string.Empty; + //عنوان صفحه + public string Title { get; set; } = string.Empty; + //توضیح متا برای SEO + public string? MetaDescription { get; set; } + //عنوان Hero + public string? HeroTitle { get; set; } + //زیرعنوان Hero + public string? HeroSubtitle { get; set; } + //مسیر تصویر Hero + public string? HeroImagePath { get; set; } + //فعال؟ + public bool IsActive { get; set; } = true; + + //بخش‌های صفحه + public virtual ICollection Sections { get; set; } = new List(); +} diff --git a/src/CMSMicroservice.Domain/Entities/Content/SitePageSection.cs b/src/CMSMicroservice.Domain/Entities/Content/SitePageSection.cs new file mode 100644 index 0000000..cde18b1 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Content/SitePageSection.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Content; + +/// +/// بخش‌های یک صفحه دینامیک +/// هر صفحه می‌تواند N بخش با ترتیب نمایش داشته باشد +/// +public class SitePageSection : BaseAuditableEntity +{ + public long SitePageId { get; set; } + //کلید بخش (mission, vision, team-member-1, ...) + public string SectionKey { get; set; } = string.Empty; + //عنوان بخش + public string Title { get; set; } = string.Empty; + //زیرعنوان + public string? Subtitle { get; set; } + //محتوای HTML + public string? HtmlContent { get; set; } + //نام آیکون Material + public string? IconName { get; set; } + //مسیر تصویر + public string? ImagePath { get; set; } + //مسیر تامبنیل + public string? ImageThumbnailPath { get; set; } + //ترتیب نمایش + public int SortOrder { get; set; } + //فعال؟ + public bool IsActive { get; set; } = true; + //داده اضافی JSON (برای فیلدهای انعطاف‌پذیر) + public string? ExtraData { get; set; } + + public virtual SitePage SitePage { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Entities/DiscountShop/DiscountProduct.cs b/src/CMSMicroservice.Domain/Entities/DiscountShop/DiscountProduct.cs index 5985d1b..85f314c 100644 --- a/src/CMSMicroservice.Domain/Entities/DiscountShop/DiscountProduct.cs +++ b/src/CMSMicroservice.Domain/Entities/DiscountShop/DiscountProduct.cs @@ -83,4 +83,9 @@ public class DiscountProduct : BaseAuditableEntity /// دسته‌بندی‌های این محصول /// public virtual ICollection ProductCategories { get; set; } + + /// + /// تصاویر گالری محصول + /// + public virtual ICollection Images { get; set; } } diff --git a/src/CMSMicroservice.Domain/Entities/DiscountShop/DiscountProductImage.cs b/src/CMSMicroservice.Domain/Entities/DiscountShop/DiscountProductImage.cs new file mode 100644 index 0000000..e8e19e0 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/DiscountShop/DiscountProductImage.cs @@ -0,0 +1,47 @@ +namespace CMSMicroservice.Domain.Entities.DiscountShop; + +/// +/// تصویر گالری محصول تخفیفی +/// +public class DiscountProductImage : BaseAuditableEntity +{ + /// + /// شناسه محصول + /// + public long DiscountProductId { get; set; } + + /// + /// محصول + /// + public virtual DiscountProduct DiscountProduct { get; set; } = null!; + + /// + /// عنوان تصویر + /// + public string? Title { get; set; } + + /// + /// متن جایگزین (Alt) + /// + public string? AltText { get; set; } + + /// + /// مسیر تصویر اصلی + /// + public string ImagePath { get; set; } = string.Empty; + + /// + /// مسیر تصویر کوچک + /// + public string? ThumbnailPath { get; set; } + + /// + /// ترتیب نمایش + /// + public int SortOrder { get; set; } + + /// + /// آیا فعال است؟ + /// + public bool IsActive { get; set; } = true; +} diff --git a/src/CMSMicroservice.Domain/Entities/InventoryItem.cs b/src/CMSMicroservice.Domain/Entities/InventoryItem.cs new file mode 100644 index 0000000..5762a0b --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/InventoryItem.cs @@ -0,0 +1,105 @@ +using CMSMicroservice.Domain.Common; +using CMSMicroservice.Domain.Enums; +using CMSMicroservice.Domain.Entities.DiscountShop; + +namespace CMSMicroservice.Domain.Entities; + +/// +/// موجودی کالا در انبار +/// +public class InventoryItem : BaseAuditableEntity +{ + // ========== شناسه محصول (یکی از دو فیلد زیر پر است) ========== + + /// + /// شناسه محصول فروشگاه معمولی + /// + public long? ProductId { get; set; } + + /// + /// محصول فروشگاه معمولی + /// + public Product? Product { get; set; } + + /// + /// شناسه محصول فروشگاه تخفیفی + /// + public long? DiscountProductId { get; set; } + + /// + /// محصول فروشگاه تخفیفی + /// + public DiscountProduct? DiscountProduct { get; set; } + + // ========== نوع محصول ========== + + /// + /// نوع محصول (معمولی یا تخفیفی) + /// + public ProductType ProductType { get; set; } + + // ========== موجودی ========== + + /// + /// موجودی فعلی (منبع اصلی) + /// + public int Quantity { get; set; } + + /// + /// مقدار رزرو شده برای سفارشات pending + /// + public int ReservedQuantity { get; set; } + + /// + /// موجودی قابل فروش (محاسباتی) + /// + public int AvailableQuantity => Quantity - ReservedQuantity; + + // ========== تنظیمات انبار ========== + + /// + /// آستانه هشدار کم‌موجودی + /// + public int LowStockThreshold { get; set; } = 10; + + /// + /// نقطه سفارش مجدد + /// + public int ReorderPoint { get; set; } = 5; + + /// + /// حداکثر موجودی مجاز + /// + public int MaxStockLevel { get; set; } = 1000; + + // ========== آمار ========== + + /// + /// تاریخ آخرین ورود کالا + /// + public DateTime? LastRestockedAt { get; set; } + + /// + /// تاریخ آخرین فروش + /// + public DateTime? LastSoldAt { get; set; } + + // ========== انبار (برای آینده) ========== + + /// + /// شناسه انبار (پیش‌فرض: انبار اصلی) + /// + public long WarehouseId { get; set; } = 1; + + /// + /// انبار + /// + public Warehouse? Warehouse { get; set; } + + // ========== Navigation Properties ========== + + /// + /// حرکات انبار مرتبط با این آیتم + /// + public ICollection StockMovements { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/CMSMicroservice.Domain/Entities/OtpToken.cs b/src/CMSMicroservice.Domain/Entities/OtpToken.cs index 5846726..cfa8268 100644 --- a/src/CMSMicroservice.Domain/Entities/OtpToken.cs +++ b/src/CMSMicroservice.Domain/Entities/OtpToken.cs @@ -6,6 +6,8 @@ public class OtpToken : BaseAuditableEntity public string Mobile { get; set; } //مقصود public string Purpose { get; set; } + //کد + public string Code { get; set; } //کد هش شده public string CodeHash { get; set; } //زمان انقضا @@ -14,4 +16,13 @@ public class OtpToken : BaseAuditableEntity public int Attempts { get; set; } //موفق بود؟ public bool IsUsed { get; set; } + + /// + /// Checks if token is still valid (not used and not expired). + /// Code verification should be done in the handler using IHashService. + /// + public bool IsValid() + { + return !IsUsed && DateTime.Now > ExpiresAt == false; + } } diff --git a/src/CMSMicroservice.Domain/Entities/Payment/ManualPayment.cs b/src/CMSMicroservice.Domain/Entities/Payment/ManualPayment.cs index 4fd2f8d..9334bf9 100644 --- a/src/CMSMicroservice.Domain/Entities/Payment/ManualPayment.cs +++ b/src/CMSMicroservice.Domain/Entities/Payment/ManualPayment.cs @@ -38,6 +38,16 @@ public class ManualPayment : BaseAuditableEntity /// public string? ReferenceNumber { get; set; } + /// + /// مسیر تصویر فیش واریزی (اختیاری) + /// + public string? ImagePath { get; set; } + + /// + /// شناسه سند در FMS (اختیاری) + /// + public long? ImageDocumentId { get; set; } + /// /// وضعیت تایید /// diff --git a/src/CMSMicroservice.Domain/Entities/Payment/PaymentTransaction.cs b/src/CMSMicroservice.Domain/Entities/Payment/PaymentTransaction.cs new file mode 100644 index 0000000..6ca5280 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Payment/PaymentTransaction.cs @@ -0,0 +1,73 @@ +namespace CMSMicroservice.Domain.Entities.Payment; + +/// +/// جدول ثبت تراکنش‌های درگاه پرداخت آنلاین +/// الگو از PYMS — همه فیلدهای gateway-level اینجا ذخیره می‌شوند +/// جدول Transaction فعلی دست‌نخورده باقی می‌ماند +/// +public class PaymentTransaction : BaseAuditableEntity +{ + // ── اطلاعات درخواست (مرحله Request) ── + + /// نام درگاه (zarinpal, daya, mock, ...) + public string GatewayProvider { get; set; } = string.Empty; + + /// شناسه مرچنت مورد استفاده + public string MerchantId { get; set; } = string.Empty; + + /// مبلغ به تومان + public long Amount { get; set; } + + /// آدرس بازگشت بعد از پرداخت + public string CallbackUrl { get; set; } = string.Empty; + + /// شرح تراکنش + public string Description { get; set; } = string.Empty; + + /// شماره موبایل پرداخت‌کننده + public string? Mobile { get; set; } + + /// شناسه کاربر + public long? UserId { get; set; } + + // ── پاسخ درخواست (بعد از فراخوانی Request API) ── + + /// کد وضعیت درخواست (100=موفق در زرین‌پال) + public int? RequestStatusCode { get; set; } + + /// پیام درخواست + public string? RequestStatusMessage { get; set; } + + /// Authority — کلید یکتای تراکنش در درگاه + public string? Authority { get; set; } + + // ── وضعیت پرداخت ── + + /// آیا پرداخت موفق بوده؟ + public bool PaymentStatus { get; set; } + + // ── نتیجه تأیید (بعد از Verify API) ── + + /// کد وضعیت verify (100=موفق، 101=قبلاً تأیید شده) + public int? VerificationStatusCode { get; set; } + + /// پیام verify + public string? VerificationStatusMessage { get; set; } + + /// هش کارت بانکی + public string? CardHash { get; set; } + + /// شماره کارت ماسک‌شده (مثلاً 6037-****-****-1234) + public string? CardPan { get; set; } + + /// شماره مرجع بانکی (RefId عددی زرین‌پال — کد پیگیری) + public string? RefId { get; set; } + + // ── ارتباط با سیستم داخلی ── + + /// شناسه Transaction داخلی (جدول Transactions فعلی) + public long? TransactionId { get; set; } + + /// شناسه سفارش (اگر مرتبط با سفارش باشد) + public string? OrderId { get; set; } +} diff --git a/src/CMSMicroservice.Domain/Entities/ProductGalleries.cs b/src/CMSMicroservice.Domain/Entities/ProductGalleries.cs deleted file mode 100644 index b1bd0a7..0000000 --- a/src/CMSMicroservice.Domain/Entities/ProductGalleries.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace CMSMicroservice.Domain.Entities; -//گالری تصاویر محصول -public class ProductGalleries : BaseAuditableEntity -{ - public long ProductImageId { get; set; } - //ProductImage Navigation Property - public virtual ProductImage ProductImage { get; set; } - public long ProductId { get; set; } - //Product Navigation Property - public virtual Products Product { get; set; } -} diff --git a/src/CMSMicroservice.Domain/Entities/ProductImages.cs b/src/CMSMicroservice.Domain/Entities/ProductImages.cs deleted file mode 100644 index bc56e57..0000000 --- a/src/CMSMicroservice.Domain/Entities/ProductImages.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CMSMicroservice.Domain.Entities; -//توکن Otp -public class ProductImages : BaseAuditableEntity -{ - public string Title { get; set; } - public string ImagePath { get; set; } - public string ImageThumbnailPath { get; set; } - //ProductGalleries Collection Navigation Reference - public virtual ICollection ProductGalleries { get; set; } -} diff --git a/src/CMSMicroservice.Domain/Entities/Products.cs b/src/CMSMicroservice.Domain/Entities/Products.cs deleted file mode 100644 index 24b9d5e..0000000 --- a/src/CMSMicroservice.Domain/Entities/Products.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace CMSMicroservice.Domain.Entities; -//توکن Otp -public class Products : BaseAuditableEntity -{ - public string Title { get; set; } - public string Description { get; set; } - public string ShortInfomation { get; set; } - public string FullInformation { get; set; } - public long Price { get; set; } - public int Discount { get; set; } - public int Rate { get; set; } - public string ImagePath { get; set; } - public string ThumbnailPath { get; set; } - public int SaleCount { get; set; } - public int ViewCount { get; set; } - public int RemainingCount { get; set; } - - // ============= Club Shop Fields ============= - - /// - /// آیا این محصول فقط در فروشگاه باشگاه موجود است - /// - public bool IsClubExclusive { get; set; } - - /// - /// درصد تخفیف باشگاه (0 تا 100) - /// - public int ClubDiscountPercent { get; set; } - - // ============= Navigation Properties ============= - - //UserCarts Collection Navigation Reference - public virtual ICollection UserCarts { get; set; } - //ProductGalleries Collection Navigation Reference - public virtual ICollection ProductGalleries { get; set; } - //FactorDetails Collection Navigation Reference - public virtual ICollection FactorDetails { get; set; } - //ProductCategory Collection Navigation Reference - public virtual ICollection ProductCategories { get; set; } - //ProductTag Collection Navigation Reference - public virtual ICollection ProductTags { get; set; } -} diff --git a/src/CMSMicroservice.Domain/Entities/StockMovement.cs b/src/CMSMicroservice.Domain/Entities/StockMovement.cs new file mode 100644 index 0000000..14cb0f7 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/StockMovement.cs @@ -0,0 +1,77 @@ +using CMSMicroservice.Domain.Common; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Domain.Entities; + +/// +/// حرکات انبار (تاریخچه تغییرات موجودی) +/// +public class StockMovement : BaseAuditableEntity +{ + // ========== ارتباط با InventoryItem ========== + + /// + /// شناسه آیتم انبار + /// + public long InventoryItemId { get; set; } + + /// + /// آیتم انبار + /// + public InventoryItem InventoryItem { get; set; } = null!; + + // ========== نوع حرکت ========== + + /// + /// نوع حرکت انبار + /// + public StockMovementType MovementType { get; set; } + + // ========== مقادیر ========== + + /// + /// مقدار تغییر (مثبت برای ورود، منفی برای خروج) + /// + public int Quantity { get; set; } + + /// + /// موجودی قبل از این حرکت + /// + public int QuantityBefore { get; set; } + + /// + /// موجودی بعد از این حرکت + /// + public int QuantityAfter { get; set; } + + // ========== مراجع ========== + + /// + /// شناسه سفارش (برای فروش/برگشت در فروشگاه معمولی) + /// + public long? OrderId { get; set; } + + /// + /// شناسه سفارش تخفیفی (برای فروش/برگشت در فروشگاه تخفیفی) + /// + public long? DiscountOrderId { get; set; } + + /// + /// شماره مرجع (مثل شماره فاکتور ورود کالا) + /// + public string? ReferenceNumber { get; set; } + + // ========== توضیحات ========== + + /// + /// یادداشت و توضیحات اضافی + /// + public string? Note { get; set; } + + // ========== کاربر ========== + + /// + /// شناسه کاربری که این عملیات را انجام داده + /// + public long? PerformedByUserId { get; set; } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Domain/Entities/Warehouse.cs b/src/CMSMicroservice.Domain/Entities/Warehouse.cs new file mode 100644 index 0000000..5c92797 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Warehouse.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities; + +/// +/// انبار (برای آینده - چند انبار) +/// +public class Warehouse : BaseAuditableEntity +{ + // ========== اطلاعات اصلی ========== + + /// + /// نام انبار + /// + public string Name { get; set; } = null!; + + /// + /// کد انبار + /// + public string Code { get; set; } = null!; + + /// + /// آدرس انبار + /// + public string? Address { get; set; } + + // ========== تنظیمات ========== + + /// + /// انبار پیش‌فرض سیستم + /// + public bool IsDefault { get; set; } + + /// + /// وضعیت فعال/غیرفعال + /// + public bool IsActive { get; set; } = true; + + // ========== Navigation Properties ========== + + /// + /// آیتم‌های موجودی در این انبار + /// + public ICollection InventoryItems { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/CMSMicroservice.Domain/Enums/BlogPostStatus.cs b/src/CMSMicroservice.Domain/Enums/BlogPostStatus.cs new file mode 100644 index 0000000..01ad0a5 --- /dev/null +++ b/src/CMSMicroservice.Domain/Enums/BlogPostStatus.cs @@ -0,0 +1,9 @@ +namespace CMSMicroservice.Domain.Enums; + +public enum BlogPostStatus +{ + Draft = 0, + Published = 1, + Scheduled = 2, + Archived = 3 +} diff --git a/src/CMSMicroservice.Domain/Enums/ProductType.cs b/src/CMSMicroservice.Domain/Enums/ProductType.cs new file mode 100644 index 0000000..27be3d6 --- /dev/null +++ b/src/CMSMicroservice.Domain/Enums/ProductType.cs @@ -0,0 +1,17 @@ +namespace CMSMicroservice.Domain.Enums; + +/// +/// نوع محصول در سیستم +/// +public enum ProductType +{ + /// + /// محصول فروشگاه معمولی + /// + RegularProduct = 1, + + /// + /// محصول فروشگاه تخفیفی + /// + DiscountProduct = 2 +} \ No newline at end of file diff --git a/src/CMSMicroservice.Domain/Enums/StockMovementType.cs b/src/CMSMicroservice.Domain/Enums/StockMovementType.cs new file mode 100644 index 0000000..b2d050c --- /dev/null +++ b/src/CMSMicroservice.Domain/Enums/StockMovementType.cs @@ -0,0 +1,75 @@ +namespace CMSMicroservice.Domain.Enums; + +/// +/// نوع حرکت انبار +/// +public enum StockMovementType +{ + // ========== ورودی ========== + + /// + /// موجودی اولیه محصول + /// + InitialStock = 1, + + /// + /// ورود کالا به انبار (خرید از تامین‌کننده) + /// + Restock = 2, + + /// + /// برگشت کالا از مشتری + /// + Return = 3, + + /// + /// انتقال کالا از انبار دیگر + /// + TransferIn = 4, + + // ========== خروجی ========== + + /// + /// فروش کالا به مشتری + /// + Sale = 10, + + /// + /// ضایعات (کالای خراب شده) + /// + Damaged = 11, + + /// + /// مفقودی انبار + /// + Lost = 12, + + /// + /// انتقال کالا به انبار دیگر + /// + TransferOut = 13, + + // ========== تعدیل ========== + + /// + /// تعدیل افزایشی موجودی (انبارگردانی مثبت) + /// + AdjustmentPlus = 20, + + /// + /// تعدیل کاهشی موجودی (انبارگردانی منفی) + /// + AdjustmentMinus = 21, + + // ========== رزرو ========== + + /// + /// رزرو موجودی برای سفارش pending + /// + Reserved = 30, + + /// + /// آزادسازی رزرو (لغو سفارش) + /// + Released = 31 +} \ No newline at end of file diff --git a/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs b/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs index fe088b7..ca32771 100644 --- a/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs +++ b/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs @@ -1,10 +1,20 @@ namespace CMSMicroservice.Domain.Events; public class CreateNewOtpTokenEvent : BaseEvent { - public CreateNewOtpTokenEvent(OtpToken item) + public CreateNewOtpTokenEvent(OtpToken item, string plainCode, string? signGuid = null) { Item = item; + PlainCode = plainCode; + SignGuid = signGuid; } public OtpToken Item { get; } + /// + /// کد OTP به صورت plain text برای ارسال SMS + /// + public string PlainCode { get; } + /// + /// شناسه GUID قرارداد (فقط برای امضای قرارداد) + /// + public string? SignGuid { get; } } diff --git a/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs b/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs new file mode 100644 index 0000000..47bd396 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/BackgroundServices/ExpirePendingOrdersService.cs @@ -0,0 +1,117 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.BackgroundServices; + +/// +/// سرویس پس‌زمینه برای منقضی کردن سفارشات تخفیفی که بیش از ۳۰ دقیقه در وضعیت Pending مانده‌اند. +/// موجودی رزرو شده آزاد و وضعیت پرداخت به Reject تغییر می‌کند. +/// +public class ExpirePendingOrdersService : BackgroundService +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + + /// + /// مدت زمان انقضا (۳۰ دقیقه) + /// + private static readonly TimeSpan ExpirationTime = TimeSpan.FromMinutes(30); + + /// + /// هر ۵ دقیقه چک می‌شود + /// + private static readonly TimeSpan CheckInterval = TimeSpan.FromMinutes(5); + + public ExpirePendingOrdersService( + IServiceScopeFactory scopeFactory, + ILogger logger) + { + _scopeFactory = scopeFactory; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("ExpirePendingOrdersService started — checking every {Interval} min, expiring after {Expiry} min", + CheckInterval.TotalMinutes, ExpirationTime.TotalMinutes); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + await ExpireOldPendingOrders(stoppingToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ExpirePendingOrdersService"); + } + + await Task.Delay(CheckInterval, stoppingToken); + } + } + + private async Task ExpireOldPendingOrders(CancellationToken cancellationToken) + { + using var scope = _scopeFactory.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var inventoryService = scope.ServiceProvider.GetRequiredService(); + + var cutoff = DateTime.Now - ExpirationTime; + + // پیدا کردن سفارشات Pending قدیمی + var expiredOrders = await context.DiscountOrders + .Include(o => o.OrderDetails) + .Where(o => o.PaymentStatus == PaymentStatus.Pending && o.Created < cutoff) + .ToListAsync(cancellationToken); + + if (!expiredOrders.Any()) return; + + _logger.LogInformation("Found {Count} expired pending orders to clean up", expiredOrders.Count); + + foreach (var order in expiredOrders) + { + try + { + // آزادسازی رزرو موجودی + foreach (var detail in order.OrderDetails) + { + await inventoryService.ReleaseReservationAsync( + detail.ProductId, + ProductType.DiscountProduct, + detail.Count, + order.Id, + cancellationToken); + } + + // تغییر وضعیت به Reject + order.PaymentStatus = PaymentStatus.Reject; + order.DeliveryStatus = DeliveryStatus.Cancelled; + + // آپدیت تراکنش مربوطه + if (order.TransactionId.HasValue) + { + var transaction = await context.Transactions + .FirstOrDefaultAsync(t => t.Id == order.TransactionId.Value, cancellationToken); + if (transaction != null) + { + transaction.PaymentStatus = PaymentStatus.Reject; + } + } + + _logger.LogInformation( + "Expired order #{OrderId} (created {Created:u}) — inventory released, status set to Reject", + order.Id, order.Created); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to expire order #{OrderId}", order.Id); + } + } + + await context.SaveChangesAsync(cancellationToken); + } +} diff --git a/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj b/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj index 0a33b3b..6522bf9 100644 --- a/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj +++ b/src/CMSMicroservice.Infrastructure/CMSMicroservice.Infrastructure.csproj @@ -6,6 +6,7 @@ + @@ -18,10 +19,12 @@ + + diff --git a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs index 4a7ed35..0503b4c 100644 --- a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs +++ b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs @@ -1,9 +1,12 @@ using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Authorization; using CMSMicroservice.Application.DayaLoanCQ.Services; using CMSMicroservice.Infrastructure.Persistence; using CMSMicroservice.Infrastructure.Persistence.Interceptors; using CMSMicroservice.Infrastructure.BackgroundJobs; +using CMSMicroservice.Infrastructure.BackgroundServices; using CMSMicroservice.Infrastructure.Services.Monitoring; +using CMSMicroservice.Infrastructure.Services.Authorization; using CMSMicroservice.Infrastructure.Configuration; using CMSMicroservice.Infrastructure.Services.Payment; using CMSMicroservice.Infrastructure.Services.Commission; @@ -35,6 +38,9 @@ public static class ConfigureServices services.AddScoped(); services.AddScoped(); services.AddScoped(); + // Local file manager — files are saved to wwwroot/uploads/ on CMS disk + services.AddSingleton(); + services.AddScoped(); // Daya Loan API Service - قابل تغییر بین Mock و Real var useMockDayaApi = configuration.GetValue("DayaApi:UseMock", false); @@ -69,19 +75,26 @@ public static class ConfigureServices }); } - // Payment Gateway Service - فقط Daya (درگاه اینترنتی از Gateway میاد نه CMS) - var useRealPaymentGateway = configuration.GetValue("UseRealPaymentGateway", false); + // Payment Gateway Service - Multi-Provider Architecture + // پشتیبانی از درگاه‌های مختلف: ZarinPal, Daya, Mock + var paymentProvider = configuration.GetValue("PaymentProvider", "Mock")?.ToLowerInvariant(); - if (useRealPaymentGateway) + switch (paymentProvider) { - // فقط Daya برای پرداخت به کاربران (Payout) - services.AddHttpClient() - .SetHandlerLifetime(TimeSpan.FromMinutes(5)); - } - else - { - // Mock برای Development و Testing - services.AddScoped(); + case "zarinpal": + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + break; + + case "daya": + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + break; + + case "mock": + default: + services.AddScoped(); + break; } services.AddScoped(p => p.GetRequiredService()); @@ -107,6 +120,9 @@ public static class ConfigureServices services.AddScoped(); // Hangfire Job (Scoped for DI) services.AddScoped(); // Hangfire Job for Chatika activation + // Expire pending discount orders after 30 minutes + services.AddHostedService(); + if (configuration.GetValue("UseInMemoryDatabase")) { services.AddDbContext(options => @@ -118,6 +134,10 @@ public static class ConfigureServices options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"), builder => builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); } + + // Inventory Business Service + services.AddScoped(); + #region AddAuthentication var message = ""; diff --git a/src/CMSMicroservice.Infrastructure/DependencyInjection.cs b/src/CMSMicroservice.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..705746f --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/DependencyInjection.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Infrastructure.Persistence; +using CMSMicroservice.Infrastructure.Services; + +namespace CMSMicroservice.Infrastructure; + +/// +/// کلاس تزریق وابستگی برای لایه Infrastructure +/// +public static class DependencyInjection +{ + /// + /// افزودن سرویس های Infrastructure به DI Container + /// + /// IServiceCollection + /// IConfiguration + /// IServiceCollection + public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) + { + // Database Configuration + services.AddDbContext(options => + options.UseSqlServer( + configuration.GetConnectionString("DefaultConnection"), + b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); + + // Application Context Interface + services.AddScoped(provider => provider.GetRequiredService()); + + // Business Services + services.AddScoped(); + + return services; + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs index e44cc12..ec6cb1a 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs @@ -2,6 +2,8 @@ using System.Reflection; using CMSMicroservice.Application.Common.Interfaces; using Microsoft.EntityFrameworkCore.Diagnostics; using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Entities.Blog; +using CMSMicroservice.Domain.Entities.Content; using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Entities.Geography; using CMSMicroservice.Domain.Entities.Order; @@ -14,9 +16,20 @@ namespace CMSMicroservice.Infrastructure.Persistence; public class ApplicationDbContext : DbContext, IApplicationDbContext { - private readonly IMediator _mediator; - private readonly AuditableEntitySaveChangesInterceptor _auditableEntitySaveChangesInterceptor; + private readonly IMediator? _mediator; + private readonly AuditableEntitySaveChangesInterceptor? _auditableEntitySaveChangesInterceptor; + /// + /// Constructor برای design-time (migrations) + /// + public ApplicationDbContext(DbContextOptions options) + : base(options) + { + } + + /// + /// Constructor اصلی برای runtime + /// public ApplicationDbContext( DbContextOptions options, IMediator mediator, @@ -39,7 +52,10 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.AddInterceptors(_auditableEntitySaveChangesInterceptor); + if (_auditableEntitySaveChangesInterceptor != null) + { + optionsBuilder.AddInterceptors(_auditableEntitySaveChangesInterceptor); + } // Suppress PendingModelChangesWarning in EF Core 9 optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); @@ -78,6 +94,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext // Payment public DbSet ManualPayments => Set(); + public DbSet PaymentTransactions => Set(); // Message public DbSet PublicMessages => Set(); @@ -110,6 +127,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public DbSet DiscountProducts => Set(); public DbSet DiscountCategories => Set(); public DbSet DiscountProductCategories => Set(); + public DbSet DiscountProductImages => Set(); public DbSet DiscountShoppingCarts => Set(); public DbSet DiscountOrders => Set(); public DbSet DiscountOrderDetails => Set(); @@ -118,4 +136,20 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public DbSet Countries => Set(); public DbSet States => Set(); public DbSet Cities => Set(); + + // ============= Inventory Management DbSets ============= + public DbSet Warehouses => Set(); + public DbSet InventoryItems => Set(); + public DbSet StockMovements => Set(); + + // ============= Blog DbSets ============= + public DbSet BlogPosts => Set(); + public DbSet BlogCategories => Set(); + public DbSet BlogPostCategories => Set(); + public DbSet BlogPostTags => Set(); + public DbSet BlogPostImages => Set(); + + // ============= Content Management DbSets ============= + public DbSet SitePages => Set(); + public DbSet SitePageSections => Set(); } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContextFactory.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContextFactory.cs new file mode 100644 index 0000000..7f5782d --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContextFactory.cs @@ -0,0 +1,46 @@ +using System.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; +using CMSMicroservice.Infrastructure.Persistence.Interceptors; + +namespace CMSMicroservice.Infrastructure.Persistence; + +/// +/// Factory برای ایجاد DbContext در زمان طراحی (migrations, scaffolding) +/// +public class ApplicationDbContextFactory : IDesignTimeDbContextFactory +{ + public ApplicationDbContext CreateDbContext(string[] args) + { + // سعی در خواندن connection string از appsettings.json + var basePath = Directory.GetCurrentDirectory(); + var webApiPath = Path.Combine(basePath, "../CMSMicroservice.WebApi"); + + if (Directory.Exists(webApiPath)) + { + basePath = webApiPath; + } + + var configuration = new ConfigurationBuilder() + .SetBasePath(basePath) + .AddJsonFile("appsettings.json", optional: true) + .AddJsonFile("appsettings.Development.json", optional: true) + .AddEnvironmentVariables() + .Build(); + + var connectionString = configuration.GetConnectionString("DefaultConnection"); + + // اگر connection string پیدا نشد، از یک مقدار پیش‌فرض استفاده کن + if (string.IsNullOrEmpty(connectionString)) + { + connectionString = "Server=localhost;Database=CMS;Trusted_Connection=True;TrustServerCertificate=True;"; + } + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlServer(connectionString, + b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)); + + return new ApplicationDbContext(optionsBuilder.Options); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogCategoryConfiguration.cs new file mode 100644 index 0000000..f5d9aa8 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogCategoryConfiguration.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogCategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogCategories"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.Title).IsRequired().HasMaxLength(100); + builder.Property(x => x.Slug).IsRequired().HasMaxLength(100); + builder.Property(x => x.Description).HasMaxLength(500); + builder.Property(x => x.IconName).HasMaxLength(100); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + builder.Property(x => x.IsActive).IsRequired().HasDefaultValue(true); + + // Indexes + builder.HasIndex(x => x.Slug).IsUnique().HasDatabaseName("IX_BlogCategories_Slug"); + builder.HasIndex(x => x.IsActive).HasDatabaseName("IX_BlogCategories_IsActive"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostCategoryConfiguration.cs new file mode 100644 index 0000000..d701021 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostCategoryConfiguration.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostCategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPostCategories"); + builder.HasKey(e => e.Id); + + builder.HasOne(d => d.BlogPost) + .WithMany(p => p.BlogPostCategories) + .HasForeignKey(d => d.BlogPostId); + + builder.HasOne(d => d.BlogCategory) + .WithMany(p => p.BlogPostCategories) + .HasForeignKey(d => d.BlogCategoryId); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostConfiguration.cs new file mode 100644 index 0000000..d13e6a9 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostConfiguration.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPosts"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.Title).IsRequired().HasMaxLength(200); + builder.Property(x => x.Slug).IsRequired().HasMaxLength(200); + builder.Property(x => x.Summary).HasMaxLength(500); + builder.Property(x => x.HtmlContent).IsRequired(); + builder.Property(x => x.FeaturedImagePath); + builder.Property(x => x.FeaturedImageThumbnailPath); + builder.Property(x => x.Status).IsRequired(); + builder.Property(x => x.ViewCount).IsRequired().HasDefaultValue(0); + builder.Property(x => x.AuthorUserId).IsRequired(); + builder.Property(x => x.IsFeatured).IsRequired().HasDefaultValue(false); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + + // Indexes + builder.HasIndex(x => x.Slug).IsUnique().HasDatabaseName("IX_BlogPosts_Slug"); + builder.HasIndex(x => x.Status).HasDatabaseName("IX_BlogPosts_Status"); + builder.HasIndex(x => x.PublishedAt).HasDatabaseName("IX_BlogPosts_PublishedAt"); + builder.HasIndex(x => x.IsFeatured).HasDatabaseName("IX_BlogPosts_IsFeatured"); + builder.HasIndex(x => x.AuthorUserId).HasDatabaseName("IX_BlogPosts_AuthorUserId"); + builder.HasIndex(x => new { x.Status, x.PublishedAt }) + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostImageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostImageConfiguration.cs new file mode 100644 index 0000000..0f33690 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostImageConfiguration.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostImageConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPostImages"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.BlogPostId).IsRequired(); + builder.Property(x => x.ImagePath).IsRequired(); + builder.Property(x => x.ThumbnailPath).IsRequired(); + builder.Property(x => x.AltText).HasMaxLength(200); + builder.Property(x => x.Caption).HasMaxLength(300); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + + builder.HasOne(d => d.BlogPost) + .WithMany(p => p.BlogPostImages) + .HasForeignKey(d => d.BlogPostId); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostTagConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostTagConfiguration.cs new file mode 100644 index 0000000..c369842 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostTagConfiguration.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostTagConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPostTags"); + builder.HasKey(e => e.Id); + + builder.HasOne(d => d.BlogPost) + .WithMany(p => p.BlogPostTags) + .HasForeignKey(d => d.BlogPostId); + + builder.HasOne(d => d.Tag) + .WithMany() + .HasForeignKey(d => d.TagId); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageConfiguration.cs new file mode 100644 index 0000000..38e012e --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageConfiguration.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Domain.Entities.Content; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content; + +public class SitePageConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("SitePages"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.PageKey).IsRequired().HasMaxLength(50); + builder.Property(x => x.Title).IsRequired().HasMaxLength(200); + builder.Property(x => x.MetaDescription).HasMaxLength(300); + builder.Property(x => x.HeroTitle).HasMaxLength(200); + builder.Property(x => x.HeroSubtitle).HasMaxLength(500); + builder.Property(x => x.HeroImagePath); + builder.Property(x => x.IsActive).IsRequired().HasDefaultValue(true); + + // Indexes + builder.HasIndex(x => x.PageKey).IsUnique().HasDatabaseName("IX_SitePages_PageKey"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageSectionConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageSectionConfiguration.cs new file mode 100644 index 0000000..87924cb --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageSectionConfiguration.cs @@ -0,0 +1,32 @@ +using CMSMicroservice.Domain.Entities.Content; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content; + +public class SitePageSectionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("SitePageSections"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.SitePageId).IsRequired(); + builder.Property(x => x.SectionKey).IsRequired().HasMaxLength(100); + builder.Property(x => x.Title).IsRequired().HasMaxLength(200); + builder.Property(x => x.Subtitle).HasMaxLength(300); + builder.Property(x => x.IconName).HasMaxLength(100); + builder.Property(x => x.ImagePath); + builder.Property(x => x.ImageThumbnailPath); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + builder.Property(x => x.IsActive).IsRequired().HasDefaultValue(true); + + builder.HasOne(d => d.SitePage) + .WithMany(p => p.Sections) + .HasForeignKey(d => d.SitePageId); + + // Indexes + builder.HasIndex(x => new { x.SitePageId, x.SectionKey }) + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ContractConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ContractConfiguration.cs index c310892..00efd6b 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ContractConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ContractConfiguration.cs @@ -15,6 +15,5 @@ public class ContractConfiguration : IEntityTypeConfiguration builder.Property(entity => entity.Description).IsRequired(true); builder.Property(entity => entity.HtmlContent).IsRequired(true); builder.Property(entity => entity.Type).IsRequired(true); - } } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs index d8c919e..4cef90c 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs @@ -28,8 +28,7 @@ public class DiscountCategoryConfiguration : IEntityTypeConfiguration entity.Description) .HasMaxLength(1000); - builder.Property(entity => entity.ImagePath) - .HasMaxLength(500); + builder.Property(entity => entity.ImagePath); builder.Property(entity => entity.IsActive) .IsRequired() diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs index dae4d65..1d6b473 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs @@ -36,12 +36,10 @@ public class DiscountProductConfiguration : IEntityTypeConfiguration entity.ImagePath) - .IsRequired() - .HasMaxLength(500); + .IsRequired(); builder.Property(entity => entity.ThumbnailPath) - .IsRequired() - .HasMaxLength(500); + .IsRequired(); builder.Property(entity => entity.IsActive) .IsRequired() diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductImageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductImageConfiguration.cs new file mode 100644 index 0000000..1219b7b --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductImageConfiguration.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Domain.Entities.DiscountShop; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.DiscountShop; + +public class DiscountProductImageConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("DiscountProductImages"); + + builder.HasKey(x => x.Id); + + builder.Property(x => x.Title) + .HasMaxLength(200); + + builder.Property(x => x.AltText) + .HasMaxLength(500); + + builder.Property(x => x.ImagePath) + .IsRequired(); + + builder.Property(x => x.ThumbnailPath); + + builder.HasOne(x => x.DiscountProduct) + .WithMany(p => p.Images) + .HasForeignKey(x => x.DiscountProductId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasIndex(x => x.DiscountProductId); + builder.HasIndex(x => new { x.DiscountProductId, x.SortOrder }); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/InventoryItemConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/InventoryItemConfiguration.cs new file mode 100644 index 0000000..3588e26 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/InventoryItemConfiguration.cs @@ -0,0 +1,122 @@ +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Entities.DiscountShop; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +/// +/// تنظیمات Entity Framework برای موجودی کالا +/// +public class InventoryItemConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // ========== تنظیمات پایه ========== + builder.HasQueryFilter(i => !i.IsDeleted); + builder.Ignore(entity => entity.DomainEvents); + builder.HasKey(entity => entity.Id); + builder.Property(entity => entity.Id).UseIdentityColumn(); + + // ========== فیلدهای اصلی ========== + builder.Property(e => e.ProductType) + .IsRequired() + .HasConversion(); + + builder.Property(e => e.Quantity) + .IsRequired() + .HasDefaultValue(0); + + builder.Property(e => e.ReservedQuantity) + .IsRequired() + .HasDefaultValue(0); + + // ========== تنظیمات انبار ========== + builder.Property(e => e.LowStockThreshold) + .IsRequired() + .HasDefaultValue(10); + + builder.Property(e => e.ReorderPoint) + .IsRequired() + .HasDefaultValue(5); + + builder.Property(e => e.MaxStockLevel) + .IsRequired() + .HasDefaultValue(1000); + + // ========== تاریخ‌ها ========== + builder.Property(e => e.LastRestockedAt) + .IsRequired(false); + + builder.Property(e => e.LastSoldAt) + .IsRequired(false); + + // ========== انبار ========== + builder.Property(e => e.WarehouseId) + .IsRequired() + .HasDefaultValue(1); + + // ========== روابط ========== + + // رابطه با Product (اختیاری) + builder.HasOne(e => e.Product) + .WithMany() + .HasForeignKey(e => e.ProductId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Cascade); + + // رابطه با DiscountProduct (اختیاری) + builder.HasOne(e => e.DiscountProduct) + .WithMany() + .HasForeignKey(e => e.DiscountProductId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Cascade); + + // رابطه با Warehouse + builder.HasOne(e => e.Warehouse) + .WithMany(w => w.InventoryItems) + .HasForeignKey(e => e.WarehouseId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + + // ========== Index ها ========== + + // Index روی ProductId برای جستجوی سریع محصولات معمولی + builder.HasIndex(e => e.ProductId) + .HasDatabaseName("IX_InventoryItems_ProductId"); + + // Index روی DiscountProductId برای جستجوی سریع محصولات تخفیفی + builder.HasIndex(e => e.DiscountProductId) + .HasDatabaseName("IX_InventoryItems_DiscountProductId"); + + // Index ترکیبی روی ProductType و Quantity برای گزارش‌گیری + builder.HasIndex(e => new { e.ProductType, e.Quantity }) + .HasDatabaseName("IX_InventoryItems_ProductType_Quantity"); + + // Index ساده روی WarehouseId برای انبار + builder.HasIndex(e => e.WarehouseId) + .HasDatabaseName("IX_InventoryItems_WarehouseId"); + + // ========== محدودیت‌ها ========== + + // محدودیت: یا ProductId یا DiscountProductId باید پر باشد (نه هر دو، نه هیچ‌کدام) + builder.HasCheckConstraint("CK_InventoryItem_ProductReference", + "(ProductId IS NOT NULL AND DiscountProductId IS NULL) OR (ProductId IS NULL AND DiscountProductId IS NOT NULL)"); + + // محدودیت: ProductType باید با نوع محصول مطابقت داشته باشد + builder.HasCheckConstraint("CK_InventoryItem_ProductType_Match", + "(ProductType = 1 AND ProductId IS NOT NULL) OR (ProductType = 2 AND DiscountProductId IS NOT NULL)"); + + // محدودیت: موجودی نمی‌تواند منفی باشد + builder.HasCheckConstraint("CK_InventoryItem_Quantity_NonNegative", + "Quantity >= 0"); + + // محدودیت: موجودی رزرو شده نمی‌تواند منفی باشد + builder.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_NonNegative", + "ReservedQuantity >= 0"); + + // محدودیت: موجودی رزرو شده نمی‌تواند بیشتر از موجودی کل باشد + builder.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_LessOrEqualQuantity", + "ReservedQuantity <= Quantity"); + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs index b93e304..ddc5b58 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs @@ -8,6 +8,7 @@ public class ManualPaymentConfiguration : IEntityTypeConfiguration builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("ManualPayments"); builder.HasKey(x => x.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs index 5d98f86..b7e76ee 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs @@ -8,6 +8,7 @@ public class OrderVATConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("OrderVATs"); builder.HasKey(x => x.Id); @@ -37,7 +38,7 @@ public class OrderVATConfiguration : IEntityTypeConfiguration // Foreign Key builder.HasOne(x => x.Order) - .WithOne() + .WithOne(x => x.OrderVAT) .HasForeignKey(x => x.OrderId) .OnDelete(DeleteBehavior.Restrict); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OtpTokenConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OtpTokenConfiguration.cs index e80d5ae..9bfb65c 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OtpTokenConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OtpTokenConfiguration.cs @@ -13,6 +13,10 @@ public class OtpTokenConfiguration : IEntityTypeConfiguration builder.Property(entity => entity.Id).UseIdentityColumn(); builder.Property(entity => entity.Mobile).IsRequired(true); builder.Property(entity => entity.Purpose).IsRequired(true); + + // Code is not persisted to database, only used in-memory before hashing + builder.Ignore(entity => entity.Code); + builder.Property(entity => entity.CodeHash).IsRequired(true); builder.Property(entity => entity.ExpiresAt).IsRequired(true); builder.Property(entity => entity.Attempts).IsRequired(true); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cs new file mode 100644 index 0000000..967e3e1 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Domain.Entities.Payment; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +public class PaymentTransactionConfiguration : 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(e => e.GatewayProvider).IsRequired().HasMaxLength(50); + builder.Property(e => e.MerchantId).IsRequired().HasMaxLength(200); + builder.Property(e => e.Amount).IsRequired(); + builder.Property(e => e.CallbackUrl).IsRequired().HasMaxLength(500); + builder.Property(e => e.Description).IsRequired().HasMaxLength(500); + builder.Property(e => e.Mobile).HasMaxLength(20); + builder.Property(e => e.UserId); + + // پاسخ درخواست + builder.Property(e => e.RequestStatusCode); + builder.Property(e => e.RequestStatusMessage).HasMaxLength(500); + builder.Property(e => e.Authority).HasMaxLength(200); + + // وضعیت پرداخت + builder.Property(e => e.PaymentStatus).IsRequired(); + + // نتیجه verify + builder.Property(e => e.VerificationStatusCode); + builder.Property(e => e.VerificationStatusMessage).HasMaxLength(500); + builder.Property(e => e.CardHash).HasMaxLength(200); + builder.Property(e => e.CardPan).HasMaxLength(30); + builder.Property(e => e.RefId).HasMaxLength(200); + + // ارتباط داخلی + builder.Property(e => e.TransactionId); + builder.Property(e => e.OrderId).HasMaxLength(100); + + // ایندکس‌ها + builder.HasIndex(e => e.Authority); + builder.HasIndex(e => e.GatewayProvider); + builder.HasIndex(e => e.UserId); + builder.HasIndex(e => e.TransactionId); + builder.HasIndex(e => e.RefId); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs index ff87209..6906551 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs @@ -7,6 +7,7 @@ public class ProductCategoryConfiguration : IEntityTypeConfiguration builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("ProductCategories", "CMS"); builder.HasKey(e => e.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs index 4c82748..93fa38e 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs @@ -7,6 +7,7 @@ public class ProductTagConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("ProductTags", "CMS"); builder.HasKey(e => e.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs index c4906ce..1513bc9 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs @@ -8,6 +8,7 @@ public class PublicMessageConfiguration : IEntityTypeConfiguration builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("PublicMessages"); builder.HasKey(x => x.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/StockMovementConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/StockMovementConfiguration.cs new file mode 100644 index 0000000..e4596f1 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/StockMovementConfiguration.cs @@ -0,0 +1,117 @@ +using CMSMicroservice.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +/// +/// تنظیمات Entity Framework برای حرکات انبار +/// +public class StockMovementConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // ========== تنظیمات پایه ========== + builder.HasQueryFilter(s => !s.IsDeleted); + builder.Ignore(entity => entity.DomainEvents); + builder.HasKey(entity => entity.Id); + builder.Property(entity => entity.Id).UseIdentityColumn(); + + // ========== فیلدهای اصلی ========== + + // نوع حرکت (enum به int) + builder.Property(e => e.MovementType) + .IsRequired() + .HasConversion(); + + // مقدار تغییر (می‌تواند منفی باشد) + builder.Property(e => e.Quantity) + .IsRequired(); + + // موجودی قبل و بعد + builder.Property(e => e.QuantityBefore) + .IsRequired(); + + builder.Property(e => e.QuantityAfter) + .IsRequired(); + + // ========== فیلدهای اختیاری ========== + + // شناسه سفارش معمولی + builder.Property(e => e.OrderId) + .IsRequired(false); + + // شناسه سفارش تخفیفی + builder.Property(e => e.DiscountOrderId) + .IsRequired(false); + + // شماره مرجع + builder.Property(e => e.ReferenceNumber) + .IsRequired(false) + .HasMaxLength(100); + + // یادداشت + builder.Property(e => e.Note) + .IsRequired(false) + .HasMaxLength(500); + + // کاربر انجام‌دهنده + builder.Property(e => e.PerformedByUserId) + .IsRequired(false); + + // ========== روابط ========== + + // رابطه با InventoryItem (اجباری) + builder.HasOne(e => e.InventoryItem) + .WithMany(i => i.StockMovements) + .HasForeignKey(e => e.InventoryItemId) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); + + // ========== Index ها ========== + + // Index روی InventoryItemId برای جستجوی حرکات یک آیتم + builder.HasIndex(e => e.InventoryItemId) + .HasDatabaseName("IX_StockMovements_InventoryItemId"); + + // Index روی MovementType برای فیلتر بر اساس نوع حرکت + builder.HasIndex(e => e.MovementType) + .HasDatabaseName("IX_StockMovements_MovementType"); + + // Index روی Created برای مرتب‌سازی زمانی + builder.HasIndex(e => e.Created) + .HasDatabaseName("IX_StockMovements_Created"); + + // Index ترکیبی برای گزارش‌گیری + builder.HasIndex(e => new { e.InventoryItemId, e.MovementType, e.Created }) + .HasDatabaseName("IX_StockMovements_Item_Type_Date"); + + // Index روی OrderId برای ردیابی حرکات مرتبط با سفارش + builder.HasIndex(e => e.OrderId) + .HasDatabaseName("IX_StockMovements_OrderId") + .HasFilter("[OrderId] IS NOT NULL"); + + // Index روی DiscountOrderId برای ردیابی حرکات مرتبط با سفارش تخفیفی + builder.HasIndex(e => e.DiscountOrderId) + .HasDatabaseName("IX_StockMovements_DiscountOrderId") + .HasFilter("[DiscountOrderId] IS NOT NULL"); + + // Index روی ReferenceNumber برای جستجوی سریع با شماره مرجع + builder.HasIndex(e => e.ReferenceNumber) + .HasDatabaseName("IX_StockMovements_ReferenceNumber") + .HasFilter("[ReferenceNumber] IS NOT NULL"); + + // ========== محدودیت‌ها ========== + + // محدودیت: QuantityAfter باید برابر QuantityBefore + Quantity باشد + builder.HasCheckConstraint("CK_StockMovement_QuantityAfter_Calculation", + "QuantityAfter = QuantityBefore + Quantity"); + + // محدودیت: QuantityBefore و QuantityAfter نمی‌توانند منفی باشند + builder.HasCheckConstraint("CK_StockMovement_QuantityBefore_NonNegative", + "QuantityBefore >= 0"); + + builder.HasCheckConstraint("CK_StockMovement_QuantityAfter_NonNegative", + "QuantityAfter >= 0"); + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/WarehouseConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/WarehouseConfiguration.cs new file mode 100644 index 0000000..f700e3e --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/WarehouseConfiguration.cs @@ -0,0 +1,89 @@ +using CMSMicroservice.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +/// +/// تنظیمات Entity Framework برای انبار +/// +public class WarehouseConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // ========== تنظیمات پایه ========== + builder.HasQueryFilter(w => !w.IsDeleted); + builder.Ignore(entity => entity.DomainEvents); + builder.HasKey(entity => entity.Id); + builder.Property(entity => entity.Id).UseIdentityColumn(); + + // ========== فیلدهای اصلی ========== + + // نام انبار (اجباری) + builder.Property(e => e.Name) + .IsRequired() + .HasMaxLength(200); + + // کد انبار (اجباری و یکتا) + builder.Property(e => e.Code) + .IsRequired() + .HasMaxLength(50); + + // آدرس انبار (اختیاری) + builder.Property(e => e.Address) + .IsRequired(false) + .HasMaxLength(1000); + + // ========== تنظیمات ========== + + // انبار پیش‌فرض + builder.Property(e => e.IsDefault) + .IsRequired() + .HasDefaultValue(false); + + // وضعیت فعال/غیرفعال + builder.Property(e => e.IsActive) + .IsRequired() + .HasDefaultValue(true); + + // ========== Index ها ========== + + // Index یکتا روی کد انبار + builder.HasIndex(e => e.Code) + .IsUnique() + .HasDatabaseName("IX_Warehouses_Code_Unique"); + + // Index روی IsDefault برای پیدا کردن سریع انبار پیش‌فرض + builder.HasIndex(e => e.IsDefault) + .HasDatabaseName("IX_Warehouses_IsDefault") + .HasFilter("[IsDefault] = 1"); + + // Index روی IsActive برای فیلتر انبارهای فعال + builder.HasIndex(e => e.IsActive) + .HasDatabaseName("IX_Warehouses_IsActive"); + + // ========== روابط ========== + + // رابطه یک-به-چند با InventoryItems + builder.HasMany(w => w.InventoryItems) + .WithOne(i => i.Warehouse) + .HasForeignKey(i => i.WarehouseId) + .OnDelete(DeleteBehavior.Restrict); // جلوگیری از حذف انبار در صورت وجود موجودی + + // ========== داده‌های اولیه ========== + + // انبار پیش‌فرض + builder.HasData(new Warehouse + { + Id = 1, + Name = "انبار اصلی", + Code = "WH-001", + Address = "تهران - انبار مرکزی فروشگاه", + IsDefault = true, + IsActive = true, + Created = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc), + CreatedBy = "System", + IsDeleted = false + }); + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231190505_AddDiscountProductImages.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231190505_AddDiscountProductImages.Designer.cs new file mode 100644 index 0000000..7804568 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231190505_AddDiscountProductImages.Designer.cs @@ -0,0 +1,3632 @@ +// +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("20251231190505_AddDiscountProductImages")] + partial class AddDiscountProductImages + { + /// + 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.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("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "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("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("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + 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("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("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + 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.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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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.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("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("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + 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("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .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("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "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("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.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.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("OrderVATId") + .HasColumnType("bigint"); + + 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("OrderVATId"); + + 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("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + 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("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("WalletId"); + + b.ToTable("UserWalletChangeLogs", "CMS"); + }); + + 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.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.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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.Network.NetworkWeeklyBalance", b => + { + 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("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne() + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + 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.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.Order.OrderVAT", "OrderVAT") + .WithMany() + .HasForeignKey("OrderVATId"); + + 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("OrderVAT"); + + 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() + .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.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Wallet"); + }); + + 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("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.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.Package", b => + { + 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"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Navigation("UserWalletChangeLogs"); + }); + + 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/20251231190505_AddDiscountProductImages.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231190505_AddDiscountProductImages.cs new file mode 100644 index 0000000..7029023 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231190505_AddDiscountProductImages.cs @@ -0,0 +1,67 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddDiscountProductImages : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DiscountProductImages", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + DiscountProductId = table.Column(type: "bigint", nullable: false), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + AltText = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + ImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + ThumbnailPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + SortOrder = table.Column(type: "int", nullable: false), + IsActive = table.Column(type: "bit", nullable: false), + 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_DiscountProductImages", x => x.Id); + table.ForeignKey( + name: "FK_DiscountProductImages_DiscountProducts_DiscountProductId", + column: x => x.DiscountProductId, + principalSchema: "CMS", + principalTable: "DiscountProducts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DiscountProductImages_DiscountProductId", + schema: "CMS", + table: "DiscountProductImages", + column: "DiscountProductId"); + + migrationBuilder.CreateIndex( + name: "IX_DiscountProductImages_DiscountProductId_SortOrder", + schema: "CMS", + table: "DiscountProductImages", + columns: new[] { "DiscountProductId", "SortOrder" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DiscountProductImages", + schema: "CMS"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231234634_AddInventorySystem.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231234634_AddInventorySystem.Designer.cs new file mode 100644 index 0000000..6f5604a --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231234634_AddInventorySystem.Designer.cs @@ -0,0 +1,3942 @@ +// +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("20251231234634_AddInventorySystem")] + partial class AddInventorySystem + { + /// + 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.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("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "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("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("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + 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("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("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + 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.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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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("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("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + 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("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .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("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "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("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.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("OrderVATId") + .HasColumnType("bigint"); + + 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("OrderVATId"); + + 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("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + 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("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + 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.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.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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.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("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne() + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + 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.Order.OrderVAT", "OrderVAT") + .WithMany() + .HasForeignKey("OrderVATId"); + + 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("OrderVAT"); + + 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() + .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.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Wallet"); + }); + + 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("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.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("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"); + }); + + 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/20251231234634_AddInventorySystem.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231234634_AddInventorySystem.cs new file mode 100644 index 0000000..d2b5020 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20251231234634_AddInventorySystem.cs @@ -0,0 +1,242 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddInventorySystem : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Warehouses", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Code = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Address = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + IsDefault = table.Column(type: "bit", nullable: false, defaultValue: false), + IsActive = 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_Warehouses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "InventoryItems", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ProductId = table.Column(type: "bigint", nullable: true), + DiscountProductId = table.Column(type: "bigint", nullable: true), + ProductType = table.Column(type: "int", nullable: false), + Quantity = table.Column(type: "int", nullable: false, defaultValue: 0), + ReservedQuantity = table.Column(type: "int", nullable: false, defaultValue: 0), + LowStockThreshold = table.Column(type: "int", nullable: false, defaultValue: 10), + ReorderPoint = table.Column(type: "int", nullable: false, defaultValue: 5), + MaxStockLevel = table.Column(type: "int", nullable: false, defaultValue: 1000), + LastRestockedAt = table.Column(type: "datetime2", nullable: true), + LastSoldAt = table.Column(type: "datetime2", nullable: true), + WarehouseId = table.Column(type: "bigint", nullable: false, defaultValue: 1L), + 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_InventoryItems", x => x.Id); + table.CheckConstraint("CK_InventoryItem_ProductReference", "(ProductId IS NOT NULL AND DiscountProductId IS NULL) OR (ProductId IS NULL AND DiscountProductId IS NOT NULL)"); + table.CheckConstraint("CK_InventoryItem_ProductType_Match", "(ProductType = 1 AND ProductId IS NOT NULL) OR (ProductType = 2 AND DiscountProductId IS NOT NULL)"); + table.CheckConstraint("CK_InventoryItem_Quantity_NonNegative", "Quantity >= 0"); + table.CheckConstraint("CK_InventoryItem_ReservedQuantity_LessOrEqualQuantity", "ReservedQuantity <= Quantity"); + table.CheckConstraint("CK_InventoryItem_ReservedQuantity_NonNegative", "ReservedQuantity >= 0"); + table.ForeignKey( + name: "FK_InventoryItems_DiscountProducts_DiscountProductId", + column: x => x.DiscountProductId, + principalSchema: "CMS", + principalTable: "DiscountProducts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_InventoryItems_Products_ProductId", + column: x => x.ProductId, + principalSchema: "CMS", + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_InventoryItems_Warehouses_WarehouseId", + column: x => x.WarehouseId, + principalSchema: "CMS", + principalTable: "Warehouses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "StockMovements", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + InventoryItemId = table.Column(type: "bigint", nullable: false), + MovementType = table.Column(type: "int", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + QuantityBefore = table.Column(type: "int", nullable: false), + QuantityAfter = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "bigint", nullable: true), + DiscountOrderId = table.Column(type: "bigint", nullable: true), + ReferenceNumber = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Note = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + PerformedByUserId = table.Column(type: "bigint", 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_StockMovements", x => x.Id); + table.CheckConstraint("CK_StockMovement_QuantityAfter_Calculation", "QuantityAfter = QuantityBefore + Quantity"); + table.CheckConstraint("CK_StockMovement_QuantityAfter_NonNegative", "QuantityAfter >= 0"); + table.CheckConstraint("CK_StockMovement_QuantityBefore_NonNegative", "QuantityBefore >= 0"); + table.ForeignKey( + name: "FK_StockMovements_InventoryItems_InventoryItemId", + column: x => x.InventoryItemId, + principalSchema: "CMS", + principalTable: "InventoryItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + schema: "CMS", + table: "Warehouses", + columns: new[] { "Id", "Address", "Code", "Created", "CreatedBy", "IsActive", "IsDefault", "IsDeleted", "LastModified", "LastModifiedBy", "Name" }, + values: new object[] { 1L, "تهران - انبار مرکزی فروشگاه", "WH-001", new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), "System", true, true, false, null, null, "انبار اصلی" }); + + migrationBuilder.CreateIndex( + name: "IX_InventoryItems_DiscountProductId", + schema: "CMS", + table: "InventoryItems", + column: "DiscountProductId"); + + migrationBuilder.CreateIndex( + name: "IX_InventoryItems_ProductId", + schema: "CMS", + table: "InventoryItems", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_InventoryItems_ProductType_Quantity", + schema: "CMS", + table: "InventoryItems", + columns: new[] { "ProductType", "Quantity" }); + + migrationBuilder.CreateIndex( + name: "IX_InventoryItems_WarehouseId", + schema: "CMS", + table: "InventoryItems", + column: "WarehouseId"); + + migrationBuilder.CreateIndex( + name: "IX_StockMovements_Created", + schema: "CMS", + table: "StockMovements", + column: "Created"); + + migrationBuilder.CreateIndex( + name: "IX_StockMovements_DiscountOrderId", + schema: "CMS", + table: "StockMovements", + column: "DiscountOrderId", + filter: "[DiscountOrderId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_StockMovements_InventoryItemId", + schema: "CMS", + table: "StockMovements", + column: "InventoryItemId"); + + migrationBuilder.CreateIndex( + name: "IX_StockMovements_Item_Type_Date", + schema: "CMS", + table: "StockMovements", + columns: new[] { "InventoryItemId", "MovementType", "Created" }); + + migrationBuilder.CreateIndex( + name: "IX_StockMovements_MovementType", + schema: "CMS", + table: "StockMovements", + column: "MovementType"); + + migrationBuilder.CreateIndex( + name: "IX_StockMovements_OrderId", + schema: "CMS", + table: "StockMovements", + column: "OrderId", + filter: "[OrderId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_StockMovements_ReferenceNumber", + schema: "CMS", + table: "StockMovements", + column: "ReferenceNumber", + filter: "[ReferenceNumber] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Warehouses_Code_Unique", + schema: "CMS", + table: "Warehouses", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Warehouses_IsActive", + schema: "CMS", + table: "Warehouses", + column: "IsActive"); + + migrationBuilder.CreateIndex( + name: "IX_Warehouses_IsDefault", + schema: "CMS", + table: "Warehouses", + column: "IsDefault", + filter: "[IsDefault] = 1"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StockMovements", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "InventoryItems", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "Warehouses", + schema: "CMS"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101222401_u19.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101222401_u19.Designer.cs new file mode 100644 index 0000000..a4b2631 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101222401_u19.Designer.cs @@ -0,0 +1,3945 @@ +// +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("20260101222401_u19")] + partial class u19 + { + /// + 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.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("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "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("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("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + 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("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("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + 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.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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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("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("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + 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("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .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("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "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("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.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("OrderVATId") + .HasColumnType("bigint"); + + 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("OrderVATId"); + + 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("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + 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("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + 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.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.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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.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("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne() + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + 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.Order.OrderVAT", "OrderVAT") + .WithMany() + .HasForeignKey("OrderVATId"); + + 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("OrderVAT"); + + 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() + .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.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Wallet"); + }); + + 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("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.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("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"); + }); + + 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/20260101222401_u19.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101222401_u19.cs new file mode 100644 index 0000000..94aa655 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101222401_u19.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class u19 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImagePath", + schema: "CMS", + table: "ManualPayments", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImagePath", + schema: "CMS", + table: "ManualPayments"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101224430_u20.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101224430_u20.Designer.cs new file mode 100644 index 0000000..4ae632e --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101224430_u20.Designer.cs @@ -0,0 +1,3948 @@ +// +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("20260101224430_u20")] + partial class u20 + { + /// + 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.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("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "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("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("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + 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("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("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + 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.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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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("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("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + 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("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .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("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "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.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("OrderVATId") + .HasColumnType("bigint"); + + 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("OrderVATId"); + + 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("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + 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("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + 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.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.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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.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("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne() + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + 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.Order.OrderVAT", "OrderVAT") + .WithMany() + .HasForeignKey("OrderVATId"); + + 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("OrderVAT"); + + 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() + .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.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Wallet"); + }); + + 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("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.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("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"); + }); + + 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/20260101224430_u20.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101224430_u20.cs new file mode 100644 index 0000000..de184a4 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260101224430_u20.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class u20 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImageDocumentId", + schema: "CMS", + table: "ManualPayments", + type: "bigint", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImageDocumentId", + schema: "CMS", + table: "ManualPayments"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.Designer.cs new file mode 100644 index 0000000..0bb9c60 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.Designer.cs @@ -0,0 +1,4431 @@ +// +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("20260210232742_AddBlogAndContentEntities")] + partial class AddBlogAndContentEntities + { + /// + 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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FeaturedImageThumbnailPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "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("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("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + 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("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("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + 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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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.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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ImageThumbnailPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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.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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + 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("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("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + 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("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .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("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "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.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("OrderVATId") + .HasColumnType("bigint"); + + 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("OrderVATId"); + + 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("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + 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("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + 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.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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.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("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne() + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + 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.Order.OrderVAT", "OrderVAT") + .WithMany() + .HasForeignKey("OrderVATId"); + + 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("OrderVAT"); + + 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() + .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.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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("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.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("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"); + }); + + 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/20260210232742_AddBlogAndContentEntities.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.cs new file mode 100644 index 0000000..29ee152 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.cs @@ -0,0 +1,345 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddBlogAndContentEntities : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BlogCategories", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Title = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Slug = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Description = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + IconName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + IsActive = 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_BlogCategories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BlogPosts", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Slug = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Summary = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + HtmlContent = table.Column(type: "nvarchar(max)", nullable: false), + FeaturedImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + FeaturedImageThumbnailPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Status = table.Column(type: "int", nullable: false), + PublishedAt = table.Column(type: "datetime2", nullable: true), + ScheduledPublishAt = table.Column(type: "datetime2", nullable: true), + ViewCount = table.Column(type: "int", nullable: false, defaultValue: 0), + AuthorUserId = table.Column(type: "bigint", nullable: false), + IsFeatured = table.Column(type: "bit", nullable: false, defaultValue: false), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + 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_BlogPosts", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SitePages", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + PageKey = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + MetaDescription = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + HeroTitle = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + HeroSubtitle = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + HeroImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + IsActive = 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_SitePages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BlogPostCategories", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BlogPostId = table.Column(type: "bigint", nullable: false), + BlogCategoryId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogPostCategories", x => x.Id); + table.ForeignKey( + name: "FK_BlogPostCategories_BlogCategories_BlogCategoryId", + column: x => x.BlogCategoryId, + principalSchema: "CMS", + principalTable: "BlogCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BlogPostCategories_BlogPosts_BlogPostId", + column: x => x.BlogPostId, + principalSchema: "CMS", + principalTable: "BlogPosts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BlogPostImages", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BlogPostId = table.Column(type: "bigint", nullable: false), + ImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + ThumbnailPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + AltText = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Caption = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + 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_BlogPostImages", x => x.Id); + table.ForeignKey( + name: "FK_BlogPostImages_BlogPosts_BlogPostId", + column: x => x.BlogPostId, + principalSchema: "CMS", + principalTable: "BlogPosts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BlogPostTags", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BlogPostId = table.Column(type: "bigint", nullable: false), + TagId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogPostTags", x => x.Id); + table.ForeignKey( + name: "FK_BlogPostTags_BlogPosts_BlogPostId", + column: x => x.BlogPostId, + principalSchema: "CMS", + principalTable: "BlogPosts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BlogPostTags_Tags_TagId", + column: x => x.TagId, + principalSchema: "CMS", + principalTable: "Tags", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SitePageSections", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + SitePageId = table.Column(type: "bigint", nullable: false), + SectionKey = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Subtitle = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + HtmlContent = table.Column(type: "nvarchar(max)", nullable: true), + IconName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + ImageThumbnailPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), + ExtraData = table.Column(type: "nvarchar(max)", 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_SitePageSections", x => x.Id); + table.ForeignKey( + name: "FK_SitePageSections_SitePages_SitePageId", + column: x => x.SitePageId, + principalSchema: "CMS", + principalTable: "SitePages", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BlogCategories_IsActive", + schema: "CMS", + table: "BlogCategories", + column: "IsActive"); + + migrationBuilder.CreateIndex( + name: "IX_BlogCategories_Slug", + schema: "CMS", + table: "BlogCategories", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostCategories_BlogCategoryId", + schema: "CMS", + table: "BlogPostCategories", + column: "BlogCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostCategories_BlogPostId", + schema: "CMS", + table: "BlogPostCategories", + column: "BlogPostId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostImages_BlogPostId", + schema: "CMS", + table: "BlogPostImages", + column: "BlogPostId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_AuthorUserId", + schema: "CMS", + table: "BlogPosts", + column: "AuthorUserId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_IsFeatured", + schema: "CMS", + table: "BlogPosts", + column: "IsFeatured"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_PublishedAt", + schema: "CMS", + table: "BlogPosts", + column: "PublishedAt"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_Slug", + schema: "CMS", + table: "BlogPosts", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_Status", + schema: "CMS", + table: "BlogPosts", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_Status_PublishedAt", + schema: "CMS", + table: "BlogPosts", + columns: new[] { "Status", "PublishedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostTags_BlogPostId", + schema: "CMS", + table: "BlogPostTags", + column: "BlogPostId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostTags_TagId", + schema: "CMS", + table: "BlogPostTags", + column: "TagId"); + + migrationBuilder.CreateIndex( + name: "IX_SitePages_PageKey", + schema: "CMS", + table: "SitePages", + column: "PageKey", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SitePageSections_PageId_SectionKey", + schema: "CMS", + table: "SitePageSections", + columns: new[] { "SitePageId", "SectionKey" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BlogPostCategories", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogPostImages", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogPostTags", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "SitePageSections", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogCategories", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogPosts", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "SitePages", + schema: "CMS"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.Designer.cs new file mode 100644 index 0000000..8361931 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.Designer.cs @@ -0,0 +1,4410 @@ +// +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("20260213123943_RemoveImagePathMaxLength")] + partial class RemoveImagePathMaxLength + { + /// + 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("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "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("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("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + 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("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("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + 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.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.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() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + 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("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("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + 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("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .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("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "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.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("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + 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("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + 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.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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.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("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.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() + .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.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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("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.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("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/20260213123943_RemoveImagePathMaxLength.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.cs new file mode 100644 index 0000000..c8f72fd --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.cs @@ -0,0 +1,309 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class RemoveImagePathMaxLength : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_UserOrders_OrderVATs_OrderVATId", + schema: "CMS", + table: "UserOrders"); + + migrationBuilder.DropIndex( + name: "IX_UserOrders_OrderVATId", + schema: "CMS", + table: "UserOrders"); + + migrationBuilder.DropColumn( + name: "OrderVATId", + schema: "CMS", + table: "UserOrders"); + + migrationBuilder.AlterColumn( + name: "ImageThumbnailPath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "HeroImagePath", + schema: "CMS", + table: "SitePages", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountCategories", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImageThumbnailPath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImagePath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OrderVATId", + schema: "CMS", + table: "UserOrders", + type: "bigint", + nullable: true); + + migrationBuilder.AlterColumn( + name: "ImageThumbnailPath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "HeroImagePath", + schema: "CMS", + table: "SitePages", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountCategories", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImageThumbnailPath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImagePath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateIndex( + name: "IX_UserOrders_OrderVATId", + schema: "CMS", + table: "UserOrders", + column: "OrderVATId"); + + migrationBuilder.AddForeignKey( + name: "FK_UserOrders_OrderVATs_OrderVATId", + schema: "CMS", + table: "UserOrders", + column: "OrderVATId", + principalSchema: "CMS", + principalTable: "OrderVATs", + principalColumn: "Id"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260215201802_AddPaymentTransactionTable.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260215201802_AddPaymentTransactionTable.Designer.cs new file mode 100644 index 0000000..4e69f95 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260215201802_AddPaymentTransactionTable.Designer.cs @@ -0,0 +1,4518 @@ +// +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("20260215201802_AddPaymentTransactionTable")] + partial class AddPaymentTransactionTable + { + /// + 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("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "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("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("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + 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("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("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + 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.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.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() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + 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("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("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + 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("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .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("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "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("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + 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("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + 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.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.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.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("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.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() + .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.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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("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.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("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/20260215201802_AddPaymentTransactionTable.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260215201802_AddPaymentTransactionTable.cs new file mode 100644 index 0000000..5ffb9a0 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260215201802_AddPaymentTransactionTable.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddPaymentTransactionTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PaymentTransactions", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + GatewayProvider = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + MerchantId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Amount = table.Column(type: "bigint", nullable: false), + CallbackUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Description = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Mobile = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + UserId = table.Column(type: "bigint", nullable: true), + RequestStatusCode = table.Column(type: "int", nullable: true), + RequestStatusMessage = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Authority = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + PaymentStatus = table.Column(type: "bit", nullable: false), + VerificationStatusCode = table.Column(type: "int", nullable: true), + VerificationStatusMessage = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + CardHash = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + CardPan = table.Column(type: "nvarchar(30)", maxLength: 30, nullable: true), + RefId = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + TransactionId = table.Column(type: "bigint", nullable: true), + OrderId = table.Column(type: "nvarchar(100)", maxLength: 100, 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_PaymentTransactions", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_PaymentTransactions_Authority", + schema: "CMS", + table: "PaymentTransactions", + column: "Authority"); + + migrationBuilder.CreateIndex( + name: "IX_PaymentTransactions_GatewayProvider", + schema: "CMS", + table: "PaymentTransactions", + column: "GatewayProvider"); + + migrationBuilder.CreateIndex( + name: "IX_PaymentTransactions_RefId", + schema: "CMS", + table: "PaymentTransactions", + column: "RefId"); + + migrationBuilder.CreateIndex( + name: "IX_PaymentTransactions_TransactionId", + schema: "CMS", + table: "PaymentTransactions", + column: "TransactionId"); + + migrationBuilder.CreateIndex( + name: "IX_PaymentTransactions_UserId", + schema: "CMS", + table: "PaymentTransactions", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PaymentTransactions", + schema: "CMS"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 988662b..1510348 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -23,6 +23,267 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -503,6 +764,142 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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.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.Contract", b => { b.Property("Id") @@ -622,8 +1019,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .HasColumnType("nvarchar(1000)"); b.Property("ImagePath") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("IsActive") .ValueGeneratedOnAdd() @@ -808,8 +1204,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("ImagePath") .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("IsActive") .ValueGeneratedOnAdd() @@ -847,8 +1242,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("ThumbnailPath") .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("Title") .IsRequired() @@ -902,6 +1296,62 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -1445,6 +1895,102 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -1725,6 +2271,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .HasMaxLength(1000) .HasColumnType("nvarchar(1000)"); + b.Property("ImageDocumentId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .HasColumnType("bit"); @@ -1776,6 +2328,114 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -2152,6 +2812,97 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -2516,9 +3267,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); - b.Property("OrderVATId") - .HasColumnType("bigint"); - b.Property("PackageId") .HasColumnType("bigint"); @@ -2545,8 +3293,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasKey("Id"); - b.HasIndex("OrderVATId"); - b.HasIndex("PackageId"); b.HasIndex("TransactionId"); @@ -2760,6 +3506,83 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -2839,6 +3662,55 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -2935,6 +3807,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("WeekDefinition"); }); + 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") @@ -3026,6 +3909,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -3116,6 +4010,31 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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.User", "User") @@ -3138,7 +4057,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => { b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") - .WithOne() + .WithOne("OrderVAT") .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); @@ -3221,6 +4140,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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") @@ -3282,10 +4212,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => { - b.HasOne("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderVAT") - .WithMany() - .HasForeignKey("OrderVATId"); - b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") .WithMany("UserOrders") .HasForeignKey("PackageId"); @@ -3306,8 +4232,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("OrderVAT"); - b.Navigation("Package"); b.Navigation("Transaction"); @@ -3391,6 +4315,20 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations 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"); @@ -3420,6 +4358,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("UserCommissionPayouts"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Navigation("Sections"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => { b.Navigation("UserContracts"); @@ -3439,6 +4382,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => { + b.Navigation("Images"); + b.Navigation("OrderDetails"); b.Navigation("ProductCategories"); @@ -3456,6 +4401,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("Cities"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Navigation("StockMovements"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => { b.Navigation("UserOrders"); @@ -3533,6 +4483,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => { b.Navigation("FactorDetails"); + + b.Navigation("OrderVAT"); }); modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => @@ -3540,6 +4492,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("UserWalletChangeLogs"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Navigation("InventoryItems"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => { b.Navigation("CommissionPayoutHistories"); diff --git a/src/CMSMicroservice.Infrastructure/Services/Authorization/PermissionService.cs b/src/CMSMicroservice.Infrastructure/Services/Authorization/PermissionService.cs new file mode 100644 index 0000000..a1b731f --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Authorization/PermissionService.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Security.Claims; +using CMSMicroservice.Application.Common.Authorization; +using Microsoft.AspNetCore.Http; + +namespace CMSMicroservice.Infrastructure.Services.Authorization; + +/// +/// پیاده‌سازی سرویس مجوز — نقش‌ها از JWT Claims خوانده میشن +/// +public class PermissionService : IPermissionService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public PermissionService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public Task> GetUserRolesAsync(CancellationToken cancellationToken) + { + var user = _httpContextAccessor.HttpContext?.User; + if (user?.Identity is not { IsAuthenticated: true }) + return Task.FromResult>(Array.Empty()); + + var roles = user.Claims + .Where(c => c.Type == ClaimTypes.Role + || string.Equals(c.Type, "role", StringComparison.OrdinalIgnoreCase)) + .Select(c => c.Value) + .Where(v => !string.IsNullOrWhiteSpace(v)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + return Task.FromResult>(roles); + } + + public async Task HasPermissionAsync(string permission, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(permission)) return true; + + var roles = await GetUserRolesAsync(cancellationToken); + if (roles.Count == 0) return false; + + foreach (var role in roles) + { + if (RolePermissionConfig.HasPermission(role, permission)) + return true; + } + + return false; + } +} diff --git a/src/CMSMicroservice.Infrastructure/Services/InventoryService.cs b/src/CMSMicroservice.Infrastructure/Services/InventoryService.cs new file mode 100644 index 0000000..423e7ee --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/InventoryService.cs @@ -0,0 +1,662 @@ +using System.Collections.Generic; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; +using CMSMicroservice.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.Services; + +/// +/// پیاده‌سازی سرویس مدیریت موجودی +/// این سرویس Source of Truth برای موجودی است و مسئول همگام‌سازی با Product.RemainingCount +/// +public class InventoryService : IInventoryService +{ + private readonly ApplicationDbContext _context; + private readonly ILogger _logger; + private const long DefaultWarehouseId = 1; // انبار پیش‌فرض + + public InventoryService(ApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + #region Initialization + + public async Task InitializeInventoryAsync( + long productId, + ProductType productType, + int initialQuantity, + long? warehouseId = null, + int lowStockThreshold = 10, + CancellationToken ct = default) + { + var effectiveWarehouseId = warehouseId ?? DefaultWarehouseId; + + // چک کردن اینکه آیا قبلاً InventoryItem برای این محصول وجود دارد + var existingItem = productType == ProductType.RegularProduct + ? await _context.InventoryItems.FirstOrDefaultAsync( + x => x.ProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct) + : await _context.InventoryItems.FirstOrDefaultAsync( + x => x.DiscountProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct); + + if (existingItem != null) + { + _logger.LogWarning("InventoryItem already exists for {ProductType} with Id {ProductId}", + productType, productId); + return existingItem.Id; + } + + // ایجاد InventoryItem جدید + var inventoryItem = new InventoryItem + { + ProductId = productType == ProductType.RegularProduct ? productId : null, + DiscountProductId = productType == ProductType.DiscountProduct ? productId : null, + ProductType = productType, + Quantity = initialQuantity, + ReservedQuantity = 0, + LowStockThreshold = lowStockThreshold, + WarehouseId = effectiveWarehouseId + }; + + _context.InventoryItems.Add(inventoryItem); + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement اولیه + if (initialQuantity > 0) + { + await LogMovementAsync( + inventoryItem.Id, + StockMovementType.InitialStock, + initialQuantity, + 0, + initialQuantity, + null, + null, + null, + "موجودی اولیه", + null, + ct); + } + + // همگام‌سازی با Product.RemainingCount + await SyncRemainingCountAsync(inventoryItem, ct); + + _logger.LogInformation( + "Initialized inventory for {ProductType} Id={ProductId}, Quantity={Quantity}", + productType, productId, initialQuantity); + + return inventoryItem.Id; + } + + #endregion + + #region Query Operations + + public async Task GetInventoryAsync( + long productId, + ProductType productType, + long? warehouseId = null, + CancellationToken ct = default) + { + var effectiveWarehouseId = warehouseId ?? DefaultWarehouseId; + + return productType == ProductType.RegularProduct + ? await _context.InventoryItems.FirstOrDefaultAsync( + x => x.ProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct) + : await _context.InventoryItems.FirstOrDefaultAsync( + x => x.DiscountProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct); + } + + public async Task GetAvailableQuantityAsync( + long productId, + ProductType productType, + long? warehouseId = null, + CancellationToken ct = default) + { + var item = await GetInventoryAsync(productId, productType, warehouseId, ct); + return item?.AvailableQuantity ?? 0; + } + + public async Task CheckAvailabilityAsync( + long productId, + ProductType productType, + int requiredQuantity, + long? warehouseId = null, + CancellationToken ct = default) + { + var available = await GetAvailableQuantityAsync(productId, productType, warehouseId, ct); + return available >= requiredQuantity; + } + + public async Task> GetLowStockItemsAsync( + ProductType? productType = null, + long? warehouseId = null, + int count = 50, + CancellationToken ct = default) + { + var query = _context.InventoryItems + .Where(x => !x.IsDeleted) + .Where(x => x.Quantity <= x.LowStockThreshold); + + if (productType.HasValue) + query = query.Where(x => x.ProductType == productType.Value); + + if (warehouseId.HasValue) + query = query.Where(x => x.WarehouseId == warehouseId.Value); + + return await query + .OrderBy(x => x.Quantity) + .Take(count) + .ToListAsync(ct); + } + + public async Task> GetStockMovementsAsync( + long productId, + ProductType productType, + DateTime? fromDate = null, + DateTime? toDate = null, + CancellationToken ct = default) + { + var inventoryItem = await GetInventoryAsync(productId, productType, null, ct); + if (inventoryItem == null) + return new List(); + + var query = _context.StockMovements + .Where(x => x.InventoryItemId == inventoryItem.Id && !x.IsDeleted); + + if (fromDate.HasValue) + query = query.Where(x => x.Created >= fromDate.Value); + + if (toDate.HasValue) + query = query.Where(x => x.Created <= toDate.Value); + + return await query + .OrderByDescending(x => x.Created) + .ToListAsync(ct); + } + + #endregion + + #region Order Flow Operations + + public async Task ReserveStockAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + CancellationToken ct = default) + { + var item = await GetInventoryAsync(productId, productType, null, ct); + if (item == null) + { + _logger.LogWarning("Cannot reserve: InventoryItem not found for {ProductType} Id={ProductId}", + productType, productId); + return false; + } + + if (item.AvailableQuantity < quantity) + { + _logger.LogWarning( + "Cannot reserve: Insufficient stock. Available={Available}, Requested={Requested}", + item.AvailableQuantity, quantity); + return false; + } + + var quantityBefore = item.ReservedQuantity; + item.ReservedQuantity += quantity; + + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement + await LogMovementAsync( + item.Id, + StockMovementType.Reserved, + quantity, + quantityBefore, + item.ReservedQuantity, + orderId, + productType == ProductType.DiscountProduct ? orderId : null, + null, + "رزرو برای سفارش", + null, + ct); + + _logger.LogInformation( + "Reserved {Quantity} units for {ProductType} Id={ProductId}, OrderId={OrderId}", + quantity, productType, productId, orderId); + + return true; + } + + public async Task ReleaseReservationAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + CancellationToken ct = default) + { + var item = await GetInventoryAsync(productId, productType, null, ct); + if (item == null) + { + _logger.LogWarning("Cannot release: InventoryItem not found for {ProductType} Id={ProductId}", + productType, productId); + return false; + } + + var quantityBefore = item.ReservedQuantity; + item.ReservedQuantity = Math.Max(0, item.ReservedQuantity - quantity); + + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement + await LogMovementAsync( + item.Id, + StockMovementType.Released, + quantity, + quantityBefore, + item.ReservedQuantity, + orderId, + productType == ProductType.DiscountProduct ? orderId : null, + null, + "آزادسازی رزرو", + null, + ct); + + _logger.LogInformation( + "Released {Quantity} reserved units for {ProductType} Id={ProductId}, OrderId={OrderId}", + quantity, productType, productId, orderId); + + return true; + } + + public async Task ConfirmSaleAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + CancellationToken ct = default) + { + var item = await GetInventoryAsync(productId, productType, null, ct); + if (item == null) + { + _logger.LogWarning("Cannot confirm sale: InventoryItem not found for {ProductType} Id={ProductId}", + productType, productId); + return false; + } + + var quantityBefore = item.Quantity; + + // کاهش موجودی واقعی + item.Quantity -= quantity; + + // کاهش رزرو (اگر رزرو شده بود) + item.ReservedQuantity = Math.Max(0, item.ReservedQuantity - quantity); + + // آپدیت آخرین فروش + item.LastSoldAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement + await LogMovementAsync( + item.Id, + StockMovementType.Sale, + -quantity, // منفی برای خروج + quantityBefore, + item.Quantity, + orderId, + productType == ProductType.DiscountProduct ? orderId : null, + null, + "فروش", + null, + ct); + + // همگام‌سازی با Product.RemainingCount + await SyncRemainingCountAsync(item, ct); + + _logger.LogInformation( + "Confirmed sale of {Quantity} units for {ProductType} Id={ProductId}, OrderId={OrderId}. New Quantity={NewQuantity}", + quantity, productType, productId, orderId, item.Quantity); + + return true; + } + + #endregion + + #region Stock Management Operations + + public async Task AddStockAsync( + long productId, + ProductType productType, + int quantity, + string? referenceNumber = null, + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default) + { + var item = await GetInventoryAsync(productId, productType, null, ct); + if (item == null) + { + _logger.LogWarning("Cannot add stock: InventoryItem not found for {ProductType} Id={ProductId}", + productType, productId); + return false; + } + + var quantityBefore = item.Quantity; + item.Quantity += quantity; + item.LastRestockedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement + await LogMovementAsync( + item.Id, + StockMovementType.Restock, + quantity, + quantityBefore, + item.Quantity, + null, + null, + referenceNumber, + note ?? "ورود کالا", + performedByUserId, + ct); + + // همگام‌سازی با Product.RemainingCount + await SyncRemainingCountAsync(item, ct); + + _logger.LogInformation( + "Added {Quantity} units to {ProductType} Id={ProductId}. New Quantity={NewQuantity}", + quantity, productType, productId, item.Quantity); + + return true; + } + + public async Task AdjustStockAsync( + long productId, + ProductType productType, + int newQuantity, + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default) + { + var item = await GetInventoryAsync(productId, productType, null, ct); + if (item == null) + { + _logger.LogWarning("Cannot adjust stock: InventoryItem not found for {ProductType} Id={ProductId}", + productType, productId); + return false; + } + + var quantityBefore = item.Quantity; + var difference = newQuantity - quantityBefore; + + item.Quantity = newQuantity; + + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement + var movementType = difference >= 0 + ? StockMovementType.AdjustmentPlus + : StockMovementType.AdjustmentMinus; + + await LogMovementAsync( + item.Id, + movementType, + difference, + quantityBefore, + newQuantity, + null, + null, + null, + note ?? "تعدیل موجودی", + performedByUserId, + ct); + + // همگام‌سازی با Product.RemainingCount + await SyncRemainingCountAsync(item, ct); + + _logger.LogInformation( + "Adjusted stock for {ProductType} Id={ProductId}. Before={Before}, After={After}", + productType, productId, quantityBefore, newQuantity); + + return true; + } + + public async Task ProcessReturnAsync( + long productId, + ProductType productType, + int quantity, + long? orderId = null, + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default) + { + var item = await GetInventoryAsync(productId, productType, null, ct); + if (item == null) + { + _logger.LogWarning("Cannot process return: InventoryItem not found for {ProductType} Id={ProductId}", + productType, productId); + return false; + } + + var quantityBefore = item.Quantity; + item.Quantity += quantity; + + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement + await LogMovementAsync( + item.Id, + StockMovementType.Return, + quantity, + quantityBefore, + item.Quantity, + orderId, + productType == ProductType.DiscountProduct ? orderId : null, + null, + note ?? "برگشت از مشتری", + performedByUserId, + ct); + + // همگام‌سازی با Product.RemainingCount + await SyncRemainingCountAsync(item, ct); + + _logger.LogInformation( + "Processed return of {Quantity} units for {ProductType} Id={ProductId}. New Quantity={NewQuantity}", + quantity, productType, productId, item.Quantity); + + return true; + } + + public async Task RecordLossAsync( + long productId, + ProductType productType, + int quantity, + StockMovementType lossType, + string? note = null, + long? performedByUserId = null, + CancellationToken ct = default) + { + if (lossType != StockMovementType.Damaged && lossType != StockMovementType.Lost) + { + _logger.LogWarning("Invalid loss type: {LossType}. Must be Damaged or Lost.", lossType); + return false; + } + + var item = await GetInventoryAsync(productId, productType, null, ct); + if (item == null) + { + _logger.LogWarning("Cannot record loss: InventoryItem not found for {ProductType} Id={ProductId}", + productType, productId); + return false; + } + + var quantityBefore = item.Quantity; + item.Quantity = Math.Max(0, item.Quantity - quantity); + + await _context.SaveChangesAsync(ct); + + // ثبت StockMovement + await LogMovementAsync( + item.Id, + lossType, + -quantity, + quantityBefore, + item.Quantity, + null, + null, + null, + note ?? (lossType == StockMovementType.Damaged ? "ضایعات" : "مفقودی"), + performedByUserId, + ct); + + // همگام‌سازی با Product.RemainingCount + await SyncRemainingCountAsync(item, ct); + + _logger.LogInformation( + "Recorded {LossType} of {Quantity} units for {ProductType} Id={ProductId}. New Quantity={NewQuantity}", + lossType, quantity, productType, productId, item.Quantity); + + return true; + } + + #endregion + + #region Bulk Operations + + public async Task BulkReserveStockAsync( + IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items, + long? orderId = null, + CancellationToken ct = default) + { + foreach (var (productId, productType, quantity) in items) + { + var result = await ReserveStockAsync(productId, productType, quantity, orderId, ct); + if (!result) + { + // در صورت خطا، رزروهای قبلی را آزاد کنید + _logger.LogError( + "Bulk reserve failed at {ProductType} Id={ProductId}. Rolling back...", + productType, productId); + // TODO: Implement rollback logic + return false; + } + } + return true; + } + + public async Task BulkReleaseReservationAsync( + IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items, + long? orderId = null, + CancellationToken ct = default) + { + foreach (var (productId, productType, quantity) in items) + { + await ReleaseReservationAsync(productId, productType, quantity, orderId, ct); + } + return true; + } + + public async Task BulkConfirmSaleAsync( + IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items, + long? orderId = null, + CancellationToken ct = default) + { + foreach (var (productId, productType, quantity) in items) + { + var result = await ConfirmSaleAsync(productId, productType, quantity, orderId, ct); + if (!result) + { + _logger.LogError( + "Bulk confirm sale failed at {ProductType} Id={ProductId}", + productType, productId); + return false; + } + } + return true; + } + + #endregion + + #region Private Helper Methods + + /// + /// همگام‌سازی موجودی InventoryItem با Product.RemainingCount + /// این متد اطمینان می‌دهد که داده‌های قدیمی (RemainingCount) همیشه با سیستم جدید sync است + /// + private async Task SyncRemainingCountAsync(InventoryItem item, CancellationToken ct) + { + try + { + if (item.ProductType == ProductType.RegularProduct && item.ProductId.HasValue) + { + var product = await _context.Products.FindAsync(new object[] { item.ProductId.Value }, ct); + if (product != null) + { + product.RemainingCount = item.Quantity; + await _context.SaveChangesAsync(ct); + _logger.LogDebug("Synced RemainingCount for Product Id={ProductId} to {Quantity}", + item.ProductId, item.Quantity); + } + } + else if (item.ProductType == ProductType.DiscountProduct && item.DiscountProductId.HasValue) + { + var discountProduct = await _context.DiscountProducts.FindAsync( + new object[] { item.DiscountProductId.Value }, ct); + if (discountProduct != null) + { + discountProduct.RemainingCount = item.Quantity; + await _context.SaveChangesAsync(ct); + _logger.LogDebug("Synced RemainingCount for DiscountProduct Id={ProductId} to {Quantity}", + item.DiscountProductId, item.Quantity); + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to sync RemainingCount for InventoryItem Id={ItemId}", item.Id); + // Don't throw - sync failure shouldn't break the main operation + } + } + + /// + /// ثبت حرکت موجودی در StockMovements + /// + private async Task LogMovementAsync( + long inventoryItemId, + StockMovementType movementType, + int quantity, + int quantityBefore, + int quantityAfter, + long? orderId, + long? discountOrderId, + string? referenceNumber, + string? note, + long? performedByUserId, + CancellationToken ct) + { + var movement = new StockMovement + { + InventoryItemId = inventoryItemId, + MovementType = movementType, + Quantity = quantity, + QuantityBefore = quantityBefore, + QuantityAfter = quantityAfter, + OrderId = orderId, + DiscountOrderId = discountOrderId, + ReferenceNumber = referenceNumber, + Note = note, + PerformedByUserId = performedByUserId + }; + + _context.StockMovements.Add(movement); + await _context.SaveChangesAsync(ct); + } + + #endregion +} diff --git a/src/CMSMicroservice.Infrastructure/Services/LocalFileManager.cs b/src/CMSMicroservice.Infrastructure/Services/LocalFileManager.cs new file mode 100644 index 0000000..0df4695 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/LocalFileManager.cs @@ -0,0 +1,260 @@ +using System.IO; +using System.Net.Http; +using CMSMicroservice.Application.Common.FileManager; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; + +namespace CMSMicroservice.Infrastructure.Services; + +/// +/// فایل‌منیجر محلی — فایل‌ها روی دیسک ذخیره می‌شوند +/// مسیر نسبی در دیتابیس ذخیره می‌شود +/// موقع واکشی: فایل از دیسک خوانده و به base64 data-URI تبدیل می‌شود +/// +public sealed class LocalFileManager : IFileManager +{ + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + private readonly string _uploadRoot; + private readonly string _fmsBaseUrl; + + // ── تنظیمات بهینه‌سازی تصویر ── + private const int MainMaxWidth = 1200; + private const int MainMaxHeight = 1200; + private const int ThumbMaxWidth = 300; + private const int ThumbMaxHeight = 300; + private const int JpegQuality = 75; + + public LocalFileManager(IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger logger) + { + _logger = logger; + _httpClientFactory = httpClientFactory; + + // مسیر ذخیره فایل‌ها — پیش‌فرض: پوشه Uploads در کنار WebApi + _uploadRoot = configuration["FileStorage:UploadPath"] + ?? Path.Combine(AppContext.BaseDirectory, "Uploads"); + + _fmsBaseUrl = configuration["FMS:Address"]?.TrimEnd('/') ?? "https://dl.afrino.co"; + + Directory.CreateDirectory(_uploadRoot); + _logger.LogInformation("LocalFileManager initialized — UploadRoot: {Root}", _uploadRoot); + } + + // ──────────────────────────────────────────────────── + // آپلود فایل خام → ذخیره روی دیسک → برگرداندن مسیر نسبی + // ──────────────────────────────────────────────────── + public async Task UploadAsync( + string directory, byte[] fileBytes, string mime, + string? fileName = null, CancellationToken ct = default) + { + if (fileBytes is not { Length: > 0 }) + throw new FileUploadException("فایلی برای آپلود ارسال نشده است"); + + try + { + var ext = GetExtension(mime, fileName); + var uniqueName = $"{Guid.NewGuid():N}{ext}"; + var relativePath = Path.Combine(directory, uniqueName).Replace('\\', '/'); + + var fullPath = Path.Combine(_uploadRoot, relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); + + await File.WriteAllBytesAsync(fullPath, fileBytes, ct); + + _logger.LogInformation( + "File saved — Path: {Path}, Size: {Size}KB", + relativePath, fileBytes.Length / 1024); + + return new UploadedFile(0, relativePath); + } + catch (FileUploadException) { throw; } + catch (Exception ex) + { + _logger.LogError(ex, "خطا در ذخیره فایل — Directory: {Dir}", directory); + throw new FileUploadException($"خطا در ذخیره فایل: {ex.Message}", ex); + } + } + + // ──────────────────────────────────────────────────── + // آپلود تصویر + بندانگشتی → ذخیره روی دیسک + // ──────────────────────────────────────────────────── + public async Task UploadImageAsync( + string directory, byte[] fileBytes, string mime, + string? fileName = null, CancellationToken ct = default) + { + if (fileBytes is not { Length: > 0 }) + throw new FileUploadException("تصویری برای آپلود ارسال نشده است"); + + var baseName = Guid.NewGuid().ToString("N"); + + // ① بهینه‌سازی و ذخیره تصویر اصلی + var mainBytes = await OptimizeAsync(fileBytes, MainMaxWidth, MainMaxHeight); + var mainRelative = Path.Combine(directory, $"{baseName}.jpg").Replace('\\', '/'); + var mainFull = Path.Combine(_uploadRoot, mainRelative); + Directory.CreateDirectory(Path.GetDirectoryName(mainFull)!); + await File.WriteAllBytesAsync(mainFull, mainBytes, ct); + var main = new UploadedFile(0, mainRelative); + + // ② ساخت و ذخیره بندانگشتی + var thumbBytes = await OptimizeAsync(fileBytes, ThumbMaxWidth, ThumbMaxHeight); + var thumbRelative = Path.Combine(directory, $"{baseName}_thumb.jpg").Replace('\\', '/'); + var thumbFull = Path.Combine(_uploadRoot, thumbRelative); + await File.WriteAllBytesAsync(thumbFull, thumbBytes, ct); + var thumb = new UploadedFile(0, thumbRelative); + + _logger.LogInformation( + "Image saved — Main: {MainPath} ({MainKB}KB), Thumb: {ThumbPath} ({ThumbKB}KB)", + mainRelative, mainBytes.Length / 1024, + thumbRelative, thumbBytes.Length / 1024); + + return new UploadedImage(main, thumb); + } + + // ──────────────────────────────────────────────────── + // حذف فایل از دیسک + // ──────────────────────────────────────────────────── + public Task DeleteAsync(long fileId, CancellationToken ct = default) + { + _logger.LogWarning("DeleteAsync called with fileId={Id} — file deletion by ID not supported in disk mode", fileId); + return Task.CompletedTask; + } + + // ──────────────────────────────────────────────────── + // خواندن فایل از دیسک → تبدیل به base64 data-URI + // ──────────────────────────────────────────────────── + public string ResolveImageUrl(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + return string.Empty; + + // اگر از قبل data-URI یا URL مطلق هست، همان را برگردان + if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + return path; + + try + { + var fullPath = Path.Combine(_uploadRoot, path.TrimStart('/')); + if (!File.Exists(fullPath)) + { + _logger.LogWarning("Image file not found on disk, trying FMS fallback: {Path}", fullPath); + + // ── FMS Fallback: دانلود از dl.afrino.co و کش محلی (برای مهاجرت) ── + if (!TryDownloadFromFms(path.TrimStart('/'), fullPath)) + return string.Empty; + + _logger.LogInformation("Downloaded and cached from FMS: {Path}", path); + } + + var bytes = File.ReadAllBytes(fullPath); + var mime = GetMimeFromExtension(Path.GetExtension(fullPath)); + return $"data:{mime};base64,{Convert.ToBase64String(bytes)}"; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading image from disk: {Path}", path); + return string.Empty; + } + } + + // ──────────────────────────────────────────────────── + // FMS Fallback — دانلود از سرور قدیمی و کش محلی (مهاجرت) + // ──────────────────────────────────────────────────── + private bool TryDownloadFromFms(string relativePath, string localPath) + { + try + { + var fmsUrl = $"{_fmsBaseUrl}/{relativePath}"; + _logger.LogInformation("Attempting FMS download: {Url}", fmsUrl); + + using var client = _httpClientFactory.CreateClient("FMS"); + using var response = client.Send(new HttpRequestMessage(HttpMethod.Get, fmsUrl), + HttpCompletionOption.ResponseHeadersRead); + + if (!response.IsSuccessStatusCode) + { + _logger.LogWarning("FMS returned {Status} for {Url}", response.StatusCode, fmsUrl); + return false; + } + + // ذخیره روی دیسک + var directory = Path.GetDirectoryName(localPath)!; + Directory.CreateDirectory(directory); + + using var responseStream = response.Content.ReadAsStream(); + using var fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None); + responseStream.CopyTo(fileStream); + + _logger.LogInformation("Cached FMS file locally: {Path} ({Size} bytes)", relativePath, fileStream.Length); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to download from FMS: {Path}", relativePath); + return false; + } + } + + // ──────────────────────────────────────────────────── + // بهینه‌سازی تصویر (ریسایز + فشرده‌سازی JPEG) + // ──────────────────────────────────────────────────── + private static async Task OptimizeAsync(byte[] imageBytes, int maxWidth, int maxHeight) + { + using var image = SixLabors.ImageSharp.Image.Load(imageBytes); + + if (image.Width > maxWidth || image.Height > maxHeight) + { + image.Mutate(x => x.Resize(new ResizeOptions + { + Size = new Size(maxWidth, maxHeight), + Mode = ResizeMode.Max + })); + } + + using var ms = new MemoryStream(); + await image.SaveAsJpegAsync(ms, new JpegEncoder { Quality = JpegQuality }); + return ms.ToArray(); + } + + // ──────────────────────────────────────────────────── + // پسوند فایل از mime type + // ──────────────────────────────────────────────────── + private static string GetExtension(string mime, string? fileName) + { + if (!string.IsNullOrEmpty(fileName)) + { + var ext = Path.GetExtension(fileName); + if (!string.IsNullOrEmpty(ext)) + return ext.ToLowerInvariant(); + } + + return mime.ToLowerInvariant() switch + { + "image/jpeg" or "image/jpg" => ".jpg", + "image/png" => ".png", + "image/gif" => ".gif", + "image/webp" => ".webp", + "image/svg+xml" => ".svg", + "application/pdf" => ".pdf", + _ => ".bin" + }; + } + + private static string GetMimeFromExtension(string extension) + { + return extension.ToLowerInvariant() switch + { + ".jpg" or ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".gif" => "image/gif", + ".webp" => "image/webp", + ".svg" => "image/svg+xml", + ".pdf" => "application/pdf", + _ => "application/octet-stream" + }; + } +} diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs new file mode 100644 index 0000000..46dab44 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs @@ -0,0 +1,361 @@ +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +/// +/// پیاده‌سازی درگاه پرداخت زرین‌پال +/// ساپورت Sandbox (تست) و Production +/// +public class ZarinPalPaymentService : IPaymentGatewayService +{ + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + private readonly string _merchantId; + private readonly bool _useSandbox; + + // آدرس‌های Production + private const string ProductionApiBase = "https://api.zarinpal.com"; + private const string ProductionStartPayBase = "https://www.zarinpal.com"; + + // آدرس‌های Sandbox + private const string SandboxApiBase = "https://sandbox.zarinpal.com"; + private const string SandboxStartPayBase = "https://sandbox.zarinpal.com"; + + // مسیرهای API (مشترک) + private const string RequestEndpoint = "/pg/v4/payment/request.json"; + private const string VerifyEndpoint = "/pg/v4/payment/verify.json"; + private const string StartPayPath = "/pg/StartPay/"; + + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + public ZarinPalPaymentService( + HttpClient httpClient, + IConfiguration configuration, + ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + + _merchantId = configuration["ZarinPal:MerchantId"] + ?? throw new InvalidOperationException("ZarinPal:MerchantId is not configured."); + _useSandbox = configuration.GetValue("ZarinPal:UseSandbox", true); + + var apiBase = _useSandbox ? SandboxApiBase : ProductionApiBase; + _httpClient.BaseAddress = new Uri(apiBase); + + _logger.LogInformation("ZarinPal payment service initialized. Mode: {Mode}", + _useSandbox ? "🧪 Sandbox" : "🏦 Production"); + } + + /// + /// مرحله ۱: ارسال درخواست پرداخت به زرین‌پال و دریافت Authority + /// + public async Task InitiatePaymentAsync( + PaymentRequest request, + CancellationToken cancellationToken = default) + { + try + { + // زرین‌پال مبلغ را به ریال می‌خواهد — تبدیل تومان به ریال + var amountInRials = (long)(request.Amount * 10); + + var zarinPalRequest = new ZarinPalPaymentRequest + { + MerchantId = _merchantId, + Amount = amountInRials, + Description = request.Description, + CallbackUrl = request.CallbackUrl, + Metadata = new ZarinPalMetadata + { + Mobile = string.IsNullOrWhiteSpace(request.Mobile) ? null : request.Mobile + } + }; + + var jsonContent = JsonSerializer.Serialize(zarinPalRequest, JsonOptions); + 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); + + var response = await _httpClient.PostAsync(RequestEndpoint, content, cancellationToken); + var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + + _logger.LogDebug("ZarinPal request response: {StatusCode} - {Body}", + response.StatusCode, responseBody); + + var result = JsonSerializer.Deserialize(responseBody, JsonOptions); + + if (result?.Data?.Code == 100 && !string.IsNullOrEmpty(result.Data.Authority)) + { + var startPayBase = _useSandbox ? SandboxStartPayBase : ProductionStartPayBase; + var gatewayUrl = $"{startPayBase}{StartPayPath}{result.Data.Authority}"; + + _logger.LogInformation( + "ZarinPal payment initiated successfully: Authority={Authority}, GatewayUrl={Url}", + result.Data.Authority, gatewayUrl); + + return new PaymentInitiateResult + { + IsSuccess = true, + RefId = result.Data.Authority, + GatewayUrl = gatewayUrl + }; + } + + // خطا + var errorCode = result?.Errors?.Code ?? result?.Data?.Code ?? -1; + var errorMessage = result?.Errors?.Message ?? "خطای ناشناخته از زرین‌پال"; + + _logger.LogError( + "ZarinPal payment request failed: Code={Code}, Message={Message}", + errorCode, errorMessage); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطای درگاه زرین‌پال (کد {errorCode}): {errorMessage}" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "ZarinPal InitiatePayment exception"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطا در ارتباط با درگاه زرین‌پال: {ex.Message}" + }; + } + } + + /// + /// تأیید پرداخت بدون مبلغ — برای سازگاری با اینترفیس. + /// ⚠ زرین‌پال مبلغ را در Verify نیاز دارد. از overload با amount استفاده کنید. + /// + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("ZarinPal VerifyPaymentAsync called without amount — verification may fail!"); + return VerifyPaymentWithAmountAsync(refId, verificationToken, 0, cancellationToken); + } + + /// + /// تأیید پرداخت با مبلغ — نسخه اصلی برای زرین‌پال + /// refId = Authority، verificationToken = Status (OK/NOK)، amountInToman = مبلغ به تومان + /// + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + return VerifyPaymentWithAmountAsync(refId, verificationToken, amountInToman, cancellationToken); + } + + private async Task VerifyPaymentWithAmountAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken) + { + try + { + // verificationToken باید "OK" باشد — در غیر اینصورت کاربر لغو کرده + if (!string.Equals(verificationToken, "OK", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning("ZarinPal payment cancelled by user: Authority={Authority}", refId); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "پرداخت توسط کاربر لغو شد" + }; + } + + // تبدیل تومان → ریال (×۱۰) + var amountInRials = (long)(amountInToman * 10); + + var verifyRequest = new ZarinPalVerifyRequest + { + MerchantId = _merchantId, + Authority = refId, + 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", + refId, amountInRials); + + var response = await _httpClient.PostAsync(VerifyEndpoint, content, cancellationToken); + var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + + _logger.LogDebug("ZarinPal verify response: {StatusCode} - {Body}", + response.StatusCode, responseBody); + + var result = JsonSerializer.Deserialize(responseBody, JsonOptions); + + // code 100 = موفق | code 101 = قبلاً تأیید شده + if (result?.Data?.Code is 100 or 101) + { + _logger.LogInformation( + "ZarinPal payment verified: Authority={Authority}, RefId={RefId}, CardPan={CardPan}", + refId, result.Data.RefId, result.Data.CardPan); + + return new PaymentVerificationResult + { + IsSuccess = true, + RefId = refId, + TrackingCode = result.Data.RefId?.ToString(), + Amount = (result.Data.Amount ?? 0) / 10m, // ریال → تومان + CardPan = result.Data.CardPan, + CardHash = result.Data.CardHash, + VerificationCode = result.Data.Code, + Message = result.Data.Code == 101 + ? "تراکنش قبلاً تأیید شده" + : "تراکنش موفق" + }; + } + + var errorCode = result?.Errors?.Code ?? result?.Data?.Code ?? -1; + var errorMessage = result?.Errors?.Message ?? "تأیید تراکنش ناموفق"; + + _logger.LogError( + "ZarinPal verify failed: Authority={Authority}, Code={Code}, Message={Message}", + refId, errorCode, errorMessage); + + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = $"تأیید پرداخت ناموفق (کد {errorCode}): {errorMessage}" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "ZarinPal VerifyPayment exception: Authority={Authority}", refId); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = $"خطا در تأیید تراکنش: {ex.Message}" + }; + } + } + + /// + /// زرین‌پال Payout مستقیم ندارد — این متد NotSupported برمی‌گرداند + /// برای Payout باید از سرویس دیگری (مثل دایا) استفاده شود + /// + public Task ProcessPayoutAsync( + PayoutRequest request, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("ZarinPal does not support direct payout. Use a different provider for payouts."); + return Task.FromResult(new PayoutResult + { + IsSuccess = false, + Message = "درگاه زرین‌پال از قابلیت واریز مستقیم پشتیبانی نمی‌کند", + ProcessedAt = DateTime.UtcNow + }); + } + + // ── ZarinPal Request/Response DTOs ── + + private class ZarinPalPaymentRequest + { + public string MerchantId { get; set; } = string.Empty; + public long Amount { get; set; } + public string Description { get; set; } = string.Empty; + public string CallbackUrl { get; set; } = string.Empty; + public ZarinPalMetadata? Metadata { get; set; } + } + + private class ZarinPalMetadata + { + public string? Mobile { get; set; } + public string? Email { get; set; } + } + + private class ZarinPalVerifyRequest + { + public string MerchantId { get; set; } = string.Empty; + public long Amount { get; set; } + public string Authority { get; set; } = string.Empty; + } + + private class ZarinPalResponse + { + public ZarinPalResponseData? Data { get; set; } + + [JsonConverter(typeof(ZarinPalErrorsConverter))] + public ZarinPalResponseErrors? Errors { get; set; } + } + + private class ZarinPalResponseData + { + public int? Code { get; set; } + public string? Message { get; set; } + public string? Authority { get; set; } + public long? RefId { get; set; } + public long? Amount { get; set; } + public string? CardPan { get; set; } + public string? CardHash { get; set; } + public string? FeeType { get; set; } + public long? Fee { get; set; } + } + + private class ZarinPalResponseErrors + { + public int? Code { get; set; } + public string? Message { get; set; } + } + + /// + /// ZarinPal returns errors as [] (empty array) when no error, or as {...} object when there's an error. + /// This converter handles both cases. + /// + private class ZarinPalErrorsConverter : JsonConverter + { + public override ZarinPalResponseErrors? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartArray) + { + // Skip the empty array [] + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) { } + return null; + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + return JsonSerializer.Deserialize(ref reader); + } + + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + reader.Skip(); + return null; + } + + public override void Write(Utf8JsonWriter writer, ZarinPalResponseErrors? value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); + } + } +} diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 4e605f0..d888312 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.162 + 0.0.179 None False False @@ -41,6 +41,7 @@ + @@ -48,6 +49,8 @@ + + @@ -59,14 +62,25 @@ + + + + + + + + + + + - + - $(PackageOutputPath)$(PackageId).$(Version).nupkg - dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate + $(PackageOutputPath)/$(PackageId).$(Version).nupkg + dotnet nuget push "$(NugetPackagePath)" --source foursat-hosted --api-key admin:87zH26nbqT --skip-duplicate --configfile "$(MSBuildThisFileDirectory)../NuGet.config" - + diff --git a/src/CMSMicroservice.Protobuf/Protos/City.proto b/src/CMSMicroservice.Protobuf/Protos/City.proto new file mode 100644 index 0000000..9e53a8e --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/City.proto @@ -0,0 +1,167 @@ +syntax = "proto3"; + +package city; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.City"; + +service CityContract { + // Customer Methods - برای مشتریان + rpc GetCitiesForCustomer(GetCitiesForCustomerRequest) returns (GetCitiesForCustomerResponse) { + option (google.api.http) = { + get: "/Customer/Cities/GetCities" + }; + }; + + rpc GetCityByIdForCustomer(GetCityByIdForCustomerRequest) returns (GetCityByIdForCustomerResponse) { + option (google.api.http) = { + get: "/Customer/Cities/GetCity/{id}" + }; + }; + + rpc GetCitiesByStateForCustomer(GetCitiesByStateForCustomerRequest) returns (GetCitiesByStateForCustomerResponse) { + option (google.api.http) = { + get: "/Customer/Cities/GetCitiesByState/{state_id}" + }; + }; + + // Admin Methods + rpc GetAllCitiesByFilter(GetAllCitiesByFilterRequest) returns (GetAllCitiesByFilterResponse) { + option (google.api.http) = { + get: "/Cities/GetAllCitiesByFilter" + }; + }; + + rpc CreateCity(CreateCityRequest) returns (CreateCityResponse) { + option (google.api.http) = { + post: "/Cities/CreateCity" + body: "*" + }; + }; + + rpc UpdateCity(UpdateCityRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/Cities/UpdateCity" + body: "*" + }; + }; + + rpc DeleteCity(DeleteCityRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/Cities/DeleteCity/{id}" + }; + }; +} + +// Customer Messages +message GetCitiesForCustomerRequest { + google.protobuf.Int64Value state_id = 1; + google.protobuf.StringValue search_term = 2; + int32 page_number = 3; + int32 page_size = 4; +} + +message GetCitiesForCustomerResponse { + repeated CityDto cities = 1; + MetaData meta_data = 2; +} + +message GetCityByIdForCustomerRequest { + int64 id = 1; +} + +message GetCityByIdForCustomerResponse { + CityDto city = 1; +} + +message GetCitiesByStateForCustomerRequest { + int64 state_id = 1; + int32 page_number = 2; + int32 page_size = 3; +} + +message GetCitiesByStateForCustomerResponse { + repeated CityDto cities = 1; + MetaData meta_data = 2; +} + +// Admin Messages +message GetAllCitiesByFilterRequest { + PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllCitiesByFilterFilter filter = 3; +} + +message GetAllCitiesByFilterFilter { + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue name = 2; + google.protobuf.StringValue native = 3; + google.protobuf.Int64Value state_id = 4; +} + +message GetAllCitiesByFilterResponse { + MetaData meta_data = 1; + repeated CityDto cities = 2; +} + +message CreateCityRequest { + int64 external_id = 1; + string name = 2; + string native = 3; + google.protobuf.StringValue latitude = 4; + google.protobuf.StringValue longitude = 5; + int64 state_id = 6; +} + +message CreateCityResponse { + int64 id = 1; + string message = 2; +} + +message UpdateCityRequest { + int64 id = 1; + int64 external_id = 2; + string name = 3; + string native = 4; + google.protobuf.StringValue latitude = 5; + google.protobuf.StringValue longitude = 6; + int64 state_id = 7; +} + +message DeleteCityRequest { + int64 id = 1; +} + +// Common DTOs +message CityDto { + int64 id = 1; + int64 external_id = 2; + string name = 3; + string native = 4; + google.protobuf.StringValue latitude = 5; + google.protobuf.StringValue longitude = 6; + int64 state_id = 7; + string state_name = 8; + string state_native = 9; + google.protobuf.Timestamp created = 10; + google.protobuf.Timestamp last_modified = 11; +} + +// Common Messages +message PaginationState { + int32 page_number = 1; + int32 page_size = 2; +} + +message MetaData { + int64 current_page = 1; + int64 total_page = 2; + int64 page_size = 3; + int64 total_count = 4; + bool has_previous = 5; + bool has_next = 6; +} \ No newline at end of file diff --git a/src/CMSMicroservice.Protobuf/Protos/blogcategory.proto b/src/CMSMicroservice.Protobuf/Protos/blogcategory.proto new file mode 100644 index 0000000..0143fff --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/blogcategory.proto @@ -0,0 +1,115 @@ +syntax = "proto3"; + +package blogcategory; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.BlogCategory"; + +service BlogCategoryContract +{ + rpc CreateBlogCategory(CreateBlogCategoryRequest) returns (CreateBlogCategoryResponse){ + option (google.api.http) = { post: "/CreateBlogCategory" body: "*" }; + }; + rpc UpdateBlogCategory(UpdateBlogCategoryRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateBlogCategory" body: "*" }; + }; + rpc DeleteBlogCategory(DeleteBlogCategoryRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteBlogCategory" body: "*" }; + }; + rpc GetBlogCategory(GetBlogCategoryRequest) returns (GetBlogCategoryResponse){ + option (google.api.http) = { get: "/GetBlogCategory" }; + }; + rpc GetAllBlogCategories(GetAllBlogCategoriesRequest) returns (GetAllBlogCategoriesResponse){ + option (google.api.http) = { get: "/GetAllBlogCategories" }; + }; + rpc GetActiveBlogCategories(GetActiveBlogCategoriesRequest) returns (GetActiveBlogCategoriesResponse){ + option (google.api.http) = { get: "/GetActiveBlogCategories" }; + }; +} + +// ── Create ── +message CreateBlogCategoryRequest +{ + string title = 1; + string slug = 2; + google.protobuf.StringValue description = 3; + google.protobuf.StringValue icon_name = 4; + int32 sort_order = 5; + bool is_active = 6; +} +message CreateBlogCategoryResponse +{ + int64 id = 1; +} + +// ── Update ── +message UpdateBlogCategoryRequest +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue description = 4; + google.protobuf.StringValue icon_name = 5; + int32 sort_order = 6; + bool is_active = 7; +} + +// ── Delete ── +message DeleteBlogCategoryRequest +{ + int64 id = 1; +} + +// ── Get ── +message GetBlogCategoryRequest +{ + int64 id = 1; +} +message GetBlogCategoryResponse +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue description = 4; + google.protobuf.StringValue icon_name = 5; + int32 sort_order = 6; + bool is_active = 7; + int32 post_count = 8; + google.protobuf.Timestamp created = 9; +} + +// ── Get All (Admin) ── +message GetAllBlogCategoriesRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; +} +message GetAllBlogCategoriesResponse +{ + messages.MetaData meta_data = 1; + repeated BlogCategoryListItem models = 2; +} +message BlogCategoryListItem +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue description = 4; + google.protobuf.StringValue icon_name = 5; + int32 sort_order = 6; + bool is_active = 7; + int32 post_count = 8; +} + +// ── Get Active (Customer) ── +message GetActiveBlogCategoriesRequest {} +message GetActiveBlogCategoriesResponse +{ + repeated BlogCategoryListItem categories = 1; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/blogpost.proto b/src/CMSMicroservice.Protobuf/Protos/blogpost.proto new file mode 100644 index 0000000..22127de --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/blogpost.proto @@ -0,0 +1,221 @@ +syntax = "proto3"; + +package blogpost; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.BlogPost"; + +service BlogPostContract +{ + rpc CreateBlogPost(CreateBlogPostRequest) returns (CreateBlogPostResponse){ + option (google.api.http) = { post: "/CreateBlogPost" body: "*" }; + }; + rpc UpdateBlogPost(UpdateBlogPostRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateBlogPost" body: "*" }; + }; + rpc DeleteBlogPost(DeleteBlogPostRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteBlogPost" body: "*" }; + }; + rpc GetBlogPost(GetBlogPostRequest) returns (GetBlogPostResponse){ + option (google.api.http) = { get: "/GetBlogPost" }; + }; + rpc GetBlogPostBySlug(GetBlogPostBySlugRequest) returns (GetBlogPostResponse){ + option (google.api.http) = { get: "/GetBlogPostBySlug" }; + }; + rpc GetAllBlogPosts(GetAllBlogPostsRequest) returns (GetAllBlogPostsResponse){ + option (google.api.http) = { get: "/GetAllBlogPosts" }; + }; + rpc GetPublishedBlogPosts(GetPublishedBlogPostsRequest) returns (GetAllBlogPostsResponse){ + option (google.api.http) = { get: "/GetPublishedBlogPosts" }; + }; + rpc GetFeaturedBlogPosts(GetFeaturedBlogPostsRequest) returns (GetAllBlogPostsResponse){ + option (google.api.http) = { get: "/GetFeaturedBlogPosts" }; + }; + rpc PublishBlogPost(PublishBlogPostRequest) returns (PublishBlogPostResponse){ + option (google.api.http) = { post: "/PublishBlogPost" body: "*" }; + }; + rpc ArchiveBlogPost(ArchiveBlogPostRequest) returns (ArchiveBlogPostResponse){ + option (google.api.http) = { post: "/ArchiveBlogPost" body: "*" }; + }; + rpc IncrementViewCount(IncrementViewCountRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { post: "/IncrementViewCount" body: "*" }; + }; +} + +// ── Create ── +message CreateBlogPostRequest +{ + string title = 1; + string slug = 2; + google.protobuf.StringValue summary = 3; + string html_content = 4; + google.protobuf.StringValue featured_image_path = 5; + google.protobuf.StringValue featured_image_thumbnail_path = 6; + repeated int64 category_ids = 7; + repeated int64 tag_ids = 8; + bool is_featured = 9; + int32 sort_order = 10; + BlogImageFileModel image_file = 11; +} +message CreateBlogPostResponse +{ + int64 id = 1; +} + +// ── Update ── +message UpdateBlogPostRequest +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue summary = 4; + string html_content = 5; + google.protobuf.StringValue featured_image_path = 6; + google.protobuf.StringValue featured_image_thumbnail_path = 7; + repeated int64 category_ids = 8; + repeated int64 tag_ids = 9; + bool is_featured = 10; + int32 sort_order = 11; + BlogImageFileModel image_file = 12; +} + +// ── Delete ── +message DeleteBlogPostRequest +{ + int64 id = 1; +} + +// ── Get Single ── +message GetBlogPostRequest +{ + int64 id = 1; +} +message GetBlogPostBySlugRequest +{ + string slug = 1; +} +message GetBlogPostResponse +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue summary = 4; + string html_content = 5; + google.protobuf.StringValue featured_image_path = 6; + google.protobuf.StringValue featured_image_thumbnail_path = 7; + int32 status = 8; + string status_name = 9; + google.protobuf.Timestamp published_at = 10; + int32 view_count = 11; + int64 author_user_id = 12; + bool is_featured = 13; + int32 sort_order = 14; + google.protobuf.Timestamp created = 15; + google.protobuf.Timestamp last_modified = 16; + repeated BlogPostCategoryInfo categories = 17; + repeated BlogPostTagInfo tags = 18; +} +message BlogPostCategoryInfo +{ + int64 id = 1; + string title = 2; + string slug = 3; +} +message BlogPostTagInfo +{ + int64 id = 1; + string title = 2; + string name = 3; +} + +// ── Get All (Admin) ── +message GetAllBlogPostsRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllBlogPostsFilter filter = 3; +} +message GetAllBlogPostsFilter +{ + google.protobuf.StringValue search_term = 1; + google.protobuf.Int32Value status = 2; + google.protobuf.Int64Value category_id = 3; + google.protobuf.BoolValue is_featured = 4; +} +message GetAllBlogPostsResponse +{ + messages.MetaData meta_data = 1; + repeated BlogPostListItem models = 2; +} +message BlogPostListItem +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue summary = 4; + google.protobuf.StringValue featured_image_thumbnail_path = 5; + int32 status = 6; + string status_name = 7; + google.protobuf.Timestamp published_at = 8; + int32 view_count = 9; + bool is_featured = 10; + google.protobuf.Timestamp created = 11; + repeated BlogPostCategoryInfo categories = 12; +} + +// ── Get Published (Customer) ── +message GetPublishedBlogPostsRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue search_term = 2; + google.protobuf.Int64Value category_id = 3; +} + +// ── Get Featured ── +message GetFeaturedBlogPostsRequest +{ + int32 count = 1; +} + +// ── Publish ── +message PublishBlogPostRequest +{ + int64 id = 1; +} +message PublishBlogPostResponse +{ + bool success = 1; + string message = 2; + google.protobuf.Timestamp published_at = 3; +} + +// ── Archive ── +message ArchiveBlogPostRequest +{ + int64 id = 1; +} +message ArchiveBlogPostResponse +{ + bool success = 1; + string message = 2; +} + +// ── View Count ── +message IncrementViewCountRequest +{ + int64 id = 1; +} + +// ── File upload model for binary image uploads from BackOffice ── +message BlogImageFileModel +{ + bytes file = 1; + string mime = 2; + string file_name = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/blogpostimage.proto b/src/CMSMicroservice.Protobuf/Protos/blogpostimage.proto new file mode 100644 index 0000000..5dab1e4 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/blogpostimage.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package blogpostimage; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.BlogPostImage"; + +service BlogPostImageContract +{ + rpc AddBlogPostImage(AddBlogPostImageRequest) returns (AddBlogPostImageResponse){ + option (google.api.http) = { post: "/AddBlogPostImage" body: "*" }; + }; + rpc DeleteBlogPostImage(DeleteBlogPostImageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteBlogPostImage" body: "*" }; + }; + rpc GetBlogPostImages(GetBlogPostImagesRequest) returns (GetBlogPostImagesResponse){ + option (google.api.http) = { get: "/GetBlogPostImages" }; + }; + rpc ReorderBlogPostImages(ReorderBlogPostImagesRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/ReorderBlogPostImages" body: "*" }; + }; +} + +// ── Add ── +message AddBlogPostImageRequest +{ + int64 blog_post_id = 1; + string image_path = 2; + string thumbnail_path = 3; + google.protobuf.StringValue alt_text = 4; + google.protobuf.StringValue caption = 5; + int32 sort_order = 6; +} +message AddBlogPostImageResponse +{ + int64 id = 1; +} + +// ── Delete ── +message DeleteBlogPostImageRequest +{ + int64 id = 1; +} + +// ── Get All for Post ── +message GetBlogPostImagesRequest +{ + int64 blog_post_id = 1; +} +message GetBlogPostImagesResponse +{ + repeated BlogPostImageItem images = 1; +} +message BlogPostImageItem +{ + int64 id = 1; + int64 blog_post_id = 2; + string image_path = 3; + string thumbnail_path = 4; + google.protobuf.StringValue alt_text = 5; + google.protobuf.StringValue caption = 6; + int32 sort_order = 7; +} + +// ── Reorder ── +message ReorderBlogPostImagesRequest +{ + repeated ImageSortItem items = 1; +} +message ImageSortItem +{ + int64 id = 1; + int32 sort_order = 2; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/category.proto b/src/CMSMicroservice.Protobuf/Protos/category.proto index 74928cc..ed6cfdb 100644 --- a/src/CMSMicroservice.Protobuf/Protos/category.proto +++ b/src/CMSMicroservice.Protobuf/Protos/category.proto @@ -43,6 +43,22 @@ service CategoryContract }; }; + rpc GetAllCategoriesForCustomer(GetAllCategoriesForCustomerRequest) returns (GetAllCategoriesForCustomerResponse){ + option (google.api.http) = { + get: "/Customer/Categories/GetAllCategories" + }; + }; + rpc GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest) returns (GetCategoryByIdForCustomerResponse){ + option (google.api.http) = { + get: "/Customer/Categories/GetCategory/{id}" + }; + }; + rpc GetAllCategories(GetAllCategoriesRequest) returns (GetAllCategoriesResponse){ + option (google.api.http) = { + get: "/Customer/GetAllCategories" + + }; + }; } message CreateNewCategoryRequest { @@ -120,4 +136,45 @@ message GetAllCategoryByFilterResponseModel google.protobuf.Int64Value parent_id = 6; bool is_active = 7; int32 sort_order = 8; + // تعداد محصولات این دسته‌بندی + int32 product_count = 9; +} +message GetAllCategoriesRequest { + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllCategoryByFilterFilter filter = 3; +} +message GetAllCategoriesResponse { + messages.MetaData meta_data = 1; + repeated GetAllCategoryFilterResponseModel models = 2; +} +message GetAllCategoryFilterResponseModel { + int64 id = 1; + string name = 2; + string title = 3; + string description = 4; + google.protobuf.StringValue image_path = 5; + google.protobuf.Int64Value parent_id = 6; + bool is_active = 7; + int32 sort_order = 8; +} + +// Customer Messages +message GetAllCategoriesForCustomerRequest { + google.protobuf.Int64Value parent_id = 1; + int32 page_number = 2; + int32 page_size = 3; +} + +message GetAllCategoriesForCustomerResponse { + messages.MetaData meta_data = 1; + repeated GetAllCategoryFilterResponseModel models = 2; +} + +message GetCategoryByIdForCustomerRequest { + int64 id = 1; +} + +message GetCategoryByIdForCustomerResponse { + GetAllCategoryFilterResponseModel category = 1; } diff --git a/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto b/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto index 356588d..9951376 100644 --- a/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto @@ -116,6 +116,8 @@ message GetClubMembershipResponse bool is_active = 8; google.protobuf.Timestamp created = 9; repeated MembershipFeatureModel features = 10; + string status = 11; // Trial/Active/Expired/Inactive - for frontend compatibility + int32 days_remaining = 12; // Days until expiration - calculated field } message MembershipFeatureModel diff --git a/src/CMSMicroservice.Protobuf/Protos/commission.proto b/src/CMSMicroservice.Protobuf/Protos/commission.proto index fab3faa..5cf3999 100644 --- a/src/CMSMicroservice.Protobuf/Protos/commission.proto +++ b/src/CMSMicroservice.Protobuf/Protos/commission.proto @@ -117,6 +117,19 @@ service CommissionContract get: "/Commission/GetWeekDefinitions" }; }; + + // Customer-facing APIs + rpc GetMyCommissionPayouts(GetMyCommissionPayoutsRequest) returns (GetMyCommissionPayoutsResponse){ + option (google.api.http) = { + get: "/Commission/Customer/MyPayouts" + }; + }; + + rpc GetMyWeeklyBalances(GetMyWeeklyBalancesRequest) returns (GetMyWeeklyBalancesResponse){ + option (google.api.http) = { + get: "/Commission/Customer/MyWeeklyBalances" + }; + }; // Financial Reports rpc GetWithdrawalReports(GetWithdrawalReportsRequest) returns (GetWithdrawalReportsResponse){ @@ -335,8 +348,8 @@ message UserWeeklyBalanceModel // GetAllWeeklyPools Query message GetAllWeeklyPoolsRequest { - google.protobuf.StringValue from_week = 1; // Format: "YYYY-Www" (optional) - google.protobuf.StringValue to_week = 2; // Format: "YYYY-Www" (optional) + google.protobuf.Int64Value from_week_definition_id = 1; // WeekDefinitionId filter (optional) + google.protobuf.Int64Value to_week_definition_id = 2; // WeekDefinitionId filter (optional) google.protobuf.BoolValue only_calculated = 3; // Only show calculated pools int32 page_index = 4; int32 page_size = 5; @@ -525,6 +538,14 @@ message GetWeekDefinitionsRequest google.protobuf.StringValue sort_by = 2; //فیلتر GetWeekDefinitionsFilter filter = 3; + + // Frontend compatibility fields (aliases) + int32 page_number = 20; + int32 page_size = 21; + string search_text = 22; + google.protobuf.Int32Value gregorian_year = 23; + google.protobuf.Int32Value persian_year = 24; + google.protobuf.BoolValue is_active = 25; } message GetWeekDefinitionsFilter @@ -564,4 +585,77 @@ message WeekDefinitionItem int32 persian_year = 9; bool is_active = 10; bool is_current_week = 11; + string start_date_persian = 12; // Frontend expects Persian date string + string end_date_persian = 13; // Frontend expects Persian date string +} + +// ============ Customer APIs ============ + +// GetMyCommissionPayouts - for frontend customer display +message GetMyCommissionPayoutsRequest +{ + int32 page_number = 1; + int32 page_size = 2; + google.protobuf.Int64Value week_definition_id = 3; + google.protobuf.Int32Value status = 4; // 0=Pending, 1=Calculated, 2=Paid, 3=Withdrawn +} + +message GetMyCommissionPayoutsResponse +{ + CustomerMetaData meta_data = 1; + repeated CustomerCommissionPayoutModel payouts = 2; +} + +message CustomerMetaData +{ + int64 total_count = 1; +} + +message CustomerCommissionPayoutModel +{ + int64 id = 1; + int64 week_definition_id = 2; + string week_display_name = 3; + int32 balances_earned = 4; + int64 total_amount = 5; + string amount_formatted = 6; + 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; +} + +// GetMyWeeklyBalances - for frontend customer display +message GetMyWeeklyBalancesRequest +{ + int32 page_number = 1; + int32 page_size = 2; + google.protobuf.Int64Value week_definition_id = 3; + bool only_active = 4; +} + +message GetMyWeeklyBalancesResponse +{ + CustomerMetaData meta_data = 1; + repeated CustomerWeeklyBalanceModel balances = 2; + int32 total_left_balances = 3; + int32 total_right_balances = 4; + string weaker_leg = 5; +} + +message CustomerWeeklyBalanceModel +{ + int64 id = 1; + int64 week_definition_id = 2; + string week_display_name = 3; + int32 left_leg_balances = 4; + int32 right_leg_balances = 5; + int32 total_balances = 6; + int64 weekly_pool_contribution = 7; + bool is_expired = 8; + google.protobuf.Timestamp calculated_at = 9; + string date_persian = 10; + int32 left_leg_carryover = 11; + int32 right_leg_carryover = 12; + int32 left_leg_new_members = 13; + int32 right_leg_new_members = 14; } diff --git a/src/CMSMicroservice.Protobuf/Protos/configuration.proto b/src/CMSMicroservice.Protobuf/Protos/configuration.proto index 4cbda2b..125c1e6 100644 --- a/src/CMSMicroservice.Protobuf/Protos/configuration.proto +++ b/src/CMSMicroservice.Protobuf/Protos/configuration.proto @@ -39,6 +39,19 @@ service ConfigurationContract get: "/Configuration/GetHistory" }; }; + + // Customer-facing APIs + rpc GetClubConfiguration(google.protobuf.Empty) returns (GetClubConfigurationResponse){ + option (google.api.http) = { + get: "/Configuration/Customer/Club" + }; + }; + + rpc GetClubFeatures(google.protobuf.Empty) returns (GetClubFeaturesResponse){ + option (google.api.http) = { + get: "/Configuration/Customer/ClubFeatures" + }; + }; } // CreateOrUpdate Command @@ -138,3 +151,30 @@ message ConfigurationHistoryModel string reason = 11; google.protobuf.Timestamp created = 12; } + +// ============ Customer APIs ============ + +// GetClubConfiguration - for frontend customer display +message GetClubConfigurationResponse +{ + int64 activation_fee = 1; + int64 membership_gift_value = 2; +} + +// GetClubFeatures - for frontend customer display +message GetClubFeaturesResponse +{ + repeated ClubFeatureModel features = 1; +} + +message ClubFeatureModel +{ + int64 id = 1; + string title = 2; + string description = 3; + bool is_enabled = 4; + int32 display_order = 5; + google.protobuf.Timestamp granted_at = 6; + google.protobuf.Timestamp created_at = 7; + string notes = 8; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/discountorder.proto b/src/CMSMicroservice.Protobuf/Protos/discountorder.proto index e84b4ff..8de7f08 100644 --- a/src/CMSMicroservice.Protobuf/Protos/discountorder.proto +++ b/src/CMSMicroservice.Protobuf/Protos/discountorder.proto @@ -40,6 +40,20 @@ service DiscountOrderContract get: "/GetUserOrders" }; }; + + // Admin: Get All Discount Orders with filters + rpc GetAllDiscountOrders(GetAllDiscountOrdersRequest) returns (GetAllDiscountOrdersResponse){ + option (google.api.http) = { + get: "/GetAllDiscountOrders" + }; + }; + + // Admin: Get Sales Report + rpc GetDiscountSalesReport(GetDiscountSalesReportRequest) returns (GetDiscountSalesReportResponse){ + option (google.api.http) = { + get: "/GetDiscountSalesReport" + }; + }; } // Place Order (Initial Step - Create Order) @@ -123,6 +137,7 @@ message GetOrderByIdResponse repeated OrderItemDto items = 14; google.protobuf.Timestamp created = 15; google.protobuf.Timestamp last_modified = 16; + PaymentStatus payment_status = 17; } message AddressInfo @@ -144,6 +159,9 @@ message OrderItemDto int64 total_price = 6; int64 discount_amount = 7; int64 final_price = 8; + // آدرس تصویر محصول + string image_path = 9; + string thumbnail_path = 10; } // Get User Orders @@ -174,4 +192,120 @@ message OrderSummaryDto google.protobuf.StringValue tracking_code = 8; int32 items_count = 9; google.protobuf.Timestamp created = 10; + PaymentStatus payment_status = 11; +} + +// ===== Admin: Get All Discount Orders ===== + +message GetAllDiscountOrdersRequest +{ + google.protobuf.Int64Value user_id = 1; + google.protobuf.Int32Value payment_status = 2; + google.protobuf.Int32Value delivery_status = 3; + google.protobuf.StringValue user_mobile = 4; + google.protobuf.StringValue tracking_code = 5; + google.protobuf.Timestamp from_date = 6; + google.protobuf.Timestamp to_date = 7; + google.protobuf.Int64Value min_amount = 8; + google.protobuf.Int64Value max_amount = 9; + int32 page_number = 10; + int32 page_size = 11; +} + +message GetAllDiscountOrdersResponse +{ + messages.MetaData meta_data = 1; + repeated AdminOrderDto models = 2; +} + +message AdminOrderDto +{ + int64 id = 1; + int64 user_id = 2; + string user_full_name = 3; + string user_mobile = 4; + int64 total_amount = 5; + int64 discount_balance_used = 6; + int64 gateway_amount_paid = 7; + int64 vat_amount = 8; + PaymentStatus payment_status = 9; + google.protobuf.Timestamp payment_date = 10; + DeliveryStatus delivery_status = 11; + google.protobuf.Timestamp delivery_date = 12; + google.protobuf.StringValue shipping_address = 13; + google.protobuf.StringValue receiver_name = 14; + google.protobuf.StringValue receiver_mobile = 15; + google.protobuf.StringValue tracking_code = 16; + google.protobuf.StringValue admin_note = 17; + google.protobuf.Timestamp created = 18; + google.protobuf.Timestamp last_modified = 19; + int32 items_count = 20; +} + +enum PaymentStatus +{ + PAYMENT_PENDING = 0; + PAYMENT_COMPLETED = 1; + PAYMENT_FAILED = 2; + PAYMENT_REFUNDED = 3; +} + +// ===== Sales Report Messages ===== + +enum SalesReportType +{ + REPORT_SUMMARY = 0; + REPORT_DAILY = 1; + REPORT_WEEKLY = 2; + REPORT_MONTHLY = 3; +} + +message GetDiscountSalesReportRequest +{ + google.protobuf.Timestamp from_date = 1; + google.protobuf.Timestamp to_date = 2; + SalesReportType report_type = 3; +} + +message GetDiscountSalesReportResponse +{ + SalesSummary summary = 1; + repeated SalesPeriodDto periods = 2; + repeated TopSellingProductDto top_products = 3; +} + +message SalesSummary +{ + int32 total_orders = 1; + int32 completed_orders = 2; + int32 pending_orders = 3; + int32 cancelled_orders = 4; + int64 total_sales_amount = 5; + int64 total_discount_used = 6; + int64 total_gateway_paid = 7; + int64 total_vat_amount = 8; + int64 average_order_value = 9; + int32 unique_customers = 10; + int32 total_products_sold = 11; +} + +message SalesPeriodDto +{ + string period_label = 1; + google.protobuf.Timestamp period_start = 2; + google.protobuf.Timestamp period_end = 3; + int32 orders_count = 4; + int64 total_amount = 5; + int64 discount_used = 6; + int64 gateway_paid = 7; +} + +message TopSellingProductDto +{ + int64 product_id = 1; + string product_title = 2; + google.protobuf.StringValue image_path = 3; + int32 quantity_sold = 4; + int64 total_revenue = 5; + int32 orders_count = 6; } diff --git a/src/CMSMicroservice.Protobuf/Protos/discountproduct.proto b/src/CMSMicroservice.Protobuf/Protos/discountproduct.proto index 1e3048c..c549fba 100644 --- a/src/CMSMicroservice.Protobuf/Protos/discountproduct.proto +++ b/src/CMSMicroservice.Protobuf/Protos/discountproduct.proto @@ -40,6 +40,37 @@ service DiscountProductContract get: "/GetDiscountProducts" }; }; + + // Product Image Gallery Operations + rpc AddDiscountProductImage(AddDiscountProductImageRequest) returns (AddDiscountProductImageResponse){ + option (google.api.http) = { + post: "/AddDiscountProductImage" + body: "*" + }; + }; + rpc UpdateDiscountProductImage(UpdateDiscountProductImageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateDiscountProductImage" + body: "*" + }; + }; + rpc DeleteDiscountProductImage(DeleteDiscountProductImageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteDiscountProductImage" + body: "*" + }; + }; + rpc ReorderDiscountProductImages(ReorderDiscountProductImagesRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/ReorderDiscountProductImages" + body: "*" + }; + }; + rpc GetDiscountProductImages(GetDiscountProductImagesRequest) returns (GetDiscountProductImagesResponse){ + option (google.api.http) = { + get: "/GetDiscountProductImages" + }; + }; } // Create Product @@ -56,6 +87,8 @@ message CreateDiscountProductRequest int32 sort_order = 9; bool is_active = 10; repeated int64 category_ids = 11; + ImageFileModel image_file = 12; + ImageFileModel thumbnail_file = 13; } message CreateDiscountProductResponse @@ -77,6 +110,8 @@ message UpdateDiscountProductRequest int32 sort_order = 9; bool is_active = 10; repeated int64 category_ids = 11; + ImageFileModel image_file = 12; + ImageFileModel thumbnail_file = 13; } // Delete Product @@ -150,3 +185,76 @@ message DiscountProductDto bool is_active = 10; google.protobuf.Timestamp created = 11; } + +// ===== Product Image Gallery Messages ===== + +// Add Product Image +message AddDiscountProductImageRequest +{ + int64 discount_product_id = 1; + string image_path = 2; + string thumbnail_path = 3; + google.protobuf.StringValue title = 4; + google.protobuf.StringValue alt_text = 5; +} + +message AddDiscountProductImageResponse +{ + int64 image_id = 1; +} + +// Update Product Image +message UpdateDiscountProductImageRequest +{ + int64 id = 1; + string image_path = 2; + string thumbnail_path = 3; + google.protobuf.StringValue title = 4; + google.protobuf.StringValue alt_text = 5; + bool is_active = 6; +} + +// Delete Product Image +message DeleteDiscountProductImageRequest +{ + int64 id = 1; +} + +// Reorder Product Images +message ReorderDiscountProductImagesRequest +{ + int64 discount_product_id = 1; + repeated int64 image_ids = 2; +} + +// Get Product Images +message GetDiscountProductImagesRequest +{ + int64 discount_product_id = 1; + bool only_active = 2; +} + +message GetDiscountProductImagesResponse +{ + repeated DiscountProductImageDto images = 1; +} + +message DiscountProductImageDto +{ + int64 id = 1; + int64 discount_product_id = 2; + string image_path = 3; + string thumbnail_path = 4; + google.protobuf.StringValue title = 5; + google.protobuf.StringValue alt_text = 6; + int32 sort_order = 7; + bool is_active = 8; +} + +// File upload model for binary image uploads from BackOffice +message ImageFileModel +{ + bytes file = 1; + string mime = 2; + string file_name = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/fms.proto b/src/CMSMicroservice.Protobuf/Protos/fms.proto new file mode 100644 index 0000000..2b6d224 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/fms.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; + +package fms; + +import "google/protobuf/wrappers.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.FMS"; + +service FileInfoContract +{ + rpc CreateNewFileInfo(CreateNewFileInfoRequest) returns (CreateNewFileInfoResponse); + rpc DeleteFileInfo(DeleteFileInfoRequest) returns (DeleteFileInfoResponse); +} + +message CreateNewFileInfoRequest +{ + string directory = 1; + bytes file = 2; + string mime = 3; + bool is_base64 = 4; + google.protobuf.StringValue file_name = 5; +} + +message CreateNewFileInfoResponse +{ + int64 id = 1; + string file = 2; +} + +message DeleteFileInfoRequest +{ + int64 id = 1; +} + +message DeleteFileInfoResponse +{ + bool success = 1; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/health.proto b/src/CMSMicroservice.Protobuf/Protos/health.proto new file mode 100644 index 0000000..2211bff --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/health.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package health; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.Health"; + +service HealthContract +{ + rpc GetSystemHealth(google.protobuf.Empty) returns (GetSystemHealthResponse){ + option (google.api.http) = { + get: "/Health/System" + }; + }; + + rpc GetServiceHealth(GetServiceHealthRequest) returns (GetServiceHealthResponse){ + option (google.api.http) = { + get: "/Health/Service/{service_name}" + }; + }; +} + +message GetServiceHealthRequest +{ + string service_name = 1; +} + +message GetSystemHealthResponse +{ + bool overall_healthy = 1; + repeated ServiceHealthModel services = 2; + google.protobuf.Timestamp checked_at = 3; + string version = 4; + string environment = 5; +} + +message GetServiceHealthResponse +{ + ServiceHealthModel service = 1; + google.protobuf.Timestamp checked_at = 2; +} + +message ServiceHealthModel +{ + string service_name = 1; + HealthStatus status = 2; + string description = 3; + int64 response_time_ms = 4; + google.protobuf.Timestamp last_check = 5; + repeated HealthDetail details = 6; +} + +message HealthDetail +{ + string key = 1; + string value = 2; + HealthStatus status = 3; +} + +enum HealthStatus +{ + UNKNOWN = 0; + HEALTHY = 1; + DEGRADED = 2; + UNHEALTHY = 3; +} \ No newline at end of file diff --git a/src/CMSMicroservice.Protobuf/Protos/imageresolver.proto b/src/CMSMicroservice.Protobuf/Protos/imageresolver.proto new file mode 100644 index 0000000..e4c627b --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/imageresolver.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package imageresolver; + +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.ImageResolver"; + +service ImageResolverContract +{ + // تبدیل لیست مسیرهای تصویر به base64 data-URI + rpc ResolveImages(ResolveImagesRequest) returns (ResolveImagesResponse){ + option (google.api.http) = { post: "/ResolveImages" body: "*" }; + }; +} + +message ResolveImagesRequest +{ + repeated string paths = 1; +} + +message ResolveImagesResponse +{ + repeated ResolvedImage images = 1; +} + +message ResolvedImage +{ + string original_path = 1; + string data_uri = 2; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/inventory.proto b/src/CMSMicroservice.Protobuf/Protos/inventory.proto new file mode 100644 index 0000000..37b7878 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/inventory.proto @@ -0,0 +1,531 @@ +syntax = "proto3"; + +package inventory; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.Inventory"; + +// ============================================= +// 📦 Inventory Management Service +// ============================================= + +service InventoryContract { + // ========== Warehouse Management ========== + rpc CreateWarehouse(CreateWarehouseRequest) returns (CreateWarehouseResponse) { + option (google.api.http) = { + post: "/api/inventory/warehouses" + body: "*" + }; + }; + rpc UpdateWarehouse(UpdateWarehouseRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/api/inventory/warehouses/{id}" + body: "*" + }; + }; + rpc DeleteWarehouse(DeleteWarehouseRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/api/inventory/warehouses/{id}" + }; + }; + rpc GetWarehouse(GetWarehouseRequest) returns (GetWarehouseResponse) { + option (google.api.http) = { + get: "/api/inventory/warehouses/{id}" + }; + }; + rpc GetAllWarehouses(GetAllWarehousesRequest) returns (GetAllWarehousesResponse) { + option (google.api.http) = { + get: "/api/inventory/warehouses" + }; + }; + rpc SetDefaultWarehouse(SetDefaultWarehouseRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/api/inventory/warehouses/{id}/set-default" + body: "*" + }; + }; + + // ========== Inventory Item Management ========== + rpc GetInventoryItem(GetInventoryItemRequest) returns (GetInventoryItemResponse) { + option (google.api.http) = { + get: "/api/inventory/items/{id}" + }; + }; + rpc GetInventoryByProduct(GetInventoryByProductRequest) returns (GetInventoryByProductResponse) { + option (google.api.http) = { + get: "/api/inventory/by-product/{product_id}" + }; + }; + rpc GetAllInventoryItems(GetAllInventoryItemsRequest) returns (GetAllInventoryItemsResponse) { + option (google.api.http) = { + get: "/api/inventory/items" + }; + }; + rpc GetLowStockItems(GetLowStockItemsRequest) returns (GetLowStockItemsResponse) { + option (google.api.http) = { + get: "/api/inventory/low-stock" + }; + }; + rpc UpdateInventorySettings(UpdateInventorySettingsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/api/inventory/items/{id}/settings" + body: "*" + }; + }; + + // ========== Stock Operations ========== + rpc AddStock(AddStockRequest) returns (AddStockResponse) { + option (google.api.http) = { + post: "/api/inventory/stock/add" + body: "*" + }; + }; + rpc AdjustStock(AdjustStockRequest) returns (AdjustStockResponse) { + option (google.api.http) = { + post: "/api/inventory/stock/adjust" + body: "*" + }; + }; + rpc ReserveStock(ReserveStockRequest) returns (ReserveStockResponse) { + option (google.api.http) = { + post: "/api/inventory/stock/reserve" + body: "*" + }; + }; + rpc ReleaseReservation(ReleaseReservationRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/api/inventory/stock/release" + body: "*" + }; + }; + rpc ConfirmSale(ConfirmSaleRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/api/inventory/stock/confirm-sale" + body: "*" + }; + }; + rpc ProcessReturn(ProcessReturnRequest) returns (ProcessReturnResponse) { + option (google.api.http) = { + post: "/api/inventory/stock/return" + body: "*" + }; + }; + rpc RecordLoss(RecordLossRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/api/inventory/stock/loss" + body: "*" + }; + }; + + // ========== Bulk Operations ========== + rpc BulkAddStock(BulkAddStockRequest) returns (BulkAddStockResponse) { + option (google.api.http) = { + post: "/api/inventory/stock/bulk-add" + body: "*" + }; + }; + rpc BulkAdjustStock(BulkAdjustStockRequest) returns (BulkAdjustStockResponse) { + option (google.api.http) = { + post: "/api/inventory/stock/bulk-adjust" + body: "*" + }; + }; + + // ========== Stock Movements ========== + rpc GetStockMovements(GetStockMovementsRequest) returns (GetStockMovementsResponse) { + option (google.api.http) = { + get: "/api/inventory/movements" + }; + }; + rpc GetStockMovementsByInventoryItem(GetStockMovementsByInventoryItemRequest) returns (GetStockMovementsByInventoryItemResponse) { + option (google.api.http) = { + get: "/api/inventory/items/{inventory_item_id}/movements" + }; + }; + + // ========== Reports ========== + rpc GetInventorySummary(GetInventorySummaryRequest) returns (GetInventorySummaryResponse) { + option (google.api.http) = { + get: "/api/inventory/summary" + }; + }; + rpc GetStockValueReport(GetStockValueReportRequest) returns (GetStockValueReportResponse) { + option (google.api.http) = { + get: "/api/inventory/reports/stock-value" + }; + }; +} + +// ============================================= +// Enums +// ============================================= + +enum ProductType { + PRODUCT_TYPE_UNSPECIFIED = 0; + REGULAR_PRODUCT = 1; + DISCOUNT_PRODUCT = 2; +} + +enum StockMovementType { + MOVEMENT_TYPE_UNSPECIFIED = 0; + INITIAL_STOCK = 1; + RESTOCK = 2; + RETURN = 3; + SALE = 10; + ADJUSTMENT_INCREASE = 20; + ADJUSTMENT_DECREASE = 21; + RESERVED = 30; + RELEASED = 31; + LOSS = 40; + DAMAGED = 41; + EXPIRED = 42; + TRANSFER_OUT = 50; + TRANSFER_IN = 51; +} + +// ============================================= +// Warehouse Messages +// ============================================= + +message WarehouseDto { + int64 id = 1; + string name = 2; + string code = 3; + string address = 4; + bool is_default = 5; + bool is_active = 6; + google.protobuf.Timestamp created = 7; + google.protobuf.Timestamp last_modified = 8; +} + +message CreateWarehouseRequest { + string name = 1; + string code = 2; + string address = 3; + bool is_default = 4; +} + +message CreateWarehouseResponse { + int64 id = 1; +} + +message UpdateWarehouseRequest { + int64 id = 1; + string name = 2; + string code = 3; + string address = 4; + bool is_active = 5; +} + +message DeleteWarehouseRequest { + int64 id = 1; +} + +message GetWarehouseRequest { + int64 id = 1; +} + +message GetWarehouseResponse { + WarehouseDto warehouse = 1; +} + +message GetAllWarehousesRequest { + google.protobuf.BoolValue is_active = 1; + int32 page = 2; + int32 page_size = 3; +} + +message GetAllWarehousesResponse { + repeated WarehouseDto warehouses = 1; + int32 total_count = 2; +} + +message SetDefaultWarehouseRequest { + int64 id = 1; +} + +// ============================================= +// Inventory Item Messages +// ============================================= + +message InventoryItemDto { + int64 id = 1; + google.protobuf.Int64Value product_id = 2; + google.protobuf.Int64Value discount_product_id = 3; + ProductType product_type = 4; + int32 quantity = 5; + int32 reserved_quantity = 6; + int32 available_quantity = 7; + int32 low_stock_threshold = 8; + int32 reorder_point = 9; + int32 max_stock_level = 10; + google.protobuf.Timestamp last_restocked_at = 11; + google.protobuf.Timestamp last_sold_at = 12; + int64 warehouse_id = 13; + string warehouse_name = 14; + string product_title = 15; + int64 product_price = 16; + google.protobuf.Timestamp created = 17; + // آیا موجودی کم است (موجودی قابل فروش کمتر از حد هشدار) + bool is_low_stock = 18; +} + +message GetInventoryItemRequest { + int64 id = 1; +} + +message GetInventoryItemResponse { + InventoryItemDto item = 1; +} + +message GetInventoryByProductRequest { + int64 product_id = 1; + ProductType product_type = 2; +} + +message GetInventoryByProductResponse { + InventoryItemDto item = 1; +} + +message GetAllInventoryItemsRequest { + google.protobuf.Int64Value warehouse_id = 1; + ProductType product_type = 2; + string search = 3; + int32 page = 4; + int32 page_size = 5; + string sort_by = 6; + bool sort_desc = 7; +} + +message GetAllInventoryItemsResponse { + repeated InventoryItemDto items = 1; + int32 total_count = 2; +} + +message GetLowStockItemsRequest { + google.protobuf.Int64Value warehouse_id = 1; + ProductType product_type = 2; + int32 page = 3; + int32 page_size = 4; +} + +message GetLowStockItemsResponse { + repeated InventoryItemDto items = 1; + int32 total_count = 2; +} + +message UpdateInventorySettingsRequest { + int64 id = 1; + int32 low_stock_threshold = 2; + int32 reorder_point = 3; + int32 max_stock_level = 4; +} + +// ============================================= +// Stock Operation Messages +// ============================================= + +message AddStockRequest { + int64 product_id = 1; + ProductType product_type = 2; + int32 quantity = 3; + string reference_number = 4; + string note = 5; + google.protobuf.Int64Value warehouse_id = 6; +} + +message AddStockResponse { + int64 inventory_item_id = 1; + int32 new_quantity = 2; +} + +message AdjustStockRequest { + int64 product_id = 1; + ProductType product_type = 2; + int32 new_quantity = 3; + string reason = 4; + string reference_number = 5; +} + +message AdjustStockResponse { + int32 previous_quantity = 1; + int32 new_quantity = 2; + int32 difference = 3; +} + +message ReserveStockRequest { + int64 product_id = 1; + ProductType product_type = 2; + int32 quantity = 3; + google.protobuf.Int64Value order_id = 4; + google.protobuf.Int64Value discount_order_id = 5; +} + +message ReserveStockResponse { + bool success = 1; + string message = 2; + int32 available_quantity = 3; +} + +message ReleaseReservationRequest { + int64 product_id = 1; + ProductType product_type = 2; + int32 quantity = 3; + google.protobuf.Int64Value order_id = 4; + google.protobuf.Int64Value discount_order_id = 5; +} + +message ConfirmSaleRequest { + int64 product_id = 1; + ProductType product_type = 2; + int32 quantity = 3; + google.protobuf.Int64Value order_id = 4; + google.protobuf.Int64Value discount_order_id = 5; + bool from_reservation = 6; +} + +message ProcessReturnRequest { + int64 product_id = 1; + ProductType product_type = 2; + int32 quantity = 3; + google.protobuf.Int64Value order_id = 4; + google.protobuf.Int64Value discount_order_id = 5; + string reason = 6; +} + +message ProcessReturnResponse { + int32 new_quantity = 1; +} + +message RecordLossRequest { + int64 product_id = 1; + ProductType product_type = 2; + int32 quantity = 3; + StockMovementType loss_type = 4; // LOSS, DAMAGED, or EXPIRED + string reason = 5; + string reference_number = 6; +} + +// ============================================= +// Bulk Operation Messages +// ============================================= + +message BulkStockItem { + int64 product_id = 1; + ProductType product_type = 2; + int32 quantity = 3; +} + +message BulkAddStockRequest { + repeated BulkStockItem items = 1; + string reference_number = 2; + string note = 3; + google.protobuf.Int64Value warehouse_id = 4; +} + +message BulkAddStockResponse { + int32 success_count = 1; + int32 failed_count = 2; + repeated string errors = 3; +} + +message BulkAdjustStockRequest { + repeated BulkStockItem items = 1; + string reason = 2; + string reference_number = 3; +} + +message BulkAdjustStockResponse { + int32 success_count = 1; + int32 failed_count = 2; + repeated string errors = 3; +} + +// ============================================= +// Stock Movement Messages +// ============================================= + +message StockMovementDto { + int64 id = 1; + int64 inventory_item_id = 2; + StockMovementType movement_type = 3; + int32 quantity = 4; + int32 quantity_before = 5; + int32 quantity_after = 6; + google.protobuf.Int64Value order_id = 7; + google.protobuf.Int64Value discount_order_id = 8; + string reference_number = 9; + string note = 10; + google.protobuf.Int64Value performed_by_user_id = 11; + google.protobuf.Timestamp created = 12; + string product_title = 13; +} + +message GetStockMovementsRequest { + google.protobuf.Int64Value inventory_item_id = 1; + google.protobuf.Int64Value product_id = 2; + ProductType product_type = 3; + StockMovementType movement_type = 4; + google.protobuf.Timestamp from_date = 5; + google.protobuf.Timestamp to_date = 6; + int32 page = 7; + int32 page_size = 8; +} + +message GetStockMovementsResponse { + repeated StockMovementDto movements = 1; + int32 total_count = 2; +} + +message GetStockMovementsByInventoryItemRequest { + int64 inventory_item_id = 1; + int32 page = 2; + int32 page_size = 3; +} + +message GetStockMovementsByInventoryItemResponse { + repeated StockMovementDto movements = 1; + int32 total_count = 2; +} + +// ============================================= +// Report Messages +// ============================================= + +message GetInventorySummaryRequest { + google.protobuf.Int64Value warehouse_id = 1; +} + +message GetInventorySummaryResponse { + int32 total_products = 1; + int32 total_discount_products = 2; + int32 total_quantity = 3; + int32 total_reserved = 4; + int32 low_stock_count = 5; + int32 out_of_stock_count = 6; + int64 total_stock_value = 7; +} + +message GetStockValueReportRequest { + google.protobuf.Int64Value warehouse_id = 1; + ProductType product_type = 2; +} + +message StockValueItem { + int64 product_id = 1; + string product_title = 2; + ProductType product_type = 3; + int32 quantity = 4; + int64 unit_price = 5; + int64 total_value = 6; +} + +message GetStockValueReportResponse { + repeated StockValueItem items = 1; + int64 total_value = 2; + int32 total_items = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto index a53a107..150e1ca 100644 --- a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto +++ b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto @@ -76,6 +76,9 @@ message CreateManualPaymentRequest ManualPaymentType type = 3; string description = 4; google.protobuf.StringValue reference_number = 5; + google.protobuf.StringValue image_path = 6; + google.protobuf.Int64Value image_document_id = 7; + FileUploadModel image_file = 8; } message CreateManualPaymentResponse @@ -133,6 +136,8 @@ message ManualPaymentModel google.protobuf.StringValue rejection_reason = 17; google.protobuf.Int64Value transaction_id = 18; google.protobuf.Timestamp created = 19; + google.protobuf.StringValue image_path = 20; + google.protobuf.Int64Value image_document_id = 21; } message ProcessManualMembershipPaymentRequest @@ -151,3 +156,11 @@ message ProcessManualMembershipPaymentResponse string message = 4; } +// File upload model for binary image uploads from BackOffice +message FileUploadModel +{ + bytes file = 1; + string file_name = 2; + string mime = 3; +} + diff --git a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto index 651e709..be37c2a 100644 --- a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto @@ -50,6 +50,25 @@ service NetworkMembershipContract get: "/NetworkMembership/GetStatistics" }; }; + + // Customer-facing APIs + rpc GetMyNetworkTree(GetMyNetworkTreeRequest) returns (GetMyNetworkTreeResponse){ + option (google.api.http) = { + get: "/NetworkMembership/Customer/MyTree" + }; + }; + + rpc GetSubordinateTree(GetSubordinateTreeRequest) returns (GetMyNetworkTreeResponse){ + option (google.api.http) = { + get: "/NetworkMembership/Customer/SubordinateTree/{target_user_id}" + }; + }; + + rpc GetMyNetworkStatistics(google.protobuf.Empty) returns (GetMyNetworkStatisticsResponse){ + option (google.api.http) = { + get: "/NetworkMembership/Customer/MyStats" + }; + }; } // JoinNetwork Command @@ -168,9 +187,11 @@ message NetworkTreeNodeModel { int64 user_id = 1; string user_name = 2; + string full_name = 20; // Alias for frontend compatibility google.protobuf.Int64Value parent_id = 3; int32 network_leg = 4; int32 network_level = 5; + int32 level = 21; // Alias for frontend compatibility bool is_active = 6; google.protobuf.Timestamp joined_at = 7; google.protobuf.Timestamp club_activated_at = 8; // تاریخ فعال‌سازی در باشگاه @@ -179,6 +200,11 @@ message NetworkTreeNodeModel bool is_activated_in_target_week = 11; // آیا در هفته هدف فعال شده google.protobuf.Timestamp user_created = 12; // تاریخ ایجاد کاربر string referral_code = 13; // کد معرف کاربر + string mobile = 14; // Mobile number for display + google.protobuf.StringValue avatar = 15; // Avatar path + string position = 16; // Root/Left/Right position indicator + NetworkTreeNodeModel left_child = 17; // Left child node + NetworkTreeNodeModel right_child = 18; // Right child node } // GetHistory Query @@ -254,3 +280,51 @@ message TopNetworkUser int32 left_count = 5; int32 right_count = 6; } + +// ============ Customer APIs ============ + +// GetMyNetworkTree - for frontend customer display +message GetMyNetworkTreeRequest +{ + int32 max_depth = 1; // Default: 3 +} + +// GetSubordinateTree - for frontend customer display +message GetSubordinateTreeRequest +{ + int64 target_user_id = 1; + int32 max_depth = 2; // Default: 3 +} + +message GetMyNetworkTreeResponse +{ + NetworkTreeNodeModel root_node = 1; + int32 total_members = 2; + int32 current_depth = 3; +} + +// GetMyNetworkStatistics - for frontend customer display +message GetMyNetworkStatisticsResponse +{ + int32 total_members = 1; + int32 active_members = 2; + int32 left_leg_count = 3; + int32 right_leg_count = 4; + double left_percentage = 5; + double right_percentage = 6; + double average_depth = 7; + int32 max_depth = 8; + string weaker_leg = 9; + int32 my_network_level = 10; + string my_network_leg = 11; + string my_referral_code = 12; + CustomerLastMemberModel last_member = 13; +} + +message CustomerLastMemberModel +{ + int64 user_id = 1; + string full_name = 2; + string position = 3; + int32 total_children = 4; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/otptoken.proto b/src/CMSMicroservice.Protobuf/Protos/otptoken.proto index 0c787e1..5d58eec 100644 --- a/src/CMSMicroservice.Protobuf/Protos/otptoken.proto +++ b/src/CMSMicroservice.Protobuf/Protos/otptoken.proto @@ -36,6 +36,7 @@ message CreateNewOtpTokenRequest { string mobile = 1; string purpose = 2; + google.protobuf.StringValue sign_guid = 3; } message CreateNewOtpTokenResponse { diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto index 2dc3d24..2cc189d 100644 --- a/src/CMSMicroservice.Protobuf/Protos/package.proto +++ b/src/CMSMicroservice.Protobuf/Protos/package.proto @@ -76,6 +76,36 @@ service PackageContract body: "*" }; }; + + // ============= Customer-specific Methods ============= + + rpc GetCustomerPackages(GetCustomerPackagesRequest) returns (GetCustomerPackagesResponse){ + option (google.api.http) = { + get: "/Customer/GetPackages" + }; + }; + rpc GetCustomerPackageDetails(GetCustomerPackageDetailsRequest) returns (GetCustomerPackageDetailsResponse){ + option (google.api.http) = { + get: "/Customer/GetPackageDetails" + }; + }; + rpc CustomerPurchasePackage(CustomerPurchasePackageRequest) returns (CustomerPurchasePackageResponse){ + option (google.api.http) = { + post: "/Customer/PurchasePackage" + body: "*" + }; + }; + rpc CustomerVerifyPackagePurchase(CustomerVerifyPackagePurchaseRequest) returns (CustomerVerifyPackagePurchaseResponse){ + option (google.api.http) = { + post: "/Customer/VerifyPackagePurchase" + body: "*" + }; + }; + rpc GetCustomerPurchaseHistory(GetCustomerPurchaseHistoryRequest) returns (GetCustomerPurchaseHistoryResponse){ + option (google.api.http) = { + get: "/Customer/GetPurchaseHistory" + }; + }; } message CreateNewPackageRequest { @@ -83,6 +113,7 @@ message CreateNewPackageRequest string description = 2; string image_path = 3; int64 price = 4; + BoostCardFileModel image_file = 5; } message CreateNewPackageResponse { @@ -95,6 +126,7 @@ message UpdatePackageRequest string description = 3; string image_path = 4; int64 price = 5; + BoostCardFileModel image_file = 6; } message DeletePackageRequest { @@ -196,6 +228,7 @@ message GetUserPackageStatusResponse message InitiateBasePackagePaymentRequest { int64 user_id = 1; + string callback_url = 2; // Payment gateway callback URL } message InitiateBasePackagePaymentResponse @@ -205,6 +238,7 @@ message InitiateBasePackagePaymentResponse int64 order_id = 3; int64 transaction_id = 4; int64 amount = 5; + string payment_gateway_url = 6; } message VerifyBasePackagePaymentRequest @@ -226,3 +260,165 @@ message VerifyBasePackagePaymentResponse int64 wallet_balance = 6; int64 discount_balance = 7; } + +// ============= Customer Message Types ============= + +message GetCustomerPackagesRequest +{ + bool include_inactive = 1; + PackageTypeEnum package_type_filter = 2; +} + +message GetCustomerPackagesResponse +{ + repeated CustomerPackageModel models = 1; +} + +message GetCustomerPackageDetailsRequest +{ + int64 package_id = 1; +} + +message GetCustomerPackageDetailsResponse +{ + int64 id = 1; + string title = 2; + string description = 3; + int64 price = 4; + google.protobuf.StringValue image_path = 5; + repeated PackageFeature features = 6; + PurchaseRequirements requirements = 7; +} + +message CustomerPurchasePackageRequest +{ + int64 package_id = 1; + PurchaseMethodEnum purchase_method = 2; + string callback_url = 3; +} + +message CustomerPurchasePackageResponse +{ + bool success = 1; + string message = 2; + int64 order_id = 3; + string payment_gateway_url = 4; + string authority = 5; +} + +message CustomerVerifyPackagePurchaseRequest +{ + int64 order_id = 1; + string authority = 2; + string status = 3; +} + +message CustomerVerifyPackagePurchaseResponse +{ + bool success = 1; + string message = 2; + int64 transaction_id = 3; + string reference_code = 4; + PackagePurchaseInfo purchase_info = 5; +} + +message GetCustomerPurchaseHistoryRequest +{ + int64 user_id = 1; + messages.PaginationState pagination_state = 2; + PackageTypeEnum package_type_filter = 3; + google.protobuf.Timestamp from_date = 4; + google.protobuf.Timestamp to_date = 5; +} + +message GetCustomerPurchaseHistoryResponse +{ + messages.MetaData meta_data = 1; + repeated PackagePurchaseHistory purchases = 2; +} + +message CustomerPackageModel +{ + int64 id = 1; + string name = 2; + string description = 3; + int64 price = 4; + string currency = 5; + PackageTypeEnum package_type = 6; + bool is_available = 7; + string image_url = 8; + 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) +} + +message PackageFeature +{ + string title = 1; + string description = 2; + string icon = 3; + bool is_highlighted = 4; +} + +message PurchaseRequirements +{ + bool requires_membership = 1; + int64 minimum_wallet_balance = 2; + repeated string restrictions = 3; +} + +message PackagePurchaseInfo +{ + int64 package_id = 1; + string package_name = 2; + int64 amount_paid = 3; + google.protobuf.Timestamp purchase_date = 4; + google.protobuf.Timestamp expiry_date = 5; +} + +message PackagePurchaseHistory +{ + int64 id = 1; + int64 package_id = 2; + string package_name = 3; + int64 amount = 4; + PackageTypeEnum package_type = 5; + google.protobuf.Timestamp purchase_date = 6; + google.protobuf.Timestamp expiry_date = 7; + PaymentStatusEnum status = 8; + string status_message = 9; + string reference_code = 10; +} + +enum PackageTypeEnum +{ + PACKAGE_TYPE_BASIC = 0; + PACKAGE_TYPE_GOLDEN = 1; + PACKAGE_TYPE_PREMIUM = 2; + PACKAGE_TYPE_SPECIAL = 3; +} + +enum PurchaseMethodEnum +{ + PURCHASE_METHOD_WALLET = 0; + PURCHASE_METHOD_GATEWAY = 1; + PURCHASE_METHOD_MIXED = 2; +} + +enum PaymentStatusEnum +{ + PAYMENT_STATUS_PENDING = 0; + PAYMENT_STATUS_SUCCESS = 1; + PAYMENT_STATUS_FAILED = 2; + PAYMENT_STATUS_REFUNDED = 3; +} + +// File upload model for binary image uploads from BackOffice +message BoostCardFileModel +{ + bytes file = 1; + string file_name = 2; + string mime = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/products.proto b/src/CMSMicroservice.Protobuf/Protos/products.proto index 0c1f73c..9acbae2 100644 --- a/src/CMSMicroservice.Protobuf/Protos/products.proto +++ b/src/CMSMicroservice.Protobuf/Protos/products.proto @@ -66,6 +66,63 @@ service ProductsContract body: "*" }; }; + + // ============= Customer-specific Methods ============= + + rpc GetCustomerProducts(GetProductsRequest) returns (GetProductsResponse){ + option (google.api.http) = { + get: "/Customer/GetProduct" + }; + }; + rpc GetCustomerProductsByFilter(GetAllProductsByFilterRequest) returns (GetCustomerProductsByFilterResponse){ + option (google.api.http) = { + get: "/Customer/GetProducts" + }; + }; + + // ============= Category-Product DragDrop Methods ============= + + rpc GetProductsForCategory(GetProductsForCategoryRequest) returns (GetProductsForCategoryResponse){ + option (google.api.http) = { + get: "/GetProductsForCategory" + }; + }; + rpc GetCategories(GetCategoriesRequest) returns (GetCategoriesResponse){ + option (google.api.http) = { + get: "/GetCategories" + }; + }; + rpc UpdateProductCategories(UpdateProductCategoriesRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/UpdateProductCategories" + body: "*" + }; + }; + rpc UpdateCategoryProducts(UpdateCategoryProductsRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/UpdateCategoryProducts" + body: "*" + }; + }; + + // ============= Product Image Management ============= + + rpc AddProductImage(AddProductImageRequest) returns (AddProductImageResponse){ + option (google.api.http) = { + post: "/AddProductImage" + body: "*" + }; + }; + rpc RemoveProductImage(RemoveProductImageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/RemoveProductImage" + }; + }; + rpc GetProductGallery(GetProductGalleryRequest) returns (GetProductGalleryResponse){ + option (google.api.http) = { + get: "/GetProductGallery" + }; + }; } message CreateNewProductsRequest { @@ -83,6 +140,10 @@ message CreateNewProductsRequest int32 remaining_count = 12; // لیست شناسه دسته‌بندی‌های محصول repeated int64 category_ids = 13; + // فایل تصویر اصلی محصول (آپلود باینری) + ImageFileModel image_file = 14; + // فایل تصویر بندانگشتی محصول (آپلود باینری) + ImageFileModel thumbnail_file = 15; } message CreateNewProductsResponse { @@ -105,6 +166,10 @@ message UpdateProductsRequest int32 remaining_count = 13; // لیست شناسه دسته‌بندی‌های محصول repeated int64 category_ids = 14; + // فایل تصویر اصلی محصول (آپلود باینری) + ImageFileModel image_file = 15; + // فایل تصویر بندانگشتی محصول (آپلود باینری) + ImageFileModel thumbnail_file = 16; } message DeleteProductsRequest { @@ -129,8 +194,17 @@ message GetProductsResponse int32 sale_count = 11; int32 view_count = 12; int32 remaining_count = 13; - // لیست شناسه دسته‌بندی‌های محصول - repeated int64 category_ids = 14; + repeated ProductGalleryItem gallery = 14; + repeated ProductCategoryPath categories = 15; +} + +message ProductGalleryItem +{ + int64 product_gallery_id = 1; + int64 product_image_id = 2; + string title = 3; + string image_path = 4; + string image_thumbnail_path = 5; } message GetAllProductsByFilterRequest { @@ -154,6 +228,7 @@ message GetAllProductsByFilterFilter google.protobuf.Int32Value view_count = 12; google.protobuf.Int32Value remaining_count = 13; google.protobuf.Int64Value category_id = 14; + google.protobuf.BoolValue is_active = 15; } message GetAllProductsByFilterResponse { @@ -177,6 +252,46 @@ message GetAllProductsByFilterResponseModel int32 remaining_count = 13; // لیست شناسه دسته‌بندی‌های محصول repeated int64 category_ids = 14; + // وضعیت فعال/غیرفعال (معکوس IsDeleted) + bool is_active = 15; +} + +message GetCustomerProductsByFilterResponse +{ + messages.MetaData meta_data = 1; + repeated GetCustomerProductsByFilterResponseModel models = 2; +} + +message GetCustomerProductsByFilterResponseModel +{ + int64 id = 1; + string title = 2; + string description = 3; + string short_infomation = 4; + string full_information = 5; + int64 price = 6; + int32 discount = 7; + int32 rate = 8; + string image_path = 9; + string thumbnail_path = 10; + int32 sale_count = 11; + int32 view_count = 12; + int32 remaining_count = 13; + repeated ProductCategoryPath categories = 14; +} + +message ProductCategoryPath +{ + int64 category_id = 1; + string title = 2; + repeated CategoryNode path = 3; +} + +message CategoryNode +{ + int64 id = 1; + string title = 2; + google.protobuf.Int64Value parent_id = 3; } // Bulk Update Product Prices @@ -274,3 +389,97 @@ message ToggleProductStatusResponse int32 failed = 3; repeated BulkOperationError errors = 4; } + +// Category Product Item (for drag-drop UI) +message CategoryProductItem +{ + int64 id = 1; + string title = 2; + bool selected = 3; +} + +// Get Products for Category +message GetProductsForCategoryRequest +{ + int64 category_id = 1; +} + +message GetProductsForCategoryResponse +{ + repeated CategoryProductItem items = 1; +} + +// Category Item (for product categories drag-drop) +message CategoryItem +{ + int64 id = 1; + string title = 2; + bool selected = 3; +} + +// Get Categories for Product +message GetCategoriesRequest +{ + int64 product_id = 1; +} + +message GetCategoriesResponse +{ + repeated CategoryItem items = 1; +} + +// Update Product Categories +message UpdateProductCategoriesRequest +{ + int64 product_id = 1; + repeated int64 category_ids = 2; +} + +// Update Category Products +message UpdateCategoryProductsRequest +{ + int64 category_id = 1; + repeated int64 product_ids = 2; +} + +// Image File Model +message ImageFileModel +{ + bytes file = 1; + string mime = 2; + string file_name = 3; +} + +// Get Product Gallery +message GetProductGalleryRequest +{ + int64 product_id = 1; +} + +message GetProductGalleryResponse +{ + repeated ProductGalleryItem items = 1; +} + +// Add Product Image +message AddProductImageRequest +{ + int64 product_id = 1; + string title = 2; + ImageFileModel image_file = 3; +} + +message AddProductImageResponse +{ + int64 product_gallery_id = 1; + int64 product_image_id = 2; + string title = 3; + string image_path = 4; + string image_thumbnail_path = 5; +} + +// Remove Product Image +message RemoveProductImageRequest +{ + int64 product_gallery_id = 1; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/sitepage.proto b/src/CMSMicroservice.Protobuf/Protos/sitepage.proto new file mode 100644 index 0000000..4ba416c --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/sitepage.proto @@ -0,0 +1,195 @@ +syntax = "proto3"; + +package sitepage; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.SitePage"; + +service SitePageContract +{ + rpc GetSitePage(GetSitePageRequest) returns (GetSitePageResponse){ + option (google.api.http) = { get: "/GetSitePage" }; + }; + rpc GetSitePageByKey(GetSitePageByKeyRequest) returns (GetSitePageResponse){ + option (google.api.http) = { get: "/GetSitePageByKey" }; + }; + rpc CreateSitePage(CreateSitePageRequest) returns (CreateSitePageResponse){ + option (google.api.http) = { post: "/CreateSitePage" body: "*" }; + }; + rpc UpdateSitePage(UpdateSitePageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateSitePage" body: "*" }; + }; + rpc DeleteSitePage(DeleteSitePageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteSitePage" body: "*" }; + }; + rpc GetAllSitePages(GetAllSitePagesRequest) returns (GetAllSitePagesResponse){ + option (google.api.http) = { get: "/GetAllSitePages" }; + }; + rpc CreateSitePageSection(CreateSitePageSectionRequest) returns (CreateSitePageSectionResponse){ + option (google.api.http) = { post: "/CreateSitePageSection" body: "*" }; + }; + rpc UpdateSitePageSection(UpdateSitePageSectionRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateSitePageSection" body: "*" }; + }; + rpc DeleteSitePageSection(DeleteSitePageSectionRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteSitePageSection" body: "*" }; + }; + rpc ReorderSitePageSections(ReorderSitePageSectionsRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/ReorderSitePageSections" body: "*" }; + }; +} + +// ── Get Site Page ── +message GetSitePageRequest +{ + int64 id = 1; +} +message GetSitePageByKeyRequest +{ + string page_key = 1; +} +message GetSitePageResponse +{ + int64 id = 1; + string page_key = 2; + string title = 3; + google.protobuf.StringValue meta_description = 4; + google.protobuf.StringValue hero_title = 5; + google.protobuf.StringValue hero_subtitle = 6; + google.protobuf.StringValue hero_image_path = 7; + bool is_active = 8; + repeated SitePageSectionItem sections = 9; +} +message SitePageSectionItem +{ + int64 id = 1; + string section_key = 2; + string title = 3; + google.protobuf.StringValue subtitle = 4; + google.protobuf.StringValue html_content = 5; + google.protobuf.StringValue icon_name = 6; + google.protobuf.StringValue image_path = 7; + google.protobuf.StringValue image_thumbnail_path = 8; + int32 sort_order = 9; + bool is_active = 10; + google.protobuf.StringValue extra_data = 11; +} + +// ── Update Site Page ── +message UpdateSitePageRequest +{ + int64 id = 1; + string title = 2; + google.protobuf.StringValue meta_description = 3; + google.protobuf.StringValue hero_title = 4; + google.protobuf.StringValue hero_subtitle = 5; + google.protobuf.StringValue hero_image_path = 6; + bool is_active = 7; + SitePageImageFileModel image_file = 8; +} + +// ── Get All ── +message GetAllSitePagesRequest {} +message GetAllSitePagesResponse +{ + repeated SitePageSummary pages = 1; +} +message SitePageSummary +{ + int64 id = 1; + string page_key = 2; + string title = 3; + bool is_active = 4; + int32 section_count = 5; + google.protobuf.Timestamp last_modified = 6; +} + +// ── Create Section ── +message CreateSitePageSectionRequest +{ + int64 site_page_id = 1; + string section_key = 2; + string title = 3; + google.protobuf.StringValue subtitle = 4; + google.protobuf.StringValue html_content = 5; + google.protobuf.StringValue icon_name = 6; + google.protobuf.StringValue image_path = 7; + google.protobuf.StringValue image_thumbnail_path = 8; + int32 sort_order = 9; + google.protobuf.StringValue extra_data = 10; + SitePageImageFileModel image_file = 11; +} +message CreateSitePageSectionResponse +{ + int64 id = 1; +} + +// ── Update Section ── +message UpdateSitePageSectionRequest +{ + int64 id = 1; + string section_key = 2; + string title = 3; + google.protobuf.StringValue subtitle = 4; + google.protobuf.StringValue html_content = 5; + google.protobuf.StringValue icon_name = 6; + google.protobuf.StringValue image_path = 7; + google.protobuf.StringValue image_thumbnail_path = 8; + int32 sort_order = 9; + bool is_active = 10; + google.protobuf.StringValue extra_data = 11; + SitePageImageFileModel image_file = 12; +} + +// ── Delete Section ── +message DeleteSitePageSectionRequest +{ + int64 id = 1; +} + +// ── Reorder Sections ── +message ReorderSitePageSectionsRequest +{ + repeated SectionSortItem items = 1; +} +message SectionSortItem +{ + int64 id = 1; + int32 sort_order = 2; +} + +// ── Create Site Page ── +message CreateSitePageRequest +{ + string page_key = 1; + string title = 2; + google.protobuf.StringValue meta_description = 3; + google.protobuf.StringValue hero_title = 4; + google.protobuf.StringValue hero_subtitle = 5; + bool is_active = 6; + SitePageImageFileModel image_file = 7; +} +message CreateSitePageResponse +{ + int64 id = 1; +} + +// ── Delete Site Page ── +message DeleteSitePageRequest +{ + int64 id = 1; +} + +// ── File upload model for binary image uploads from BackOffice ── +message SitePageImageFileModel +{ + bytes file = 1; + string mime = 2; + string file_name = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/transactions.proto b/src/CMSMicroservice.Protobuf/Protos/transactions.proto index ae75cad..2255bac 100644 --- a/src/CMSMicroservice.Protobuf/Protos/transactions.proto +++ b/src/CMSMicroservice.Protobuf/Protos/transactions.proto @@ -55,6 +55,31 @@ service TransactionsContract body: "*" }; }; + + // ============= Customer-specific Methods ============= + + rpc GetCustomerTransaction(GetCustomerTransactionRequest) returns (GetCustomerTransactionResponse){ + option (google.api.http) = { + get: "/Customer/GetTransaction" + }; + }; + rpc GetCustomerTransactionsByFilter(GetCustomerTransactionsByFilterRequest) returns (GetCustomerTransactionsByFilterResponse){ + option (google.api.http) = { + get: "/Customer/GetTransactions" + }; + }; + rpc CustomerPaymentRequest(CustomerPaymentRequestRequest) returns (CustomerPaymentRequestResponse){ + option (google.api.http) = { + post: "/Customer/PaymentRequest" + body: "*" + }; + }; + rpc CustomerPaymentVerification(CustomerPaymentVerificationRequest) returns (CustomerPaymentVerificationResponse){ + option (google.api.http) = { + post: "/Customer/PaymentVerification" + body: "*" + }; + }; } message CreateNewTransactionsRequest { @@ -191,3 +216,131 @@ message RefundTransactionResponse int64 refund_amount = 3; string message = 4; } + +// ============= Customer-specific Messages ============= + +// Customer Transaction Models +message GetCustomerTransactionRequest +{ + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue authority = 2; +} + +message GetCustomerTransactionResponse +{ + int64 id = 1; + string merchant_id = 2; + int64 amount = 3; + string callback_url = 4; + string description = 5; + google.protobuf.StringValue mobile = 6; + google.protobuf.StringValue email = 7; + google.protobuf.Int32Value request_status_code = 8; + google.protobuf.StringValue request_status_message = 9; + google.protobuf.StringValue authority = 10; + google.protobuf.StringValue fee_type = 11; + google.protobuf.Int64Value fee = 12; + CurrencyEnum currency = 13; + bool payment_status = 14; + google.protobuf.Int32Value verification_status_code = 15; + google.protobuf.StringValue verification_status_message = 16; + google.protobuf.StringValue card_hash = 17; + google.protobuf.StringValue card_pan = 18; + google.protobuf.StringValue ref_id = 19; + google.protobuf.StringValue order_id = 20; + TransactionTypeEnum type = 21; +} + +// Customer Filter Messages +message GetCustomerTransactionsByFilterRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetCustomerTransactionsByFilterFilter filter = 3; +} + +message GetCustomerTransactionsByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.Int64Value amount = 2; + google.protobuf.StringValue description = 3; + google.protobuf.StringValue authority = 4; + google.protobuf.BoolValue payment_status = 5; + google.protobuf.StringValue ref_id = 6; + google.protobuf.StringValue order_id = 7; + CurrencyEnum currency = 8; + TransactionTypeEnum type = 9; +} + +message GetCustomerTransactionsByFilterResponse +{ + messages.MetaData meta_data = 1; + repeated GetCustomerTransactionsByFilterResponseModel models = 2; +} + +message GetCustomerTransactionsByFilterResponseModel +{ + int64 id = 1; + string merchant_id = 2; + int64 amount = 3; + string callback_url = 4; + string description = 5; + google.protobuf.StringValue mobile = 6; + google.protobuf.StringValue email = 7; + google.protobuf.StringValue authority = 8; + google.protobuf.Int64Value fee = 9; + CurrencyEnum currency = 10; + bool payment_status = 11; + google.protobuf.StringValue card_hash = 12; + google.protobuf.StringValue card_pan = 13; + google.protobuf.StringValue ref_id = 14; + google.protobuf.StringValue order_id = 15; + TransactionTypeEnum type = 16; +} + +// Customer Payment Request/Verification +message CustomerPaymentRequestRequest +{ + int64 amount = 1; + string callback_url = 2; + google.protobuf.StringValue description = 3; + google.protobuf.StringValue mobile = 4; + google.protobuf.StringValue email = 5; + CurrencyEnum currency = 6; + TransactionTypeEnum type = 7; + google.protobuf.StringValue order_id = 8; +} + +message CustomerPaymentRequestResponse +{ + string payment_g_w_url = 1; +} + +message CustomerPaymentVerificationRequest +{ + string authority = 1; + string status = 2; +} + +message CustomerPaymentVerificationResponse +{ + int64 id = 1; + bool payment_status = 2; + string message = 3; + google.protobuf.StringValue ref_id = 4; + google.protobuf.StringValue order_id = 5; + google.protobuf.Int32Value verification_status_code = 6; +} + +// Enums for Customer API +enum CurrencyEnum +{ + IRR = 0; + IRT = 1; +} + +enum TransactionTypeEnum +{ + Real = 0; + Sandbox = 1; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/user.proto b/src/CMSMicroservice.Protobuf/Protos/user.proto index ad410c6..367470c 100644 --- a/src/CMSMicroservice.Protobuf/Protos/user.proto +++ b/src/CMSMicroservice.Protobuf/Protos/user.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package user; -import "public_messages.proto"; +import "City.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/wrappers.proto"; import "google/protobuf/duration.proto"; @@ -67,6 +67,72 @@ service UserContract body: "*" }; }; + rpc CreateNewOtpToken(CreateNewOtpTokenRequest) returns (CreateNewOtpTokenResponse){ + option (google.api.http) = { + post: "/Customer/CreateNewOtpToken" + body: "*" + }; + }; + rpc VerifyOtpToken(VerifyOtpTokenRequest) returns (VerifyOtpTokenResponse){ + option (google.api.http) = { + post: "/Customer/VerifyOtpToken" + body: "*" + }; + }; + rpc AcceptContract(AcceptContractRequest) returns (AcceptContractResponse){ + option (google.api.http) = { + post: "/Customer/AcceptContract" + body: "*" + }; + }; + rpc GetUserForCustomer(GetUserForCustomerRequest) returns (GetUserForCustomerResponse){ + option (google.api.http) = { + get: "/Customer/GetUser" + + }; + }; + rpc UpdateCustomerProfile(UpdateCustomerProfileRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/Customer/UpdateProfile" + body: "*" + }; + }; + rpc GetCustomerProfile(GetCustomerProfileRequest) returns (GetCustomerProfileResponse){ + option (google.api.http) = { + get: "/Customer/GetProfile" + + }; + }; + rpc ChangeCustomerPassword(ChangeCustomerPasswordRequest) returns (ChangeCustomerPasswordResponse){ + option (google.api.http) = { + post: "/Customer/ChangePassword" + body: "*" + }; + }; + rpc GetCustomerReferrals(GetCustomerReferralsRequest) returns (GetCustomerReferralsResponse){ + option (google.api.http) = { + get: "/Customer/GetReferrals" + + }; + }; + rpc UploadCustomerAvatar(UploadCustomerAvatarRequest) returns (UploadCustomerAvatarResponse){ + option (google.api.http) = { + post: "/Customer/UploadAvatar" + body: "*" + }; + }; + rpc GetCustomerSettings(GetCustomerSettingsRequest) returns (GetCustomerSettingsResponse){ + option (google.api.http) = { + get: "/Customer/GetSettings" + + }; + }; + rpc UpdateCustomerSettings(UpdateCustomerSettingsRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/Customer/UpdateSettings" + body: "*" + }; + }; } message CreateNewUserRequest { @@ -129,7 +195,7 @@ message GetUserResponse } message GetAllUserByFilterRequest { - messages.PaginationState pagination_state = 1; + city.PaginationState pagination_state = 1; google.protobuf.StringValue sort_by = 2; GetAllUserByFilterFilter filter = 3; } @@ -153,7 +219,7 @@ message GetAllUserByFilterFilter } message GetAllUserByFilterResponse { - messages.MetaData meta_data = 1; + city.MetaData meta_data = 1; repeated GetAllUserByFilterResponseModel models = 2; } message GetAllUserByFilterResponseModel @@ -207,3 +273,194 @@ message RefreshTokenResponse bool success = 2; string message = 3; } +message CreateNewOtpTokenRequest +{ + string mobile = 1; + string purpose = 2; + google.protobuf.StringValue sign_guid = 3; +} +message CreateNewOtpTokenResponse +{ + bool success = 1; + string message = 2; + int32 remaining_attempts = 3; + int32 remaining_seconds = 4; +} +message VerifyOtpTokenRequest +{ + string mobile = 1; + string purpose = 2; + string code = 3; + google.protobuf.StringValue parent_referral_code = 4; +} +message VerifyOtpTokenResponse +{ + bool success = 1; + string message = 2; + google.protobuf.StringValue token = 3; + int32 remaining_attempts = 4; +} +message AcceptContractRequest +{ + string code = 1; + string contract_html = 2; + string sign_guid = 3; +} +message AcceptContractResponse +{ + string token = 1; +} +message GetUserForCustomerRequest +{ + // Empty request - user identified by token +} +message GetUserForCustomerResponse +{ + int64 id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + string mobile = 4; + google.protobuf.StringValue email = 5; + google.protobuf.StringValue national_code = 6; + google.protobuf.StringValue avatar_path = 7; + google.protobuf.Int64Value parent_id = 8; + string referral_code = 9; + bool is_mobile_verified = 10; + google.protobuf.Timestamp mobile_verified_at = 11; + bool email_notifications = 12; + bool sms_notifications = 13; + bool push_notifications = 14; + google.protobuf.Timestamp birth_date = 15; + string token = 16; // JWT token for authentication refresh +} + +// ============= Customer Profile Messages ============= + +message UpdateCustomerProfileRequest +{ + google.protobuf.StringValue first_name = 1; + google.protobuf.StringValue last_name = 2; + google.protobuf.StringValue email = 3; + google.protobuf.StringValue national_code = 4; + google.protobuf.Timestamp birth_date = 5; +} + +message GetCustomerProfileRequest +{ + // Empty request - user identified by token +} + +message GetCustomerProfileResponse +{ + int64 id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + string mobile = 4; + google.protobuf.StringValue email = 5; + google.protobuf.StringValue national_code = 6; + google.protobuf.StringValue avatar_path = 7; + google.protobuf.Int64Value parent_id = 8; + string referral_code = 9; + bool is_mobile_verified = 10; + google.protobuf.Timestamp mobile_verified_at = 11; + bool email_notifications = 12; + bool sms_notifications = 13; + bool push_notifications = 14; + google.protobuf.Timestamp birth_date = 15; + string full_name = 16; + int32 profile_completion_percentage = 17; +} + +message ChangeCustomerPasswordRequest +{ + string current_password = 1; + string new_password = 2; + string confirm_password = 3; +} + +message ChangeCustomerPasswordResponse +{ + bool success = 1; + string message = 2; +} + +// ============= Customer Referrals Messages ============= + +message GetCustomerReferralsRequest +{ + city.PaginationState pagination_state = 1; + google.protobuf.StringValue status_filter = 2; // ACTIVE, INACTIVE, ALL +} + +message GetCustomerReferralsResponse +{ + city.MetaData meta_data = 1; + repeated CustomerReferralModel referrals = 2; + CustomerReferralStats stats = 3; +} + +message CustomerReferralModel +{ + int64 id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + string mobile = 4; + google.protobuf.Timestamp join_date = 5; + bool is_active = 6; + string status_message = 7; + int32 level = 8; + int64 total_commission = 9; +} + +message CustomerReferralStats +{ + int32 total_referrals = 1; + int32 active_referrals = 2; + int64 total_commission_earned = 3; + int64 this_month_commission = 4; +} + +// ============= Customer Avatar Messages ============= + +message UploadCustomerAvatarRequest +{ + string file_name = 1; + string file_mime_type = 2; + bytes file_data = 3; +} + +message UploadCustomerAvatarResponse +{ + bool success = 1; + string message = 2; + google.protobuf.StringValue avatar_url = 3; +} + +// ============= Customer Settings Messages ============= + +message GetCustomerSettingsRequest +{ + // Empty request - user identified by token +} + +message GetCustomerSettingsResponse +{ + bool email_notifications = 1; + bool sms_notifications = 2; + bool push_notifications = 3; + bool marketing_notifications = 4; + string preferred_language = 5; + string time_zone = 6; + bool two_factor_auth_enabled = 7; +} + +message UpdateCustomerSettingsRequest +{ + bool email_notifications = 1; + bool sms_notifications = 2; + bool push_notifications = 3; + bool marketing_notifications = 4; + google.protobuf.StringValue preferred_language = 5; + google.protobuf.StringValue time_zone = 6; + google.protobuf.BoolValue two_factor_auth_enabled = 7; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/useraddress.proto b/src/CMSMicroservice.Protobuf/Protos/useraddress.proto index 7dd1944..5271b61 100644 --- a/src/CMSMicroservice.Protobuf/Protos/useraddress.proto +++ b/src/CMSMicroservice.Protobuf/Protos/useraddress.proto @@ -49,6 +49,38 @@ service UserAddressContract body: "*" }; }; + + // ============= Customer-specific Methods ============= + + rpc GetCustomerAddresses(GetCustomerAddressesRequest) returns (GetCustomerAddressesResponse){ + option (google.api.http) = { + get: "/Customer/GetAddresses" + }; + }; + rpc CreateCustomerAddress(CreateCustomerAddressRequest) returns (CreateCustomerAddressResponse){ + option (google.api.http) = { + post: "/Customer/CreateAddress" + body: "*" + }; + }; + rpc UpdateCustomerAddress(UpdateCustomerAddressRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/Customer/UpdateAddress" + body: "*" + }; + }; + rpc DeleteCustomerAddress(DeleteCustomerAddressRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/Customer/DeleteAddress" + body: "*" + }; + }; + rpc SetCustomerDefaultAddress(SetCustomerDefaultAddressRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/Customer/SetDefaultAddress" + body: "*" + }; + }; } message CreateNewUserAddressRequest { @@ -126,3 +158,59 @@ message SetAddressAsDefaultRequest { int64 id = 1; } + +// ============= Customer Messages ============= + +message GetCustomerAddressesRequest +{ + // user_id will be extracted from JWT token +} +message GetCustomerAddressesResponse +{ + repeated CustomerAddressModel models = 1; +} +message CustomerAddressModel +{ + int64 id = 1; + string title = 2; + string address = 3; + string postal_code = 4; + bool is_default = 5; + int64 city_id = 6; + string city_name = 7; + string province_name = 8; +} +message CreateCustomerAddressRequest +{ + string title = 1; + string address = 2; + string postal_code = 3; + bool is_default = 4; + int64 city_id = 5; + // user_id will be extracted from JWT token +} +message CreateCustomerAddressResponse +{ + int64 id = 1; + string message = 2; +} +message UpdateCustomerAddressRequest +{ + int64 id = 1; + string title = 2; + string address = 3; + string postal_code = 4; + bool is_default = 5; + int64 city_id = 6; + // user_id will be extracted from JWT token +} +message DeleteCustomerAddressRequest +{ + int64 id = 1; + // user_id will be extracted from JWT token +} +message SetCustomerDefaultAddressRequest +{ + int64 id = 1; + // user_id will be extracted from JWT token +} diff --git a/src/CMSMicroservice.Protobuf/Protos/usercarts.proto b/src/CMSMicroservice.Protobuf/Protos/usercarts.proto index a162a6b..5e0894e 100644 --- a/src/CMSMicroservice.Protobuf/Protos/usercarts.proto +++ b/src/CMSMicroservice.Protobuf/Protos/usercarts.proto @@ -13,73 +13,158 @@ option csharp_namespace = "CMSMicroservice.Protobuf.Protos.UserCarts"; service UserCartsContract { - rpc CreateNewUserCarts(CreateNewUserCartsRequest) returns (CreateNewUserCartsResponse){ + // ============= Admin Methods ============= + + rpc AddNewUserCart(AddNewUserCartRequest) returns (AddNewUserCartResponse){ option (google.api.http) = { - post: "/CreateNewUserCarts" + post: "/AddNewUserCart" body: "*" }; }; - rpc UpdateUserCarts(UpdateUserCartsRequest) returns (google.protobuf.Empty){ + rpc UpdateUserCart(UpdateUserCartRequest) returns (google.protobuf.Empty){ option (google.api.http) = { - put: "/UpdateUserCarts" + put: "/UpdateUserCart" body: "*" }; }; - rpc DeleteUserCarts(DeleteUserCartsRequest) returns (google.protobuf.Empty){ + rpc DeleteUserCart(DeleteUserCartRequest) returns (google.protobuf.Empty){ option (google.api.http) = { - delete: "/DeleteUserCarts" + delete: "/DeleteUserCart" body: "*" }; }; - rpc GetUserCarts(GetUserCartsRequest) returns (GetUserCartsResponse){ + rpc GetUserCart(GetUserCartRequest) returns (GetUserCartResponse){ option (google.api.http) = { - get: "/GetUserCarts" - + get: "/GetUserCart" }; }; rpc GetAllUserCartsByFilter(GetAllUserCartsByFilterRequest) returns (GetAllUserCartsByFilterResponse){ option (google.api.http) = { get: "/GetAllUserCartsByFilter" - }; }; - rpc ClearCart(ClearCartRequest) returns (ClearCartResponse){ + + // ============= Customer-specific Methods ============= + + rpc AddNewUserCartForCustomer(AddNewUserCartForCustomerRequest) returns (AddNewUserCartForCustomerResponse){ option (google.api.http) = { - post: "/ClearCart" + post: "/Customer/AddToCart" body: "*" }; }; + rpc UpdateUserCartForCustomer(UpdateUserCartForCustomerRequest) returns (UpdateUserCartForCustomerResponse){ + option (google.api.http) = { + put: "/Customer/UpdateCart" + body: "*" + }; + }; + rpc RemoveUserCartForCustomer(RemoveUserCartForCustomerRequest) returns (RemoveUserCartForCustomerResponse){ + option (google.api.http) = { + delete: "/Customer/RemoveFromCart" + body: "*" + }; + }; + rpc GetCustomerCart(GetUserCartForCustomerRequest) returns (GetUserCartForCustomerResponse){ + option (google.api.http) = { + get: "/Customer/GetCart" + }; + }; } -message CreateNewUserCartsRequest +// ============= Admin Messages ============= + +message AddNewUserCartRequest { int64 product_id = 1; int64 user_id = 2; int32 count = 3; } -message CreateNewUserCartsResponse +message AddNewUserCartResponse { int64 id = 1; } -message UpdateUserCartsRequest +message UpdateUserCartRequest { int64 id = 1; + int64 user_cart_id = 11; // Alias for frontend compatibility int32 count = 2; } -message DeleteUserCartsRequest +message DeleteUserCartRequest { int64 id = 1; } -message GetUserCartsRequest +message GetUserCartRequest { int64 id = 1; } -message GetUserCartsResponse +message GetUserCartResponse { int64 id = 1; int64 product_id = 2; int64 user_id = 3; int32 count = 4; } + +// ============= Customer Messages ============= + +message AddNewUserCartForCustomerRequest +{ + int64 product_id = 1; + int32 count = 2; + // user_id will be extracted from JWT token +} +message AddNewUserCartForCustomerResponse +{ + int64 id = 1; + string message = 2; + bool success = 3; +} +message UpdateUserCartForCustomerRequest +{ + int64 cart_item_id = 1; + int32 count = 2; + // user_id will be extracted from JWT token +} +message UpdateUserCartForCustomerResponse +{ + string message = 1; + bool success = 2; +} +message RemoveUserCartForCustomerRequest +{ + int64 cart_item_id = 1; + // user_id will be extracted from JWT token +} +message RemoveUserCartForCustomerResponse +{ + string message = 1; + bool success = 2; +} +message GetUserCartForCustomerRequest +{ + // user_id will be extracted from JWT token + // pagination could be added if needed +} +message GetUserCartForCustomerResponse +{ + repeated UserCartItem models = 1; + int64 total_price = 2; + int32 total_items_count = 3; + string message = 4; +} +message UserCartItem +{ + int64 id = 1; + int64 product_id = 2; + string product_title = 3; + string product_short_information = 4; + string product_short_infomation = 14; // Alias for typo compatibility + int64 product_price = 5; + int32 product_discount = 6; + string product_thumbnail_path = 7; + int32 count = 8; + int64 total_item_price = 9; + google.protobuf.Timestamp created = 10; // Creation timestamp for frontend +} message GetAllUserCartsByFilterRequest { messages.PaginationState pagination_state = 1; @@ -112,15 +197,4 @@ message GetAllUserCartsByFilterResponseModel google.protobuf.Timestamp created = 10; } -// ClearCart Messages -message ClearCartRequest -{ - int64 user_id = 1; -} -message ClearCartResponse -{ - int64 user_id = 1; - int32 removed_items_count = 2; - string message = 3; -} diff --git a/src/CMSMicroservice.Protobuf/Protos/userorder.proto b/src/CMSMicroservice.Protobuf/Protos/userorder.proto index 1c74810..4ca6ce0 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userorder.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userorder.proto @@ -79,6 +79,61 @@ service UserOrderContract get: "/CalculateOrderPV" }; }; + + // ============= Customer-specific Methods ============= + + rpc CreateNewOrderForCustomer(CreateNewUserOrderRequest) returns (CreateNewUserOrderResponse){ + option (google.api.http) = { + post: "/Customer/CreateOrder" + body: "*" + }; + }; + rpc SubmitOrderForCustomer(SubmitShopBuyOrderRequest) returns (SubmitShopBuyOrderResponse){ + option (google.api.http) = { + post: "/Customer/SubmitOrder" + body: "*" + }; + }; + rpc GetCustomerOrders(GetAllUserOrderByFilterRequest) returns (GetAllUserOrderByFilterResponse){ + option (google.api.http) = { + get: "/Customer/GetOrders" + + }; + }; + rpc GetCustomerOrder(GetUserOrderRequest) returns (GetUserOrderResponse){ + option (google.api.http) = { + get: "/Customer/GetOrder" + + }; + }; + rpc CustomerCancelOrder(CustomerCancelOrderRequest) returns (CustomerCancelOrderResponse){ + option (google.api.http) = { + post: "/Customer/CancelOrder" + body: "*" + }; + }; + rpc GetCustomerOrderHistory(GetCustomerOrderHistoryRequest) returns (GetCustomerOrderHistoryResponse){ + option (google.api.http) = { + get: "/Customer/GetOrderHistory" + }; + }; + rpc CustomerTrackOrder(CustomerTrackOrderRequest) returns (CustomerTrackOrderResponse){ + option (google.api.http) = { + get: "/Customer/TrackOrder" + }; + }; + rpc CustomerReorderPreviousOrder(CustomerReorderRequest) returns (CustomerReorderResponse){ + option (google.api.http) = { + post: "/Customer/Reorder" + body: "*" + }; + }; + + rpc GetVATRate(google.protobuf.Empty) returns (GetVATRateResponse){ + option (google.api.http) = { + get: "/Customer/GetVATRate" + }; + }; } message CreateNewUserOrderRequest { @@ -214,6 +269,9 @@ message GetAllUserOrderByFilterFilter { messages.DeliveryStatus delivery_status = 10; } + // فیلتر بازه تاریخ + google.protobuf.Timestamp from_date = 11; + google.protobuf.Timestamp to_date = 12; } message GetAllUserOrderByFilterResponse { @@ -348,6 +406,115 @@ message ApplyDiscountToOrderResponse int64 final_amount = 5; } +// ============= Customer Message Types ============= + +message CustomerCancelOrderRequest +{ + int64 order_id = 1; + string cancellation_reason = 2; +} + +message CustomerCancelOrderResponse +{ + bool success = 1; + string message = 2; + int64 refund_amount = 3; + string refund_transaction_id = 4; +} + +message GetCustomerOrderHistoryRequest +{ + int64 user_id = 1; + messages.PaginationState pagination_state = 2; + OrderStatusEnum status_filter = 3; + google.protobuf.Timestamp from_date = 4; + google.protobuf.Timestamp to_date = 5; +} + +message GetCustomerOrderHistoryResponse +{ + messages.MetaData meta_data = 1; + repeated CustomerOrderModel orders = 2; +} + +message CustomerTrackOrderRequest +{ + int64 order_id = 1; +} + +message CustomerTrackOrderResponse +{ + CustomerOrderModel order = 1; + repeated OrderStatusHistory status_history = 2; + DeliveryTrackingInfo delivery_info = 3; +} + +message CustomerReorderRequest +{ + int64 original_order_id = 1; + bool use_current_prices = 2; +} + +message CustomerReorderResponse +{ + bool success = 1; + string message = 2; + int64 new_order_id = 3; + int64 total_amount = 4; +} + +message CustomerOrderModel +{ + int64 id = 1; + int64 amount = 2; + int64 package_id = 3; + string package_name = 4; + OrderStatusEnum status = 5; + string status_message = 6; + google.protobuf.Timestamp order_date = 7; + google.protobuf.Timestamp delivery_date = 8; + string tracking_code = 9; + int32 items_count = 10; + bool can_cancel = 11; + bool can_reorder = 12; +} + +message OrderStatusHistory +{ + OrderStatusEnum status = 1; + string status_message = 2; + google.protobuf.Timestamp changed_at = 3; + string changed_by = 4; +} + +message DeliveryTrackingInfo +{ + string tracking_code = 1; + string courier_name = 2; + string estimated_delivery = 3; + string current_location = 4; + repeated DeliveryStep delivery_steps = 5; +} + +message DeliveryStep +{ + string step_name = 1; + string step_description = 2; + google.protobuf.Timestamp step_time = 3; + bool is_completed = 4; +} + +enum OrderStatusEnum +{ + ORDER_STATUS_PENDING = 0; + ORDER_STATUS_CONFIRMED = 1; + ORDER_STATUS_PROCESSING = 2; + ORDER_STATUS_SHIPPED = 3; + ORDER_STATUS_DELIVERED = 4; + ORDER_STATUS_CANCELLED = 5; + ORDER_STATUS_REFUNDED = 6; +} + message CalculateOrderPVRequest { int64 order_id = 1; @@ -368,3 +535,11 @@ message ProductPVDto int64 unit_pv = 4; int64 total_pv = 5; } + +// GetVATRate - for frontend customer display +message GetVATRateResponse +{ + double vat_rate = 1; + int32 vat_percentage = 2; + bool is_enabled = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto index 1e98a3f..76c31f4 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto @@ -43,6 +43,37 @@ service UserWalletContract }; }; + + // ============= Customer-specific Methods ============= + + rpc GetCustomerWallet(google.protobuf.Empty) returns (GetCustomerWalletResponse){ + option (google.api.http) = { + get: "/Customer/GetWallet" + }; + }; + rpc GetCustomerWalletChangeLog(GetCustomerWalletChangeLogRequest) returns (GetCustomerWalletChangeLogResponse){ + option (google.api.http) = { + post: "/Customer/GetWalletChangeLog" + body: "*" + }; + }; + rpc CustomerWithdrawBalance(CustomerWithdrawBalanceRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/Customer/WithdrawBalance" + body: "*" + }; + }; + rpc GetCustomerWithdrawals(GetCustomerWithdrawalsRequest) returns (GetCustomerWithdrawalsResponse){ + option (google.api.http) = { + post: "/Customer/GetWithdrawals" + body: "*" + }; + }; + rpc GetCustomerWithdrawalSettings(google.protobuf.Empty) returns (GetCustomerWithdrawalSettingsResponse){ + option (google.api.http) = { + get: "/Customer/GetWithdrawalSettings" + }; + }; } message CreateNewUserWalletRequest { @@ -101,3 +132,72 @@ message GetAllUserWalletByFilterResponseModel int64 balance = 3; int64 network_balance = 4; } + +// ============= Customer-specific Messages ============= + +message GetCustomerWalletResponse +{ + int64 balance = 1; + int64 network_balance = 2; + int64 discount_balance = 3; +} + +message GetCustomerWalletChangeLogRequest +{ + google.protobuf.Int64Value reference_id = 1; + google.protobuf.BoolValue is_increase = 2; +} + +message GetCustomerWalletChangeLogResponse +{ + messages.MetaData meta_data = 1; + repeated CustomerWalletChangeLogModel models = 2; +} + +message CustomerWalletChangeLogModel +{ + int64 current_balance = 1; + int64 change_value = 2; + int64 current_network_balance = 3; + int64 change_nerwork_value = 4; + int64 current_discount_balance = 5; + int64 change_discount_value = 6; + bool is_increase = 7; + google.protobuf.Int64Value refrence_id = 8; + google.protobuf.Timestamp created_at = 9; +} + +message CustomerWithdrawBalanceRequest +{ + int64 payout_id = 1; + int32 withdrawal_method = 2; // 0: Cash, 1: Diamond + google.protobuf.StringValue iban_number = 3; +} + +message GetCustomerWithdrawalsRequest +{ + google.protobuf.Int32Value status = 1; +} + +message GetCustomerWithdrawalsResponse +{ + messages.MetaData meta_data = 1; + repeated CustomerWithdrawalModel models = 2; +} + +message CustomerWithdrawalModel +{ + int64 id = 1; + int64 week_definition_id = 2; + string week_display_name = 3; + int64 total_amount = 4; + int32 status = 5; + google.protobuf.Int32Value withdrawal_method = 6; + string iban_number = 7; + google.protobuf.Timestamp created = 8; +} + +message GetCustomerWithdrawalSettingsResponse +{ + int64 min_withdrawal_amount = 1; +} \ No newline at end of file diff --git a/src/CMSMicroservice.Protobuf/Validator/User/VerifyOtpTokenRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/User/VerifyOtpTokenRequestValidator.cs new file mode 100644 index 0000000..534e370 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Validator/User/VerifyOtpTokenRequestValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using CMSMicroservice.Protobuf.Protos.User; +namespace CMSMicroservice.Protobuf.Validator.User; + +public class VerifyOtpTokenRequestValidator : AbstractValidator +{ + public VerifyOtpTokenRequestValidator() + { + RuleFor(model => model.Mobile) + .NotEmpty(); + RuleFor(model => model.Purpose) + .NotEmpty(); + RuleFor(model => model.Code) + .NotEmpty(); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((VerifyOtpTokenRequest)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/UserCarts/CreateNewUserCartsRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserCarts/AddNewUserCartRequestValidator.cs similarity index 64% rename from src/CMSMicroservice.Protobuf/Validator/UserCarts/CreateNewUserCartsRequestValidator.cs rename to src/CMSMicroservice.Protobuf/Validator/UserCarts/AddNewUserCartRequestValidator.cs index ed45011..17fad3d 100644 --- a/src/CMSMicroservice.Protobuf/Validator/UserCarts/CreateNewUserCartsRequestValidator.cs +++ b/src/CMSMicroservice.Protobuf/Validator/UserCarts/AddNewUserCartRequestValidator.cs @@ -2,9 +2,9 @@ using FluentValidation; using CMSMicroservice.Protobuf.Protos.UserCarts; namespace CMSMicroservice.Protobuf.Validator.UserCarts; -public class CreateNewUserCartsRequestValidator : AbstractValidator +public class AddNewUserCartRequestValidator : AbstractValidator { - public CreateNewUserCartsRequestValidator() + public AddNewUserCartRequestValidator() { RuleFor(model => model.ProductId) .NotNull(); @@ -15,7 +15,7 @@ public class CreateNewUserCartsRequestValidator : AbstractValidator>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((CreateNewUserCartsRequest)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((AddNewUserCartRequest)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/UserCarts/DeleteUserCartsRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserCarts/DeleteUserCartRequestValidator.cs similarity index 67% rename from src/CMSMicroservice.Protobuf/Validator/UserCarts/DeleteUserCartsRequestValidator.cs rename to src/CMSMicroservice.Protobuf/Validator/UserCarts/DeleteUserCartRequestValidator.cs index a61b208..4b5a377 100644 --- a/src/CMSMicroservice.Protobuf/Validator/UserCarts/DeleteUserCartsRequestValidator.cs +++ b/src/CMSMicroservice.Protobuf/Validator/UserCarts/DeleteUserCartRequestValidator.cs @@ -2,16 +2,16 @@ using FluentValidation; using CMSMicroservice.Protobuf.Protos.UserCarts; namespace CMSMicroservice.Protobuf.Validator.UserCarts; -public class DeleteUserCartsRequestValidator : AbstractValidator +public class DeleteUserCartRequestValidator : AbstractValidator { - public DeleteUserCartsRequestValidator() + public DeleteUserCartRequestValidator() { RuleFor(model => model.Id) .NotNull(); } public Func>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserCartsRequest)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((DeleteUserCartRequest)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/UserCarts/GetUserCartsRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserCarts/GetUserCartRequestValidator.cs similarity index 69% rename from src/CMSMicroservice.Protobuf/Validator/UserCarts/GetUserCartsRequestValidator.cs rename to src/CMSMicroservice.Protobuf/Validator/UserCarts/GetUserCartRequestValidator.cs index 4641324..e90fb0a 100644 --- a/src/CMSMicroservice.Protobuf/Validator/UserCarts/GetUserCartsRequestValidator.cs +++ b/src/CMSMicroservice.Protobuf/Validator/UserCarts/GetUserCartRequestValidator.cs @@ -2,16 +2,16 @@ using FluentValidation; using CMSMicroservice.Protobuf.Protos.UserCarts; namespace CMSMicroservice.Protobuf.Validator.UserCarts; -public class GetUserCartsRequestValidator : AbstractValidator +public class GetUserCartRequestValidator : AbstractValidator { - public GetUserCartsRequestValidator() + public GetUserCartRequestValidator() { RuleFor(model => model.Id) .NotNull(); } public Func>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserCartsRequest)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((GetUserCartRequest)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/UserCarts/UpdateUserCartsRequestValidator.cs b/src/CMSMicroservice.Protobuf/Validator/UserCarts/UpdateUserCartRequestValidator.cs similarity index 70% rename from src/CMSMicroservice.Protobuf/Validator/UserCarts/UpdateUserCartsRequestValidator.cs rename to src/CMSMicroservice.Protobuf/Validator/UserCarts/UpdateUserCartRequestValidator.cs index 1e4e809..a4cf031 100644 --- a/src/CMSMicroservice.Protobuf/Validator/UserCarts/UpdateUserCartsRequestValidator.cs +++ b/src/CMSMicroservice.Protobuf/Validator/UserCarts/UpdateUserCartRequestValidator.cs @@ -2,9 +2,9 @@ using FluentValidation; using CMSMicroservice.Protobuf.Protos.UserCarts; namespace CMSMicroservice.Protobuf.Validator.UserCarts; -public class UpdateUserCartsRequestValidator : AbstractValidator +public class UpdateUserCartRequestValidator : AbstractValidator { - public UpdateUserCartsRequestValidator() + public UpdateUserCartRequestValidator() { RuleFor(model => model.Id) .NotNull(); @@ -13,7 +13,7 @@ public class UpdateUserCartsRequestValidator : AbstractValidator>> ValidateValue => async (model, propertyName) => { - var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserCartsRequest)model, x => x.IncludeProperties(propertyName))); + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UpdateUserCartRequest)model, x => x.IncludeProperties(propertyName))); if (result.IsValid) return Array.Empty(); return result.Errors.Select(e => e.ErrorMessage); diff --git a/src/CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj b/src/CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj index de1a330..d05686a 100644 --- a/src/CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj +++ b/src/CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj @@ -5,6 +5,8 @@ enable Linux ..\..\.. + true + $(NoWarn);1591 @@ -12,7 +14,7 @@ - + @@ -29,6 +31,7 @@ + diff --git a/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs b/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs index 8481daa..d9b9f42 100644 --- a/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs +++ b/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs @@ -1,13 +1,20 @@ +using Google.Protobuf; using Grpc.Core.Interceptors; using Microsoft.Extensions.Logging; using CMSMicroservice.Application.Common.Interfaces; +using System.Text.RegularExpressions; namespace CMSMicroservice.WebApi.Common.Behaviours; -public class LoggingBehaviour : Interceptor +public partial class LoggingBehaviour : Interceptor { private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; + + // فیلدهایی که نباید لاگ شوند (بایت‌های تصویر / فایل) + [GeneratedRegex(@"""(File|ImageFile|image_file|file)"":\s*\{[^}]*\}", RegexOptions.Singleline)] + private static partial Regex BinaryFieldPattern(); + public LoggingBehaviour(ILogger logger, ICurrentUserService currentUserService) { _logger = logger; @@ -21,8 +28,11 @@ public class LoggingBehaviour : Interceptor { var requestName = typeof(TRequest).Name; var userId = _currentUserService.UserId ?? string.Empty; - _logger.LogInformation("gRPC Starting receiving call. Type/Method: {Type} / {Method} Request: {Name} {@UserId} {@Request}", - MethodType.Unary, context.Method , requestName, userId, request); + + // لاگ بدون بایت‌های فایل + var safeLog = SanitizeForLog(request); + _logger.LogInformation("gRPC Starting receiving call. Type/Method: {Type} / {Method} Request: {Name} {UserId} {Request}", + MethodType.Unary, context.Method, requestName, userId, safeLog); try { @@ -30,9 +40,26 @@ public class LoggingBehaviour : Interceptor } catch (Exception ex) { - _logger.LogError(ex, "gRPC Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); - + _logger.LogError(ex, "gRPC Request: Unhandled Exception for Request {Name} {Request}", requestName, safeLog); throw; } } + + /// + /// حذف بایت‌های فایل از لاگ — جایگزینی با [BINARY DATA] + /// + private static string SanitizeForLog(T request) + { + if (request is IMessage protoMessage) + { + var json = JsonFormatter.Default.Format(protoMessage); + // حذف محتوای فیلدهای باینری + json = BinaryFieldPattern().Replace(json, "\"$1\": \"[BINARY DATA]\""); + // اگر هنوز رشته‌های base64 طولانی هست، خلاصه کن + if (json.Length > 2000) + return json[..2000] + "... [TRUNCATED]"; + return json; + } + return request?.ToString() ?? ""; + } } diff --git a/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs b/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs index 0a1f266..488a7c3 100644 --- a/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs +++ b/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs @@ -1,15 +1,21 @@ +using Google.Protobuf; using Grpc.Core.Interceptors; using Microsoft.Extensions.Logging; using System.Diagnostics; +using System.Text.RegularExpressions; using CMSMicroservice.Application.Common.Interfaces; namespace CMSMicroservice.WebApi.Common.Behaviours; -public class PerformanceBehaviour : Interceptor +public partial class PerformanceBehaviour : Interceptor { private readonly Stopwatch _timer; private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; + + [GeneratedRegex(@"""(File|ImageFile|image_file|file)"":\s*\{[^}]*\}", RegexOptions.Singleline)] + private static partial Regex BinaryFieldPattern(); + public PerformanceBehaviour(ILogger logger, ICurrentUserService currentUserService) { _timer = new Stopwatch(); @@ -34,11 +40,25 @@ public class PerformanceBehaviour : Interceptor { var requestName = typeof(TRequest).Name; var userId = _currentUserService.UserId ?? string.Empty; + var safeLog = SanitizeForLog(request); - _logger.LogWarning("gRPC Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@Request}", - requestName, elapsedMilliseconds, userId, request); + _logger.LogWarning("gRPC Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {UserId} {Request}", + requestName, elapsedMilliseconds, userId, safeLog); } return response; } + + private static string SanitizeForLog(T request) + { + if (request is IMessage protoMessage) + { + var json = JsonFormatter.Default.Format(protoMessage); + json = BinaryFieldPattern().Replace(json, "\"$1\": \"[BINARY DATA]\""); + if (json.Length > 2000) + return json[..2000] + "... [TRUNCATED]"; + return json; + } + return request?.ToString() ?? ""; + } } diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/BlogCategoryProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/BlogCategoryProfile.cs new file mode 100644 index 0000000..a1076c9 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/BlogCategoryProfile.cs @@ -0,0 +1,14 @@ +using Mapster; +using ProtoBlogCategory = CMSMicroservice.Protobuf.Protos.BlogCategory; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class BlogCategoryProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // CreateBlogCategory: long → CreateBlogCategoryResponse + config.NewConfig() + .Map(dest => dest.Id, src => src); + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/BlogPostProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/BlogPostProfile.cs new file mode 100644 index 0000000..8afc3f0 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/BlogPostProfile.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; +using Google.Protobuf.WellKnownTypes; +using Mapster; +using ProtoBlogPost = CMSMicroservice.Protobuf.Protos.BlogPost; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class BlogPostProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // PublishBlogPost: Command result → Proto response + config.NewConfig() + .Map(dest => dest.Success, src => src.Success) + .Map(dest => dest.Message, src => src.Message) + .Map(dest => dest.PublishedAt, src => src.PublishedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.PublishedAt.Value, DateTimeKind.Utc)) + : null); + + // ArchiveBlogPost: Command result → Proto response + config.NewConfig() + .Map(dest => dest.Success, src => src.Success) + .Map(dest => dest.Message, src => src.Message); + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/CityProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/CityProfile.cs index bdea9b9..71e8806 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/CityProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/CityProfile.cs @@ -24,9 +24,9 @@ public class CityProfile : IRegister // Response: Application → Proto config.NewConfig() .Map(dest => dest.MetaData, src => src.MetaData) - .Map(dest => dest.Models, src => src.Models); + .Map(dest => dest.Cities, src => src.Models); - config.NewConfig() + config.NewConfig() .Map(dest => dest.Id, src => src.Id) .Map(dest => dest.ExternalId, src => src.ExternalId) .Map(dest => dest.Name, src => src.Name) diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs index 0512568..170a483 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs @@ -1,4 +1,5 @@ using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships; +using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership; using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics; using CMSMicroservice.Protobuf.Protos.ClubMembership; using Google.Protobuf.WellKnownTypes; @@ -11,6 +12,37 @@ public class ClubMembershipProfile : IRegister { public void Register(TypeAdapterConfig config) { + // ===================== + // GetClubMembership + // ===================== + + // GetClubMembershipRequest -> GetClubMembershipQuery + config.NewConfig() + .MapWith(src => new GetClubMembershipQuery + { + UserId = src.UserId + }); + + // ClubMembershipDto -> GetClubMembershipResponse + config.NewConfig() + .MapWith(src => new GetClubMembershipResponse + { + Id = src.Id, + UserId = src.UserId, + PackageId = 0, // Not available + PackageName = string.Empty, // Not available + ActivationCode = string.Empty, // Not available + ActivatedAt = src.ActivatedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.ActivatedAt.Value, DateTimeKind.Utc)) + : null, + ExpiresAt = null, // Not available + IsActive = src.IsActive, + Created = Timestamp.FromDateTimeOffset(src.Created), + Features = { }, // Empty for now + Status = src.IsActive ? "Active" : "Inactive", + DaysRemaining = 0 // Calculate if ExpiresAt is available + }); + // ===================== // GetAllClubMemberships // ===================== diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs index e75847c..c31a0db 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/CommissionProfile.cs @@ -1,8 +1,12 @@ +using System.Linq; using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; +using CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances; using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks; using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts; +using CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts; using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions; using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; using CMSMicroservice.Protobuf.Protos.Commission; using Google.Protobuf.WellKnownTypes; using Mapster; @@ -124,5 +128,77 @@ public class CommissionProfile : IRegister : null) .Map(dest => dest.IsExpired, src => src.IsExpired) .Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created)); + + // GetMyCommissionPayouts Request Mapping + config.NewConfig() + .Map(dest => dest.Status, src => src.Status != null && src.Status.Value >= 0 + ? (CommissionPayoutStatus?)src.Status.Value + : null) + .Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null) + .Map(dest => dest.PaginationState, src => new PaginationState + { + PageNumber = src.PageNumber > 0 ? src.PageNumber : 1, + PageSize = src.PageSize > 0 ? src.PageSize : 10 + }); + + // GetMyCommissionPayouts Response Mapping + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData != null + ? new CustomerMetaData { TotalCount = src.MetaData.TotalCount } + : new CustomerMetaData()) + .Map(dest => dest.Payouts, src => src.Models); + + // CustomerCommissionPayoutModel Mapping + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId) + .Map(dest => dest.WeekDisplayName, src => src.WeekDisplayName) + .Map(dest => dest.BalancesEarned, src => src.BalancesEarned) + .Map(dest => dest.TotalAmount, src => src.TotalAmount) + .Map(dest => dest.AmountFormatted, src => src.AmountFormatted) + .Map(dest => dest.Status, src => (int)src.Status) + .Map(dest => dest.CalculatedDate, src => src.CalculatedDate.HasValue + ? Timestamp.FromDateTime(src.CalculatedDate.Value.ToUniversalTime()) + : null) + .Map(dest => dest.DatePersian, src => src.DatePersian); + + // 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.PaginationState, src => new PaginationState + { + PageNumber = src.PageNumber > 0 ? src.PageNumber : 1, + PageSize = src.PageSize > 0 ? src.PageSize : 10 + }); + + // GetMyWeeklyBalances Response Mapping + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData != null + ? new CustomerMetaData { TotalCount = src.MetaData.TotalCount } + : new CustomerMetaData()) + .Map(dest => dest.Balances, src => src.Models) + .Map(dest => dest.TotalLeftBalances, src => src.Models.Sum(m => m.LeftLegTotal)) + .Map(dest => dest.TotalRightBalances, src => src.Models.Sum(m => m.RightLegTotal)) + .Map(dest => dest.WeakerLeg, src => + src.Models.Sum(m => m.LeftLegTotal) < src.Models.Sum(m => m.RightLegTotal) + ? "Left" + : src.Models.Sum(m => m.LeftLegTotal) > src.Models.Sum(m => m.RightLegTotal) + ? "Right" + : "Equal"); + + // CustomerWeeklyBalanceModel Mapping + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId) + .Map(dest => dest.WeekDisplayName, src => src.WeekDisplayName) + .Map(dest => dest.LeftLegBalances, src => src.LeftLegTotal) + .Map(dest => dest.RightLegBalances, src => src.RightLegTotal) + .Map(dest => dest.TotalBalances, src => src.TotalBalances) + .Map(dest => dest.WeeklyPoolContribution, src => src.WeeklyPoolContribution) + .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); } } diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/DiscountCategoryProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/DiscountCategoryProfile.cs new file mode 100644 index 0000000..6929cee --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/DiscountCategoryProfile.cs @@ -0,0 +1,61 @@ +using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountCategories; +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using Mapster; +using AppDiscountCategoryDto = CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountCategories.DiscountCategoryDto; +using ProtoDiscountCategoryDto = CMSMicroservice.Protobuf.Protos.DiscountCategory.DiscountCategoryDto; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class DiscountCategoryProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // Map from Application DTO to Proto Response + config.NewConfig() + .MapWith(src => MapToResponse(src)); + + // Map individual category DTO + config.NewConfig() + .MapWith(src => MapCategory(src)); + } + + private static GetDiscountCategoriesResponse MapToResponse(GetDiscountCategoriesResponseDto src) + { + var response = new GetDiscountCategoriesResponse(); + if (src.Categories != null) + { + foreach (var category in src.Categories) + { + response.Categories.Add(MapCategory(category)); + } + } + return response; + } + + private static ProtoDiscountCategoryDto MapCategory(AppDiscountCategoryDto src) + { + var proto = new ProtoDiscountCategoryDto + { + Id = src.Id, + Name = src.Name ?? string.Empty, + Title = src.Title ?? string.Empty, + Description = src.Description, + ImagePath = src.ImagePath, + ParentCategoryId = src.ParentCategoryId, + SortOrder = src.SortOrder, + IsActive = src.IsActive, + ProductCount = src.ProductCount + }; + + // Recursively map children + if (src.Children != null) + { + foreach (var child in src.Children) + { + proto.Children.Add(MapCategory(child)); + } + } + + return proto; + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/HealthProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/HealthProfile.cs new file mode 100644 index 0000000..d83c3e5 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/HealthProfile.cs @@ -0,0 +1,36 @@ +using CMSMicroservice.Protobuf.Protos.Health; +using CMSMicroservice.Application.HealthCQ.Queries.GetSystemHealth; +using Google.Protobuf.WellKnownTypes; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class HealthProfile : IRegister +{ + public void Register(TypeAdapterConfig config) + { + // DTO to Proto mappings + config.NewConfig() + .Map(dest => dest.OverallHealthy, src => src.OverallHealthy) + .Map(dest => dest.Services, src => src.Services) + .Map(dest => dest.CheckedAt, src => Timestamp.FromDateTime(src.CheckedAt)) + .Map(dest => dest.Version, src => src.Version) + .Map(dest => dest.Environment, src => src.Environment); + + config.NewConfig() + .Map(dest => dest.ServiceName, src => src.ServiceName) + .Map(dest => dest.Status, src => src.Status) + .Map(dest => dest.Description, src => src.Description) + .Map(dest => dest.ResponseTimeMs, src => src.ResponseTimeMs) + .Map(dest => dest.LastCheck, src => Timestamp.FromDateTime(src.LastCheck)) + .Map(dest => dest.Details, src => src.Details); + + config.NewConfig() + .Map(dest => dest.Key, src => src.Key) + .Map(dest => dest.Value, src => src.Value) + .Map(dest => dest.Status, src => src.Status); + + // Enum mappings + config.NewConfig() + .Map(dest => dest, src => (HealthStatus)(int)src); + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/InventoryProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/InventoryProfile.cs new file mode 100644 index 0000000..a271138 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/InventoryProfile.cs @@ -0,0 +1,484 @@ +using CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses; +using CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem; +using CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity; +using CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory; +using CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory; +using CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory; +using CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems; +using CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement; +using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements; +using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem; +using CMSMicroservice.Protobuf.Protos.Inventory; +using CMSMicroservice.Domain.Enums; +using Google.Protobuf.WellKnownTypes; +using Mapster; +using DomainStockMovementType = CMSMicroservice.Domain.Enums.StockMovementType; +using ProtoStockMovementType = CMSMicroservice.Protobuf.Protos.Inventory.StockMovementType; +using DomainProductType = CMSMicroservice.Domain.Enums.ProductType; +using ProtoProductType = CMSMicroservice.Protobuf.Protos.Inventory.ProductType; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class InventoryProfile : IRegister +{ + public void Register(TypeAdapterConfig config) + { + // ==================== Warehouse Mappings ==================== + + // CreateWarehouse: Proto -> Command + config.NewConfig() + .Map(dest => dest.Name, src => src.Name) + .Map(dest => dest.Code, src => src.Code) + .Map(dest => dest.Address, src => src.Address) + .Map(dest => dest.IsDefault, src => src.IsDefault) + .Map(dest => dest.IsActive, src => true); // Proto ندارد + + // CreateWarehouse: ResponseDto -> Proto Response + config.NewConfig() + .Map(dest => dest.Id, src => src.Id); + + // UpdateWarehouse: Proto -> Command + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.Name, src => src.Name) + .Map(dest => dest.Code, src => src.Code) + .Map(dest => dest.Address, src => src.Address) + .Map(dest => dest.IsActive, src => src.IsActive); + + // DeleteWarehouse: Proto -> Command + config.NewConfig() + .Map(dest => dest.Id, src => src.Id); + + // SetDefaultWarehouse: Proto -> Command + config.NewConfig() + .Map(dest => dest.Id, src => src.Id); + + // GetWarehouse Query: Proto -> Query + config.NewConfig() + .MapWith(src => new GetWarehouseQuery(src.Id)); + + // GetWarehouse Response: ResponseDto -> Proto (با WarehouseDto) + config.NewConfig() + .Map(dest => dest.Warehouse, src => new WarehouseDto + { + Id = src.Id, + Name = src.Name, + Code = src.Code, + Address = src.Address ?? string.Empty, + IsDefault = src.IsDefault, + IsActive = src.IsActive, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)), + LastModified = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)) + }); + + // GetAllWarehouses Query: Proto -> Query + config.NewConfig() + .MapWith(src => new GetAllWarehousesQuery()); + + // GetAllWarehouses Response: ResponseDto -> Proto + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .AfterMapping((src, dest) => + { + dest.Warehouses.Clear(); + foreach (var warehouse in src.Warehouses) + { + dest.Warehouses.Add(new WarehouseDto + { + Id = warehouse.Id, + Name = warehouse.Name, + Code = warehouse.Code, + Address = warehouse.Address ?? string.Empty, + IsDefault = warehouse.IsDefault, + IsActive = warehouse.IsActive, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)), + LastModified = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)) + }); + } + }); + + // ==================== Inventory Item Query Mappings ==================== + + // GetInventoryItem: Proto -> Query + config.NewConfig() + .MapWith(src => new GetInventoryItemQuery(src.Id)); + + // GetInventoryItem Response: ResponseDto -> Proto (با InventoryItemDto) + config.NewConfig() + .Map(dest => dest.Item, src => MapToInventoryItemDto(src)); + + // GetInventoryByProduct: Proto -> Query + config.NewConfig() + .Map(dest => dest.ProductId, src => src.ProductId) + .Map(dest => dest.ProductType, src => MapToDomainProductType(src.ProductType)); + + // GetInventoryByProduct Response: ResponseDto -> Proto + config.NewConfig() + .Map(dest => dest.Item, src => MapToInventoryItemDto(src)); + + // GetAllInventoryItems: Proto -> Query + config.NewConfig() + .Map(dest => dest.WarehouseId, src => src.WarehouseId != null ? (long?)src.WarehouseId.Value : null) + .Map(dest => dest.ProductType, src => src.ProductType != ProtoProductType.Unspecified + ? (DomainProductType?)MapToDomainProductType(src.ProductType) : null) + .Map(dest => dest.SearchTerm, src => src.Search) + .Map(dest => dest.Skip, src => src.Page > 0 ? (src.Page - 1) * src.PageSize : 0) + .Map(dest => dest.Take, src => src.PageSize > 0 ? src.PageSize : 50); + + // GetAllInventoryItems Response: ResponseDto -> Proto + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .AfterMapping((src, dest) => + { + dest.Items.Clear(); + foreach (var item in src.Items) + { + dest.Items.Add(MapListItemToInventoryItemDto(item)); + } + }); + + // GetLowStockItems: Proto -> Query + config.NewConfig() + .Map(dest => dest.WarehouseId, src => src.WarehouseId != null ? (long?)src.WarehouseId.Value : null) + .Map(dest => dest.Count, src => src.PageSize > 0 ? src.PageSize : 50); + + // GetLowStockItems Response: ResponseDto -> Proto + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .AfterMapping((src, dest) => + { + dest.Items.Clear(); + foreach (var item in src.Items) + { + dest.Items.Add(MapLowStockItemToInventoryItemDto(item)); + } + }); + + // UpdateInventorySettings: Proto -> UpdateInventoryItemCommand + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.MinimumStock, src => (int?)src.LowStockThreshold) + .Map(dest => dest.MaximumStock, src => (int?)src.MaxStockLevel) + .Map(dest => dest.ReorderPoint, src => (int?)src.ReorderPoint); + + // ==================== Stock Operation Mappings ==================== + + // AddStock: Proto -> IncreaseInventoryCommand + // نکته: این نیاز به lookup در Service داره چون Proto از ProductId استفاده میکنه + config.NewConfig() + .Map(dest => dest.Quantity, src => src.Quantity) + .Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber) + .Map(dest => dest.Note, src => src.Note); + // Id باید در Service تنظیم بشه + + // AdjustStock: Proto -> UpdateInventoryQuantityCommand + config.NewConfig() + .Map(dest => dest.NewQuantity, src => src.NewQuantity) + .Map(dest => dest.Note, src => src.Reason) + .Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber); + // Id باید در Service تنظیم بشه + + // ReserveStock: Proto -> ReserveInventoryCommand + config.NewConfig() + .Map(dest => dest.Quantity, src => src.Quantity) + .Map(dest => dest.OrderId, src => src.OrderId != null ? (long?)src.OrderId.Value : null) + .Map(dest => dest.DiscountOrderId, src => src.DiscountOrderId != null ? (long?)src.DiscountOrderId.Value : null); + // Id باید در Service تنظیم بشه + + // ReleaseReservation: Proto -> ReleaseReservedInventoryCommand + config.NewConfig() + .Map(dest => dest.Quantity, src => src.Quantity) + .Map(dest => dest.OrderId, src => src.OrderId != null ? (long?)src.OrderId.Value : null) + .Map(dest => dest.DiscountOrderId, src => src.DiscountOrderId != null ? (long?)src.DiscountOrderId.Value : null); + // Id باید در Service تنظیم بشه + + // ConfirmSale: Proto -> ReduceInventoryCommand + config.NewConfig() + .Map(dest => dest.Quantity, src => src.Quantity) + .Map(dest => dest.OrderId, src => src.OrderId != null ? (long?)src.OrderId.Value : null) + .Map(dest => dest.DiscountOrderId, src => src.DiscountOrderId != null ? (long?)src.DiscountOrderId.Value : null) + .Map(dest => dest.FromReserved, src => src.FromReservation); + // Id باید در Service تنظیم بشه + + // ProcessReturn: Proto -> IncreaseInventoryCommand + config.NewConfig() + .Map(dest => dest.Quantity, src => src.Quantity) + .Map(dest => dest.Note, src => src.Reason); + // Id باید در Service تنظیم بشه + + // RecordLoss: Proto -> CreateStockMovementCommand + config.NewConfig() + .Map(dest => dest.Quantity, src => src.Quantity) + .Map(dest => dest.MovementType, src => MapToDomainStockMovementType(src.LossType)) + .Map(dest => dest.Note, src => src.Reason) + .Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber); + // InventoryItemId باید در Service تنظیم بشه + + // ==================== Stock Movement Query Mappings ==================== + + // GetStockMovements: Proto -> Query + config.NewConfig() + .Map(dest => dest.InventoryItemId, src => src.InventoryItemId != null ? (long?)src.InventoryItemId.Value : null) + .Map(dest => dest.ProductId, src => src.ProductId != null ? (long?)src.ProductId.Value : null) + .Map(dest => dest.ProductType, src => src.ProductType != ProtoProductType.Unspecified + ? (DomainProductType?)MapToDomainProductType(src.ProductType) : null) + .Map(dest => dest.MovementType, src => src.MovementType != ProtoStockMovementType.MovementTypeUnspecified + ? (DomainStockMovementType?)MapToDomainStockMovementType(src.MovementType) : null) + .Map(dest => dest.StartDate, src => src.FromDate != null ? (DateTime?)src.FromDate.ToDateTime() : null) + .Map(dest => dest.EndDate, src => src.ToDate != null ? (DateTime?)src.ToDate.ToDateTime() : null) + .Map(dest => dest.Skip, src => src.Page > 0 ? (src.Page - 1) * src.PageSize : 0) + .Map(dest => dest.Take, src => src.PageSize > 0 ? src.PageSize : 50); + + // GetStockMovements Response: ResponseDto -> Proto + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .AfterMapping((src, dest) => + { + dest.Movements.Clear(); + foreach (var movement in src.Movements) + { + dest.Movements.Add(MapToStockMovementDto(movement)); + } + }); + + // GetStockMovementsByInventoryItem: Proto -> Query + config.NewConfig() + .Map(dest => dest.InventoryItemId, src => src.InventoryItemId) + .Map(dest => dest.Skip, src => src.Page > 0 ? (src.Page - 1) * src.PageSize : 0) + .Map(dest => dest.Take, src => src.PageSize > 0 ? src.PageSize : 50); + + // GetStockMovementsByInventoryItem Response: ResponseDto -> Proto + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .AfterMapping((src, dest) => + { + dest.Movements.Clear(); + foreach (var movement in src.Movements) + { + dest.Movements.Add(MapHistoryToStockMovementDto(movement)); + } + }); + } + + // ==================== Helper Methods ==================== + + private static InventoryItemDto MapToInventoryItemDto(GetInventoryItemResponseDto src) + { + return new InventoryItemDto + { + Id = src.Id, + ProductId = src.ProductId, + DiscountProductId = src.DiscountProductId, + ProductType = MapToProtoProductType(src.ProductType), + Quantity = src.Quantity, + ReservedQuantity = src.ReservedQuantity, + AvailableQuantity = src.AvailableQuantity, + LowStockThreshold = src.LowStockThreshold, + ReorderPoint = 0, // Entity ندارد + MaxStockLevel = src.MaxStockLevel, + LastRestockedAt = src.LastRestockedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastRestockedAt.Value, DateTimeKind.Utc)) + : null, + LastSoldAt = src.LastSoldAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastSoldAt.Value, DateTimeKind.Utc)) + : null, + WarehouseId = src.WarehouseId, + WarehouseName = src.WarehouseName ?? string.Empty, + ProductTitle = src.ProductTitle ?? string.Empty, + ProductPrice = src.ProductPrice.HasValue ? (long)src.ProductPrice.Value : 0, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)) + }; + } + + private static InventoryItemDto MapToInventoryItemDto(GetInventoryByProductResponseDto src) + { + return new InventoryItemDto + { + Id = src.Id, + ProductId = src.ProductId, + DiscountProductId = src.DiscountProductId, + ProductType = MapToProtoProductType(src.ProductType), + Quantity = src.Quantity, + ReservedQuantity = src.ReservedQuantity, + AvailableQuantity = src.AvailableQuantity, + LowStockThreshold = src.LowStockThreshold, + ReorderPoint = 0, + MaxStockLevel = src.MaxStockLevel, + LastRestockedAt = src.LastRestockedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastRestockedAt.Value, DateTimeKind.Utc)) + : null, + LastSoldAt = src.LastSoldAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastSoldAt.Value, DateTimeKind.Utc)) + : null, + WarehouseId = src.WarehouseId, + WarehouseName = src.WarehouseName ?? string.Empty, + ProductTitle = src.ProductTitle ?? string.Empty, + ProductPrice = src.ProductPrice.HasValue ? (long)src.ProductPrice.Value : 0, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)) + }; + } + + private static InventoryItemDto MapListItemToInventoryItemDto(InventoryItemListDto src) + { + return new InventoryItemDto + { + Id = src.Id, + ProductId = src.ProductId, + DiscountProductId = src.DiscountProductId, + ProductType = MapToProtoProductType(src.ProductType), + Quantity = src.Quantity, + ReservedQuantity = src.ReservedQuantity, + AvailableQuantity = src.AvailableQuantity, + LowStockThreshold = src.LowStockThreshold, + ReorderPoint = 0, + MaxStockLevel = src.MaxStockLevel, + LastRestockedAt = src.LastRestockedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastRestockedAt.Value, DateTimeKind.Utc)) + : null, + LastSoldAt = src.LastSoldAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastSoldAt.Value, DateTimeKind.Utc)) + : null, + WarehouseId = src.WarehouseId, + WarehouseName = src.WarehouseName ?? string.Empty, + ProductTitle = src.ProductTitle ?? string.Empty, + ProductPrice = src.ProductPrice.HasValue ? (long)src.ProductPrice.Value : 0, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)) + }; + } + + private static InventoryItemDto MapLowStockItemToInventoryItemDto(LowStockItemDto src) + { + return new InventoryItemDto + { + Id = src.Id, + ProductId = src.ProductId, + DiscountProductId = src.DiscountProductId, + ProductType = MapToProtoProductType(src.ProductType), + Quantity = src.Quantity, + ReservedQuantity = src.ReservedQuantity, + AvailableQuantity = src.AvailableQuantity, + LowStockThreshold = src.LowStockThreshold, + ReorderPoint = 0, + MaxStockLevel = 0, + LastRestockedAt = null, + LastSoldAt = null, + WarehouseId = src.WarehouseId, + WarehouseName = src.WarehouseName ?? string.Empty, + ProductTitle = src.ProductTitle ?? string.Empty, + ProductPrice = 0, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)) + }; + } + + private static StockMovementDto MapToStockMovementDto(StockMovementListDto src) + { + return new StockMovementDto + { + Id = src.Id, + InventoryItemId = src.InventoryItemId, + MovementType = MapToProtoStockMovementType(src.MovementType), + Quantity = src.Quantity, + QuantityBefore = src.QuantityBefore, + QuantityAfter = src.QuantityAfter, + OrderId = src.OrderId, + DiscountOrderId = src.DiscountOrderId, + ReferenceNumber = src.ReferenceNumber ?? string.Empty, + Note = src.Note ?? string.Empty, + PerformedByUserId = src.PerformedByUserId, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)), + ProductTitle = src.ProductTitle ?? string.Empty + }; + } + + private static StockMovementDto MapHistoryToStockMovementDto(StockMovementHistoryDto src) + { + return new StockMovementDto + { + Id = src.Id, + InventoryItemId = 0, // Not available in history DTO + MovementType = MapToProtoStockMovementType(src.MovementType), + Quantity = src.Quantity, + QuantityBefore = src.QuantityBefore, + QuantityAfter = src.QuantityAfter, + OrderId = src.OrderId, + DiscountOrderId = src.DiscountOrderId, + ReferenceNumber = src.ReferenceNumber ?? string.Empty, + Note = src.Note ?? string.Empty, + PerformedByUserId = src.PerformedByUserId, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)), + ProductTitle = string.Empty + }; + } + + // ==================== Enum Mapping Methods ==================== + + private static DomainProductType MapToDomainProductType(ProtoProductType protoType) + { + return protoType switch + { + ProtoProductType.RegularProduct => DomainProductType.RegularProduct, + ProtoProductType.DiscountProduct => DomainProductType.DiscountProduct, + _ => DomainProductType.RegularProduct + }; + } + + private static ProtoProductType MapToProtoProductType(DomainProductType domainType) + { + return domainType switch + { + DomainProductType.RegularProduct => ProtoProductType.RegularProduct, + DomainProductType.DiscountProduct => ProtoProductType.DiscountProduct, + _ => ProtoProductType.Unspecified + }; + } + + private static DomainStockMovementType MapToDomainStockMovementType(ProtoStockMovementType protoType) + { + return protoType switch + { + ProtoStockMovementType.InitialStock => DomainStockMovementType.InitialStock, + ProtoStockMovementType.Restock => DomainStockMovementType.Restock, + ProtoStockMovementType.Return => DomainStockMovementType.Return, + ProtoStockMovementType.Sale => DomainStockMovementType.Sale, + ProtoStockMovementType.AdjustmentIncrease => DomainStockMovementType.AdjustmentPlus, + ProtoStockMovementType.AdjustmentDecrease => DomainStockMovementType.AdjustmentMinus, + ProtoStockMovementType.Reserved => DomainStockMovementType.Reserved, + ProtoStockMovementType.Released => DomainStockMovementType.Released, + ProtoStockMovementType.Loss => DomainStockMovementType.Lost, + ProtoStockMovementType.Damaged => DomainStockMovementType.Damaged, + ProtoStockMovementType.Expired => DomainStockMovementType.Damaged, // Domain ندارد Expired + ProtoStockMovementType.TransferOut => DomainStockMovementType.TransferOut, + ProtoStockMovementType.TransferIn => DomainStockMovementType.TransferIn, + _ => DomainStockMovementType.AdjustmentPlus + }; + } + + private static ProtoStockMovementType MapToProtoStockMovementType(DomainStockMovementType domainType) + { + return domainType switch + { + DomainStockMovementType.InitialStock => ProtoStockMovementType.InitialStock, + DomainStockMovementType.Restock => ProtoStockMovementType.Restock, + DomainStockMovementType.Return => ProtoStockMovementType.Return, + DomainStockMovementType.Sale => ProtoStockMovementType.Sale, + DomainStockMovementType.AdjustmentPlus => ProtoStockMovementType.AdjustmentIncrease, + DomainStockMovementType.AdjustmentMinus => ProtoStockMovementType.AdjustmentDecrease, + DomainStockMovementType.Reserved => ProtoStockMovementType.Reserved, + DomainStockMovementType.Released => ProtoStockMovementType.Released, + DomainStockMovementType.Lost => ProtoStockMovementType.Loss, + DomainStockMovementType.Damaged => ProtoStockMovementType.Damaged, + DomainStockMovementType.TransferOut => ProtoStockMovementType.TransferOut, + DomainStockMovementType.TransferIn => ProtoStockMovementType.TransferIn, + _ => ProtoStockMovementType.MovementTypeUnspecified + }; + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/ProductsProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/ProductsProfile.cs deleted file mode 100644 index 4f71145..0000000 --- a/src/CMSMicroservice.WebApi/Common/Mappings/ProductsProfile.cs +++ /dev/null @@ -1,93 +0,0 @@ -using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices; -using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock; -using CMSMicroservice.Application.ProductsCQ.Commands.ToggleProductStatus; -using CMSMicroservice.Application.ProductsCQ.Queries.GetLowStockProducts; -using CMSMicroservice.Protobuf.Protos.Products; -using ProtoProductPriceUpdate = CMSMicroservice.Protobuf.Protos.Products.ProductPriceUpdate; -using AppProductPriceUpdate = CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices.ProductPriceUpdate; -using ProtoProductStockUpdate = CMSMicroservice.Protobuf.Protos.Products.ProductStockUpdate; -using AppProductStockUpdate = CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock.ProductStockUpdate; -using ProtoStockUpdateType = CMSMicroservice.Protobuf.Protos.Products.StockUpdateType; -using AppStockUpdateType = CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock.StockUpdateType; -using System.Linq; - -namespace CMSMicroservice.WebApi.Common.Mappings; - -public class ProductsProfile : IRegister -{ - void IRegister.Register(TypeAdapterConfig config) - { - // BulkUpdateProductPrices mappings - config.NewConfig() - .Map(dest => dest.Products, src => src.Products); - - config.NewConfig() - .Map(dest => dest.ProductId, src => src.ProductId) - .Map(dest => dest.NewPrice, src => src.NewPrice) - .Map(dest => dest.NewDiscount, src => src.NewDiscount != null ? (int?)src.NewDiscount.Value : null) - .Map(dest => dest.NewClubDiscountPercent, src => src.NewClubDiscountPercent != null ? (int?)src.NewClubDiscountPercent.Value : null); - - config.NewConfig() - .Map(dest => dest.Total, src => src.UpdatedCount + src.FailedCount) - .Map(dest => dest.Succeeded, src => src.UpdatedCount) - .Map(dest => dest.Failed, src => src.FailedCount) - .Map(dest => dest.Errors, src => src.Errors.Select((msg, idx) => new BulkOperationError - { - ProductId = 0, // We don't have the ID in the error message - ErrorMessage = msg - }).ToList()); - - // BulkUpdateProductStock mappings - config.NewConfig() - .Map(dest => dest.Products, src => src.Products) - .Map(dest => dest.UpdateType, src => (AppStockUpdateType)src.UpdateType); - - config.NewConfig() - .Map(dest => dest.ProductId, src => src.ProductId) - .Map(dest => dest.Quantity, src => src.Quantity); - - config.NewConfig() - .Map(dest => dest.Total, src => src.UpdatedCount + src.FailedCount) - .Map(dest => dest.Succeeded, src => src.UpdatedCount) - .Map(dest => dest.Failed, src => src.FailedCount) - .Map(dest => dest.Errors, src => src.Errors.Select(msg => new BulkOperationError - { - ProductId = 0, - ErrorMessage = msg - }).ToList()); - - // GetLowStockProducts mappings - config.NewConfig() - .Map(dest => dest.Threshold, src => src.Threshold) - .Map(dest => dest.PageIndex, src => src.PageIndex) - .Map(dest => dest.PageSize, src => src.PageSize) - .Map(dest => dest.IsClubExclusive, src => src.IsClubExclusive != null ? (bool?)src.IsClubExclusive.Value : null); - - config.NewConfig() - .Map(dest => dest.MetaData, src => src.MetaData) - .Map(dest => dest.Products, src => src.Products); - - config.NewConfig() - .Map(dest => dest.Id, src => src.Id) - .Map(dest => dest.Title, src => src.Title) - .Map(dest => dest.RemainingCount, src => src.RemainingCount) - .Map(dest => dest.Price, src => src.Price) - .Map(dest => dest.IsClubExclusive, src => src.IsClubExclusive); - - // ToggleProductStatus mappings - config.NewConfig() - .Map(dest => dest.ProductIds, src => src.ProductIds) - .Map(dest => dest.Enable, src => src.Enable) - .Map(dest => dest.DefaultStock, src => src.DefaultStock); - - config.NewConfig() - .Map(dest => dest.Total, src => src.UpdatedCount + src.FailedCount) - .Map(dest => dest.Succeeded, src => src.UpdatedCount) - .Map(dest => dest.Failed, src => src.FailedCount) - .Map(dest => dest.Errors, src => src.Errors.Select(msg => new BulkOperationError - { - ProductId = 0, - ErrorMessage = msg - }).ToList()); - } -} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/UserOrderProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/UserOrderProfile.cs deleted file mode 100644 index 6164a65..0000000 --- a/src/CMSMicroservice.WebApi/Common/Mappings/UserOrderProfile.cs +++ /dev/null @@ -1,44 +0,0 @@ -using CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus; -using CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder; -using CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange; -using CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV; -using Google.Protobuf.WellKnownTypes; - -namespace CMSMicroservice.WebApi.Common.Mappings; - -public class UserOrderProfile : IRegister -{ - void IRegister.Register(TypeAdapterConfig config) - { - config.NewConfig() - .IgnoreIf((src, dest) => src.Filter == null || !src.Filter.HasPaymentStatus, dest => dest.Filter.PaymentStatus) - .IgnoreIf((src, dest) => src.Filter == null || !src.Filter.HasPaymentMethod, dest => dest.Filter.PaymentMethod) - .IgnoreIf((src, dest) => src.Filter == null || !src.Filter.HasDeliveryStatus, dest => dest.Filter.DeliveryStatus); - - // UpdateOrderStatus - config.NewConfig(); - config.NewConfig(); - - // GetOrdersByDateRange - config.NewConfig() - .Map(dest => dest.StartDate, src => src.StartDate.ToDateTime()) - .Map(dest => dest.EndDate, src => src.EndDate.ToDateTime()) - .Map(dest => dest.Status, src => src.Status != null ? (int?)src.Status.Value : null) - .Map(dest => dest.UserId, src => src.UserId != null ? (long?)src.UserId.Value : null); - - config.NewConfig() - .Map(dest => dest.Orders, src => src.Orders); - - config.NewConfig() - .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTime(src.Created.ToUniversalTime())); - - // ApplyDiscountToOrder - config.NewConfig(); - config.NewConfig(); - - // CalculateOrderPV - config.NewConfig(); - config.NewConfig(); - config.NewConfig(); - } -} diff --git a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs new file mode 100644 index 0000000..2a63b17 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs @@ -0,0 +1,146 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment; +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 با مبلغ از دیتابیس (تومان) + 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"); + } + } +} diff --git a/src/CMSMicroservice.WebApi/Controllers/UploadsController.cs b/src/CMSMicroservice.WebApi/Controllers/UploadsController.cs new file mode 100644 index 0000000..4fa8fc3 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Controllers/UploadsController.cs @@ -0,0 +1,150 @@ +using System.IO; +using System.Net.Http; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.WebApi.Controllers; + +/// +/// سرویس عمومی سرو تصاویر — فایل‌ها مستقیماً از پوشه Uploads سرو می‌شوند. +/// اگر فایل محلی وجود نداشت، از FMS قدیمی (dl.afrino.co) دانلود و کش می‌شود. +/// +[ApiController] +[AllowAnonymous] +[ApiExplorerSettings(GroupName = "cms")] +public class UploadsController : ControllerBase +{ + private readonly string _uploadRoot; + private readonly string _fmsBaseUrl; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly FileExtensionContentTypeProvider _contentTypeProvider = new(); + + // حداکثر طول مسیر مجاز (جلوگیری از path traversal) + private const int MaxPathLength = 500; + + public UploadsController( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + ILogger logger) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + + _uploadRoot = configuration["FileStorage:UploadPath"] + ?? Path.Combine(AppContext.BaseDirectory, "Uploads"); + + _fmsBaseUrl = configuration["FMS:Address"]?.TrimEnd('/') ?? string.Empty; + } + + /// + /// سرو عمومی فایل از پوشه Uploads. + /// اگر فایل محلی وجود نداشت و FMS تنظیم شده باشد، از FMS دانلود و کش می‌شود. + /// + /// مسیر نسبی فایل (مثلاً blog/image.jpg) + [HttpGet("uploads/{**path}")] + [ResponseCache(Duration = 86400, Location = ResponseCacheLocation.Any)] // کش مرورگر ۲۴ ساعت + public async Task GetFile(string path) + { + // ── اعتبارسنجی مسیر ── + if (string.IsNullOrWhiteSpace(path) || path.Length > MaxPathLength) + return BadRequest("مسیر نامعتبر"); + + // جلوگیری از path traversal + if (path.Contains("..") || path.Contains('\\')) + return BadRequest("مسیر نامعتبر"); + + var sanitizedPath = path.TrimStart('/'); + var fullPath = Path.GetFullPath(Path.Combine(_uploadRoot, sanitizedPath)); + + // اطمینان از اینکه مسیر درون _uploadRoot باقی می‌ماند + if (!fullPath.StartsWith(Path.GetFullPath(_uploadRoot), StringComparison.OrdinalIgnoreCase)) + return BadRequest("مسیر نامعتبر"); + + // ── سرو فایل محلی ── + if (System.IO.File.Exists(fullPath)) + return ServeFile(fullPath); + + // ── Fallback: دانلود از FMS قدیمی ── + if (string.IsNullOrWhiteSpace(_fmsBaseUrl)) + { + _logger.LogWarning("File not found locally and no FMS configured: {Path}", sanitizedPath); + return NotFound(); + } + + var downloaded = await TryDownloadFromFmsAsync(sanitizedPath, fullPath); + if (downloaded) + { + _logger.LogInformation("Downloaded and cached from FMS: {Path}", sanitizedPath); + return ServeFile(fullPath); + } + + return NotFound(); + } + + // ──────────────────────────────────────────────────── + // سرو فایل با Content-Type مناسب + // ──────────────────────────────────────────────────── + private IActionResult ServeFile(string fullPath) + { + if (!_contentTypeProvider.TryGetContentType(fullPath, out var contentType)) + contentType = "application/octet-stream"; + + var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return File(stream, contentType, enableRangeProcessing: true); + } + + // ──────────────────────────────────────────────────── + // دانلود از FMS قدیمی و ذخیره محلی + // ──────────────────────────────────────────────────── + private async Task TryDownloadFromFmsAsync(string relativePath, string localPath) + { + try + { + var fmsUrl = $"{_fmsBaseUrl}/{relativePath}"; + _logger.LogInformation("Attempting FMS download: {Url}", fmsUrl); + + using var client = _httpClientFactory.CreateClient("FMS"); + using var response = await client.GetAsync(fmsUrl, HttpCompletionOption.ResponseHeadersRead); + + if (!response.IsSuccessStatusCode) + { + _logger.LogWarning("FMS returned {Status} for {Url}", response.StatusCode, fmsUrl); + return false; + } + + // بررسی Content-Type — فقط فایل‌های تصویری/مجاز + var mediaType = response.Content.Headers.ContentType?.MediaType ?? string.Empty; + if (!IsAllowedMediaType(mediaType)) + { + _logger.LogWarning("FMS returned disallowed content type {Type} for {Url}", mediaType, fmsUrl); + return false; + } + + // ذخیره روی دیسک + var directory = Path.GetDirectoryName(localPath)!; + Directory.CreateDirectory(directory); + + await using var fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None); + await response.Content.CopyToAsync(fileStream); + + _logger.LogInformation("Cached FMS file locally: {Path} ({Size} bytes)", relativePath, fileStream.Length); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to download from FMS: {Path}", relativePath); + return false; + } + } + + private static bool IsAllowedMediaType(string mediaType) + { + return mediaType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) + || mediaType is "application/pdf" + or "application/octet-stream"; + } +} diff --git a/src/CMSMicroservice.WebApi/Dockerfile b/src/CMSMicroservice.WebApi/Dockerfile index ad54a5e..29ea4c6 100644 --- a/src/CMSMicroservice.WebApi/Dockerfile +++ b/src/CMSMicroservice.WebApi/Dockerfile @@ -1,12 +1,12 @@ #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM 194.5.195.53:32082/dotnet/aspnet:9.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM 194.5.195.53:32082/dotnet/sdk:9.0 AS build WORKDIR /src COPY ["CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj", "CMSMicroservice.WebApi/"] COPY ["CMSMicroservice.Application/CMSMicroservice.Application.csproj", "CMSMicroservice.Application/"] diff --git a/src/CMSMicroservice.WebApi/Interceptors/ImagePathResolverInterceptor.cs b/src/CMSMicroservice.WebApi/Interceptors/ImagePathResolverInterceptor.cs new file mode 100644 index 0000000..30322b5 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Interceptors/ImagePathResolverInterceptor.cs @@ -0,0 +1,164 @@ +using CMSMicroservice.Application.Common.FileManager; +using Google.Protobuf; +using Google.Protobuf.Reflection; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Microsoft.Extensions.Logging; +using System.Collections; +using System.Collections.Generic; + +namespace CMSMicroservice.WebApi.Interceptors; + +/// +/// gRPC Interceptor — بعد از اجرای هر سرویس، فیلدهای تصویری response را +/// از مسیر نسبی دیسک به base64 data-URI تبدیل می‌کند +/// +public class ImagePathResolverInterceptor : Interceptor +{ + private readonly IFileManager _fileManager; + private readonly ILogger _logger; + + // نام فیلدهایی که مسیر تصویر هستند + private static readonly HashSet ImageFieldNames = new(StringComparer.OrdinalIgnoreCase) + { + "image_path", + "thumbnail_path", + "image_thumbnail_path", + "featured_image_path", + "featured_image_thumbnail_path", + "hero_image_path", + "product_thumbnail_path", + "avatar_path", + "avatar_url", + "avatar" + }; + + public ImagePathResolverInterceptor(IFileManager fileManager, ILogger logger) + { + _fileManager = fileManager; + _logger = logger; + } + + // ── Unary call (اکثر gRPC‌ها) ── + public override async Task UnaryServerHandler( + TRequest request, + ServerCallContext context, + UnaryServerMethod continuation) + { + var response = await continuation(request, context); + + if (response is IMessage message) + { + ResolveImagePaths(message); + } + + return response; + } + + // ── Server streaming ── + public override async Task ServerStreamingServerHandler( + TRequest request, + IServerStreamWriter responseStream, + ServerCallContext context, + ServerStreamingServerMethod continuation) + { + var wrappedStream = new ImageResolvingStreamWriter(responseStream, this); + await continuation(request, wrappedStream, context); + } + + /// + /// بازگشتی: تمام فیلدهای string با نام تصویری را resolve می‌کند + /// شامل فیلدهای تکراری (repeated) و زیر-پیام‌ها (sub-messages) + /// + internal void ResolveImagePaths(IMessage message) + { + var descriptor = message.Descriptor; + + foreach (var field in descriptor.Fields.InFieldNumberOrder()) + { + try + { + if (field.FieldType == FieldType.String && ImageFieldNames.Contains(field.Name)) + { + // فیلد string ساده + var accessor = field.Accessor; + var value = accessor.GetValue(message) as string; + if (!string.IsNullOrEmpty(value)) + { + var resolved = _fileManager.ResolveImageUrl(value); + accessor.SetValue(message, resolved); + } + } + else if (field.FieldType == FieldType.Message) + { + if (field.IsRepeated) + { + // repeated sub-message + var list = field.Accessor.GetValue(message) as System.Collections.IList; + if (list != null) + { + foreach (var item in list) + { + if (item is IMessage subMsg) + ResolveImagePaths(subMsg); + } + } + } + else + { + // فیلد oneof یا فیلد optional message + var subMessage = field.Accessor.GetValue(message) as IMessage; + if (subMessage != null) + { + // Google.Protobuf.WellKnownTypes.StringValue wrapper + if (subMessage is Google.Protobuf.WellKnownTypes.StringValue sv + && ImageFieldNames.Contains(field.Name)) + { + if (!string.IsNullOrEmpty(sv.Value)) + { + sv.Value = _fileManager.ResolveImageUrl(sv.Value); + } + } + else + { + ResolveImagePaths(subMessage); + } + } + } + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error resolving image path for field {Field}", field.Name); + } + } + } + + /// + /// Wrapper برای Server Streaming — هر پیام قبل از ارسال resolve می‌شود + /// + private class ImageResolvingStreamWriter : IServerStreamWriter where T : class + { + private readonly IServerStreamWriter _inner; + private readonly ImagePathResolverInterceptor _interceptor; + + public ImageResolvingStreamWriter(IServerStreamWriter inner, ImagePathResolverInterceptor interceptor) + { + _inner = inner; + _interceptor = interceptor; + } + + public WriteOptions? WriteOptions + { + get => _inner.WriteOptions; + set => _inner.WriteOptions = value; + } + + public Task WriteAsync(T message) + { + if (message is IMessage msg) + _interceptor.ResolveImagePaths(msg); + return _inner.WriteAsync(message); + } + } +} diff --git a/src/CMSMicroservice.WebApi/Interceptors/PermissionInterceptor.cs b/src/CMSMicroservice.WebApi/Interceptors/PermissionInterceptor.cs new file mode 100644 index 0000000..7aa82ce --- /dev/null +++ b/src/CMSMicroservice.WebApi/Interceptors/PermissionInterceptor.cs @@ -0,0 +1,73 @@ +using CMSMicroservice.Application.Common.Authorization; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.WebApi.Interceptors; + +/// +/// gRPC Interceptor برای بررسی مجوز دسترسی +/// بر اساس [RequiresPermission] attribute روی سرویس‌ها/متدها +/// +public class PermissionInterceptor : Interceptor +{ + private readonly IPermissionService _permissionService; + private readonly ILogger _logger; + private readonly IHttpContextAccessor _httpContextAccessor; + + public PermissionInterceptor( + IPermissionService permissionService, + ILogger logger, + IHttpContextAccessor httpContextAccessor) + { + _permissionService = permissionService; + _logger = logger; + _httpContextAccessor = httpContextAccessor; + } + + public override async Task UnaryServerHandler( + TRequest request, ServerCallContext context, + UnaryServerMethod continuation) + { + await EnsureHasPermissionAsync(context); + return await continuation(request, context); + } + + public override async Task ClientStreamingServerHandler( + IAsyncStreamReader requestStream, ServerCallContext context, + ClientStreamingServerMethod continuation) + { + await EnsureHasPermissionAsync(context); + return await continuation(requestStream, context); + } + + private async Task EnsureHasPermissionAsync(ServerCallContext context) + { + var httpContext = context.GetHttpContext() ?? _httpContextAccessor.HttpContext; + if (httpContext == null) return; + + var endpoint = httpContext.GetEndpoint(); + if (endpoint == null) return; + + var permissionAttributes = endpoint.Metadata.GetOrderedMetadata(); + if (permissionAttributes == null || permissionAttributes.Count == 0) return; + + foreach (var attribute in permissionAttributes) + { + var hasPermission = await _permissionService.HasPermissionAsync( + attribute.Permission, httpContext.RequestAborted); + + if (!hasPermission) + { + _logger.LogWarning( + "Permission denied: {Permission} for method {Method}", + attribute.Permission, context.Method); + + throw new RpcException(new Status( + StatusCode.PermissionDenied, + $"شما مجوز دسترسی به این عملیات را ندارید ({attribute.Permission})")); + } + } + } +} diff --git a/src/CMSMicroservice.WebApi/Program.cs b/src/CMSMicroservice.WebApi/Program.cs index 89ea0ed..c674c6f 100644 --- a/src/CMSMicroservice.WebApi/Program.cs +++ b/src/CMSMicroservice.WebApi/Program.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; +using System.Linq; using Serilog.Core; using Serilog; using System.Reflection; @@ -17,18 +18,19 @@ using CMSMicroservice.WebApi.Common.Behaviours; using Hangfire; using Hangfire.SqlServer; using Microsoft.AspNetCore.Server.Kestrel.Core; +using System.IO; var builder = WebApplication.CreateBuilder(args); -if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) +// Configure Kestrel to support both HTTP/1.1 and HTTP/2 +builder.WebHost.ConfigureKestrel(options => { - builder.WebHost.ConfigureKestrel(options => + // Enable both HTTP/1.1 and HTTP/2 for all endpoints + options.ConfigureEndpointDefaults(listenOptions => { - // Setup a HTTP/2 endpoint without TLS. - options.ListenLocalhost(5000, o => o.Protocols = - HttpProtocols.Http2); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); -} +}); var levelSwitch = new LoggingLevelSwitch(); // Read Seq configuration from appsettings.json @@ -62,6 +64,8 @@ builder.Services.AddGrpc(options => { options.Interceptors.Add(); options.Interceptors.Add(); + options.Interceptors.Add(); + options.Interceptors.Add(); //options.Interceptors.Add(); options.EnableDetailedErrors = true; options.MaxReceiveMessageSize = 1000 * 1024 * 1024; // 1 GB @@ -89,6 +93,13 @@ builder.Services.AddHealthChecks() // Add Controllers for REST APIs builder.Services.AddControllers(); +// HttpClient for FMS fallback image download +builder.Services.AddHttpClient("FMS", client => +{ + client.Timeout = TimeSpan.FromSeconds(30); + client.DefaultRequestHeaders.Add("User-Agent", "FourSat-CMS/1.0"); +}); + #region Configure Cors builder.Services.AddCors(options => @@ -102,15 +113,76 @@ builder.Services.AddCors(options => builder.Services.AddGrpcSwagger(); builder.Services.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "gRPC transcoding", Version = "v1" }); - c.CustomSchemaIds(type=>type.ToString()); + // CMS Core Services Documentation + c.SwaggerDoc("cms", new OpenApiInfo + { + Title = "FourSat CMS - Core Services", + Version = "v1", + Description = "Core CMS microservice APIs - Internal business logic and data management", + Contact = new OpenApiContact + { + Name = "FourSat Development Team", + Email = "dev@foursat.com" + } + }); + + // Admin API Documentation (BFF) + c.SwaggerDoc("admin", new OpenApiInfo + { + Title = "FourSat CMS - Admin BFF", + Version = "v1", + Description = "Admin Backend-for-Frontend API - User Management, Products, Commission, Network, Reports", + Contact = new OpenApiContact + { + Name = "FourSat Development Team", + Email = "dev@foursat.com" + } + }); + + // Customer API Documentation (BFF) + c.SwaggerDoc("customer", new OpenApiInfo + { + Title = "FourSat CMS - Customer BFF", + Version = "v1", + Description = "Customer Backend-for-Frontend API - Profile, Shop, Commission, Network Statistics", + Contact = new OpenApiContact + { + Name = "FourSat Development Team", + Email = "dev@foursat.com" + } + }); + + // Unified API Documentation (All endpoints) + c.SwaggerDoc("unified", new OpenApiInfo + { + Title = "FourSat CMS - Unified API", + Version = "v1", + Description = "Complete API Documentation - All Core, Admin & Customer endpoints in one place" + }); + + c.CustomSchemaIds(type => type.ToString()); + + // Resolve conflicting actions for Swagger + c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); + + // Include XML documentation for gRPC services + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + if (File.Exists(xmlPath)) + { + c.IncludeXmlComments(xmlPath); + } + + // Security Definition c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, - Description = "Please insert JWT with Bearer into field", - Name = "Authorization", - Type = SecuritySchemeType.ApiKey + Description = "Please insert JWT with Bearer into field (Format: Bearer {token})", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement { { @@ -125,7 +197,96 @@ builder.Services.AddSwaggerGen(c => new string[] { } } }); + + // Group endpoints by functionality + c.TagActionsBy(api => + { + var serviceName = api.ActionDescriptor.RouteValues?["controller"] ?? + GetGrpcServiceName(api.RelativePath); + + // Define service categories for better organization + return serviceName switch + { + var name when name.Contains("User") => new[] { "👤 User Management" }, + var name when name.Contains("Product") => new[] { "📦 Product Catalog" }, + var name when name.Contains("Category") => new[] { "🗂️ Categories" }, + var name when name.Contains("Commission") => new[] { "💰 Commission & Earnings" }, + var name when name.Contains("Network") => new[] { "🌐 Network & Binary Tree" }, + var name when name.Contains("Club") => new[] { "🏆 Club Management" }, + var name when name.Contains("Discount") => new[] { "🏷️ Discount Shop" }, + var name when name.Contains("Order") => new[] { "🛒 Orders & Shopping" }, + var name when name.Contains("Payment") => new[] { "💳 Payments & Transactions" }, + var name when name.Contains("Inventory") => new[] { "📋 Inventory Management" }, + var name when name.Contains("Health") => new[] { "🔧 System Health" }, + var name when name.Contains("Configuration") => new[] { "⚙️ Configuration" }, + var name when name.Contains("Admin") => new[] { "👑 Administration" }, + _ => new[] { $"📋 {serviceName}" } + }; + }); + + c.DocInclusionPredicate((docName, apiDesc) => + { + // Get service name from both REST controllers and gRPC services + var controllerName = apiDesc.ActionDescriptor.RouteValues?["controller"] ?? ""; + var grpcServiceName = GetGrpcServiceName(apiDesc.RelativePath); + var serviceName = !string.IsNullOrEmpty(controllerName) ? controllerName : grpcServiceName; + + return docName switch + { + "cms" => IsCoreService(serviceName), + "admin" => IsAdminService(serviceName), + "customer" => IsCustomerService(serviceName), + "unified" => true, // Show all endpoints + _ => true + }; + }); }); + +// Helper functions for gRPC service name extraction and categorization +static string GetGrpcServiceName(string? relativePath) +{ + if (string.IsNullOrEmpty(relativePath)) return ""; + + var segments = relativePath.Split('/', StringSplitOptions.RemoveEmptyEntries); + return segments.Length > 0 ? segments[0] : ""; +} + +static bool IsCoreService(string serviceName) +{ + var coreServices = new[] + { + "Health", "Configuration", "OtpToken", "Contract", "AppVersion" + }; + + return coreServices.Any(core => serviceName.Contains(core, StringComparison.OrdinalIgnoreCase)); +} + +static bool IsAdminService(string serviceName) +{ + var adminServices = new[] + { + "Admin", "Role", "UserRole", "ManualPayment", "Inventory", + "Tag", "ProductTag", "ProductGalleries", "ProductImages", + "DiscountOrder", "FactorDetails", "Products", "ProductCategory", + "Category", "DiscountCategory", "DiscountProduct" + }; + + return adminServices.Any(admin => serviceName.Contains(admin, StringComparison.OrdinalIgnoreCase)); +} + +static bool IsCustomerService(string serviceName) +{ + var customerServices = new[] + { + "User", "UserAddress", "Commission", "NetworkMembership", + "ClubMembership", "UserOrder", "UserWallet", "UserCarts", + "DiscountShoppingCart", "City", "Package", "Transactions", + "UserContract", "UserWalletChangeLog" + }; + + return customerServices.Any(customer => serviceName.Contains(customer, StringComparison.OrdinalIgnoreCase)) && + !IsAdminService(serviceName); // Exclude admin services +} var app = builder.Build(); // Configure the HTTP request pipeline. @@ -169,6 +330,9 @@ app.UseCors("AllowAll"); app.UseAuthentication(); app.UseAuthorization(); +// Enable static files for Swagger custom CSS +app.UseStaticFiles(); + // Map Health Check endpoints app.MapHealthChecks("/health"); app.MapHealthChecks("/health/ready", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions @@ -184,6 +348,7 @@ app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); // Configure the H // Map SignalR Hub for token notifications app.MapHub("/hubs/token-notification"); +app.MapHub("/hubs/token-relay"); // Alias for FrontOffice backward compatibility app.ConfigureGrpcEndpoints(Assembly.GetExecutingAssembly(), endpoints => { @@ -194,7 +359,32 @@ app.MapGet("/", () => "Communication with gRPC endpoints must be made through a app.UseSwagger(); app.UseSwaggerUI(c => { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + // CMS Core Services + c.SwaggerEndpoint("/swagger/cms/swagger.json", "🔧 CMS Core Services"); + + // Admin BFF + c.SwaggerEndpoint("/swagger/admin/swagger.json", "👑 Admin BFF"); + + // Customer BFF + c.SwaggerEndpoint("/swagger/customer/swagger.json", "👥 Customer BFF"); + + // Unified API (All endpoints) + c.SwaggerEndpoint("/swagger/unified/swagger.json", "🌐 Unified API (All)"); + + // UI Customization + c.DocumentTitle = "FourSat CMS API Documentation"; + c.RoutePrefix = "swagger"; // Available at /swagger + + // Default to CMS core view + c.DefaultModelExpandDepth(2); + c.DefaultModelsExpandDepth(-1); + c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None); + c.EnableFilter(); + c.EnableDeepLinking(); + c.EnableValidator(); + + // Custom CSS + c.InjectStylesheet("/swagger-ui/custom.css"); }); // Configure Hangfire Dashboard diff --git a/src/CMSMicroservice.WebApi/Services/AppVersionService.cs b/src/CMSMicroservice.WebApi/Services/AppVersionService.cs index b3772b1..53fe15d 100644 --- a/src/CMSMicroservice.WebApi/Services/AppVersionService.cs +++ b/src/CMSMicroservice.WebApi/Services/AppVersionService.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.Authorization; using CMSMicroservice.Protobuf.Protos.AppVersion; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.AppVersionCQ.Queries.GetAppVersion; @@ -20,11 +21,13 @@ public class AppVersionService : AppVersionContract.AppVersionContractBase return await _dispatchRequestToCQRS.Handle(request, context); } + [RequiresPermission(PermissionNames.SettingsManageConfiguration)] public override async Task UpdateAppVersion(UpdateAppVersionRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } + [RequiresPermission(PermissionNames.SettingsView)] public override async Task GetAllAppVersions(GetAllAppVersionsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); diff --git a/src/CMSMicroservice.WebApi/Services/BlogCategoryService.cs b/src/CMSMicroservice.WebApi/Services/BlogCategoryService.cs new file mode 100644 index 0000000..d5f22dc --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/BlogCategoryService.cs @@ -0,0 +1,116 @@ +using CMSMicroservice.Protobuf.Protos.BlogCategory; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Commands.DeleteBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetActiveBlogCategories; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Mapster; +using MediatR; +using AppModels = CMSMicroservice.Application.Common.Models; +using ProtoMetaData = CMSMicroservice.Protobuf.Protos.MetaData; + +namespace CMSMicroservice.WebApi.Services; + +public class BlogCategoryService : BlogCategoryContract.BlogCategoryContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public BlogCategoryService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task CreateBlogCategory(CreateBlogCategoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task UpdateBlogCategory(UpdateBlogCategoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task DeleteBlogCategory(DeleteBlogCategoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetBlogCategory(GetBlogCategoryRequest request, ServerCallContext context) + { + var query = new GetBlogCategoryQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + return MapCategoryToResponse(result); + } + + public override async Task GetAllBlogCategories(GetAllBlogCategoriesRequest request, ServerCallContext context) + { + var query = new GetAllBlogCategoriesQuery + { + PageNumber = request.PaginationState?.PageNumber ?? 1, + PageSize = request.PaginationState?.PageSize ?? 20, + SearchTerm = request.SortBy + }; + + var result = await _sender.Send(query, context.CancellationToken); + var response = new GetAllBlogCategoriesResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var item in result.Models) + response.Models.Add(MapToListItem(item)); + + return response; + } + + public override async Task GetActiveBlogCategories(GetActiveBlogCategoriesRequest request, ServerCallContext context) + { + var query = new GetActiveBlogCategoriesQuery(); + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetActiveBlogCategoriesResponse(); + foreach (var item in result) + response.Categories.Add(MapToListItem(item)); + + return response; + } + + // ── Private Mapping Helpers ── + + private static GetBlogCategoryResponse MapCategoryToResponse(BlogCategoryDto dto) + { + return new GetBlogCategoryResponse + { + Id = dto.Id, + Title = dto.Title ?? string.Empty, + Slug = dto.Slug ?? string.Empty, + Description = dto.Description, + IconName = dto.IconName, + SortOrder = dto.SortOrder, + IsActive = dto.IsActive, + PostCount = dto.PostCount, + Created = dto.Created != default ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.Created, DateTimeKind.Utc)) : null + }; + } + + private static BlogCategoryListItem MapToListItem(BlogCategoryDto dto) + { + return new BlogCategoryListItem + { + Id = dto.Id, + Title = dto.Title ?? string.Empty, + Slug = dto.Slug ?? string.Empty, + Description = dto.Description, + IconName = dto.IconName, + SortOrder = dto.SortOrder, + IsActive = dto.IsActive, + PostCount = dto.PostCount + }; + } +} diff --git a/src/CMSMicroservice.WebApi/Services/BlogPostImageService.cs b/src/CMSMicroservice.WebApi/Services/BlogPostImageService.cs new file mode 100644 index 0000000..1c22019 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/BlogPostImageService.cs @@ -0,0 +1,72 @@ +using System.Linq; +using CMSMicroservice.Protobuf.Protos.BlogPostImage; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; +using CMSMicroservice.Application.BlogPostImageCQ.Commands.DeleteBlogPostImage; +using CMSMicroservice.Application.BlogPostImageCQ.Commands.ReorderBlogPostImages; +using CMSMicroservice.Application.BlogPostImageCQ.Queries.GetBlogPostImages; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using MediatR; + +namespace CMSMicroservice.WebApi.Services; + +public class BlogPostImageService : BlogPostImageContract.BlogPostImageContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public BlogPostImageService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task AddBlogPostImage(AddBlogPostImageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task DeleteBlogPostImage(DeleteBlogPostImageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetBlogPostImages(GetBlogPostImagesRequest request, ServerCallContext context) + { + var query = new GetBlogPostImagesQuery { BlogPostId = request.BlogPostId }; + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetBlogPostImagesResponse(); + foreach (var item in result) + { + response.Images.Add(new BlogPostImageItem + { + Id = item.Id, + BlogPostId = item.BlogPostId, + ImagePath = item.ImagePath ?? string.Empty, + ThumbnailPath = item.ThumbnailPath ?? string.Empty, + AltText = item.AltText, + Caption = item.Caption, + SortOrder = item.SortOrder + }); + } + + return response; + } + + public override async Task ReorderBlogPostImages(ReorderBlogPostImagesRequest request, ServerCallContext context) + { + var command = new ReorderBlogPostImagesCommand + { + Items = request.Items.Select(x => new Application.BlogPostImageCQ.Commands.ReorderBlogPostImages.ImageSortItem + { + Id = x.Id, + SortOrder = x.SortOrder + }).ToList() + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } +} diff --git a/src/CMSMicroservice.WebApi/Services/BlogPostService.cs b/src/CMSMicroservice.WebApi/Services/BlogPostService.cs new file mode 100644 index 0000000..18450a8 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/BlogPostService.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; +using System.Linq; +using CMSMicroservice.Protobuf.Protos.BlogPost; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.DeleteBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.IncrementViewCount; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPostBySlug; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetPublishedBlogPosts; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetFeaturedBlogPosts; +using CMSMicroservice.Domain.Enums; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Mapster; +using MediatR; +using AppModels = CMSMicroservice.Application.Common.Models; +using ProtoMetaData = CMSMicroservice.Protobuf.Protos.MetaData; + +namespace CMSMicroservice.WebApi.Services; + +public class BlogPostService : BlogPostContract.BlogPostContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public BlogPostService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task CreateBlogPost(CreateBlogPostRequest request, ServerCallContext context) + { + var command = new CreateBlogPostCommand + { + Title = request.Title, + Slug = request.Slug, + Summary = request.Summary, + HtmlContent = request.HtmlContent, + FeaturedImagePath = request.FeaturedImagePath, + FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath, + CategoryIds = request.CategoryIds.ToList(), + TagIds = request.TagIds.ToList(), + IsFeatured = request.IsFeatured, + SortOrder = request.SortOrder, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + var result = await _sender.Send(command, context.CancellationToken); + return new CreateBlogPostResponse { Id = result }; + } + + public override async Task UpdateBlogPost(UpdateBlogPostRequest request, ServerCallContext context) + { + var command = new UpdateBlogPostCommand + { + Id = request.Id, + Title = request.Title, + Slug = request.Slug, + Summary = request.Summary, + HtmlContent = request.HtmlContent, + FeaturedImagePath = request.FeaturedImagePath, + FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath, + CategoryIds = request.CategoryIds.ToList(), + TagIds = request.TagIds.ToList(), + IsFeatured = request.IsFeatured, + SortOrder = request.SortOrder, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task DeleteBlogPost(DeleteBlogPostRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetBlogPost(GetBlogPostRequest request, ServerCallContext context) + { + var query = new GetBlogPostQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + return MapBlogPostDtoToResponse(result); + } + + public override async Task GetBlogPostBySlug(GetBlogPostBySlugRequest request, ServerCallContext context) + { + var query = new GetBlogPostBySlugQuery { Slug = request.Slug }; + var result = await _sender.Send(query, context.CancellationToken); + return MapBlogPostDtoToResponse(result); + } + + public override async Task GetAllBlogPosts(GetAllBlogPostsRequest request, ServerCallContext context) + { + var query = new GetAllBlogPostsQuery + { + PageNumber = request.PaginationState?.PageNumber ?? 1, + PageSize = request.PaginationState?.PageSize ?? 10, + SortBy = request.SortBy, + SearchTerm = request.Filter?.SearchTerm, + Status = request.Filter?.Status.HasValue == true ? (BlogPostStatus?)request.Filter.Status.Value : null, + CategoryId = request.Filter?.CategoryId, + IsFeatured = request.Filter?.IsFeatured + }; + + var result = await _sender.Send(query, context.CancellationToken); + return MapAllBlogPostsResponse(result); + } + + public override async Task GetPublishedBlogPosts(GetPublishedBlogPostsRequest request, ServerCallContext context) + { + var query = new GetPublishedBlogPostsQuery + { + PageNumber = request.PaginationState?.PageNumber ?? 1, + PageSize = request.PaginationState?.PageSize ?? 10, + SearchTerm = request.SearchTerm, + CategoryId = request.CategoryId + }; + + var result = await _sender.Send(query, context.CancellationToken); + return MapAllBlogPostsResponse(result); + } + + public override async Task GetFeaturedBlogPosts(GetFeaturedBlogPostsRequest request, ServerCallContext context) + { + var query = new GetFeaturedBlogPostsQuery { Count = request.Count > 0 ? request.Count : 5 }; + var items = await _sender.Send(query, context.CancellationToken); + + var response = new GetAllBlogPostsResponse(); + foreach (var item in items) + response.Models.Add(MapToListItem(item)); + return response; + } + + public override async Task PublishBlogPost(PublishBlogPostRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task ArchiveBlogPost(ArchiveBlogPostRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task IncrementViewCount(IncrementViewCountRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + // ── Private Mapping Helpers ── + + private static GetBlogPostResponse MapBlogPostDtoToResponse(BlogPostDto dto) + { + var response = new GetBlogPostResponse + { + Id = dto.Id, + Title = dto.Title ?? string.Empty, + Slug = dto.Slug ?? string.Empty, + Summary = dto.Summary, + HtmlContent = dto.HtmlContent ?? string.Empty, + FeaturedImagePath = dto.FeaturedImagePath, + FeaturedImageThumbnailPath = dto.FeaturedImageThumbnailPath, + Status = (int)dto.Status, + StatusName = dto.StatusName ?? string.Empty, + ViewCount = dto.ViewCount, + AuthorUserId = dto.AuthorUserId, + IsFeatured = dto.IsFeatured, + SortOrder = dto.SortOrder, + Created = dto.Created != default ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.Created, DateTimeKind.Utc)) : null, + LastModified = dto.LastModified.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.LastModified.Value, DateTimeKind.Utc)) : null, + PublishedAt = dto.PublishedAt.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.PublishedAt.Value, DateTimeKind.Utc)) : null + }; + + if (dto.Categories != null) + foreach (var c in dto.Categories) + response.Categories.Add(new BlogPostCategoryInfo { Id = c.Id, Title = c.Title ?? string.Empty, Slug = c.Slug ?? string.Empty }); + + if (dto.Tags != null) + foreach (var t in dto.Tags) + response.Tags.Add(new BlogPostTagInfo { Id = t.Id, Title = t.Title ?? string.Empty, Name = t.Name ?? string.Empty }); + + return response; + } + + private static GetAllBlogPostsResponse MapAllBlogPostsResponse(GetAllBlogPostsResponseDto dto) + { + var response = new GetAllBlogPostsResponse + { + MetaData = dto.MetaData.Adapt() + }; + + foreach (var item in dto.Models) + response.Models.Add(MapToListItem(item)); + + return response; + } + + private static BlogPostListItem MapToListItem(BlogPostListItemDto item) + { + var listItem = new BlogPostListItem + { + Id = item.Id, + Title = item.Title ?? string.Empty, + Slug = item.Slug ?? string.Empty, + Summary = item.Summary, + FeaturedImageThumbnailPath = item.FeaturedImageThumbnailPath, + Status = item.Status, + StatusName = item.StatusName ?? string.Empty, + ViewCount = item.ViewCount, + IsFeatured = item.IsFeatured, + Created = item.Created != default ? Timestamp.FromDateTime(DateTime.SpecifyKind(item.Created, DateTimeKind.Utc)) : null, + PublishedAt = item.PublishedAt.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(item.PublishedAt.Value, DateTimeKind.Utc)) : null + }; + + if (item.Categories != null) + foreach (var c in item.Categories) + listItem.Categories.Add(new BlogPostCategoryInfo { Id = c.Id, Title = c.Title ?? string.Empty, Slug = c.Slug ?? string.Empty }); + + return listItem; + } +} diff --git a/src/CMSMicroservice.WebApi/Services/CategoryService.cs b/src/CMSMicroservice.WebApi/Services/CategoryService.cs index 318c144..33d9231 100644 --- a/src/CMSMicroservice.WebApi/Services/CategoryService.cs +++ b/src/CMSMicroservice.WebApi/Services/CategoryService.cs @@ -5,14 +5,19 @@ using CMSMicroservice.Application.CategoryCQ.Commands.UpdateCategory; using CMSMicroservice.Application.CategoryCQ.Commands.DeleteCategory; using CMSMicroservice.Application.CategoryCQ.Queries.GetCategory; using CMSMicroservice.Application.CategoryCQ.Queries.GetAllCategoryByFilter; +using MediatR; +using AppModels = CMSMicroservice.Application.Common.Models; + namespace CMSMicroservice.WebApi.Services; public class CategoryService : CategoryContract.CategoryContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; - public CategoryService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public CategoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; } public override async Task CreateNewCategory(CreateNewCategoryRequest request, ServerCallContext context) { @@ -34,4 +39,83 @@ public class CategoryService : CategoryContract.CategoryContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + // ============= Customer-specific Methods ============= + + public override async Task GetAllCategories(GetAllCategoriesRequest request, ServerCallContext context) + { + // Reuse existing GetAllCategoryByFilter query but with customer-specific response + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetAllCategoriesForCustomer(GetAllCategoriesForCustomerRequest request, ServerCallContext context) + { + // Use GetAllCategoryByFilter to get all active categories + var query = new GetAllCategoryByFilterQuery + { + Filter = new CMSMicroservice.Application.CategoryCQ.Queries.GetAllCategoryByFilter.GetAllCategoryByFilterFilter + { + IsActive = true // Only active categories for customers + }, + PaginationState = new AppModels.PaginationState + { + PageNumber = request.PageNumber > 0 ? request.PageNumber : 1, + PageSize = request.PageSize > 0 ? request.PageSize : 100 // Default to large page size to get all + }, + SortBy = "SortOrder" // Sort by display order + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetAllCategoriesForCustomerResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = result.MetaData.CurrentPage, + PageSize = result.MetaData.PageSize, + TotalCount = result.MetaData.TotalCount, + TotalPage = result.MetaData.TotalPage, + HasNext = result.MetaData.HasNext, + HasPrevious = result.MetaData.HasPrevious + } + }; + + foreach (var cat in result.Models) + { + response.Models.Add(new GetAllCategoryFilterResponseModel + { + Id = cat.Id, + Name = cat.Name, + Title = cat.Title, + Description = cat.Description ?? string.Empty, + ImagePath = cat.ImagePath ?? string.Empty, + ParentId = cat.ParentId ?? 0, + IsActive = cat.IsActive, + SortOrder = cat.SortOrder + }); + } + + return response; + } + + public override async Task GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest request, ServerCallContext context) + { + var query = new GetCategoryQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + + return new GetCategoryByIdForCustomerResponse + { + Category = new GetAllCategoryFilterResponseModel + { + Id = result.Id, + Name = result.Name, + Title = result.Title, + Description = result.Description ?? string.Empty, + ImagePath = result.ImagePath ?? string.Empty, + ParentId = result.ParentId ?? 0, + IsActive = result.IsActive, + SortOrder = result.SortOrder + } + }; + } } diff --git a/src/CMSMicroservice.WebApi/Services/CityService.cs b/src/CMSMicroservice.WebApi/Services/CityService.cs index 132dd3d..4156424 100644 --- a/src/CMSMicroservice.WebApi/Services/CityService.cs +++ b/src/CMSMicroservice.WebApi/Services/CityService.cs @@ -1,16 +1,22 @@ using CMSMicroservice.Protobuf.Protos.City; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter; +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.EntityFrameworkCore; +using Google.Protobuf.WellKnownTypes; +using System.Linq; namespace CMSMicroservice.WebApi.Services; public class CityService : CityContract.CityContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly IApplicationDbContext _context; - public CityService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public CityService(IDispatchRequestToCQRS dispatchRequestToCQRS, IApplicationDbContext context) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _context = context; } public override async Task GetAllCitiesByFilter( @@ -22,4 +28,184 @@ public class CityService : CityContract.CityContractBase GetAllCitiesByFilterQuery, GetAllCitiesByFilterResponse>(request, context); } + + #region Customer Methods + + public override async Task GetCitiesForCustomer( + GetCitiesForCustomerRequest request, ServerCallContext context) + { + var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1; + var pageSize = request.PageSize > 0 ? request.PageSize : 50; + + var query = _context.Cities + .Include(c => c.State) + .Where(c => !c.IsDeleted); + + if (request.StateId != null) + query = query.Where(c => c.StateId == request.StateId.Value); + + if (!string.IsNullOrWhiteSpace(request.SearchTerm)) + query = query.Where(c => c.Name.Contains(request.SearchTerm) || c.Native.Contains(request.SearchTerm)); + + var totalCount = await query.CountAsync(context.CancellationToken); + + var cities = await query + .OrderBy(c => c.Native) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(c => new CityDto + { + Id = c.Id, + ExternalId = c.ExternalId, + Name = c.Name, + Native = c.Native, + Latitude = c.Latitude ?? string.Empty, + Longitude = c.Longitude ?? string.Empty, + StateId = c.StateId, + StateName = c.State != null ? c.State.Name : string.Empty, + StateNative = c.State != null ? c.State.Native : string.Empty + }) + .ToListAsync(context.CancellationToken); + + return new GetCitiesForCustomerResponse + { + Cities = { cities }, + MetaData = new CMSMicroservice.Protobuf.Protos.City.MetaData + { + CurrentPage = pageNumber, + PageSize = pageSize, + TotalCount = totalCount, + TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize), + HasNext = pageNumber * pageSize < totalCount, + HasPrevious = pageNumber > 1 + } + }; + } + + public override async Task GetCityByIdForCustomer( + GetCityByIdForCustomerRequest request, ServerCallContext context) + { + var city = await _context.Cities + .Include(c => c.State) + .Where(c => c.Id == request.Id && !c.IsDeleted) + .Select(c => new CityDto + { + Id = c.Id, + ExternalId = c.ExternalId, + Name = c.Name, + Native = c.Native, + Latitude = c.Latitude ?? string.Empty, + Longitude = c.Longitude ?? string.Empty, + StateId = c.StateId, + StateName = c.State != null ? c.State.Name : string.Empty, + StateNative = c.State != null ? c.State.Native : string.Empty + }) + .FirstOrDefaultAsync(context.CancellationToken); + + if (city == null) + throw new RpcException(new Status(StatusCode.NotFound, "شهر یافت نشد")); + + return new GetCityByIdForCustomerResponse { City = city }; + } + + public override async Task GetCitiesByStateForCustomer( + GetCitiesByStateForCustomerRequest request, ServerCallContext context) + { + var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1; + var pageSize = request.PageSize > 0 ? request.PageSize : 100; + + var query = _context.Cities + .Include(c => c.State) + .Where(c => c.StateId == request.StateId && !c.IsDeleted); + + var totalCount = await query.CountAsync(context.CancellationToken); + + var cities = await query + .OrderBy(c => c.Native) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(c => new CityDto + { + Id = c.Id, + ExternalId = c.ExternalId, + Name = c.Name, + Native = c.Native, + Latitude = c.Latitude ?? string.Empty, + Longitude = c.Longitude ?? string.Empty, + StateId = c.StateId, + StateName = c.State != null ? c.State.Name : string.Empty, + StateNative = c.State != null ? c.State.Native : string.Empty + }) + .ToListAsync(context.CancellationToken); + + return new GetCitiesByStateForCustomerResponse + { + Cities = { cities }, + MetaData = new CMSMicroservice.Protobuf.Protos.City.MetaData + { + CurrentPage = pageNumber, + PageSize = pageSize, + TotalCount = totalCount, + TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize), + HasNext = pageNumber * pageSize < totalCount, + HasPrevious = pageNumber > 1 + } + }; + } + + // Admin Methods + public override async Task CreateCity( + CreateCityRequest request, ServerCallContext context) + { + var city = new Domain.Entities.Geography.City + { + ExternalId = request.ExternalId, + Name = request.Name, + Native = request.Native, + Latitude = request.Latitude ?? string.Empty, + Longitude = request.Longitude ?? string.Empty, + StateId = request.StateId + }; + + _context.Cities.Add(city); + await _context.SaveChangesAsync(context.CancellationToken); + + return new CreateCityResponse + { + Id = city.Id, + Message = "شهر با موفقیت ایجاد شد" + }; + } + + public override async Task UpdateCity( + UpdateCityRequest request, ServerCallContext context) + { + var city = await _context.Cities.FindAsync(new object[] { request.Id }, context.CancellationToken); + if (city == null) + throw new RpcException(new Status(StatusCode.NotFound, "شهر یافت نشد")); + + city.ExternalId = request.ExternalId; + city.Name = request.Name; + city.Native = request.Native; + if (request.Latitude != null) city.Latitude = request.Latitude; + if (request.Longitude != null) city.Longitude = request.Longitude; + city.StateId = request.StateId; + + await _context.SaveChangesAsync(context.CancellationToken); + return new Empty(); + } + + public override async Task DeleteCity( + DeleteCityRequest request, ServerCallContext context) + { + var city = await _context.Cities.FindAsync(new object[] { request.Id }, context.CancellationToken); + if (city == null) + throw new RpcException(new Status(StatusCode.NotFound, "شهر یافت نشد")); + + city.IsDeleted = true; + await _context.SaveChangesAsync(context.CancellationToken); + return new Empty(); + } + + #endregion } diff --git a/src/CMSMicroservice.WebApi/Services/ClubMembershipService.cs b/src/CMSMicroservice.WebApi/Services/ClubMembershipService.cs index 0b8cfa5..94784e0 100644 --- a/src/CMSMicroservice.WebApi/Services/ClubMembershipService.cs +++ b/src/CMSMicroservice.WebApi/Services/ClubMembershipService.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Protobuf.Protos.ClubMembership; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership; @@ -10,16 +11,24 @@ using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHist using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics; using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures; using CMSMicroservice.Application.ClubFeatureCQ.Commands.ToggleUserClubFeature; +using Microsoft.Extensions.Logging; namespace CMSMicroservice.WebApi.Services; public class ClubMembershipService : ClubMembershipContract.ClubMembershipContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ICurrentUserService _currentUserService; + private readonly ILogger _logger; - public ClubMembershipService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public ClubMembershipService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ICurrentUserService currentUserService, + ILogger logger) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _currentUserService = currentUserService; + _logger = logger; } public override async Task ActivateClubMembership(ActivateClubMembershipRequest request, ServerCallContext context) @@ -39,6 +48,25 @@ public class ClubMembershipService : ClubMembershipContract.ClubMembershipContra public override async Task GetClubMembership(GetClubMembershipRequest request, ServerCallContext context) { + // اگر UserId در request نیست یا صفر است، از JWT بخوان + if (request.UserId <= 0) + { + if (long.TryParse(_currentUserService.UserId, out var tokenUserId) && tokenUserId > 0) + { + _logger.LogInformation("GetClubMembership: Reading UserId from JWT token: {UserId}", tokenUserId); + request = new GetClubMembershipRequest { UserId = tokenUserId }; + } + else + { + _logger.LogWarning("GetClubMembership: No valid UserId in request or JWT token"); + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); + } + } + else + { + _logger.LogInformation("GetClubMembership: Using UserId from request: {UserId}", request.UserId); + } + return await _dispatchRequestToCQRS.Handle(request, context); } diff --git a/src/CMSMicroservice.WebApi/Services/CommissionService.cs b/src/CMSMicroservice.WebApi/Services/CommissionService.cs index a4c6799..2b7990b 100644 --- a/src/CMSMicroservice.WebApi/Services/CommissionService.cs +++ b/src/CMSMicroservice.WebApi/Services/CommissionService.cs @@ -12,6 +12,7 @@ using CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool; using CMSMicroservice.Application.CommissionCQ.Queries.GetUserCommissionPayouts; using CMSMicroservice.Application.CommissionCQ.Queries.GetCommissionPayoutHistory; using CMSMicroservice.Application.CommissionCQ.Queries.GetUserWeeklyBalances; +using CMSMicroservice.Application.CommissionCQ.Queries.GetMyWeeklyBalances; using CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools; using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests; using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus; @@ -19,6 +20,7 @@ using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs; using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports; using CMSMicroservice.Application.CommissionCQ.Queries.GetAvailableWeeks; using CMSMicroservice.Application.CommissionCQ.Queries.GetWeekDefinitions; +using CMSMicroservice.Application.CommissionCQ.Queries.GetMyCommissionPayouts; namespace CMSMicroservice.WebApi.Services; @@ -68,6 +70,11 @@ public class CommissionService : CommissionContract.CommissionContractBase return await _dispatchRequestToCQRS.Handle(request, context); } + public override async Task GetMyCommissionPayouts(GetMyCommissionPayoutsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + public override async Task GetCommissionPayoutHistory(GetCommissionPayoutHistoryRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); @@ -128,4 +135,9 @@ public class CommissionService : CommissionContract.CommissionContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + public override async Task GetMyWeeklyBalances(GetMyWeeklyBalancesRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } } diff --git a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs index fa0fda6..8a096aa 100644 --- a/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs +++ b/src/CMSMicroservice.WebApi/Services/ConfigurationService.cs @@ -1,7 +1,11 @@ +using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures; +using CMSMicroservice.Application.Common.Authorization; +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Common; using CMSMicroservice.Protobuf.Protos.Configuration; using Google.Protobuf.WellKnownTypes; using Grpc.Core; +using MediatR; using Microsoft.Extensions.Logging; namespace CMSMicroservice.WebApi.Services; @@ -12,10 +16,17 @@ namespace CMSMicroservice.WebApi.Services; public class ConfigurationService : ConfigurationContract.ConfigurationContractBase { private readonly ILogger _logger; + private readonly ICurrentUserService _currentUserService; + private readonly IMediator _mediator; - public ConfigurationService(ILogger logger) + public ConfigurationService( + ILogger logger, + ICurrentUserService currentUserService, + IMediator mediator) { _logger = logger; + _currentUserService = currentUserService; + _mediator = mediator; } /// @@ -41,9 +52,65 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB return Task.FromResult(response); } + /// + /// دریافت تنظیمات باشگاه مشتریان + /// + public override Task GetClubConfiguration(Empty request, ServerCallContext context) + { + var response = new GetClubConfigurationResponse + { + ActivationFee = SystemConstants.ClubActivationFee, + MembershipGiftValue = SystemConstants.ClubMembershipGiftValue + }; + + _logger.LogDebug("Club configuration requested: ActivationFee={ActivationFee}, GiftValue={GiftValue}", + response.ActivationFee, response.MembershipGiftValue); + + return Task.FromResult(response); + } + + /// + /// دریافت ویژگی‌های باشگاه مشتریان برای کاربر جاری + /// + public override async Task GetClubFeatures(Empty request, ServerCallContext context) + { + // دریافت UserId از JWT + if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) + { + _logger.LogWarning("GetClubFeatures called without valid user authentication"); + return new GetClubFeaturesResponse(); // لیست خالی برای کاربران غیر احراز هویت شده + } + + // فراخوانی GetUserClubFeatures از طریق MediatR (CQRS داخلی) + var query = new GetUserClubFeaturesQuery { UserId = userId }; + var userFeatures = await _mediator.Send(query, context.CancellationToken); + + // تبدیل به فرمت GetClubFeaturesResponse + var response = new GetClubFeaturesResponse(); + foreach (var feature in userFeatures) + { + response.Features.Add(new ClubFeatureModel + { + Id = feature.ClubFeatureId, + Title = feature.FeatureTitle ?? string.Empty, + Description = feature.FeatureDescription ?? string.Empty, + IsEnabled = feature.IsActive, + DisplayOrder = feature.SortOrder, + GrantedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(feature.GrantedAt, DateTimeKind.Utc)), + CreatedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(feature.CreatedAt, DateTimeKind.Utc)), + Notes = feature.Notes ?? string.Empty + }); + } + + _logger.LogDebug("Club features requested for user {UserId}: {Count} features returned", userId, response.Features.Count); + + return response; + } + /// /// دریافت تمام تنظیمات /// + [RequiresPermission(PermissionNames.SettingsView)] public override Task GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context) { var response = new GetAllConfigurationsResponse(); @@ -80,16 +147,18 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB /// /// سایر عملیات‌ها که فعلاً پیاده‌سازی نشده‌اند (چون از constant استفاده می‌کنیم) /// + [RequiresPermission(PermissionNames.SettingsManageConfiguration)] public override Task CreateOrUpdateConfiguration(CreateOrUpdateConfigurationRequest request, ServerCallContext context) { - _logger.LogWarning("CreateOrUpdateConfiguration called but SystemConstants are read-only"); - throw new RpcException(new Status(StatusCode.Unimplemented, "تنظیمات سیستم فقط خواندنی هستند")); + _logger.LogWarning("CreateOrUpdateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key); + throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. تغییر از طریق کد انجام می‌شود")); } + [RequiresPermission(PermissionNames.SettingsManageConfiguration)] public override Task DeactivateConfiguration(DeactivateConfigurationRequest request, ServerCallContext context) { - _logger.LogWarning("DeactivateConfiguration called but SystemConstants are read-only"); - throw new RpcException(new Status(StatusCode.Unimplemented, "تنظیمات سیستم فقط خواندنی هستند")); + _logger.LogWarning("DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key); + throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. غیرفعال‌سازی از طریق کد انجام می‌شود")); } public override Task GetConfigurationHistory(GetConfigurationHistoryRequest request, ServerCallContext context) diff --git a/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs b/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs index 23c1857..2fea3a3 100644 --- a/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs @@ -1,25 +1,66 @@ using CMSMicroservice.Protobuf.Protos.DiscountOrder; using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder; using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment; using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders; +using CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders; +using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport; +using DomainEnums = CMSMicroservice.Domain.Enums; +using Grpc.Core; +using Google.Protobuf.WellKnownTypes; +using Mapster; +using MediatR; namespace CMSMicroservice.WebApi.Services; public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly ICurrentUserService _currentUserService; - public DiscountOrderService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public DiscountOrderService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + ICurrentUserService currentUserService) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _currentUserService = currentUserService; + } + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var uid) && uid > 0) + return uid; + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); } public override async Task PlaceOrder(PlaceOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new PlaceOrderCommand + { + UserId = GetCurrentUserId(), + UserAddressId = request.UserAddressId, + DiscountBalanceToUse = request.DiscountBalanceToUse + }; + var result = await _sender.Send(command); + + var response = new PlaceOrderResponse + { + Success = result.Success, + Message = result.Message ?? string.Empty, + OrderId = result.OrderId ?? 0, + GatewayAmount = result.GatewayAmountRequired, + }; + + if (!string.IsNullOrEmpty(result.PaymentUrl)) + response.PaymentUrl = result.PaymentUrl; + + return response; } public override async Task CompleteOrderPayment(CompleteOrderPaymentRequest request, ServerCallContext context) @@ -34,11 +75,121 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB public override async Task GetOrderById(GetOrderByIdRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetOrderByIdQuery + { + OrderId = request.OrderId, + UserId = GetCurrentUserId() + }; + var result = await _sender.Send(query); + if (result == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + var response = new GetOrderByIdResponse + { + Id = result.Id, + UserId = result.UserId, + TotalPrice = result.TotalAmount, + DiscountBalanceUsed = result.DiscountBalanceUsed, + GatewayAmount = result.GatewayAmountPaid, + PaymentCompleted = result.PaymentStatus == DomainEnums.PaymentStatus.Success, + PaymentStatus = MapPaymentStatus(result.PaymentStatus), + DeliveryStatus = MapDeliveryStatus(result.DeliveryStatus), + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(result.Created, DateTimeKind.Utc)), + }; + + if (result.TrackingCode != null) response.TrackingCode = result.TrackingCode; + if (result.DeliveryDescription != null) response.Notes = result.DeliveryDescription; + + if (result.Address != null) + { + response.Address = new AddressInfo + { + Title = result.Address.Title ?? "", + Address = result.Address.Address ?? "", + PostalCode = result.Address.PostalCode ?? "" + }; + } + + foreach (var item in result.Items) + { + response.Items.Add(new CMSMicroservice.Protobuf.Protos.DiscountOrder.OrderItemDto + { + ProductId = item.ProductId, + ProductTitle = item.ProductTitle ?? "", + UnitPrice = item.UnitPrice, + MaxDiscountPercent = item.DiscountPercentUsed, + Count = item.Count, + TotalPrice = item.UnitPrice * item.Count, + DiscountAmount = item.DiscountAmount, + FinalPrice = item.FinalPrice, + ImagePath = item.ImagePath ?? "", + ThumbnailPath = item.ThumbnailPath ?? "" + }); + } + + return response; } public override async Task GetUserOrders(GetUserOrdersRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetUserOrdersQuery + { + UserId = GetCurrentUserId() + }; + var result = await _sender.Send(query); + + var response = new GetUserOrdersResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var o in result.Models) + { + var summary = new CMSMicroservice.Protobuf.Protos.DiscountOrder.OrderSummaryDto + { + Id = o.Id, + TotalPrice = o.TotalAmount, + DiscountBalanceUsed = o.DiscountBalanceUsed, + GatewayAmount = o.GatewayAmountPaid, + PaymentCompleted = o.PaymentStatus == DomainEnums.PaymentStatus.Success, + PaymentStatus = MapPaymentStatus(o.PaymentStatus), + DeliveryStatus = MapDeliveryStatus(o.DeliveryStatus), + ItemsCount = o.ItemsCount, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(o.Created, DateTimeKind.Utc)), + }; + if (o.TrackingCode != null) summary.TrackingCode = o.TrackingCode; + response.Models.Add(summary); + } + + return response; } + + public override async Task GetAllDiscountOrders(GetAllDiscountOrdersRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetDiscountSalesReport(GetDiscountSalesReportRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + private static CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus MapPaymentStatus(DomainEnums.PaymentStatus status) => status switch + { + DomainEnums.PaymentStatus.Success => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentCompleted, + DomainEnums.PaymentStatus.Reject => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentFailed, + DomainEnums.PaymentStatus.Pending => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentPending, + _ => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentPending + }; + + private static DeliveryStatus MapDeliveryStatus(DomainEnums.DeliveryStatus status) => status switch + { + DomainEnums.DeliveryStatus.None => DeliveryStatus.DeliveryPending, + DomainEnums.DeliveryStatus.Pending => DeliveryStatus.DeliveryProcessing, + DomainEnums.DeliveryStatus.InTransit => DeliveryStatus.DeliveryShipped, + DomainEnums.DeliveryStatus.Delivered => DeliveryStatus.DeliveryDelivered, + DomainEnums.DeliveryStatus.Returned => DeliveryStatus.DeliveryCancelled, + DomainEnums.DeliveryStatus.Cancelled => DeliveryStatus.DeliveryCancelled, + _ => DeliveryStatus.DeliveryPending + }; } diff --git a/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs b/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs index 236fbb7..effc833 100644 --- a/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs +++ b/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs @@ -1,30 +1,86 @@ +using System.Collections.Generic; +using System.Linq; using CMSMicroservice.Protobuf.Protos.DiscountProduct; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct; using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct; using CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProduct; +using CMSMicroservice.Application.DiscountShopCQ.Commands.AddDiscountProductImage; +using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProductImage; +using CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProductImage; +using CMSMicroservice.Application.DiscountShopCQ.Commands.ReorderDiscountProductImages; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductById; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProducts; +using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductImages; +using MediatR; +using Mapster; namespace CMSMicroservice.WebApi.Services; public class DiscountProductService : DiscountProductContract.DiscountProductContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; - public DiscountProductService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public DiscountProductService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; } public override async Task CreateDiscountProduct(CreateDiscountProductRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new CreateDiscountProductCommand + { + Title = request.Title, + ShortInfomation = request.ShortInfomation, + FullInformation = request.FullInformation, + Price = request.Price, + MaxDiscountPercent = request.MaxDiscountPercent, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + CategoryIds = request.CategoryIds?.ToList() ?? new List(), + // Map binary image data + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName, + ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(), + ThumbnailFileMime = request.ThumbnailFile?.Mime, + ThumbnailFileName = request.ThumbnailFile?.FileName + }; + + var productId = await _sender.Send(command, context.CancellationToken); + return new CreateDiscountProductResponse { ProductId = productId }; } public override async Task UpdateDiscountProduct(UpdateDiscountProductRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new UpdateDiscountProductCommand + { + ProductId = request.ProductId, + Title = request.Title, + ShortInfomation = request.ShortInfomation, + FullInformation = request.FullInformation, + Price = request.Price, + MaxDiscountPercent = request.MaxDiscountPercent, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + CategoryIds = request.CategoryIds?.ToList() ?? new List(), + // Map binary image data + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName, + ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(), + ThumbnailFileMime = request.ThumbnailFile?.Mime, + ThumbnailFileName = request.ThumbnailFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); } public override async Task DeleteDiscountProduct(DeleteDiscountProductRequest request, ServerCallContext context) @@ -41,4 +97,35 @@ public class DiscountProductService : DiscountProductContract.DiscountProductCon { return await _dispatchRequestToCQRS.Handle(request, context); } + + // Product Image Gallery Operations + public override async Task AddDiscountProductImage(AddDiscountProductImageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task UpdateDiscountProductImage(UpdateDiscountProductImageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task DeleteDiscountProductImage(DeleteDiscountProductImageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task ReorderDiscountProductImages(ReorderDiscountProductImagesRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetDiscountProductImages(GetDiscountProductImagesRequest request, ServerCallContext context) + { + var query = request.Adapt(); + var images = await _sender.Send(query, context.CancellationToken); + + var response = new GetDiscountProductImagesResponse(); + response.Images.AddRange(images.Select(i => i.Adapt())); + return response; + } } diff --git a/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs b/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs index 56e2672..4e472e4 100644 --- a/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs +++ b/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs @@ -1,44 +1,111 @@ using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart; using CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart; using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount; using CMSMicroservice.Application.DiscountShopCQ.Commands.ClearCart; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserCart; +using Grpc.Core; +using Mapster; +using MediatR; namespace CMSMicroservice.WebApi.Services; public class DiscountShoppingCartService : DiscountShoppingCartContract.DiscountShoppingCartContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly ICurrentUserService _currentUserService; - public DiscountShoppingCartService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public DiscountShoppingCartService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + ICurrentUserService currentUserService) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _currentUserService = currentUserService; + } + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var uid) && uid > 0) + return uid; + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); } public override async Task AddToCart(AddToCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new AddToCartCommand + { + UserId = GetCurrentUserId(), + ProductId = request.ProductId, + Count = request.Count + }; + var result = await _sender.Send(command, context.CancellationToken); + return result.Adapt(); } public override async Task RemoveFromCart(RemoveFromCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new RemoveFromCartCommand + { + UserId = GetCurrentUserId(), + ProductId = request.ProductId + }; + var result = await _sender.Send(command, context.CancellationToken); + return result.Adapt(); } public override async Task UpdateCartItemCount(UpdateCartItemCountRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new UpdateCartItemCountCommand + { + UserId = GetCurrentUserId(), + ProductId = request.ProductId, + NewCount = request.NewCount + }; + var result = await _sender.Send(command, context.CancellationToken); + return result.Adapt(); } public override async Task GetUserCart(GetUserCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetUserCartQuery { UserId = GetCurrentUserId() }; + var cart = await _sender.Send(query, context.CancellationToken); + + var response = new GetUserCartResponse + { + TotalPrice = cart.TotalAmount, + TotalDiscountAmount = cart.MaxDiscountAmount, + FinalPrice = cart.MinPayableAmount + }; + + foreach (var item in cart.Items) + { + response.Items.Add(new CMSMicroservice.Protobuf.Protos.DiscountShoppingCart.CartItemDto + { + ProductId = item.ProductId, + ProductTitle = item.ProductTitle ?? string.Empty, + ProductImagePath = item.ProductImagePath ?? string.Empty, + UnitPrice = item.UnitPrice, + MaxDiscountPercent = item.MaxDiscountPercent, + Count = item.Count, + TotalPrice = item.SubTotal, + DiscountAmount = item.MaxDiscountAmount, + FinalPrice = item.MinPayable, + ProductRemainingCount = item.RemainingStock + }); + } + + return response; } public override async Task ClearCart(ClearCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new ClearCartCommand { UserId = GetCurrentUserId() }; + await _sender.Send(command, context.CancellationToken); + return new Empty(); } } diff --git a/src/CMSMicroservice.WebApi/Services/HealthService.cs b/src/CMSMicroservice.WebApi/Services/HealthService.cs new file mode 100644 index 0000000..4f7f3e5 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/HealthService.cs @@ -0,0 +1,48 @@ +using CMSMicroservice.Protobuf.Protos.Health; +using CMSMicroservice.Application.HealthCQ.Queries.GetSystemHealth; +using MediatR; +using Mapster; +using Grpc.Core; +using Google.Protobuf.WellKnownTypes; +using System.Linq; + +namespace CMSMicroservice.WebApi.Services; + +public class HealthService : HealthContract.HealthContractBase +{ + private readonly IMediator _mediator; + + public HealthService(IMediator mediator) + { + _mediator = mediator; + } + + public override async Task GetSystemHealth(Empty request, ServerCallContext context) + { + var query = new GetSystemHealthQuery(); + var result = await _mediator.Send(query, context.CancellationToken); + + return result.Adapt(); + } + + public override async Task GetServiceHealth(GetServiceHealthRequest request, ServerCallContext context) + { + // For now, just return system health filtered by service name + var systemHealthQuery = new GetSystemHealthQuery(); + var systemHealth = await _mediator.Send(systemHealthQuery, context.CancellationToken); + + var service = systemHealth.Services.FirstOrDefault(s => + s.ServiceName.Equals(request.ServiceName, StringComparison.OrdinalIgnoreCase)); + + if (service == null) + { + throw new ArgumentException($"Service '{request.ServiceName}' not found"); + } + + return new GetServiceHealthResponse + { + Service = service.Adapt(), + CheckedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow) + }; + } +} \ No newline at end of file diff --git a/src/CMSMicroservice.WebApi/Services/ImageResolverService.cs b/src/CMSMicroservice.WebApi/Services/ImageResolverService.cs new file mode 100644 index 0000000..1025c70 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/ImageResolverService.cs @@ -0,0 +1,42 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Protobuf.Protos.ImageResolver; +using Grpc.Core; + +namespace CMSMicroservice.WebApi.Services; + +/// +/// سرویس اختصاصی resolve تصاویر — مسیر نسبی را به base64 data-URI تبدیل می‌کند. +/// FrontOffice از طریق این سرویس تمام تصاویر دینامیک را دریافت می‌کند. +/// +public class ImageResolverService : ImageResolverContract.ImageResolverContractBase +{ + private readonly IFileManager _fileManager; + + public ImageResolverService(IFileManager fileManager) + { + _fileManager = fileManager; + } + + public override Task ResolveImages(ResolveImagesRequest request, ServerCallContext context) + { + var response = new ResolveImagesResponse(); + + foreach (var path in request.Paths) + { + var dataUri = string.Empty; + + if (!string.IsNullOrWhiteSpace(path)) + { + dataUri = _fileManager.ResolveImageUrl(path); + } + + response.Images.Add(new ResolvedImage + { + OriginalPath = path ?? string.Empty, + DataUri = dataUri ?? string.Empty + }); + } + + return Task.FromResult(response); + } +} diff --git a/src/CMSMicroservice.WebApi/Services/InventoryService.cs b/src/CMSMicroservice.WebApi/Services/InventoryService.cs new file mode 100644 index 0000000..63ee6ca --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/InventoryService.cs @@ -0,0 +1,472 @@ +using CMSMicroservice.Protobuf.Protos.Inventory; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse; +using CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses; +using CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem; +using CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory; +using CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory; +using CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory; +using CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems; +using CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems; +using CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement; +using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements; +using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem; +using CMSMicroservice.Application.Common.Interfaces; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using MediatR; +using Microsoft.EntityFrameworkCore; +using System.Linq; + +namespace CMSMicroservice.WebApi.Services; + +public class InventoryService : InventoryContract.InventoryContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly IMediator _mediator; + private readonly IApplicationDbContext _context; + + public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, IMediator mediator, IApplicationDbContext context) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + _mediator = mediator; + _context = context; + } + + // ========== Warehouse Management ========== + + public override async Task CreateWarehouse(CreateWarehouseRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task UpdateWarehouse(UpdateWarehouseRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task DeleteWarehouse(DeleteWarehouseRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetWarehouse(GetWarehouseRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetAllWarehouses(GetAllWarehousesRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task SetDefaultWarehouse(SetDefaultWarehouseRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + // ========== Inventory Item Management ========== + + public override async Task GetInventoryItem(GetInventoryItemRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetInventoryByProduct(GetInventoryByProductRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetAllInventoryItems(GetAllInventoryItemsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetLowStockItems(GetLowStockItemsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task UpdateInventorySettings(UpdateInventorySettingsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + // ========== Stock Operations ========== + + public override async Task AddStock(AddStockRequest request, ServerCallContext context) + { + // Lookup InventoryItem by ProductId + ProductType + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = request.ProductId, + ProductType = (Domain.Enums.ProductType)request.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + { + throw new RpcException(new Status(StatusCode.NotFound, + $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}")); + } + + // Execute IncreaseInventoryCommand + var response = await _mediator.Send( + new IncreaseInventoryCommand + { + Id = inventoryItem.Id, + Quantity = request.Quantity, + ReferenceNumber = request.ReferenceNumber + }, + context.CancellationToken); + + return new AddStockResponse + { + InventoryItemId = inventoryItem.Id, + NewQuantity = response.NewQuantity + }; + } + + public override async Task AdjustStock(AdjustStockRequest request, ServerCallContext context) + { + // Lookup InventoryItem + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = request.ProductId, + ProductType = (Domain.Enums.ProductType)request.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + { + throw new RpcException(new Status(StatusCode.NotFound, + $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}")); + } + + // Determine if increase or decrease + var difference = request.NewQuantity - inventoryItem.Quantity; + + if (difference > 0) + { + await _mediator.Send( + new IncreaseInventoryCommand + { + Id = inventoryItem.Id, + Quantity = difference, + ReferenceNumber = request.ReferenceNumber + }, + context.CancellationToken); + + return new AdjustStockResponse + { + PreviousQuantity = inventoryItem.Quantity, + NewQuantity = request.NewQuantity, + Difference = difference + }; + } + else if (difference < 0) + { + await _mediator.Send( + new ReduceInventoryCommand + { + Id = inventoryItem.Id, + Quantity = Math.Abs(difference), + FromReserved = false, + ReferenceNumber = request.ReferenceNumber + }, + context.CancellationToken); + + return new AdjustStockResponse + { + PreviousQuantity = inventoryItem.Quantity, + NewQuantity = request.NewQuantity, + Difference = difference + }; + } + else + { + return new AdjustStockResponse + { + PreviousQuantity = inventoryItem.Quantity, + NewQuantity = inventoryItem.Quantity, + Difference = 0 + }; + } + } + + public override async Task ReserveStock(ReserveStockRequest request, ServerCallContext context) + { + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = request.ProductId, + ProductType = (Domain.Enums.ProductType)request.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + return new ReserveStockResponse { Success = false, Message = "آیتم موجودی یافت نشد", AvailableQuantity = 0 }; + + var available = inventoryItem.Quantity - inventoryItem.ReservedQuantity; + if (available < request.Quantity) + return new ReserveStockResponse { Success = false, Message = $"موجودی کافی نیست. موجود: {available}", AvailableQuantity = available }; + + await _mediator.Send( + new ReserveInventoryCommand + { + Id = inventoryItem.Id, + Quantity = request.Quantity, + ReferenceNumber = request.OrderId != null ? $"ORDER-{request.OrderId.Value}" : $"RESERVE-{DateTime.UtcNow.Ticks}" + }, + context.CancellationToken); + + return new ReserveStockResponse { Success = true, Message = "رزرو با موفقیت انجام شد", AvailableQuantity = available - request.Quantity }; + } + + public override async Task ReleaseReservation(ReleaseReservationRequest request, ServerCallContext context) + { + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = request.ProductId, + ProductType = (Domain.Enums.ProductType)request.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد")); + + await _mediator.Send( + new ReleaseReservedInventoryCommand + { + Id = inventoryItem.Id, + Quantity = request.Quantity, + ReferenceNumber = request.OrderId != null ? $"RELEASE-ORDER-{request.OrderId.Value}" : $"RELEASE-{DateTime.UtcNow.Ticks}" + }, + context.CancellationToken); + + return new Empty(); + } + + public override async Task ConfirmSale(ConfirmSaleRequest request, ServerCallContext context) + { + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = request.ProductId, + ProductType = (Domain.Enums.ProductType)request.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد")); + + await _mediator.Send( + new ReduceInventoryCommand + { + Id = inventoryItem.Id, + Quantity = request.Quantity, + FromReserved = request.FromReservation, + ReferenceNumber = request.OrderId != null ? $"SALE-ORDER-{request.OrderId.Value}" : $"SALE-{DateTime.UtcNow.Ticks}" + }, + context.CancellationToken); + + return new Empty(); + } + + public override async Task ProcessReturn(ProcessReturnRequest request, ServerCallContext context) + { + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = request.ProductId, + ProductType = (Domain.Enums.ProductType)request.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد")); + + var result = await _mediator.Send( + new IncreaseInventoryCommand + { + Id = inventoryItem.Id, + Quantity = request.Quantity, + ReferenceNumber = request.OrderId != null ? $"RETURN-ORDER-{request.OrderId.Value}" : $"RETURN-{DateTime.UtcNow.Ticks}" + }, + context.CancellationToken); + + return new ProcessReturnResponse { NewQuantity = result.NewQuantity }; + } + + public override async Task RecordLoss(RecordLossRequest request, ServerCallContext context) + { + // Lookup InventoryItem by ProductId + ProductType + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = request.ProductId, + ProductType = (Domain.Enums.ProductType)request.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + { + throw new RpcException(new Status(StatusCode.NotFound, + $"Inventory item not found for ProductId={request.ProductId}, ProductType={request.ProductType}")); + } + + // Execute ReduceInventoryCommand to record the loss + await _mediator.Send( + new ReduceInventoryCommand + { + Id = inventoryItem.Id, + Quantity = request.Quantity, + FromReserved = false, + ReferenceNumber = request.ReferenceNumber ?? $"LOSS-{DateTime.UtcNow:yyyyMMddHHmmss}" + }, + context.CancellationToken); + + return new Empty(); + } + + // ========== Bulk Operations ========== + + public override async Task BulkAddStock(BulkAddStockRequest request, ServerCallContext context) + { + var response = new BulkAddStockResponse(); + + foreach (var item in request.Items) + { + try + { + var inventoryItem = await _mediator.Send( + new GetInventoryByProductQuery + { + ProductId = item.ProductId, + ProductType = (Domain.Enums.ProductType)item.ProductType + }, + context.CancellationToken); + + if (inventoryItem == null) + { + response.FailedCount++; + response.Errors.Add($"ProductId={item.ProductId}: آیتم موجودی یافت نشد"); + continue; + } + + await _mediator.Send( + new IncreaseInventoryCommand + { + Id = inventoryItem.Id, + Quantity = item.Quantity, + ReferenceNumber = request.ReferenceNumber ?? $"BULK-{DateTime.UtcNow.Ticks}" + }, + context.CancellationToken); + + response.SuccessCount++; + } + catch (Exception ex) + { + response.FailedCount++; + response.Errors.Add($"ProductId={item.ProductId}: {ex.Message}"); + } + } + + return response; + } + + // ========== Stock Movements ========== + + public override async Task GetStockMovements(GetStockMovementsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetStockMovementsByInventoryItem(GetStockMovementsByInventoryItemRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + // ========== Reports ========== + + public override async Task GetInventorySummary(GetInventorySummaryRequest request, ServerCallContext context) + { + var query = _context.InventoryItems + .Include(i => i.Product) + .Include(i => i.DiscountProduct) + .Where(i => !i.IsDeleted); + + if (request.WarehouseId != null) + query = query.Where(i => i.WarehouseId == request.WarehouseId.Value); + + var items = await query.ToListAsync(context.CancellationToken); + + var regularProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.RegularProduct).ToList(); + var discountProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.DiscountProduct).ToList(); + + long totalStockValue = items.Sum(i => + { + long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0; + return (long)i.Quantity * unitPrice; + }); + + return new GetInventorySummaryResponse + { + TotalProducts = regularProducts.Count, + TotalDiscountProducts = discountProducts.Count, + TotalQuantity = items.Sum(i => i.Quantity), + TotalReserved = items.Sum(i => i.ReservedQuantity), + LowStockCount = items.Count(i => i.Quantity <= i.LowStockThreshold && i.Quantity > 0), + OutOfStockCount = items.Count(i => i.Quantity == 0), + TotalStockValue = totalStockValue + }; + } + + public override async Task GetStockValueReport(GetStockValueReportRequest request, ServerCallContext context) + { + var query = _context.InventoryItems + .Include(i => i.Product) + .Include(i => i.DiscountProduct) + .Where(i => !i.IsDeleted); + + if (request.WarehouseId != null) + query = query.Where(i => i.WarehouseId == request.WarehouseId.Value); + + if (request.ProductType != ProductType.Unspecified) + query = query.Where(i => i.ProductType == (Domain.Enums.ProductType)request.ProductType); + + var dbItems = await query.ToListAsync(context.CancellationToken); + + var items = dbItems.Select(i => + { + long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0; + string title = i.Product?.Title ?? i.DiscountProduct?.Title ?? string.Empty; + return new StockValueItem + { + ProductId = i.ProductId ?? i.DiscountProductId ?? 0, + ProductTitle = title, + ProductType = (ProductType)i.ProductType, + Quantity = i.Quantity, + UnitPrice = unitPrice, + TotalValue = (long)i.Quantity * unitPrice + }; + }).ToList(); + + return new GetStockValueReportResponse + { + Items = { items }, + TotalValue = items.Sum(i => i.TotalValue), + TotalItems = items.Count + }; + } +} diff --git a/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs b/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs index 098f065..abcdb52 100644 --- a/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs +++ b/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.Authorization; using CMSMicroservice.Protobuf.Protos.ManualPayment; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment; @@ -24,6 +25,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB _sender = sender; } + [RequiresPermission(PermissionNames.ManualPaymentsCreate)] public override async Task CreateManualPayment( CreateManualPaymentRequest request, ServerCallContext context) @@ -37,6 +39,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB }; } + [RequiresPermission(PermissionNames.ManualPaymentsApprove)] public override async Task ApproveManualPayment( ApproveManualPaymentRequest request, ServerCallContext context) @@ -44,6 +47,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB return await _dispatchRequestToCQRS.Handle(request, context); } + [RequiresPermission(PermissionNames.ManualPaymentsApprove)] public override async Task RejectManualPayment( RejectManualPaymentRequest request, ServerCallContext context) @@ -51,6 +55,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB return await _dispatchRequestToCQRS.Handle(request, context); } + [RequiresPermission(PermissionNames.ManualPaymentsView)] public override async Task GetAllManualPayments( GetAllManualPaymentsRequest request, ServerCallContext context) @@ -58,6 +63,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB return await _dispatchRequestToCQRS.Handle(request, context); } + [RequiresPermission(PermissionNames.ManualPaymentsCreate)] public override async Task ProcessManualMembershipPayment( ProcessManualMembershipPaymentRequest request, ServerCallContext context) diff --git a/src/CMSMicroservice.WebApi/Services/NetworkMembershipService.cs b/src/CMSMicroservice.WebApi/Services/NetworkMembershipService.cs index 12b7b44..5f0d4e0 100644 --- a/src/CMSMicroservice.WebApi/Services/NetworkMembershipService.cs +++ b/src/CMSMicroservice.WebApi/Services/NetworkMembershipService.cs @@ -7,16 +7,27 @@ using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetUserNetworkPosi using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree; using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkMembershipHistory; using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics; +using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree; +using CMSMicroservice.Application.Common.Interfaces; +using Mapster; +using CMSMicroservice.Domain.Enums; namespace CMSMicroservice.WebApi.Services; public class NetworkMembershipService : NetworkMembershipContract.NetworkMembershipContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly ICurrentUserService _currentUserService; - public NetworkMembershipService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public NetworkMembershipService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + ICurrentUserService currentUserService) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _currentUserService = currentUserService; } public override async Task JoinNetwork(JoinNetworkRequest request, ServerCallContext context) @@ -54,4 +65,137 @@ public class NetworkMembershipService : NetworkMembershipContract.NetworkMembers { return await _dispatchRequestToCQRS.Handle(request, context); } + + // ============= Customer-specific Methods ============= + + public override async Task GetMyNetworkTree(GetMyNetworkTreeRequest request, ServerCallContext context) + { + // Use Customer-specific Query that gets UserId from ICurrentUserService + var query = new GetMyNetworkTreeQuery + { + MaxDepth = request.MaxDepth > 0 ? request.MaxDepth : 3 + }; + + var tree = await _sender.Send(query, context.CancellationToken); + + if (tree == null) + { + return new GetMyNetworkTreeResponse + { + RootNode = null, + TotalMembers = 0, + CurrentDepth = 0 + }; + } + + // Convert NetworkTreeDto to NetworkTreeNodeModel + var rootNode = ConvertToNodeModel(tree); + + return new GetMyNetworkTreeResponse + { + RootNode = rootNode, + TotalMembers = CountNodes(tree), + CurrentDepth = tree.CurrentDepth + }; + } + + public override async Task GetSubordinateTree(GetSubordinateTreeRequest request, ServerCallContext context) + { + // Get tree for a specific subordinate user + var query = new GetNetworkTreeQuery + { + UserId = request.TargetUserId, + MaxDepth = request.MaxDepth > 0 ? request.MaxDepth : 3 + }; + + var tree = await _sender.Send(query, context.CancellationToken); + + if (tree == null) + { + return new GetMyNetworkTreeResponse + { + RootNode = null, + TotalMembers = 0, + CurrentDepth = 0 + }; + } + + var rootNode = ConvertToNodeModel(tree); + + return new GetMyNetworkTreeResponse + { + RootNode = rootNode, + TotalMembers = CountNodes(tree), + CurrentDepth = tree.CurrentDepth + }; + } + + public override async Task GetMyNetworkStatistics(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) + { + // Customer endpoint: resolve userId from JWT + if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); + + var query = new GetNetworkStatisticsQuery { UserId = userId }; + var stats = await _sender.Send(query, context.CancellationToken); + + return stats.Adapt(); + } + + // Helper methods for tree conversion + private NetworkTreeNodeModel ConvertToNodeModel(NetworkTreeDto dto) + { + var node = new NetworkTreeNodeModel + { + UserId = dto.UserId, + UserName = $"{dto.FirstName} {dto.LastName}".Trim(), + FullName = $"{dto.FirstName} {dto.LastName}".Trim(), + NetworkLeg = dto.LegPosition.HasValue ? (int)dto.LegPosition.Value : 0, + NetworkLevel = dto.CurrentDepth, + Level = dto.CurrentDepth, + IsActive = dto.IsClubActive, + IsClubActive = dto.IsClubActive, + ReferralCode = dto.ReferralCode ?? "", + Mobile = dto.Mobile ?? "", + Position = dto.LegPosition.HasValue ? dto.LegPosition.Value.ToString() : "Root" + }; + + if (dto.ClubActivatedAt.HasValue) + { + node.ClubActivatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime( + DateTime.SpecifyKind(dto.ClubActivatedAt.Value, DateTimeKind.Utc)); + } + + if (dto.UserCreated != null) + { + node.UserCreated = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime( + dto.UserCreated.UtcDateTime); + } + + if (dto.ActivationWeekDefinitionId.HasValue) + { + node.ActivationWeekDefinitionId = dto.ActivationWeekDefinitionId.Value; + } + + node.IsActivatedInTargetWeek = dto.IsActivatedInTargetWeek; + + // Recursively convert children + if (dto.LeftChild != null) + { + node.LeftChild = ConvertToNodeModel(dto.LeftChild); + } + + if (dto.RightChild != null) + { + node.RightChild = ConvertToNodeModel(dto.RightChild); + } + + return node; + } + + private int CountNodes(NetworkTreeDto tree) + { + if (tree == null) return 0; + return 1 + CountNodes(tree.LeftChild) + CountNodes(tree.RightChild); + } } diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index c5d5de0..6949c93 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -10,14 +10,46 @@ using CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment; using CMSMicroservice.Application.PackageCQ.Queries.GetPackage; using CMSMicroservice.Application.PackageCQ.Queries.GetAllPackageByFilter; using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus; +using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages; +using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails; +using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Payment; +using AppModels = CMSMicroservice.Application.Common.Models; +using Grpc.Core; +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; +using System.Linq; +using CMSMicroservice.Protobuf.Protos; +using Microsoft.EntityFrameworkCore; +using MediatR; +using Mapster; +using Microsoft.Extensions.Configuration; + namespace CMSMicroservice.WebApi.Services; public class PackageService : PackageContract.PackageContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUserService; + private readonly IPaymentGatewayService _paymentGateway; + private readonly IConfiguration _configuration; - public PackageService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public PackageService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + IApplicationDbContext context, + ICurrentUserService currentUserService, + IPaymentGatewayService paymentGateway, + IConfiguration configuration) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _context = context; + _currentUserService = currentUserService; + _paymentGateway = paymentGateway; + _configuration = configuration; } public override async Task CreateNewPackage(CreateNewPackageRequest request, ServerCallContext context) { @@ -65,4 +97,296 @@ public class PackageService : PackageContract.PackageContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + // ============= Customer-specific Method Implementations ============= + + public override async Task GetCustomerPackages(GetCustomerPackagesRequest request, ServerCallContext context) + { + var query = new GetCustomerPackagesQuery + { + IncludeInactive = request.IncludeInactive, + PackageTypeFilter = request.PackageTypeFilter != PackageTypeEnum.PackageTypeBasic + ? (int?)request.PackageTypeFilter + : null + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerPackagesResponse(); + response.Models.AddRange(result.Adapt>()); + + return response; + } + + public override async Task GetCustomerPackageDetails(GetCustomerPackageDetailsRequest request, ServerCallContext context) + { + var query = new GetCustomerPackageDetailsQuery + { + PackageId = request.PackageId + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerPackageDetailsResponse + { + Id = result.Id, + Title = result.Title, + Description = result.Description, + Price = result.Price, + ImagePath = result.ImagePath + }; + + // Map Features + foreach (var feature in result.Features) + { + response.Features.Add(new PackageFeature + { + Title = feature.Title, + Description = feature.Description, + Icon = feature.Icon, + IsHighlighted = feature.IsHighlighted + }); + } + + // Map Requirements + response.Requirements = new PurchaseRequirements + { + RequiresMembership = result.Requirements.RequiresMembership, + MinimumWalletBalance = result.Requirements.MinimumWalletBalance + }; + response.Requirements.Restrictions.AddRange(result.Requirements.Restrictions); + + return response; + } + + public override async Task CustomerPurchasePackage(CustomerPurchasePackageRequest request, ServerCallContext context) + { + var userId = GetCurrentUserId(); + + // Lookup package + var package = await _context.Packages + .AsNoTracking() + .Where(p => p.Id == request.PackageId && !p.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (package == null) + throw new RpcException(new Status(StatusCode.NotFound, "پکیج مورد نظر یافت نشد")); + + // Create transaction + var transaction = new CMSMicroservice.Domain.Entities.Transaction + { + Amount = package.Price, + Description = $"خرید پکیج {package.Title}", + PaymentStatus = Domain.Enums.PaymentStatus.Pending, + Type = Domain.Enums.TransactionType.Buy + }; + _context.Transactions.Add(transaction); + await _context.SaveChangesAsync(context.CancellationToken); + + // Create purchase record + var purchaseMethod = request.PurchaseMethod == PurchaseMethodEnum.PurchaseMethodGateway + ? Domain.Enums.PackagePurchaseMethod.DirectPurchase + : Domain.Enums.PackagePurchaseMethod.DayaLoan; + + var purchase = new CMSMicroservice.Domain.Entities.UserPackagePurchase + { + UserId = userId, + PackageId = package.Id, + PurchaseMethod = purchaseMethod, + PurchasedAt = DateTime.UtcNow, + Amount = package.Price, + TransactionId = transaction.Id + }; + _context.UserPackagePurchases.Add(purchase); + await _context.SaveChangesAsync(context.CancellationToken); + + // Initiate payment with gateway + var user = await _context.Users + .AsNoTracking() + .Where(u => u.Id == userId) + .Select(u => new { u.Mobile }) + .FirstOrDefaultAsync(context.CancellationToken); + + var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest + { + Amount = package.Price, + UserId = userId, + Mobile = user?.Mobile ?? string.Empty, + Description = $"خرید پکیج {package.Title}", + CallbackUrl = request.CallbackUrl + }, context.CancellationToken); + + if (!paymentResult.IsSuccess) + { + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; + await _context.SaveChangesAsync(context.CancellationToken); + + return new CustomerPurchasePackageResponse + { + Success = false, + Message = paymentResult.ErrorMessage ?? "خطا در ارتباط با درگاه پرداخت" + }; + } + + // Save RefId + transaction.RefId = paymentResult.RefId; + + // ثبت PaymentTransaction + var paymentTx = new PaymentTransaction + { + GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", + MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", + Amount = package.Price, + CallbackUrl = request.CallbackUrl, + Description = $"خرید پکیج {package.Title}", + Mobile = user?.Mobile, + UserId = userId, + RequestStatusCode = 100, + RequestStatusMessage = "Success", + Authority = paymentResult.RefId, + PaymentStatus = false, + TransactionId = transaction.Id, + OrderId = purchase.Id.ToString() + }; + _context.PaymentTransactions.Add(paymentTx); + await _context.SaveChangesAsync(context.CancellationToken); + + return new CustomerPurchasePackageResponse + { + Success = true, + Message = "درخواست خرید پکیج با موفقیت ثبت شد", + OrderId = purchase.Id, + PaymentGatewayUrl = paymentResult.GatewayUrl ?? string.Empty, + Authority = paymentResult.RefId ?? string.Empty + }; + } + + public override async Task CustomerVerifyPackagePurchase(CustomerVerifyPackagePurchaseRequest request, ServerCallContext context) + { + // Find purchase record + var purchase = await _context.UserPackagePurchases + .Include(p => p.Package) + .Where(p => p.Id == request.OrderId && !p.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (purchase == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + // Find the associated transaction + var transaction = purchase.TransactionId.HasValue + ? await _context.Transactions + .Where(t => t.Id == purchase.TransactionId.Value) + .FirstOrDefaultAsync(context.CancellationToken) + : null; + + // If status from gateway callback is not OK + if (request.Status != "OK") + { + if (transaction != null) + { + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; + await _context.SaveChangesAsync(context.CancellationToken); + } + + return new CustomerVerifyPackagePurchaseResponse + { + Success = false, + Message = "پرداخت توسط کاربر لغو شد" + }; + } + + // Verify with payment gateway + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, request.Status, context.CancellationToken); + + // آپدیت PaymentTransaction + 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; + } + + if (verifyResult.IsSuccess && transaction != null) + { + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success; + transaction.PaymentDate = DateTime.UtcNow; + transaction.RefId = verifyResult.RefId; + } + else if (transaction != null) + { + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; + } + + await _context.SaveChangesAsync(context.CancellationToken); + + return new CustomerVerifyPackagePurchaseResponse + { + Success = verifyResult.IsSuccess, + Message = verifyResult.IsSuccess ? "خرید پکیج با موفقیت تایید شد" : (verifyResult.Message ?? "خرید پکیج ناموفق بود"), + TransactionId = transaction?.Id ?? 0, + ReferenceCode = verifyResult.RefId ?? string.Empty, + PurchaseInfo = verifyResult.IsSuccess ? new PackagePurchaseInfo + { + PackageId = purchase.PackageId, + PackageName = purchase.Package?.Title ?? string.Empty, + AmountPaid = purchase.Amount, + PurchaseDate = Timestamp.FromDateTime(DateTime.SpecifyKind(purchase.PurchasedAt, DateTimeKind.Utc)) + } : null + }; + } + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0) + return userId; + throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید")); + } + + public override async Task GetCustomerPurchaseHistory(GetCustomerPurchaseHistoryRequest request, ServerCallContext context) + { + var query = new GetCustomerPurchaseHistoryQuery + { + UserId = request.UserId, + PaginationState = request.PaginationState?.Adapt(), + PackageTypeFilter = request.PackageTypeFilter != PackageTypeEnum.PackageTypeBasic + ? (int?)request.PackageTypeFilter + : null, + FromDate = request.FromDate?.ToDateTime(), + ToDate = request.ToDate?.ToDateTime() + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerPurchaseHistoryResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var purchase in result.Purchases) + { + response.Purchases.Add(new PackagePurchaseHistory + { + Id = purchase.Id, + PackageId = purchase.PackageId, + PackageName = purchase.PackageName, + Amount = purchase.Amount, + PackageType = (PackageTypeEnum)purchase.PackageType, + PurchaseDate = Timestamp.FromDateTime(DateTime.SpecifyKind(purchase.PurchaseDate, DateTimeKind.Utc)), + ExpiryDate = purchase.ExpiryDate.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(purchase.ExpiryDate.Value, DateTimeKind.Utc)) + : null, + Status = (PaymentStatusEnum)purchase.Status, + StatusMessage = purchase.StatusMessage, + ReferenceCode = purchase.ReferenceCode + }); + } + + return response; + } } diff --git a/src/CMSMicroservice.WebApi/Services/ProductsService.cs b/src/CMSMicroservice.WebApi/Services/ProductsService.cs index 97e8cd4..9d79a54 100644 --- a/src/CMSMicroservice.WebApi/Services/ProductsService.cs +++ b/src/CMSMicroservice.WebApi/Services/ProductsService.cs @@ -1,61 +1,691 @@ using CMSMicroservice.Protobuf.Protos.Products; -using CMSMicroservice.WebApi.Common.Services; +using Grpc.Core; +using MediatR; using CMSMicroservice.Application.ProductsCQ.Commands.CreateNewProducts; using CMSMicroservice.Application.ProductsCQ.Commands.UpdateProducts; using CMSMicroservice.Application.ProductsCQ.Commands.DeleteProducts; -using CMSMicroservice.Application.ProductsCQ.Queries.GetProducts; -using CMSMicroservice.Application.ProductsCQ.Queries.GetAllProductsByFilter; -using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices; -using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock; -using CMSMicroservice.Application.ProductsCQ.Queries.GetLowStockProducts; -using CMSMicroservice.Application.ProductsCQ.Commands.ToggleProductStatus; +using CMSMicroservice.Application.ProductsCQ.Commands.AddProductImage; +using CMSMicroservice.Application.ProductsCQ.Commands.RemoveProductImage; +using CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts; +using CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter; +using CMSMicroservice.Application.ProductsCQ.Queries.GetProductGallery; +using CMSMicroservice.Application.Common.Interfaces; +using Mapster; +using Microsoft.EntityFrameworkCore; +using AppModels = CMSMicroservice.Application.Common.Models; +using System.Collections.Generic; +using System.Linq; + namespace CMSMicroservice.WebApi.Services; + public class ProductsService : ProductsContract.ProductsContractBase { - private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly IApplicationDbContext _context; - public ProductsService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public ProductsService(ISender sender, IApplicationDbContext context) { - _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _context = context; } public override async Task CreateNewProducts(CreateNewProductsRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new CreateNewProductsCommand + { + Title = request.Title, + Description = request.Description, + ShortInfomation = request.ShortInfomation, + FullInformation = request.FullInformation, + Price = request.Price, + Discount = request.Discount, + Rate = request.Rate, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + SaleCount = request.SaleCount, + ViewCount = request.ViewCount, + RemainingCount = request.RemainingCount, + CategoryIds = request.CategoryIds?.ToList() ?? new List(), + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName, + ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(), + ThumbnailFileMime = request.ThumbnailFile?.Mime, + ThumbnailFileName = request.ThumbnailFile?.FileName + }; + + var result = await _sender.Send(command, context.CancellationToken); + return new CreateNewProductsResponse { Id = result.Id }; } - public override async Task UpdateProducts(UpdateProductsRequest request, ServerCallContext context) + + public override async Task UpdateProducts(UpdateProductsRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new UpdateProductsCommand + { + Id = request.Id, + Title = request.Title, + Description = request.Description, + ShortInfomation = request.ShortInfomation, + FullInformation = request.FullInformation, + Price = request.Price, + Discount = request.Discount, + Rate = request.Rate, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + SaleCount = request.SaleCount, + ViewCount = request.ViewCount, + RemainingCount = request.RemainingCount, + CategoryIds = request.CategoryIds?.ToList() ?? new List(), + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName, + ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(), + ThumbnailFileMime = request.ThumbnailFile?.Mime, + ThumbnailFileName = request.ThumbnailFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Google.Protobuf.WellKnownTypes.Empty(); } - public override async Task DeleteProducts(DeleteProductsRequest request, ServerCallContext context) + + public override async Task DeleteProducts(DeleteProductsRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new DeleteProductsCommand { Id = request.Id }; + await _sender.Send(command, context.CancellationToken); + return new Google.Protobuf.WellKnownTypes.Empty(); } + public override async Task GetProducts(GetProductsRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetCustomerProductsQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetProductsResponse + { + Id = result.Id, + Title = result.Title, + Description = result.Description, + ShortInfomation = result.ShortInfomation, + FullInformation = result.FullInformation, + Price = result.Price, + Discount = result.Discount, + Rate = result.Rate, + ImagePath = result.ImagePath, + ThumbnailPath = result.ThumbnailPath, + SaleCount = result.SaleCount, + ViewCount = result.ViewCount, + RemainingCount = result.RemainingCount + }; + + if (result.Gallery != null) + { + foreach (var item in result.Gallery) + { + response.Gallery.Add(new ProductGalleryItem + { + ProductGalleryId = item.ProductGalleryId, + ProductImageId = item.ProductImageId, + Title = item.Title, + ImagePath = item.ImagePath, + ImageThumbnailPath = item.ImageThumbnailPath + }); + } + } + + if (result.Categories != null) + { + foreach (var cat in result.Categories) + { + var categoryPath = new ProductCategoryPath + { + CategoryId = cat.CategoryId, + Title = cat.Title + }; + if (cat.Path != null) + { + foreach (var node in cat.Path) + { + categoryPath.Path.Add(new CategoryNode + { + Id = node.Id, + Title = node.Title, + ParentId = node.ParentId + }); + } + } + response.Categories.Add(categoryPath); + } + } + + return response; } + public override async Task GetAllProductsByFilter(GetAllProductsByFilterRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + // Map to GetCustomerProductsByFilter query (public products API) + var query = new GetCustomerProductsByFilterQuery + { + Id = request.Filter?.Id, + Title = request.Filter?.Title ?? string.Empty, + Description = request.Filter?.Description ?? string.Empty, + ShortInfomation = request.Filter?.ShortInfomation ?? string.Empty, + FullInformation = request.Filter?.FullInformation ?? string.Empty, + Price = request.Filter?.Price, + Discount = request.Filter?.Discount, + Rate = request.Filter?.Rate, + SaleCount = request.Filter?.SaleCount, + ViewCount = request.Filter?.ViewCount, + RemainingCount = request.Filter?.RemainingCount, + CategoryIds = request.Filter?.CategoryId.HasValue == true + ? new List { request.Filter.CategoryId.Value } + : new List(), + IsActive = request.Filter?.IsActive, + SortBy = request.SortBy ?? string.Empty, + PaginationState = request.PaginationState != null + ? new AppModels.PaginationState + { + PageNumber = request.PaginationState.PageNumber, + PageSize = request.PaginationState.PageSize + } + : new AppModels.PaginationState { PageNumber = 1, PageSize = 20 } + }; + + var result = await _sender.Send(query, context.CancellationToken); + + return new GetAllProductsByFilterResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = result.MetaData.CurrentPage, + TotalPage = result.MetaData.TotalPage, + PageSize = result.MetaData.PageSize, + TotalCount = result.MetaData.TotalCount, + HasPrevious = result.MetaData.HasPrevious, + HasNext = result.MetaData.HasNext + }, + Models = { result.Models.Select(m => new GetAllProductsByFilterResponseModel + { + Id = m.Id, + Title = m.Title, + Description = m.Description, + ShortInfomation = m.ShortInfomation, + FullInformation = m.FullInformation, + Price = m.Price, + Discount = m.Discount, + Rate = m.Rate, + ImagePath = m.ImagePath, + ThumbnailPath = m.ThumbnailPath, + SaleCount = m.SaleCount, + ViewCount = m.ViewCount, + RemainingCount = m.RemainingCount, + CategoryIds = { m.Categories?.Select(c => c.CategoryId) ?? Enumerable.Empty() }, + IsActive = m.IsActive + }) } + }; } - + public override async Task BulkUpdateProductPrices(BulkUpdateProductPricesRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var response = new BulkUpdateProductPricesResponse { Total = request.Products.Count }; + + foreach (var item in request.Products) + { + try + { + var product = await _context.Products.FindAsync(new object[] { item.ProductId }, context.CancellationToken); + if (product == null) + { + response.Failed++; + response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = "محصول یافت نشد" }); + continue; + } + + product.Price = item.NewPrice; + if (item.NewDiscount != null) product.Discount = item.NewDiscount.Value; + if (item.NewClubDiscountPercent != null) product.ClubDiscountPercent = item.NewClubDiscountPercent.Value; + + response.Succeeded++; + } + catch (Exception ex) + { + response.Failed++; + response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = ex.Message }); + } + } + + await _context.SaveChangesAsync(context.CancellationToken); + return response; } - + public override async Task BulkUpdateProductStock(BulkUpdateProductStockRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var response = new BulkUpdateProductStockResponse { Total = request.Products.Count }; + + foreach (var item in request.Products) + { + try + { + var product = await _context.Products.FindAsync(new object[] { item.ProductId }, context.CancellationToken); + if (product == null) + { + response.Failed++; + response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = "محصول یافت نشد" }); + continue; + } + + product.RemainingCount = request.UpdateType switch + { + StockUpdateType.Set => item.Quantity, + StockUpdateType.Add => product.RemainingCount + item.Quantity, + StockUpdateType.Subtract => Math.Max(0, product.RemainingCount - item.Quantity), + _ => item.Quantity + }; + + response.Succeeded++; + } + catch (Exception ex) + { + response.Failed++; + response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = ex.Message }); + } + } + + await _context.SaveChangesAsync(context.CancellationToken); + return response; } - + public override async Task GetLowStockProducts(GetLowStockProductsRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var threshold = request.Threshold > 0 ? request.Threshold : 10; + var pageIndex = request.PageIndex > 0 ? request.PageIndex : 1; + var pageSize = request.PageSize > 0 ? request.PageSize : 20; + + var query = _context.Products + .Where(p => !p.IsDeleted && p.RemainingCount <= threshold); + + if (request.IsClubExclusive != null) + query = query.Where(p => p.IsClubExclusive == request.IsClubExclusive.Value); + + var totalCount = await query.CountAsync(context.CancellationToken); + + var products = await query + .OrderBy(p => p.RemainingCount) + .Skip((pageIndex - 1) * pageSize) + .Take(pageSize) + .Select(p => new LowStockProduct + { + Id = p.Id, + Title = p.Title, + RemainingCount = p.RemainingCount, + Price = p.Price, + IsClubExclusive = p.IsClubExclusive + }) + .ToListAsync(context.CancellationToken); + + return new GetLowStockProductsResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = pageIndex, + PageSize = pageSize, + TotalCount = totalCount, + TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize), + HasPrevious = pageIndex > 1, + HasNext = pageIndex * pageSize < totalCount + }, + Products = { products } + }; } - + public override async Task ToggleProductStatus(ToggleProductStatusRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var response = new ToggleProductStatusResponse { Total = request.ProductIds.Count }; + + foreach (var productId in request.ProductIds) + { + try + { + var product = await _context.Products.FindAsync(new object[] { productId }, context.CancellationToken); + if (product == null) + { + response.Failed++; + response.Errors.Add(new BulkOperationError { ProductId = productId, ErrorMessage = "محصول یافت نشد" }); + continue; + } + + product.IsDeleted = !request.Enable; + if (request.Enable && request.DefaultStock > 0 && product.RemainingCount == 0) + product.RemainingCount = request.DefaultStock; + + response.Succeeded++; + } + catch (Exception ex) + { + response.Failed++; + response.Errors.Add(new BulkOperationError { ProductId = productId, ErrorMessage = ex.Message }); + } + } + + await _context.SaveChangesAsync(context.CancellationToken); + return response; + } + + // ============= Category-Product DragDrop Methods ============= + + public override async Task GetProductsForCategory(GetProductsForCategoryRequest request, ServerCallContext context) + { + var assignedProductIds = await _context.ProductCategories + .Where(pc => pc.CategoryId == request.CategoryId && !pc.IsDeleted) + .Select(pc => pc.ProductId) + .ToListAsync(context.CancellationToken); + + var allProducts = await _context.Products + .Where(p => !p.IsDeleted) + .OrderBy(p => p.Title) + .Select(p => new CategoryProductItem + { + Id = p.Id, + Title = p.Title, + Selected = assignedProductIds.Contains(p.Id) + }) + .ToListAsync(context.CancellationToken); + + return new GetProductsForCategoryResponse { Items = { allProducts } }; + } + + public override async Task GetCategories(GetCategoriesRequest request, ServerCallContext context) + { + var assignedCategoryIds = await _context.ProductCategories + .Where(pc => pc.ProductId == request.ProductId && !pc.IsDeleted) + .Select(pc => pc.CategoryId) + .ToListAsync(context.CancellationToken); + + var allCategories = await _context.Categories + .Where(c => !c.IsDeleted && c.IsActive) + .OrderBy(c => c.SortOrder) + .Select(c => new CategoryItem + { + Id = c.Id, + Title = c.Title, + Selected = assignedCategoryIds.Contains(c.Id) + }) + .ToListAsync(context.CancellationToken); + + return new GetCategoriesResponse { Items = { allCategories } }; + } + + public override async Task UpdateProductCategories(UpdateProductCategoriesRequest request, ServerCallContext context) + { + var existingLinks = await _context.ProductCategories + .Where(pc => pc.ProductId == request.ProductId) + .ToListAsync(context.CancellationToken); + + // حذف لینک‌های قبلی + foreach (var link in existingLinks) + link.IsDeleted = true; + + // ایجاد لینک‌های جدید + foreach (var categoryId in request.CategoryIds) + { + var existing = existingLinks.FirstOrDefault(l => l.CategoryId == categoryId); + if (existing != null) + { + existing.IsDeleted = false; + } + else + { + _context.ProductCategories.Add(new Domain.Entities.ProductCategory + { + ProductId = request.ProductId, + CategoryId = categoryId + }); + } + } + + await _context.SaveChangesAsync(context.CancellationToken); + return new Google.Protobuf.WellKnownTypes.Empty(); + } + + public override async Task UpdateCategoryProducts(UpdateCategoryProductsRequest request, ServerCallContext context) + { + var existingLinks = await _context.ProductCategories + .Where(pc => pc.CategoryId == request.CategoryId) + .ToListAsync(context.CancellationToken); + + // حذف لینک‌های قبلی + foreach (var link in existingLinks) + link.IsDeleted = true; + + // ایجاد لینک‌های جدید + foreach (var productId in request.ProductIds) + { + var existing = existingLinks.FirstOrDefault(l => l.ProductId == productId); + if (existing != null) + { + existing.IsDeleted = false; + } + else + { + _context.ProductCategories.Add(new Domain.Entities.ProductCategory + { + ProductId = productId, + CategoryId = request.CategoryId + }); + } + } + + await _context.SaveChangesAsync(context.CancellationToken); + return new Google.Protobuf.WellKnownTypes.Empty(); + } + + // ============= Customer-specific Methods ============= + + public override async Task GetCustomerProducts(GetProductsRequest request, ServerCallContext context) + { + var query = new GetCustomerProductsQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetProductsResponse + { + Id = result.Id, + Title = result.Title, + Description = result.Description, + ShortInfomation = result.ShortInfomation, + FullInformation = result.FullInformation, + Price = result.Price, + Discount = result.Discount, + Rate = result.Rate, + ImagePath = result.ImagePath, + ThumbnailPath = result.ThumbnailPath, + SaleCount = result.SaleCount, + ViewCount = result.ViewCount, + RemainingCount = result.RemainingCount + }; + + // Add gallery items + if (result.Gallery != null) + { + foreach (var item in result.Gallery) + { + response.Gallery.Add(new ProductGalleryItem + { + ProductGalleryId = item.ProductGalleryId, + ProductImageId = item.ProductImageId, + Title = item.Title, + ImagePath = item.ImagePath, + ImageThumbnailPath = item.ImageThumbnailPath + }); + } + } + + // Add categories + if (result.Categories != null) + { + foreach (var cat in result.Categories) + { + var categoryPath = new ProductCategoryPath + { + CategoryId = cat.CategoryId, + Title = cat.Title + }; + + if (cat.Path != null) + { + foreach (var node in cat.Path) + { + categoryPath.Path.Add(new CategoryNode + { + Id = node.Id, + Title = node.Title, + ParentId = node.ParentId + }); + } + } + + response.Categories.Add(categoryPath); + } + } + + return response; + } + + public override async Task GetCustomerProductsByFilter(GetAllProductsByFilterRequest request, ServerCallContext context) + { + var query = new GetCustomerProductsByFilterQuery + { + PaginationState = request.PaginationState?.Adapt(), + SortBy = request.SortBy, + Id = request.Filter?.Id, + Title = request.Filter?.Title, + Description = request.Filter?.Description, + ShortInfomation = request.Filter?.ShortInfomation, + FullInformation = request.Filter?.FullInformation, + Price = request.Filter?.Price, + Discount = request.Filter?.Discount, + Rate = request.Filter?.Rate, + ImagePath = request.Filter?.ImagePath, + ThumbnailPath = request.Filter?.ThumbnailPath, + SaleCount = request.Filter?.SaleCount, + ViewCount = request.Filter?.ViewCount, + RemainingCount = request.Filter?.RemainingCount, + CategoryIds = request.Filter?.CategoryId != null ? new List { request.Filter.CategoryId.Value } : null + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerProductsByFilterResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = result.MetaData.CurrentPage, + TotalPage = result.MetaData.TotalPage, + PageSize = result.MetaData.PageSize, + TotalCount = result.MetaData.TotalCount, + HasPrevious = result.MetaData.HasPrevious, + HasNext = result.MetaData.HasNext + } + }; + + foreach (var model in result.Models) + { + var productModel = new GetCustomerProductsByFilterResponseModel + { + Id = model.Id, + Title = model.Title, + Description = model.Description, + ShortInfomation = model.ShortInfomation, + FullInformation = model.FullInformation, + Price = model.Price, + Discount = model.Discount, + Rate = model.Rate, + ImagePath = model.ImagePath, + ThumbnailPath = model.ThumbnailPath, + SaleCount = model.SaleCount, + ViewCount = model.ViewCount, + RemainingCount = model.RemainingCount + }; + + if (model.Categories != null) + { + foreach (var cat in model.Categories) + { + var categoryPath = new ProductCategoryPath + { + CategoryId = cat.CategoryId, + Title = cat.Title + }; + + if (cat.Path != null) + { + foreach (var node in cat.Path) + { + categoryPath.Path.Add(new CategoryNode + { + Id = node.Id, + Title = node.Title, + ParentId = node.ParentId + }); + } + } + + productModel.Categories.Add(categoryPath); + } + } + + response.Models.Add(productModel); + } + + return response; + } + + // ============= Product Image Management ============= + + public override async Task AddProductImage(AddProductImageRequest request, ServerCallContext context) + { + var command = new AddProductImageCommand + { + ProductId = request.ProductId, + Title = request.Title, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + var result = await _sender.Send(command, context.CancellationToken); + + return new AddProductImageResponse + { + ProductGalleryId = result.ProductGalleryId, + ProductImageId = result.ProductImageId, + Title = result.Title, + ImagePath = result.ImagePath, + ImageThumbnailPath = result.ImageThumbnailPath + }; + } + + public override async Task RemoveProductImage(RemoveProductImageRequest request, ServerCallContext context) + { + var command = new RemoveProductImageCommand { ProductGalleryId = request.ProductGalleryId }; + await _sender.Send(command, context.CancellationToken); + return new Google.Protobuf.WellKnownTypes.Empty(); + } + + public override async Task GetProductGallery(GetProductGalleryRequest request, ServerCallContext context) + { + var query = new GetProductGalleryQuery { ProductId = request.ProductId }; + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetProductGalleryResponse(); + foreach (var item in result.Items) + { + response.Items.Add(new ProductGalleryItem + { + ProductGalleryId = item.ProductGalleryId, + ProductImageId = item.ProductImageId, + Title = item.Title, + ImagePath = item.ImagePath, + ImageThumbnailPath = item.ImageThumbnailPath + }); + } + return response; } } diff --git a/src/CMSMicroservice.WebApi/Services/SitePageService.cs b/src/CMSMicroservice.WebApi/Services/SitePageService.cs new file mode 100644 index 0000000..1fc787f --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/SitePageService.cs @@ -0,0 +1,218 @@ +using System.Linq; +using CMSMicroservice.Protobuf.Protos.SitePage; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePage; +using CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePage; +using CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; +using CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; +using CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; +using CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePageSection; +using CMSMicroservice.Application.SitePageCQ.Commands.ReorderSitePageSections; +using CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; +using CMSMicroservice.Application.SitePageCQ.Queries.GetSitePageByKey; +using CMSMicroservice.Application.SitePageCQ.Queries.GetAllSitePages; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using MediatR; + +namespace CMSMicroservice.WebApi.Services; + +public class SitePageService : SitePageContract.SitePageContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public SitePageService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task GetSitePage(GetSitePageRequest request, ServerCallContext context) + { + var query = new GetSitePageQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + return MapToResponse(result); + } + + public override async Task GetSitePageByKey(GetSitePageByKeyRequest request, ServerCallContext context) + { + var query = new GetSitePageByKeyQuery { PageKey = request.PageKey }; + var result = await _sender.Send(query, context.CancellationToken); + return MapToResponse(result); + } + + public override async Task UpdateSitePage(UpdateSitePageRequest request, ServerCallContext context) + { + var command = new UpdateSitePageCommand + { + Id = request.Id, + Title = request.Title, + MetaDescription = request.MetaDescription, + HeroTitle = request.HeroTitle, + HeroSubtitle = request.HeroSubtitle, + HeroImagePath = request.HeroImagePath, + IsActive = request.IsActive, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task CreateSitePage(CreateSitePageRequest request, ServerCallContext context) + { + var command = new CreateSitePageCommand + { + PageKey = request.PageKey, + Title = request.Title, + MetaDescription = request.MetaDescription, + HeroTitle = request.HeroTitle, + HeroSubtitle = request.HeroSubtitle, + IsActive = request.IsActive, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + var id = await _sender.Send(command, context.CancellationToken); + return new CreateSitePageResponse { Id = id }; + } + + public override async Task DeleteSitePage(DeleteSitePageRequest request, ServerCallContext context) + { + var command = new DeleteSitePageCommand { Id = request.Id }; + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task GetAllSitePages(GetAllSitePagesRequest request, ServerCallContext context) + { + var query = new GetAllSitePagesQuery(); + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetAllSitePagesResponse(); + foreach (var item in result) + { + response.Pages.Add(new SitePageSummary + { + Id = item.Id, + PageKey = item.PageKey ?? string.Empty, + Title = item.Title ?? string.Empty, + IsActive = item.IsActive, + SectionCount = item.SectionCount, + LastModified = item.LastModified.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(item.LastModified.Value, DateTimeKind.Utc)) + : null + }); + } + + return response; + } + + public override async Task CreateSitePageSection(CreateSitePageSectionRequest request, ServerCallContext context) + { + var command = new CreateSitePageSectionCommand + { + SitePageId = request.SitePageId, + SectionKey = request.SectionKey, + Title = request.Title, + Subtitle = request.Subtitle, + HtmlContent = request.HtmlContent, + IconName = request.IconName, + ImagePath = request.ImagePath, + IsActive = true, + ExtraData = request.ExtraData, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + var result = await _sender.Send(command, context.CancellationToken); + return new CreateSitePageSectionResponse { Id = result }; + } + + public override async Task UpdateSitePageSection(UpdateSitePageSectionRequest request, ServerCallContext context) + { + var command = new UpdateSitePageSectionCommand + { + Id = request.Id, + SectionKey = request.SectionKey, + Title = request.Title, + Subtitle = request.Subtitle, + HtmlContent = request.HtmlContent, + IconName = request.IconName, + ImagePath = request.ImagePath, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + ExtraData = request.ExtraData, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task DeleteSitePageSection(DeleteSitePageSectionRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task ReorderSitePageSections(ReorderSitePageSectionsRequest request, ServerCallContext context) + { + var command = new ReorderSitePageSectionsCommand + { + Items = request.Items.Select(x => new Application.SitePageCQ.Commands.ReorderSitePageSections.SectionSortItem + { + Id = x.Id, + SortOrder = x.SortOrder + }).ToList() + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + // ── Private Mapping Helpers ── + + private static GetSitePageResponse MapToResponse(SitePageDto dto) + { + var response = new GetSitePageResponse + { + Id = dto.Id, + PageKey = dto.PageKey ?? string.Empty, + Title = dto.Title ?? string.Empty, + MetaDescription = dto.MetaDescription, + HeroTitle = dto.HeroTitle, + HeroSubtitle = dto.HeroSubtitle, + HeroImagePath = dto.HeroImagePath, + IsActive = dto.IsActive + }; + + if (dto.Sections != null) + { + foreach (var s in dto.Sections) + { + response.Sections.Add(new SitePageSectionItem + { + Id = s.Id, + SectionKey = s.SectionKey ?? string.Empty, + Title = s.Title ?? string.Empty, + Subtitle = s.Subtitle, + HtmlContent = s.HtmlContent, + IconName = s.IconName, + ImagePath = s.ImagePath, + SortOrder = s.SortOrder, + IsActive = s.IsActive, + ExtraData = s.ExtraData + }); + } + } + + return response; + } +} diff --git a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs index 9c839d1..f1043cf 100644 --- a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs +++ b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs @@ -7,15 +7,42 @@ using CMSMicroservice.Application.TransactionsCQ.Queries.GetTransactions; using CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter; using CMSMicroservice.Application.TransactionsCQ.Commands.VerifyTransaction; using CMSMicroservice.Application.TransactionsCQ.Commands.RefundTransaction; +using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction; +using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Payment; +using AppModels = CMSMicroservice.Application.Common.Models; +using Grpc.Core; +using MediatR; +using Mapster; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using System.Linq; namespace CMSMicroservice.WebApi.Services; public class TransactionsService : TransactionsContract.TransactionsContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUserService; + private readonly IPaymentGatewayService _paymentGateway; + private readonly IConfiguration _configuration; - public TransactionsService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public TransactionsService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + IApplicationDbContext context, + ICurrentUserService currentUserService, + IPaymentGatewayService paymentGateway, + IConfiguration configuration) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _context = context; + _currentUserService = currentUserService; + _paymentGateway = paymentGateway; + _configuration = configuration; } public override async Task CreateNewTransactions(CreateNewTransactionsRequest request, ServerCallContext context) { @@ -47,4 +74,212 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + // ============= Customer-specific Methods ============= + + public override async Task GetCustomerTransaction(GetCustomerTransactionRequest request, ServerCallContext context) + { + var query = new GetCustomerTransactionQuery + { + Id = request.Id, + Authority = request.Authority, + UserId = 0 // از JWT دریافت می‌شود + }; + + var result = await _sender.Send(query, context.CancellationToken); + + return new GetCustomerTransactionResponse + { + Id = result.Id, + Amount = result.Amount, + Description = result.Description, + PaymentStatus = result.PaymentStatus == Domain.Enums.PaymentStatus.Success, + RefId = result.RefId, + Type = (TransactionTypeEnum)result.Type, + Currency = CurrencyEnum.Irr + }; + } + + public override async Task GetCustomerTransactionsByFilter(GetCustomerTransactionsByFilterRequest request, ServerCallContext context) + { + var query = new GetCustomerTransactionsByFilterQuery + { + UserId = 0, // از JWT دریافت می‌شود + PaginationState = request.PaginationState?.Adapt(), + SortBy = request.SortBy, + IdFilter = request.Filter?.Id, + AmountFilter = request.Filter?.Amount, + DescriptionFilter = request.Filter?.Description, + PaymentStatusFilter = request.Filter?.PaymentStatus, + RefIdFilter = request.Filter?.RefId, + TypeFilter = request.Filter?.Type != null ? (int?)request.Filter.Type : null + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerTransactionsByFilterResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var model in result.Models) + { + response.Models.Add(new GetCustomerTransactionsByFilterResponseModel + { + Id = model.Id, + Amount = model.Amount, + Description = model.Description, + PaymentStatus = model.PaymentStatus == Domain.Enums.PaymentStatus.Success, + RefId = model.RefId, + Type = (TransactionTypeEnum)model.Type, + Currency = CurrencyEnum.Irr + }); + } + + return response; + } + + public override async Task CustomerPaymentRequest(CustomerPaymentRequestRequest request, ServerCallContext context) + { + var userId = GetCurrentUserId(); + + // Get user mobile for payment gateway + var user = await _context.Users + .AsNoTracking() + .Where(u => u.Id == userId) + .Select(u => new { u.Mobile, u.Email }) + .FirstOrDefaultAsync(context.CancellationToken); + + // Create transaction record in DB + var transaction = new CMSMicroservice.Domain.Entities.Transaction + { + Amount = request.Amount, + Description = request.Description ?? "پرداخت آنلاین", + PaymentStatus = Domain.Enums.PaymentStatus.Pending, + Type = Domain.Enums.TransactionType.DepositIpg + }; + + _context.Transactions.Add(transaction); + await _context.SaveChangesAsync(context.CancellationToken); + + // Initiate payment with gateway + var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest + { + Amount = request.Amount, + UserId = userId, + Mobile = request.Mobile ?? user?.Mobile ?? string.Empty, + Description = request.Description ?? "پرداخت آنلاین", + CallbackUrl = request.CallbackUrl + }, context.CancellationToken); + + if (!paymentResult.IsSuccess) + { + // Update transaction status to failed + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; + await _context.SaveChangesAsync(context.CancellationToken); + + throw new RpcException(new Status(StatusCode.Internal, + paymentResult.ErrorMessage ?? "خطا در ارتباط با درگاه پرداخت")); + } + + // Save RefId from gateway + transaction.RefId = paymentResult.RefId; + + // ثبت PaymentTransaction + var paymentTx = new PaymentTransaction + { + GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", + MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", + Amount = request.Amount, + CallbackUrl = request.CallbackUrl, + Description = request.Description ?? "پرداخت آنلاین", + Mobile = request.Mobile ?? user?.Mobile, + UserId = userId, + RequestStatusCode = 100, + RequestStatusMessage = "Success", + Authority = paymentResult.RefId, + PaymentStatus = false, + TransactionId = transaction.Id + }; + _context.PaymentTransactions.Add(paymentTx); + await _context.SaveChangesAsync(context.CancellationToken); + + return new CustomerPaymentRequestResponse + { + PaymentGWUrl = paymentResult.GatewayUrl ?? string.Empty + }; + } + + public override async Task CustomerPaymentVerification(CustomerPaymentVerificationRequest request, ServerCallContext context) + { + // Find the transaction by authority/refId + var transaction = await _context.Transactions + .Where(t => t.RefId == request.Authority && !t.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (transaction == null) + throw new RpcException(new Status(StatusCode.NotFound, "تراکنش یافت نشد")); + + // If status from gateway callback is not OK, mark as rejected + if (request.Status != "OK") + { + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; + await _context.SaveChangesAsync(context.CancellationToken); + + return new CustomerPaymentVerificationResponse + { + Id = transaction.Id, + PaymentStatus = false, + Message = "پرداخت توسط کاربر لغو شد", + VerificationStatusCode = -1 + }; + } + + // Verify with gateway + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, request.Status, context.CancellationToken); + + // آپدیت PaymentTransaction + 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; + } + + if (verifyResult.IsSuccess) + { + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success; + transaction.PaymentDate = DateTime.UtcNow; + transaction.RefId = verifyResult.RefId; + } + else + { + transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; + } + + await _context.SaveChangesAsync(context.CancellationToken); + + return new CustomerPaymentVerificationResponse + { + Id = transaction.Id, + PaymentStatus = verifyResult.IsSuccess, + Message = verifyResult.IsSuccess ? "پرداخت با موفقیت انجام شد" : (verifyResult.Message ?? "پرداخت ناموفق"), + RefId = verifyResult.RefId ?? string.Empty, + OrderId = string.Empty, + VerificationStatusCode = verifyResult.IsSuccess ? 100 : -1 + }; + } + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0) + return userId; + throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید")); + } } diff --git a/src/CMSMicroservice.WebApi/Services/UserAddressService.cs b/src/CMSMicroservice.WebApi/Services/UserAddressService.cs index adc57e6..977ea00 100644 --- a/src/CMSMicroservice.WebApi/Services/UserAddressService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserAddressService.cs @@ -6,15 +6,28 @@ using CMSMicroservice.Application.UserAddressCQ.Commands.DeleteUserAddress; using CMSMicroservice.Application.UserAddressCQ.Queries.GetUserAddress; using CMSMicroservice.Application.UserAddressCQ.Queries.GetAllUserAddressByFilter; using CMSMicroservice.Application.UserAddressCQ.Commands.SetAddressAsDefault; +using CMSMicroservice.Application.UserAddressCQ.Queries.GetCustomerAddresses; +using CMSMicroservice.Application.UserAddressCQ.Commands.CreateCustomerAddress; +using CMSMicroservice.Application.UserAddressCQ.Commands.UpdateCustomerAddress; +using CMSMicroservice.Application.UserAddressCQ.Commands.DeleteCustomerAddress; +using CMSMicroservice.Application.UserAddressCQ.Commands.SetCustomerDefaultAddress; +using MediatR; + namespace CMSMicroservice.WebApi.Services; + public class UserAddressService : UserAddressContract.UserAddressContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; - public UserAddressService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public UserAddressService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; } + + #region Admin Methods + public override async Task CreateNewUserAddress(CreateNewUserAddressRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); @@ -39,4 +52,96 @@ public class UserAddressService : UserAddressContract.UserAddressContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + #endregion + + #region Customer Methods + + public override async Task GetCustomerAddresses(GetCustomerAddressesRequest request, ServerCallContext context) + { + var query = new GetCustomerAddressesQuery(); + var result = await _sender.Send(query); + + var response = new GetCustomerAddressesResponse(); + + foreach (var addr in result.Addresses) + { + response.Models.Add(new CMSMicroservice.Protobuf.Protos.UserAddress.CustomerAddressModel + { + Id = addr.Id, + Title = addr.Title, + Address = addr.Address, + PostalCode = addr.PostalCode, + IsDefault = addr.IsDefault, + CityId = addr.CityId, + CityName = addr.CityName, + ProvinceName = addr.ProvinceName + }); + } + + return response; + } + + public override async Task CreateCustomerAddress(CreateCustomerAddressRequest request, ServerCallContext context) + { + var command = new CreateCustomerAddressCommand + { + Title = request.Title, + Address = request.Address, + PostalCode = request.PostalCode, + IsDefault = request.IsDefault, + CityId = request.CityId + }; + + var result = await _sender.Send(command); + + return new CreateCustomerAddressResponse + { + Id = result.Id, + Message = result.Message + }; + } + + public override async Task UpdateCustomerAddress(UpdateCustomerAddressRequest request, ServerCallContext context) + { + var command = new UpdateCustomerAddressCommand + { + Id = request.Id, + Title = request.Title, + Address = request.Address, + PostalCode = request.PostalCode, + IsDefault = request.IsDefault, + CityId = request.CityId + }; + + await _sender.Send(command); + + return new Empty(); + } + + public override async Task DeleteCustomerAddress(DeleteCustomerAddressRequest request, ServerCallContext context) + { + var command = new DeleteCustomerAddressCommand + { + Id = request.Id + }; + + await _sender.Send(command); + + return new Empty(); + } + + public override async Task SetCustomerDefaultAddress(SetCustomerDefaultAddressRequest request, ServerCallContext context) + { + var command = new SetCustomerDefaultAddressCommand + { + Id = request.Id + }; + + await _sender.Send(command); + + return new Empty(); + } + + #endregion } diff --git a/src/CMSMicroservice.WebApi/Services/UserCartsService.cs b/src/CMSMicroservice.WebApi/Services/UserCartsService.cs index fd17d07..f3804f1 100644 --- a/src/CMSMicroservice.WebApi/Services/UserCartsService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserCartsService.cs @@ -1,44 +1,238 @@ +using CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCustomerCart; +using CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCustomerCart; +using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCustomerCartItem; +using CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart; +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Protobuf.Protos.UserCarts; using CMSMicroservice.WebApi.Common.Services; -using CMSMicroservice.Application.UserCartsCQ.Commands.CreateNewUserCarts; -using CMSMicroservice.Application.UserCartsCQ.Commands.UpdateUserCarts; -using CMSMicroservice.Application.UserCartsCQ.Commands.DeleteUserCarts; -using CMSMicroservice.Application.UserCartsCQ.Queries.GetUserCarts; -using CMSMicroservice.Application.UserCartsCQ.Queries.GetAllUserCartsByFilter; -using CMSMicroservice.Application.UserCartsCQ.Commands.ClearCart; +using Google.Protobuf.WellKnownTypes; +using Microsoft.EntityFrameworkCore; +using MediatR; +using System.Linq; namespace CMSMicroservice.WebApi.Services; + public class UserCartsService : UserCartsContract.UserCartsContractBase { - private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly IDispatchRequestToCQRS _dispatcher; + private readonly ISender _sender; + private readonly IApplicationDbContext _context; - public UserCartsService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public UserCartsService(IDispatchRequestToCQRS dispatcher, ISender sender, IApplicationDbContext context) { - _dispatchRequestToCQRS = dispatchRequestToCQRS; + _dispatcher = dispatcher; + _sender = sender; + _context = context; } - public override async Task CreateNewUserCarts(CreateNewUserCartsRequest request, ServerCallContext context) + + #region Customer Methods + + public override async Task AddNewUserCartForCustomer( + AddNewUserCartForCustomerRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new AddToCustomerCartCommand + { + ProductId = request.ProductId, + Count = request.Count + }; + + var result = await _sender.Send(command); + + return new AddNewUserCartForCustomerResponse + { + Id = result.Id, + Message = result.Message, + Success = result.Success + }; } - public override async Task UpdateUserCarts(UpdateUserCartsRequest request, ServerCallContext context) + + public override async Task UpdateUserCartForCustomer( + UpdateUserCartForCustomerRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new UpdateCustomerCartItemCommand + { + CartItemId = request.CartItemId, + Count = request.Count + }; + + var result = await _sender.Send(command); + + return new UpdateUserCartForCustomerResponse + { + Message = result.Message, + Success = result.Success + }; } - public override async Task DeleteUserCarts(DeleteUserCartsRequest request, ServerCallContext context) + + public override async Task RemoveUserCartForCustomer( + RemoveUserCartForCustomerRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new RemoveFromCustomerCartCommand + { + CartItemId = request.CartItemId + }; + + var result = await _sender.Send(command); + + return new RemoveUserCartForCustomerResponse + { + Message = result.Message, + Success = result.Success + }; } - public override async Task GetUserCarts(GetUserCartsRequest request, ServerCallContext context) + + public override async Task GetCustomerCart( + GetUserCartForCustomerRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetCustomerCartQuery(); + var result = await _sender.Send(query); + + var response = new GetUserCartForCustomerResponse + { + TotalPrice = result.TotalPrice, + TotalItemsCount = result.TotalItemsCount, + Message = result.Message + }; + + foreach (var item in result.Items) + { + response.Models.Add(new UserCartItem + { + Id = item.Id, + ProductId = item.ProductId, + ProductTitle = item.ProductTitle, + ProductShortInformation = item.ProductShortInformation, + ProductShortInfomation = item.ProductShortInformation, // Alias for typo compatibility + ProductPrice = item.ProductPrice, + ProductDiscount = item.ProductDiscount, + ProductThumbnailPath = item.ProductThumbnailPath, + Count = item.Count, + TotalItemPrice = item.TotalItemPrice, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(item.Created, DateTimeKind.Utc)) + }); + } + + return response; } - public override async Task GetAllUserCartsByFilter(GetAllUserCartsByFilterRequest request, ServerCallContext context) + + #endregion + + #region Admin Methods + + public override async Task AddNewUserCart( + AddNewUserCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var entity = new Domain.Entities.UserCart + { + ProductId = request.ProductId, + UserId = request.UserId, + Count = request.Count + }; + + _context.UserCarts.Add(entity); + await _context.SaveChangesAsync(context.CancellationToken); + + return new AddNewUserCartResponse { Id = entity.Id }; } - - public override async Task ClearCart(ClearCartRequest request, ServerCallContext context) + + public override async Task UpdateUserCart( + UpdateUserCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var cartId = request.Id > 0 ? request.Id : request.UserCartId; + var cart = await _context.UserCarts.FindAsync(new object[] { cartId }, context.CancellationToken); + if (cart == null) + throw new RpcException(new Status(StatusCode.NotFound, "آیتم سبد خرید یافت نشد")); + + cart.Count = request.Count; + await _context.SaveChangesAsync(context.CancellationToken); + return new Empty(); } + + public override async Task DeleteUserCart( + DeleteUserCartRequest request, ServerCallContext context) + { + var cart = await _context.UserCarts.FindAsync(new object[] { request.Id }, context.CancellationToken); + if (cart == null) + throw new RpcException(new Status(StatusCode.NotFound, "آیتم سبد خرید یافت نشد")); + + cart.IsDeleted = true; + await _context.SaveChangesAsync(context.CancellationToken); + return new Empty(); + } + + public override async Task GetUserCart( + GetUserCartRequest request, ServerCallContext context) + { + var cart = await _context.UserCarts + .Where(c => c.Id == request.Id && !c.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (cart == null) + throw new RpcException(new Status(StatusCode.NotFound, "آیتم سبد خرید یافت نشد")); + + return new GetUserCartResponse + { + Id = cart.Id, + ProductId = cart.ProductId, + UserId = cart.UserId, + Count = cart.Count + }; + } + + public override async Task GetAllUserCartsByFilter( + GetAllUserCartsByFilterRequest request, ServerCallContext context) + { + var pageNumber = request.PaginationState?.PageNumber ?? 1; + var pageSize = request.PaginationState?.PageSize ?? 20; + if (pageNumber < 1) pageNumber = 1; + if (pageSize < 1) pageSize = 20; + + var query = _context.UserCarts + .Include(c => c.Product) + .Where(c => !c.IsDeleted); + + if (request.Filter?.Id != null) + query = query.Where(c => c.Id == request.Filter.Id.Value); + if (request.Filter?.ProductId != null) + query = query.Where(c => c.ProductId == request.Filter.ProductId.Value); + if (request.Filter?.UserId != null) + query = query.Where(c => c.UserId == request.Filter.UserId.Value); + + var totalCount = await query.CountAsync(context.CancellationToken); + + var items = await query + .OrderByDescending(c => c.Created) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(c => new GetAllUserCartsByFilterResponseModel + { + Id = c.Id, + ProductId = c.ProductId, + UserId = c.UserId, + Count = c.Count, + ProductTitle = c.Product != null ? c.Product.Title : string.Empty, + ProductShortInfomation = c.Product != null ? (c.Product.ShortInfomation ?? string.Empty) : string.Empty, + ProductPrice = c.Product != null ? c.Product.Price : 0, + ProductDiscount = c.Product != null ? c.Product.Discount : 0, + ProductThumbnailPath = c.Product != null ? (c.Product.ThumbnailPath ?? string.Empty) : string.Empty, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(c.Created, DateTimeKind.Utc)) + }) + .ToListAsync(context.CancellationToken); + + return new GetAllUserCartsByFilterResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = pageNumber, + PageSize = pageSize, + TotalCount = totalCount, + TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize), + HasPrevious = pageNumber > 1, + HasNext = pageNumber * pageSize < totalCount + }, + Models = { items } + }; + } + + #endregion } diff --git a/src/CMSMicroservice.WebApi/Services/UserCartsService.cs.bak b/src/CMSMicroservice.WebApi/Services/UserCartsService.cs.bak new file mode 100644 index 0000000..6173287 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/UserCartsService.cs.bak @@ -0,0 +1,158 @@ +using CMSMicroservice.Protobuf.Protos.UserCarts; +using CMSMicroservice.WebApi.Common.Services; + +namespace CMSMicroservice.WebApi.Services; + +public class UserCartsService : UserCartsContract.UserCartsContractBase +{ + private readonly IDispatchRequestToCQRS _dispatcher; + + public UserCartsService(IDispatchRequestToCQRS dispatcher) + { + _dispatcher = dispatcher; + } + + #region Customer Methods + + public override async Task AddNewUserCartForCustomer( + AddNewUserCartForCustomerRequest request, ServerCallContext context) + { + // TODO: Map to DiscountShop AddToCart command + return new AddNewUserCartForCustomerResponse + { + Message = "AddNewUserCartForCustomer not implemented yet" + }; + } + + public override async Task UpdateUserCartForCustomer( + UpdateUserCartForCustomerRequest request, ServerCallContext context) + { + // TODO: Map to DiscountShop UpdateCartItemCount command + return new UpdateUserCartForCustomerResponse + { + Message = "UpdateUserCartForCustomer not implemented yet" + }; + } + + public override async Task RemoveUserCartForCustomer( + RemoveUserCartForCustomerRequest request, ServerCallContext context) + { + // TODO: Map to DiscountShop RemoveFromCart command + return new RemoveUserCartForCustomerResponse + { + Message = "RemoveUserCartForCustomer not implemented yet" + }; + } + + public override async Task GetUserCartForCustomer( + GetUserCartForCustomerRequest request, ServerCallContext context) + { + // TODO: Map to DiscountShop GetUserCart query + return new GetUserCartForCustomerResponse(); + } + + #endregion + + #region Admin Methods + + public override async Task AddNewUserCart( + AddNewUserCartRequest request, ServerCallContext context) + { + throw new RpcException(new Status(StatusCode.Unimplemented, "AddNewUserCart not implemented yet")); + } + + public override async Task UpdateUserCart( + UpdateUserCartRequest request, ServerCallContext context) + { + throw new RpcException(new Status(StatusCode.Unimplemented, "UpdateUserCart not implemented yet")); + } + + public override async Task DeleteUserCart( + DeleteUserCartRequest request, ServerCallContext context) + { + throw new RpcException(new Status(StatusCode.Unimplemented, "DeleteUserCart not implemented yet")); + } + + public override async Task GetUserCart( + GetUserCartRequest request, ServerCallContext context) + { + throw new RpcException(new Status(StatusCode.Unimplemented, "GetUserCart not implemented yet")); + } + + public override async Task GetAllUserCartsByFilter( + GetAllUserCartsByFilterRequest request, ServerCallContext context) + { + throw new RpcException(new Status(StatusCode.Unimplemented, "GetAllUserCartsByFilter not implemented yet")); + } + + #endregion +} +using CMSMicroservice.Application.UserCartsCQ.Commands.ClearCart; +using Grpc.Core; + +namespace CMSMicroservice.WebApi.Services; + +public class UserCartsService : UserCartsContract.UserCartsContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public UserCartsService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task CreateNewUserCarts(CreateNewUserCartsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task UpdateUserCarts(UpdateUserCartsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task DeleteUserCarts(DeleteUserCartsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetUserCarts(GetUserCartsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetAllUserCartsByFilter(GetAllUserCartsByFilterRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task ClearCart(ClearCartRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task ClearCartForCustomer(ClearCartRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + // ============= Customer-specific Methods ============= + + public override async Task AddNewUserCartForCustomer(CreateNewUserCartsRequest request, ServerCallContext context) + { + // Reuse the existing CreateNewUserCarts logic + return await CreateNewUserCarts(request, context); + } + + public override async Task UpdateUserCartForCustomer(UpdateUserCartsRequest request, ServerCallContext context) + { + // Reuse the existing UpdateUserCarts logic + return await UpdateUserCarts(request, context); + } + + public override async Task GetUserCartForCustomer(GetAllUserCartsByFilterRequest request, ServerCallContext context) + { + // Reuse the existing GetAllUserCartsByFilter logic + return await GetAllUserCartsByFilter(request, context); + } +} diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index 9cf8b31..aacf89c 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -1,73 +1,959 @@ +using CMSMicroservice.Application.Common.Authorization; using CMSMicroservice.Protobuf.Protos.UserOrder; -using CMSMicroservice.WebApi.Common.Services; -using CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder; -using CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder; -using CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder; -using CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus; -using CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder; -using CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder; -using CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter; -using CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange; -using CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV; -using CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder; -using CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder; +using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders; +using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder; +using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory; +using CMSMicroservice.Application.OrderManagementCQ.Commands.UpdateOrderStatus; +using CMSMicroservice.Application.OrderManagementCQ.Commands.CancelOrderByAdmin; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Entities.Order; +using CMSMicroservice.Domain.Enums; +using AppModels = CMSMicroservice.Application.Common.Models; +using Grpc.Core; +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; +using System.Linq; +using CMSMicroservice.Protobuf.Protos; +using MediatR; +using Mapster; +using Microsoft.EntityFrameworkCore; +using System.Linq; namespace CMSMicroservice.WebApi.Services; + public class UserOrderService : UserOrderContract.UserOrderContractBase { - private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUserService; - public UserOrderService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public UserOrderService(ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService) { - _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _context = context; + _currentUserService = currentUserService; } + [RequiresPermission(PermissionNames.OrdersCreate)] public override async Task CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var order = new UserOrder + { + Amount = request.Amount, + PackageId = request.PackageId > 0 ? request.PackageId : null, + TransactionId = request.TransactionId, + PaymentStatus = request.HasPaymentStatus + ? (Domain.Enums.PaymentStatus)(int)request.PaymentStatus + : Domain.Enums.PaymentStatus.Pending, + PaymentDate = request.PaymentDate?.ToDateTime(), + UserId = request.UserId, + UserAddressId = request.UserAddressId, + PaymentMethod = request.HasPaymentMethod + ? (Domain.Enums.PaymentMethod)(int)request.PaymentMethod + : null, + DeliveryStatus = Domain.Enums.DeliveryStatus.Pending + }; + + _context.UserOrders.Add(order); + await _context.SaveChangesAsync(context.CancellationToken); + + return new CreateNewUserOrderResponse { Id = order.Id }; } - public override async Task UpdateUserOrder(UpdateUserOrderRequest request, ServerCallContext context) + + [RequiresPermission(PermissionNames.OrdersUpdate)] + public override async Task UpdateUserOrder(UpdateUserOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var order = await _context.UserOrders.FindAsync(new object[] { request.Id }, context.CancellationToken); + if (order == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + if (request.Amount != null) order.Amount = request.Amount.Value; + if (request.PackageId != null) order.PackageId = request.PackageId.Value; + if (request.TransactionId != null) order.TransactionId = request.TransactionId.Value; + if (request.HasPaymentStatus) order.PaymentStatus = (Domain.Enums.PaymentStatus)(int)request.PaymentStatus; + if (request.PaymentDate != null) order.PaymentDate = request.PaymentDate.ToDateTime(); + if (request.UserId != null) order.UserId = request.UserId.Value; + if (request.UserAddressId != null) order.UserAddressId = request.UserAddressId.Value; + if (request.HasPaymentMethod) order.PaymentMethod = (Domain.Enums.PaymentMethod)(int)request.PaymentMethod; + if (request.HasDeliveryStatus) order.DeliveryStatus = (Domain.Enums.DeliveryStatus)(int)request.DeliveryStatus; + if (request.TrackingCode != null) order.TrackingCode = request.TrackingCode; + if (request.DeliveryDescription != null) order.DeliveryDescription = request.DeliveryDescription; + + await _context.SaveChangesAsync(context.CancellationToken); + return new Google.Protobuf.WellKnownTypes.Empty(); } - public override async Task DeleteUserOrder(DeleteUserOrderRequest request, ServerCallContext context) + + [RequiresPermission(PermissionNames.OrdersDelete)] + public override async Task DeleteUserOrder(DeleteUserOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var order = await _context.UserOrders.FindAsync(new object[] { request.Id }, context.CancellationToken); + if (order == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + order.IsDeleted = true; + await _context.SaveChangesAsync(context.CancellationToken); + return new Google.Protobuf.WellKnownTypes.Empty(); } + + [RequiresPermission(PermissionNames.OrdersView)] public override async Task GetUserOrder(GetUserOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetCustomerOrderQuery + { + OrderId = request.Id, + UserId = 0 // Admin: no user filter, can view any order by ID + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetUserOrderResponse + { + Id = result.Id, + Amount = result.Amount, + PackageId = result.PackageId ?? 0, + TransactionId = result.TransactionId, + UserId = result.UserId, + UserAddressId = result.UserAddressId, + UserAddressText = result.UserAddressText, + TrackingCode = result.TrackingCode, + DeliveryDescription = result.DeliveryDescription, + UserFullName = result.UserFullName, + UserNationalCode = result.UserNationalCode + }; + + // VAT Info + if (result.VatAmount > 0) + { + response.VatInfo = new OrderVATInfo + { + VatRate = result.VatPercentage / 100, + BaseAmount = result.Amount - result.VatAmount, + VatAmount = result.VatAmount, + TotalAmount = result.Amount, + IsPaid = result.PaymentStatus == Domain.Enums.PaymentStatus.Success + }; + } + + response.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)result.PaymentStatus; + if (result.PaymentDate.HasValue) + response.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(result.PaymentDate.Value, DateTimeKind.Utc)); + + if (result.PaymentMethod.HasValue) + response.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)result.PaymentMethod.Value; + + response.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)result.DeliveryStatus; + + foreach (var fd in result.FactorDetails) + { + response.FactorDetails.Add(new GetUserOrderResponseFactorDetail + { + ProductId = fd.ProductId, + ProductTitle = fd.ProductTitle, + ProductThumbnailPath = fd.ProductThumbnailPath, + UnitPrice = fd.UnitPrice, + Count = fd.Count, + UnitDiscountPrice = fd.UnitDiscountPrice + }); + } + + return response; } + + [RequiresPermission(PermissionNames.OrdersView)] public override async Task GetAllUserOrderByFilter(GetAllUserOrderByFilterRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + // Admin API - can view all orders or filter by specific user + var query = new GetCustomerOrdersQuery + { + UserId = request.Filter?.UserId ?? 0, // 0 means all users (admin view) + PaginationState = request.PaginationState?.Adapt(), + PaymentStatusFilter = request.Filter?.HasPaymentStatus == true + ? (int?)request.Filter.PaymentStatus + : null, + DeliveryStatusFilter = request.Filter?.HasDeliveryStatus == true + ? (int?)request.Filter.DeliveryStatus + : null, + FromDate = request.Filter?.FromDate?.ToDateTime() ?? request.Filter?.PaymentDate?.ToDateTime(), + ToDate = request.Filter?.ToDate?.ToDateTime() + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetAllUserOrderByFilterResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var model in result.Models) + { + var orderModel = new GetAllUserOrderByFilterResponseModel + { + Id = model.Id, + Amount = model.Amount, + PackageId = model.PackageId ?? 0, + TransactionId = model.TransactionId, + UserId = model.UserId, + UserAddressId = model.UserAddressId, + UserAddressText = model.UserAddressText, + TrackingCode = model.TrackingCode, + DeliveryDescription = model.DeliveryDescription, + UserFullName = model.UserFullName, + UserNationalCode = model.UserNationalCode, + VatAmount = model.VatAmount, + VatPercentage = model.VatPercentage + }; + + orderModel.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)model.PaymentStatus; + if (model.PaymentDate.HasValue) + orderModel.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(model.PaymentDate.Value, DateTimeKind.Utc)); + + if (model.PaymentMethod.HasValue) + orderModel.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)model.PaymentMethod.Value; + + orderModel.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)model.DeliveryStatus; + + foreach (var fd in model.FactorDetails) + { + orderModel.FactorDetails.Add(new GetAllUserOrderByFilterResponseModelFactorDetail + { + ProductId = fd.ProductId, + ProductTitle = fd.ProductTitle, + ProductThumbnailPath = fd.ProductThumbnailPath, + UnitPrice = fd.UnitPrice, + Count = fd.Count, + UnitDiscountPrice = fd.UnitDiscountPrice + }); + } + + response.Models.Add(orderModel); + } + + return response; } + public override async Task SubmitShopBuyOrder(SubmitShopBuyOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + // Get UserId from JWT or request + var userId = !string.IsNullOrEmpty(_currentUserService.UserId) + ? long.Parse(_currentUserService.UserId) + : request.UserId; + + if (userId == 0) + { + throw new RpcException(new Status(StatusCode.Unauthenticated, "User not authenticated")); + } + + // Get user's cart items with product details + var cartItems = await _context.UserCarts + .Include(c => c.Product) + .Where(c => c.UserId == userId && !c.IsDeleted) + .ToListAsync(context.CancellationToken); + + if (!cartItems.Any()) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, "سبد خرید خالی است")); + } + + // Get user's default address + var defaultAddress = await _context.UserAddresses + .Where(a => a.UserId == userId && a.IsDefault && !a.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (defaultAddress == null) + { + // Check if user has any address at all + var hasAnyAddress = await _context.UserAddresses + .AnyAsync(a => a.UserId == userId && !a.IsDeleted, context.CancellationToken); + + throw new RpcException(new Status(StatusCode.FailedPrecondition, + hasAnyAddress + ? "لطفاً یک آدرس را به عنوان پیش‌فرض انتخاب کنید." + : "آدرسی ثبت نشده است. لطفاً ابتدا یک آدرس اضافه کنید.")); + } + + // Calculate amounts + const decimal vatRate = 0.09m; + long baseAmount = cartItems.Sum(c => c.Product.Price * c.Count); + long vatAmount = (long)(baseAmount * vatRate); + long totalAmount = baseAmount + vatAmount; + + // Validate total amount + if (request.TotalAmount > 0 && Math.Abs(totalAmount - request.TotalAmount) > 100) + { + throw new RpcException(new Status(StatusCode.InvalidArgument, + $"مبلغ نامعتبر است. محاسبه شده: {totalAmount}, دریافتی: {request.TotalAmount}")); + } + + // Get user's wallet + var wallet = await _context.UserWallets + .Where(w => w.UserId == userId) + .FirstOrDefaultAsync(context.CancellationToken); + + if (wallet == null) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, "کیف پول یافت نشد")); + } + + // Check wallet balance + if (wallet.Balance < totalAmount) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, + $"موجودی کیف پول کافی نیست. موجودی: {wallet.Balance:N0} تومان، مورد نیاز: {totalAmount:N0} تومان")); + } + + // Create transaction + var transaction = new Transaction + { + Amount = totalAmount, + Description = $"خرید محصولات - سفارش شماره در حال ایجاد", + PaymentStatus = CMSMicroservice.Domain.Enums.PaymentStatus.Success, + PaymentDate = DateTime.UtcNow, + Type = CMSMicroservice.Domain.Enums.TransactionType.Buy, + RefId = $"SHOP_{DateTime.UtcNow.Ticks}" + }; + + _context.Transactions.Add(transaction); + await _context.SaveChangesAsync(context.CancellationToken); + + // Deduct from wallet + var oldBalance = wallet.Balance; + wallet.Balance -= totalAmount; + + // Create wallet change log + var walletLog = new UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = -totalAmount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = wallet.DiscountBalance, + ChangeDiscountValue = 0, + IsIncrease = false, + RefrenceId = transaction.Id + }; + + _context.UserWalletChangeLogs.Add(walletLog); + + // Create order + var order = new UserOrder + { + UserId = userId, + UserAddressId = defaultAddress.Id, + Amount = totalAmount, + TransactionId = transaction.Id, + PaymentStatus = CMSMicroservice.Domain.Enums.PaymentStatus.Success, + PaymentDate = DateTime.UtcNow, + PaymentMethod = CMSMicroservice.Domain.Enums.PaymentMethod.Wallet, + DeliveryStatus = CMSMicroservice.Domain.Enums.DeliveryStatus.Pending, + HasVAT = true + }; + + _context.UserOrders.Add(order); + await _context.SaveChangesAsync(context.CancellationToken); + + // Update transaction description with order ID + transaction.Description = $"خرید محصولات - سفارش #{order.Id}"; + await _context.SaveChangesAsync(context.CancellationToken); + + // Create order VAT record + var orderVat = new OrderVAT + { + OrderId = order.Id, + VATRate = vatRate, + BaseAmount = baseAmount, + VATAmount = vatAmount, + TotalAmount = totalAmount + }; + + _context.OrderVATs.Add(orderVat); + + // Create factor details for each cart item + foreach (var cartItem in cartItems) + { + var factorDetail = new FactorDetails + { + OrderId = order.Id, + ProductId = cartItem.ProductId, + Count = cartItem.Count, + UnitPrice = cartItem.Product.Price, + UnitDiscount = 0, + UnitDiscountPrice = cartItem.Product.Price, + IsChangePrice = false + }; + + _context.FactorDetails.Add(factorDetail); + } + + await _context.SaveChangesAsync(context.CancellationToken); + + // Clear user's cart + foreach (var cartItem in cartItems) + { + cartItem.IsDeleted = true; + } + + await _context.SaveChangesAsync(context.CancellationToken); + + return new SubmitShopBuyOrderResponse + { + Id = order.Id + }; } + [RequiresPermission(PermissionNames.OrdersCancel)] public override async Task CancelOrder(CancelOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new CancelOrderByAdminCommand + { + OrderId = request.OrderId, + CancelReason = request.CancelReason, + RefundToWallet = request.RefundPayment + }; + + await _sender.Send(command, context.CancellationToken); + + return new CancelOrderResponse + { + OrderId = request.OrderId, + Status = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)(int)Domain.Enums.DeliveryStatus.Cancelled, + Message = "سفارش با موفقیت لغو شد", + RefundProcessed = request.RefundPayment + }; } + [RequiresPermission(PermissionNames.OrdersUpdate)] public override async Task UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var order = await _context.UserOrders.FindAsync(new object[] { request.OrderId }, context.CancellationToken); + if (order == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + var oldStatus = (int)order.DeliveryStatus; + + var command = new Application.OrderManagementCQ.Commands.UpdateOrderStatus.UpdateOrderStatusCommand + { + OrderId = request.OrderId, + NewStatus = (Domain.Enums.DeliveryStatus)request.NewStatus + }; + + await _sender.Send(command, context.CancellationToken); + + return new UpdateOrderStatusResponse + { + Success = true, + Message = "وضعیت سفارش با موفقیت تغییر کرد", + OldStatus = oldStatus, + NewStatus = request.NewStatus + }; } + [RequiresPermission(PermissionNames.ReportsView)] public override async Task GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1; + var pageSize = request.PageSize > 0 ? request.PageSize : 20; + + var query = _context.UserOrders + .Include(o => o.User) + .Include(o => o.FactorDetails) + .Where(o => !o.IsDeleted); + + if (request.StartDate != null) + query = query.Where(o => o.Created >= request.StartDate.ToDateTime()); + if (request.EndDate != null) + query = query.Where(o => o.Created <= request.EndDate.ToDateTime()); + if (request.Status != null) + query = query.Where(o => (int)o.DeliveryStatus == request.Status.Value); + if (request.UserId != null) + query = query.Where(o => o.UserId == request.UserId.Value); + + var totalCount = await query.CountAsync(context.CancellationToken); + + var orders = await query + .OrderByDescending(o => o.Created) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(o => new OrderSummaryDto + { + OrderId = o.Id, + OrderNumber = $"ORD-{o.Id:D6}", + UserId = o.UserId, + UserFullName = o.User != null ? (o.User.FirstName + " " + o.User.LastName) : string.Empty, + TotalAmount = o.Amount, + Status = (int)o.DeliveryStatus, + StatusName = o.DeliveryStatus.ToString(), + ItemsCount = o.FactorDetails.Count, + CreatedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(o.Created, DateTimeKind.Utc)) + }) + .ToListAsync(context.CancellationToken); + + return new GetOrdersByDateRangeResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = pageNumber, + PageSize = pageSize, + TotalCount = totalCount, + TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize), + HasPrevious = pageNumber > 1, + HasNext = pageNumber * pageSize < totalCount + }, + Orders = { orders } + }; } + [RequiresPermission(PermissionNames.OrdersUpdate)] public override async Task ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var order = await _context.UserOrders.FindAsync(new object[] { request.OrderId }, context.CancellationToken); + if (order == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + if (order.PaymentStatus == Domain.Enums.PaymentStatus.Success) + return new ApplyDiscountToOrderResponse + { + Success = false, + Message = "امکان اعمال تخفیف برای سفارش پرداخت شده وجود ندارد" + }; + + var originalAmount = order.Amount; + var discountAmount = Math.Min(request.DiscountAmount, originalAmount); + order.Amount = originalAmount - discountAmount; + + await _context.SaveChangesAsync(context.CancellationToken); + + return new ApplyDiscountToOrderResponse + { + Success = true, + Message = $"تخفیف {discountAmount:N0} تومان با موفقیت اعمال شد", + OriginalAmount = originalAmount, + DiscountAmount = discountAmount, + FinalAmount = order.Amount + }; } + [RequiresPermission(PermissionNames.OrdersView)] public override async Task CalculateOrderPV(CalculateOrderPVRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var order = await _context.UserOrders + .Include(o => o.FactorDetails) + .ThenInclude(fd => fd.Product) + .Where(o => o.Id == request.OrderId && !o.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (order == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + var products = new List(); + long totalPV = 0; + + foreach (var fd in order.FactorDetails.Where(f => !f.IsDeleted)) + { + // PV = UnitPrice * Count (simplified - can be customized) + long unitPV = fd.UnitPrice; + long itemPV = unitPV * fd.Count; + totalPV += itemPV; + + products.Add(new ProductPVDto + { + ProductId = fd.ProductId, + ProductTitle = fd.Product?.Title ?? string.Empty, + Quantity = fd.Count, + UnitPv = unitPV, + TotalPv = itemPV + }); + } + + return new CalculateOrderPVResponse + { + OrderId = request.OrderId, + TotalPv = totalPV, + Products = { products } + }; } + + // ============= Customer-specific Methods ============= + + public override async Task CreateNewOrderForCustomer(CreateNewUserOrderRequest request, ServerCallContext context) + { + // For now, return empty response - will be implemented properly later + return new CreateNewUserOrderResponse(); + } + + public override async Task SubmitOrderForCustomer(SubmitShopBuyOrderRequest request, ServerCallContext context) + { + // For now, return empty response - will be implemented properly later + return new SubmitShopBuyOrderResponse(); + } + + public override async Task GetCustomerOrders(GetAllUserOrderByFilterRequest request, ServerCallContext context) + { + // Customer endpoint: ALWAYS resolve userId from JWT (customer can only see own orders) + var customerUserId = long.TryParse(_currentUserService.UserId, out var uid) ? uid : 0; + if (customerUserId == 0) + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); + + var query = new GetCustomerOrdersQuery + { + UserId = customerUserId, + PaginationState = request.PaginationState?.Adapt(), + PaymentStatusFilter = request.Filter?.HasPaymentStatus == true + ? (int?)request.Filter.PaymentStatus + : null, + DeliveryStatusFilter = request.Filter?.HasDeliveryStatus == true + ? (int?)request.Filter.DeliveryStatus + : null, + FromDate = request.Filter?.FromDate?.ToDateTime() ?? request.Filter?.PaymentDate?.ToDateTime(), + ToDate = request.Filter?.ToDate?.ToDateTime() + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetAllUserOrderByFilterResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var model in result.Models) + { + var orderModel = new GetAllUserOrderByFilterResponseModel + { + Id = model.Id, + Amount = model.Amount, + PackageId = model.PackageId ?? 0, + TransactionId = model.TransactionId, + UserId = model.UserId, + UserAddressId = model.UserAddressId, + UserAddressText = model.UserAddressText, + TrackingCode = model.TrackingCode, + DeliveryDescription = model.DeliveryDescription, + UserFullName = model.UserFullName, + UserNationalCode = model.UserNationalCode, + VatAmount = model.VatAmount, + VatPercentage = model.VatPercentage + }; + + orderModel.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)model.PaymentStatus; + if (model.PaymentDate.HasValue) + orderModel.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(model.PaymentDate.Value, DateTimeKind.Utc)); + + if (model.PaymentMethod.HasValue) + orderModel.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)model.PaymentMethod.Value; + + orderModel.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)model.DeliveryStatus; + + foreach (var fd in model.FactorDetails) + { + orderModel.FactorDetails.Add(new GetAllUserOrderByFilterResponseModelFactorDetail + { + ProductId = fd.ProductId, + ProductTitle = fd.ProductTitle, + ProductThumbnailPath = fd.ProductThumbnailPath, + UnitPrice = fd.UnitPrice, + Count = fd.Count, + UnitDiscountPrice = fd.UnitDiscountPrice + }); + } + + response.Models.Add(orderModel); + } + + return response; + } + + public override async Task GetCustomerOrder(GetUserOrderRequest request, ServerCallContext context) + { + // Customer endpoint: ALWAYS resolve userId from JWT (customer can only see own orders) + var customerUserId = long.TryParse(_currentUserService.UserId, out var uid) ? uid : 0; + if (customerUserId == 0) + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); + + var query = new GetCustomerOrderQuery + { + OrderId = request.Id, + UserId = customerUserId + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetUserOrderResponse + { + Id = result.Id, + Amount = result.Amount, + PackageId = result.PackageId ?? 0, + TransactionId = result.TransactionId, + UserId = result.UserId, + UserAddressId = result.UserAddressId, + UserAddressText = result.UserAddressText, + TrackingCode = result.TrackingCode, + DeliveryDescription = result.DeliveryDescription, + UserFullName = result.UserFullName, + UserNationalCode = result.UserNationalCode + }; + + // VAT Info + if (result.VatAmount > 0) + { + response.VatInfo = new OrderVATInfo + { + VatRate = result.VatPercentage / 100, + BaseAmount = result.Amount - result.VatAmount, + VatAmount = result.VatAmount, + TotalAmount = result.Amount, + IsPaid = result.PaymentStatus == Domain.Enums.PaymentStatus.Success + }; + } + + response.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)result.PaymentStatus; + if (result.PaymentDate.HasValue) + response.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(result.PaymentDate.Value, DateTimeKind.Utc)); + + if (result.PaymentMethod.HasValue) + response.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)result.PaymentMethod.Value; + + response.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)result.DeliveryStatus; + + foreach (var fd in result.FactorDetails) + { + response.FactorDetails.Add(new GetUserOrderResponseFactorDetail + { + ProductId = fd.ProductId, + ProductTitle = fd.ProductTitle, + ProductThumbnailPath = fd.ProductThumbnailPath, + UnitPrice = fd.UnitPrice, + Count = fd.Count, + UnitDiscountPrice = fd.UnitDiscountPrice + }); + } + + return response; + } + + // ============= Customer-specific Method Implementations ============= + + public override async Task CustomerCancelOrder(CustomerCancelOrderRequest request, ServerCallContext context) + { + var order = await _context.UserOrders + .Where(o => o.Id == request.OrderId && !o.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (order == null) + return new CustomerCancelOrderResponse { Success = false, Message = "سفارش یافت نشد" }; + + if (order.DeliveryStatus != Domain.Enums.DeliveryStatus.Pending) + return new CustomerCancelOrderResponse { Success = false, Message = "فقط سفارش‌های در انتظار قابل لغو هستند" }; + + var refundAmount = order.Amount; + + order.DeliveryStatus = Domain.Enums.DeliveryStatus.Cancelled; + + // Refund to wallet if payment was from wallet + if (order.PaymentStatus == Domain.Enums.PaymentStatus.Success && order.PaymentMethod == Domain.Enums.PaymentMethod.Wallet) + { + var wallet = await _context.UserWallets + .Where(w => w.UserId == order.UserId) + .FirstOrDefaultAsync(context.CancellationToken); + + if (wallet != null) + { + wallet.Balance += refundAmount; + _context.UserWalletChangeLogs.Add(new UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = refundAmount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = wallet.DiscountBalance, + ChangeDiscountValue = 0, + IsIncrease = true, + RefrenceId = order.TransactionId ?? 0 + }); + } + } + + await _context.SaveChangesAsync(context.CancellationToken); + + return new CustomerCancelOrderResponse + { + Success = true, + Message = "سفارش شما با موفقیت لغو شد", + RefundAmount = refundAmount, + RefundTransactionId = $"REF{order.Id}" + }; + } + + public override async Task GetCustomerOrderHistory(GetCustomerOrderHistoryRequest request, ServerCallContext context) + { + var query = new GetCustomerOrderHistoryQuery + { + UserId = request.UserId, + PaginationState = request.PaginationState?.Adapt(), + StatusFilter = request.StatusFilter != OrderStatusEnum.OrderStatusPending + ? (int?)request.StatusFilter + : null, + FromDate = request.FromDate?.ToDateTime(), + ToDate = request.ToDate?.ToDateTime() + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerOrderHistoryResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var order in result.Orders) + { + response.Orders.Add(new Protobuf.Protos.UserOrder.CustomerOrderModel + { + Id = order.Id, + Amount = order.Amount, + PackageId = order.PackageId ?? 0, + PackageName = order.PackageName, + Status = (OrderStatusEnum)order.Status, + StatusMessage = order.StatusMessage, + OrderDate = Timestamp.FromDateTime(DateTime.SpecifyKind(order.OrderDate, DateTimeKind.Utc)), + DeliveryDate = order.DeliveryDate.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(order.DeliveryDate.Value, DateTimeKind.Utc)) + : null, + TrackingCode = order.TrackingCode, + ItemsCount = order.ItemsCount, + CanCancel = order.CanCancel, + CanReorder = order.CanReorder + }); + } + + return response; + } + + public override async Task CustomerTrackOrder(CustomerTrackOrderRequest request, ServerCallContext context) + { + var order = await _context.UserOrders + .Include(o => o.FactorDetails) + .Include(o => o.Package) + .Where(o => o.Id == request.OrderId && !o.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (order == null) + throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); + + var orderModel = new Protobuf.Protos.UserOrder.CustomerOrderModel + { + Id = order.Id, + Amount = order.Amount, + PackageId = order.PackageId ?? 0, + PackageName = order.Package?.Title ?? string.Empty, + Status = (OrderStatusEnum)(int)order.DeliveryStatus, + StatusMessage = GetDeliveryStatusPersian(order.DeliveryStatus), + OrderDate = Timestamp.FromDateTime(DateTime.SpecifyKind(order.Created, DateTimeKind.Utc)), + TrackingCode = order.TrackingCode ?? string.Empty, + ItemsCount = order.FactorDetails.Count(f => !f.IsDeleted), + CanCancel = order.DeliveryStatus == Domain.Enums.DeliveryStatus.Pending, + CanReorder = order.DeliveryStatus == Domain.Enums.DeliveryStatus.Delivered + }; + + var response = new CustomerTrackOrderResponse + { + Order = orderModel, + DeliveryInfo = new DeliveryTrackingInfo + { + TrackingCode = order.TrackingCode ?? string.Empty, + CourierName = "پست پیشتاز", + EstimatedDelivery = order.DeliveryDescription ?? string.Empty, + CurrentLocation = string.Empty + } + }; + + // Add current status to history + response.StatusHistory.Add(new OrderStatusHistory + { + Status = (OrderStatusEnum)(int)order.DeliveryStatus, + StatusMessage = GetDeliveryStatusPersian(order.DeliveryStatus), + ChangedAt = order.LastModified.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(order.LastModified.Value, DateTimeKind.Utc)) + : Timestamp.FromDateTime(DateTime.SpecifyKind(order.Created, DateTimeKind.Utc)), + ChangedBy = "سیستم" + }); + + return response; + } + + public override async Task CustomerReorderPreviousOrder(CustomerReorderRequest request, ServerCallContext context) + { + var originalOrder = await _context.UserOrders + .Include(o => o.FactorDetails) + .ThenInclude(fd => fd.Product) + .Where(o => o.Id == request.OriginalOrderId && !o.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (originalOrder == null) + return new CustomerReorderResponse { Success = false, Message = "سفارش اصلی یافت نشد" }; + + var userId = originalOrder.UserId; + + // Add items from old order to cart + foreach (var fd in originalOrder.FactorDetails.Where(f => !f.IsDeleted)) + { + var existingCartItem = await _context.UserCarts + .Where(c => c.UserId == userId && c.ProductId == fd.ProductId && !c.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (existingCartItem != null) + { + existingCartItem.Count += fd.Count; + } + else + { + _context.UserCarts.Add(new UserCart + { + UserId = userId, + ProductId = fd.ProductId, + Count = fd.Count + }); + } + } + + await _context.SaveChangesAsync(context.CancellationToken); + + // Calculate total with current prices + long totalAmount = originalOrder.FactorDetails + .Where(f => !f.IsDeleted) + .Sum(fd => request.UseCurrentPrices && fd.Product != null + ? fd.Product.Price * fd.Count + : fd.UnitPrice * fd.Count); + + return new CustomerReorderResponse + { + Success = true, + Message = "محصولات به سبد خرید اضافه شدند", + NewOrderId = 0, // Cart items added, no order created yet + TotalAmount = totalAmount + }; + } + + public override Task GetVATRate(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) + { + // VAT Rate for Iran: 9% (نرخ مالیات بر ارزش افزوده ایران) + return Task.FromResult(new GetVATRateResponse + { + VatRate = 0.09, + VatPercentage = 9, + IsEnabled = true + }); + } + + // ============= Helper Methods ============= + + private static string GetDeliveryStatusPersian(Domain.Enums.DeliveryStatus status) => status switch + { + Domain.Enums.DeliveryStatus.None => "نامشخص", + Domain.Enums.DeliveryStatus.Pending => "در انتظار ارسال", + Domain.Enums.DeliveryStatus.InTransit => "در حال ارسال", + Domain.Enums.DeliveryStatus.Delivered => "تحویل داده شده", + Domain.Enums.DeliveryStatus.Returned => "مرجوع شده", + Domain.Enums.DeliveryStatus.Cancelled => "لغو شده", + _ => status.ToString() + }; } diff --git a/src/CMSMicroservice.WebApi/Services/UserService.cs b/src/CMSMicroservice.WebApi/Services/UserService.cs index f6a0b68..72d0f53 100644 --- a/src/CMSMicroservice.WebApi/Services/UserService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserService.cs @@ -1,4 +1,5 @@ using CMSMicroservice.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Protos.City; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.UserCQ.Commands.CreateNewUser; using CMSMicroservice.Application.UserCQ.Commands.UpdateUser; @@ -9,14 +10,46 @@ using CMSMicroservice.Application.UserCQ.Queries.GetJwtToken; using CMSMicroservice.Application.UserCQ.Queries.AdminGetJwtToken; using CMSMicroservice.Application.UserCQ.Commands.SetPasswordForUser; using CMSMicroservice.Application.UserCQ.Commands.RefreshToken; +using CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken; +using CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; +using CMSMicroservice.Application.UserCQ.Commands.AcceptContract; +using CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile; +using CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals; +using CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings; +using CMSMicroservice.Application.Common.Interfaces; +using Google.Protobuf.WellKnownTypes; +using Microsoft.EntityFrameworkCore; +using Grpc.Core; +using System.Collections.Generic; +using System.Linq; +using MediatR; +using Mapster; +using AppModels = CMSMicroservice.Application.Common.Models; + namespace CMSMicroservice.WebApi.Services; public class UserService : UserContract.UserContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUserService; + private readonly IHashService _hashService; + private readonly CMSMicroservice.Application.Common.FileManager.IFileManager _fileManager; - public UserService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public UserService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + IApplicationDbContext context, + ICurrentUserService currentUserService, + IHashService hashService, + CMSMicroservice.Application.Common.FileManager.IFileManager fileManager) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _context = context; + _currentUserService = currentUserService; + _hashService = hashService; + _fileManager = fileManager; } public override async Task CreateNewUser(CreateNewUserRequest request, ServerCallContext context) { @@ -32,6 +65,10 @@ public class UserService : UserContract.UserContractBase } public override async Task GetUser(GetUserRequest request, ServerCallContext context) { + // اگر Id ارسال نشده، از JWT بخون (برای کلاینت مشتری) + if (request.Id == 0) + request.Id = GetCurrentUserId(); + return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetAllUserByFilter(GetAllUserByFilterRequest request, ServerCallContext context) @@ -54,4 +91,330 @@ public class UserService : UserContract.UserContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + // ============= Customer-specific Methods ============= + + public override async Task CreateNewOtpToken(CreateNewOtpTokenRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task VerifyOtpToken(VerifyOtpTokenRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task AcceptContract(AcceptContractRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetUserForCustomer(GetUserForCustomerRequest request, ServerCallContext context) + { + var userId = GetCurrentUserId(); + + var user = await _context.Users + .AsNoTracking() + .Where(u => u.Id == userId && !u.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (user == null) + throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد")); + + return new GetUserForCustomerResponse + { + Id = user.Id, + FirstName = user.FirstName ?? string.Empty, + LastName = user.LastName ?? string.Empty, + Mobile = user.Mobile, + Email = user.Email ?? string.Empty, + NationalCode = user.NationalCode ?? string.Empty, + AvatarPath = user.AvatarPath ?? string.Empty, + ParentId = user.NetworkParentId, + ReferralCode = user.ReferralCode ?? string.Empty, + IsMobileVerified = user.IsMobileVerified, + MobileVerifiedAt = user.MobileVerifiedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(user.MobileVerifiedAt.Value, DateTimeKind.Utc)) + : null, + EmailNotifications = user.EmailNotifications, + SmsNotifications = user.SmsNotifications, + PushNotifications = user.PushNotifications, + BirthDate = user.BirthDate.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(user.BirthDate.Value, DateTimeKind.Utc)) + : null + }; + } + + public override async Task UpdateCustomerProfile(UpdateCustomerProfileRequest request, ServerCallContext context) + { + var userId = GetCurrentUserId(); + + var user = await _context.Users + .Where(u => u.Id == userId && !u.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (user == null) + throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد")); + + if (request.FirstName != null) user.FirstName = request.FirstName; + if (request.LastName != null) user.LastName = request.LastName; + if (request.Email != null) user.Email = request.Email; + if (request.NationalCode != null) user.NationalCode = request.NationalCode; + if (request.BirthDate != null) user.BirthDate = request.BirthDate.ToDateTime(); + + await _context.SaveChangesAsync(context.CancellationToken); + return new Empty(); + } + + public override async Task GetCustomerProfile(GetCustomerProfileRequest request, ServerCallContext context) + { + var query = new GetCustomerProfileQuery { UserId = 0 }; + var result = await _sender.Send(query, context.CancellationToken); + + return new GetCustomerProfileResponse + { + Id = result.Id, + FirstName = result.FirstName, + LastName = result.LastName, + Mobile = result.Mobile, + Email = result.Email, + NationalCode = result.NationalCode, + AvatarPath = result.AvatarPath, + ParentId = result.ParentId, + ReferralCode = result.ReferralCode, + IsMobileVerified = result.IsMobileVerified, + MobileVerifiedAt = result.MobileVerifiedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(result.MobileVerifiedAt.Value, DateTimeKind.Utc)) + : null, + EmailNotifications = result.EmailNotifications, + SmsNotifications = result.SmsNotifications, + PushNotifications = result.PushNotifications, + BirthDate = result.BirthDate.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(result.BirthDate.Value, DateTimeKind.Utc)) + : null, + FullName = result.FullName, + ProfileCompletionPercentage = result.ProfileCompletionPercentage + }; + } + + public override async Task ChangeCustomerPassword(ChangeCustomerPasswordRequest request, ServerCallContext context) + { + if (request.NewPassword != request.ConfirmPassword) + { + return new ChangeCustomerPasswordResponse + { + Success = false, + Message = "رمز عبور جدید و تکرار آن یکسان نیستند" + }; + } + + if (request.NewPassword.Length < 6) + { + return new ChangeCustomerPasswordResponse + { + Success = false, + Message = "رمز عبور باید حداقل 6 کاراکتر باشد" + }; + } + + var userId = GetCurrentUserId(); + + var user = await _context.Users + .Where(u => u.Id == userId && !u.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (user == null) + throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد")); + + // Verify current password + if (!string.IsNullOrEmpty(user.HashPassword)) + { + if (!_hashService.VerifyPassword(request.CurrentPassword, user.HashPassword)) + { + return new ChangeCustomerPasswordResponse + { + Success = false, + Message = "رمز عبور فعلی نادرست است" + }; + } + } + + // Hash and save new password + user.HashPassword = _hashService.HashPassword(request.NewPassword); + await _context.SaveChangesAsync(context.CancellationToken); + + return new ChangeCustomerPasswordResponse + { + Success = true, + Message = "رمز عبور با موفقیت تغییر یافت" + }; + } + + public override async Task GetCustomerReferrals(GetCustomerReferralsRequest request, ServerCallContext context) + { + var query = new GetCustomerReferralsQuery + { + UserId = 0, + PaginationState = request.PaginationState?.Adapt(), + StatusFilter = request.StatusFilter + }; + + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerReferralsResponse + { + MetaData = new MetaData + { + CurrentPage = result.MetaData.CurrentPage, + TotalPage = result.MetaData.TotalPage, + PageSize = result.MetaData.PageSize, + TotalCount = result.MetaData.TotalCount, + HasPrevious = result.MetaData.HasPrevious, + HasNext = result.MetaData.HasNext + }, + Stats = new CMSMicroservice.Protobuf.Protos.User.CustomerReferralStats + { + TotalReferrals = result.Stats.TotalReferrals, + ActiveReferrals = result.Stats.ActiveReferrals, + TotalCommissionEarned = result.Stats.TotalCommissionEarned, + ThisMonthCommission = result.Stats.ThisMonthCommission + } + }; + + foreach (var referral in result.Referrals) + { + response.Referrals.Add(new CMSMicroservice.Protobuf.Protos.User.CustomerReferralModel + { + Id = referral.Id, + FirstName = referral.FirstName, + LastName = referral.LastName, + Mobile = referral.Mobile, + JoinDate = Timestamp.FromDateTime(DateTime.SpecifyKind(referral.JoinDate, DateTimeKind.Utc)), + IsActive = referral.IsActive, + StatusMessage = referral.StatusMessage, + Level = referral.Level, + TotalCommission = referral.TotalCommission + }); + } + + return response; + } + + public override async Task UploadCustomerAvatar(UploadCustomerAvatarRequest request, ServerCallContext context) + { + if (request.FileData == null || request.FileData.Length == 0) + { + return new UploadCustomerAvatarResponse + { + Success = false, + Message = "فایل انتخاب نشده است" + }; + } + + if (request.FileData.Length > 5 * 1024 * 1024) // 5MB limit + { + return new UploadCustomerAvatarResponse + { + Success = false, + Message = "حجم فایل نباید بیش از 5 مگابایت باشد" + }; + } + + var allowedTypes = new[] { "image/jpeg", "image/jpg", "image/png", "image/gif" }; + if (!allowedTypes.Contains(request.FileMimeType?.ToLower())) + { + return new UploadCustomerAvatarResponse + { + Success = false, + Message = "فرمت فایل مجاز نیست. فقط JPG, PNG و GIF مجاز هستند" + }; + } + + var userId = GetCurrentUserId(); + + var user = await _context.Users + .Where(u => u.Id == userId && !u.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (user == null) + throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد")); + + // Upload to local file manager + var fileBytes = request.FileData.ToByteArray(); + var fileName = $"avatar_{userId}_{DateTime.UtcNow.Ticks}"; + var mime = request.FileMimeType ?? "image/jpeg"; + + try + { + var result = await _fileManager.UploadImageAsync( + "Avatars", fileBytes, mime, fileName, context.CancellationToken); + + // Update user avatar path in DB + user.AvatarPath = result.Main.Path; + await _context.SaveChangesAsync(context.CancellationToken); + + return new UploadCustomerAvatarResponse + { + Success = true, + Message = "تصویر پروفایل با موفقیت آپلود شد", + AvatarUrl = result.Main.Path + }; + } + catch (Exception ex) + { + return new UploadCustomerAvatarResponse + { + Success = false, + Message = "خطا در آپلود فایل. لطفاً مجدد تلاش کنید" + }; + } + } + + public override async Task GetCustomerSettings(GetCustomerSettingsRequest request, ServerCallContext context) + { + var query = new GetCustomerSettingsQuery { UserId = 0 }; + var result = await _sender.Send(query, context.CancellationToken); + + return new GetCustomerSettingsResponse + { + EmailNotifications = result.EmailNotifications, + SmsNotifications = result.SmsNotifications, + PushNotifications = result.PushNotifications, + MarketingNotifications = result.MarketingNotifications, + PreferredLanguage = result.PreferredLanguage, + TimeZone = result.TimeZone, + TwoFactorAuthEnabled = result.TwoFactorAuthEnabled + }; + } + + public override async Task UpdateCustomerSettings(UpdateCustomerSettingsRequest request, ServerCallContext context) + { + var userId = GetCurrentUserId(); + + var user = await _context.Users + .Where(u => u.Id == userId && !u.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (user == null) + throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد")); + + user.EmailNotifications = request.EmailNotifications; + user.SmsNotifications = request.SmsNotifications; + user.PushNotifications = request.PushNotifications; + // MarketingNotifications, PreferredLanguage, TimeZone, TwoFactorAuth + // are stored at application level if needed in future + + await _context.SaveChangesAsync(context.CancellationToken); + return new Empty(); + } + + // ============= Helper Methods ============= + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0) + return userId; + + throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید")); + } } diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs index c487bb1..1f51dae 100644 --- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs @@ -5,14 +5,32 @@ using CMSMicroservice.Application.UserWalletCQ.Commands.UpdateUserWallet; using CMSMicroservice.Application.UserWalletCQ.Commands.DeleteUserWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter; +using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; +using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals; +using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings; +using CMSMicroservice.Application.Common.Interfaces; +using Grpc.Core; +using Microsoft.EntityFrameworkCore; +using System.Linq; + namespace CMSMicroservice.WebApi.Services; public class UserWalletService : UserWalletContract.UserWalletContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUserService; - public UserWalletService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public UserWalletService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + IApplicationDbContext context, + ICurrentUserService currentUserService) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _context = context; + _currentUserService = currentUserService; } public override async Task CreateNewUserWallet(CreateNewUserWalletRequest request, ServerCallContext context) { @@ -34,4 +52,150 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + // ============= Customer-specific Methods ============= + + public override async Task GetCustomerWallet(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) + { + // Customer endpoint: resolve userId from JWT + if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); + + var walletQuery = new GetUserWalletQuery { Id = userId }; + var wallet = await _sender.Send(walletQuery, context.CancellationToken); + + return new GetCustomerWalletResponse + { + Balance = wallet.Balance, + NetworkBalance = wallet.NetworkBalance, + DiscountBalance = wallet.DiscountBalance + }; + } + + public override async Task GetCustomerWalletChangeLog(GetCustomerWalletChangeLogRequest request, ServerCallContext context) + { + var query = new GetCustomerWalletChangeLogQuery + { + ReferenceId = request.ReferenceId, + IsIncrease = request.IsIncrease + }; + + var changeLogs = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerWalletChangeLogResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = 1, + TotalPage = 1, + PageSize = changeLogs.Count, + TotalCount = changeLogs.Count, + HasPrevious = false, + HasNext = false + } + }; + + foreach (var log in changeLogs) + { + response.Models.Add(new CustomerWalletChangeLogModel + { + CurrentBalance = log.CurrentBalance, + ChangeValue = log.ChangeValue, + CurrentNetworkBalance = log.CurrentNetworkBalance, + ChangeNerworkValue = log.ChangeNerworkValue, + CurrentDiscountBalance = log.CurrentDiscountBalance, + ChangeDiscountValue = log.ChangeDiscountValue, + IsIncrease = log.IsIncrease, + RefrenceId = log.RefrenceId, + CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.SpecifyKind(log.Created, DateTimeKind.Utc)) + }); + } + + return response; + } + + public override async Task CustomerWithdrawBalance(CustomerWithdrawBalanceRequest request, ServerCallContext context) + { + var userId = GetCurrentUserId(); + + // Find the commission payout record + var payout = await _context.UserCommissionPayouts + .Where(p => p.Id == request.PayoutId && p.UserId == userId && !p.IsDeleted) + .FirstOrDefaultAsync(context.CancellationToken); + + if (payout == null) + throw new RpcException(new Status(StatusCode.NotFound, "رکورد پرداخت کمیسیون یافت نشد")); + + // Validate status - can only withdraw if already paid to wallet + if (payout.Status != Domain.Enums.CommissionPayoutStatus.Paid) + throw new RpcException(new Status(StatusCode.FailedPrecondition, + "فقط کمیسیون‌های واریز شده به کیف پول قابل برداشت هستند")); + + // Update payout with withdrawal info + payout.WithdrawalMethod = (Domain.Enums.WithdrawalMethod)request.WithdrawalMethod; + payout.IbanNumber = request.IbanNumber; + payout.Status = Domain.Enums.CommissionPayoutStatus.WithdrawRequested; + + await _context.SaveChangesAsync(context.CancellationToken); + + return new Google.Protobuf.WellKnownTypes.Empty(); + } + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0) + return userId; + throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید")); + } + + public override async Task GetCustomerWithdrawals(GetCustomerWithdrawalsRequest request, ServerCallContext context) + { + var query = new GetCustomerWithdrawalsQuery + { + Status = request.Status + }; + + var withdrawals = await _sender.Send(query, context.CancellationToken); + + var response = new GetCustomerWithdrawalsResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = 1, + TotalPage = 1, + PageSize = withdrawals.Count, + TotalCount = withdrawals.Count, + HasPrevious = false, + HasNext = false + } + }; + + foreach (var withdrawal in withdrawals) + { + response.Models.Add(new CustomerWithdrawalModel + { + Id = withdrawal.Id, + WeekDefinitionId = withdrawal.WeekDefinitionId, + WeekDisplayName = withdrawal.WeekDisplayName, + TotalAmount = withdrawal.TotalAmount, + Status = withdrawal.Status, + WithdrawalMethod = withdrawal.WithdrawalMethod, + IbanNumber = withdrawal.IbanNumber, + Created = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.SpecifyKind(withdrawal.Created, DateTimeKind.Utc)) + }); + } + + return response; + } + + public override async Task GetCustomerWithdrawalSettings(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) + { + var query = new GetCustomerWithdrawalSettingsQuery(); + var settings = await _sender.Send(query, context.CancellationToken); + + return new GetCustomerWithdrawalSettingsResponse + { + MinWithdrawalAmount = settings.MinWithdrawalAmount + }; + } } diff --git a/src/CMSMicroservice.WebApi/appsettings.Development.json b/src/CMSMicroservice.WebApi/appsettings.Development.json index 45353d8..5e17cea 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Development.json +++ b/src/CMSMicroservice.WebApi/appsettings.Development.json @@ -1,5 +1,11 @@ { - "UseRealPaymentGateway": false, + "PaymentProvider": "zarinpal", + "ZarinPal": { + "MerchantId": "00000000-0000-0000-0000-000000000000", + "UseSandbox": true + }, + "CmsBaseUrl": "http://localhost:32847", + "FrontOfficeBaseUrl": "http://localhost:5268", "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", "JwtIssuer": "https://localhost", "JwtAudience": "https://localhost", diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json new file mode 100644 index 0000000..c0f39e7 --- /dev/null +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -0,0 +1,85 @@ +{ + "PaymentProvider": "zarinpal", + "ZarinPal": { + "MerchantId": "6b098fc8-f490-47a1-aac3-1de1a1b84404", + "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" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, + "Authentication": { + "Authority": "https://ids.domain.com/", + "Audience": "domain_api" + }, + "Seq": { + "ServerUrl": "https://seq.afrino.co", + "ApiKey": "oxpvpUzU1pZxMS4s3Fqq" + } +} diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index 3f95d04..0d4b04e 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -1,5 +1,14 @@ { - "UseRealPaymentGateway": false, + "PaymentProvider": "zarinpal", + "ZarinPal": { + "MerchantId": "6b098fc8-f490-47a1-aac3-1de1a1b84404", + "UseSandbox": true + }, + "CmsBaseUrl": "https://cms.kbs1.ir", + "FrontOfficeBaseUrl": "https://foursat.kbs1.ir", + "FMS": { + "Address": "https://dl.afrino.co" + }, "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", "JwtIssuer": "https://localhost", "JwtAudience": "https://localhost", diff --git a/src/CMSMicroservice.WebApi/wwwroot/swagger-ui/custom.css b/src/CMSMicroservice.WebApi/wwwroot/swagger-ui/custom.css new file mode 100644 index 0000000..9f2e899 --- /dev/null +++ b/src/CMSMicroservice.WebApi/wwwroot/swagger-ui/custom.css @@ -0,0 +1,158 @@ +/* FourSat CMS Swagger Custom Styles */ + +/* Header styling */ +.swagger-ui .topbar { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-bottom: 3px solid #5a67d8; +} + +.swagger-ui .topbar .download-url-wrapper { + display: none; +} + +/* API sections styling */ +.swagger-ui .scheme-container { + background: #f7fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 15px; + margin-bottom: 20px; +} + +/* Tag headers (API groups) */ +.swagger-ui .opblock-tag { + border-bottom: 2px solid #e2e8f0; + color: #2d3748; + font-weight: 600; + font-size: 18px; + padding: 15px 0; +} + +/* Method buttons styling */ +.swagger-ui .opblock.opblock-get .opblock-summary-method { + background: #48bb78; +} + +.swagger-ui .opblock.opblock-post .opblock-summary-method { + background: #4299e1; +} + +.swagger-ui .opblock.opblock-put .opblock-summary-method { + background: #ed8936; +} + +.swagger-ui .opblock.opblock-delete .opblock-summary-method { + background: #f56565; +} + +/* Response sections */ +.swagger-ui .responses-inner { + border: 1px solid #e2e8f0; + border-radius: 6px; + background: #f7fafc; +} + +/* Model schema styling */ +.swagger-ui .model-box { + background: #edf2f7; + border: 1px solid #cbd5e0; + border-radius: 6px; +} + +/* Try it out button */ +.swagger-ui .btn.try-out { + background: #667eea; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: 500; +} + +.swagger-ui .btn.try-out:hover { + background: #5a67d8; +} + +/* Execute button */ +.swagger-ui .btn.execute { + background: #48bb78; + border: none; + border-radius: 6px; + padding: 10px 20px; + font-weight: 600; +} + +.swagger-ui .btn.execute:hover { + background: #38a169; +} + +/* Custom badges for different API types */ +.swagger-ui .info .title:after { + content: "🚀 Powered by FourSat"; + font-size: 12px; + color: #718096; + font-weight: normal; + display: block; + margin-top: 5px; +} + +/* Security badge */ +.swagger-ui .auth-btn-wrapper { + display: flex; + justify-content: flex-end; + padding: 10px; +} + +.swagger-ui .btn.authorize { + background: #805ad5; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: 500; +} + +.swagger-ui .btn.authorize:hover { + background: #6b46c1; +} + +/* Loading states */ +.swagger-ui .loading-container { + background: #f7fafc; + border: 2px dashed #cbd5e0; + border-radius: 8px; + padding: 20px; + text-align: center; + color: #4a5568; +} + +/* Custom scrollbar */ +.swagger-ui ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.swagger-ui ::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +.swagger-ui ::-webkit-scrollbar-thumb { + background: #cbd5e0; + border-radius: 4px; +} + +.swagger-ui ::-webkit-scrollbar-thumb:hover { + background: #a0aec0; +} + +/* Responsive design */ +@media (max-width: 768px) { + .swagger-ui .topbar { + padding: 10px; + } + + .swagger-ui .info .title { + font-size: 24px; + } +} \ No newline at end of file diff --git a/src/NuGet.config b/src/NuGet.config new file mode 100644 index 0000000..59d2375 --- /dev/null +++ b/src/NuGet.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file