39 Commits

Author SHA1 Message Date
masoodafar-web 2609efafa5 fix: increase Docker daemon startup timeout to 3 minutes
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 0s
2026-01-29 17:13:49 +03:30
masoodafar-web 22e1b2b2b4 trigger: test offline CI/CD pipeline
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 9m1s
2026-01-29 07:03:26 +03:30
masoodafar-web 89d304cfbc refactor: use docker-sshpass image (fully offline)
Build and Deploy to Kubernetes / build-and-deploy (push) Has been cancelled
2026-01-29 06:49:36 +03:30
masoodafar-web 376db96f5c refactor: simplified single-job pipeline with offline Nexus support
Build and Deploy to Kubernetes / build-and-deploy (push) Has been cancelled
2026-01-29 06:10:26 +03:30
masoodafar-web 48b5aa7efa fix: add --allow-insecure-connections to nuget push
Build and Deploy / publish-packages (push) Failing after 3m8s
Build and Deploy / build (push) Has been skipped
2026-01-29 04:56:41 +03:30
masoodafar-web 864a760953 feat: add publish-packages job to CI/CD workflow
Build and Deploy / publish-packages (push) Failing after 0s
Build and Deploy / build (push) Has been skipped
- Auto-detect and pack *Protobuf*.csproj files
- Push to Nexus with --skip-duplicate
- Build job now depends on publish-packages
2026-01-29 04:45:18 +03:30
masoodafar-web fa23901980 Revert: use only Nexus (no fallback)
Build and Deploy / build (push) Failing after 5s
2026-01-29 03:44:40 +03:30
masoodafar-web 6b7ca9cd62 Add nuget.org as fallback source
Build and Deploy / build (push) Has been cancelled
2026-01-29 03:41:18 +03:30
masoodafar-web a9648e250e Remove afrino NuGet sources - use only Nexus
Build and Deploy / build (push) Failing after 5s
2026-01-29 03:14:40 +03:30
masoodafar-web 3219d07dea chore: تنظیم Nexus برای NuGet و Docker offline
Build and Deploy / build (push) Failing after 5s
- Dockerfile: استفاده از Nexus Docker (194.5.195.53:32082)
- NuGet.config: استفاده از Nexus NuGet با HTTP و allowInsecureConnections
- workflow: حذف kubectl dependency، اضافه کردن 32082 به insecure-registries
- csproj: تغییر push آدرس به Nexus foursat-nuget-hosted
2026-01-29 02:56:26 +03:30
masoodafar-web 24cbb378c9 fix: Update DNS configuration and enhance cached image loading in kub-deploy.yml
Build and Deploy / build (push) Failing after 4s
2026-01-27 23:30:31 +03:30
masoodafar-web 0674324487 fix: Remove proxy environment variables from Docker container configuration
Build and Deploy / build (push) Failing after 22s
2026-01-27 23:21:40 +03:30
masoodafar-web b86e417e58 fix: Update Docker daemon configuration to enhance image caching and warning messages
Build and Deploy / build (push) Failing after 37s
2026-01-27 23:19:03 +03:30
masoodafar-web 8b66bbcf3d fix: Change Docker build option from --pull=never to --pull=false for better image handling
Build and Deploy / build (push) Failing after 35s
2026-01-27 23:16:26 +03:30
masoodafar-web 273ae390c5 fix: Update Docker daemon configuration to improve image caching and reduce concurrent downloads
Build and Deploy / build (push) Failing after 4s
2026-01-27 23:08:46 +03:30
masoodafar-web 828109f44d chore: Comment out dependency installation steps in kub-deploy.yml
Build and Deploy / build (push) Failing after 8m32s
2026-01-27 22:30:53 +03:30
masoodafar-web 710051f0a0 feat: Add mappings for Update and Delete Discount Category commands in DiscountCategoryProfile
Build and Deploy / build (push) Failing after 2m33s
2026-01-27 21:52:32 +03:30
masoodafar-web aca28fce40 feat: Implement DiscountProductProfile for mapping Discount Product commands and queries
Build and Deploy / build (push) Successful in 2m50s
2026-01-08 18:09:30 +03:30
masoodafar-web b39d1cb2e1 update
Build and Deploy / build (push) Successful in 5m34s
2026-01-08 16:32:00 +03:30
masoodafar-web a420c51b47 fix: Add null check for Description field in Create and Update Discount Category command validators
Build and Deploy / build (push) Successful in 2m17s
2026-01-05 22:57:09 +03:30
masoodafar-web de412dd9e5 fix: Handle null values in response categories and map properties correctly in GetDiscountCategoriesQueryHandler
Build and Deploy / build (push) Successful in 1m52s
2026-01-05 02:14:45 +03:30
masoodafar-web f627cbf46a fix: Handle null response metadata and models in GetDiscountProductsQueryHandler
Build and Deploy / build (push) Successful in 2m2s
2026-01-05 02:07:16 +03:30
masoodafar-web 23b7b15a66 fix: Ensure ShortInformation and FullInformation default to empty string in Create and Update Discount Product commands
Build and Deploy / build (push) Successful in 1m59s
2026-01-05 01:33:50 +03:30
masoodafar-web 0307d0655f refactor: Remove NotEmpty validation for ShortInformation and FullInformation in discount product validators
Build and Deploy / build (push) Successful in 1m52s
2026-01-05 01:29:40 +03:30
masoodafar-web a7a1b61a81 feat: Add image and thumbnail file fields to Create and Update Discount Product requests
Build and Deploy / build (push) Successful in 1m59s
2026-01-05 01:23:41 +03:30
masoodafar-web 1dbe81faaa refactor: Remove InitialCount and RemainingCount properties from discount product commands
Build and Deploy / build (push) Successful in 1m53s
2026-01-04 23:51:47 +03:30
masoodafar-web 3aabf10a51 feat: Implement DiscountCategoryProfile for mapping GetDiscountCategories response to protobuf
Build and Deploy / build (push) Successful in 2m16s
2026-01-04 22:38:39 +03:30
masoodafar-web d2521a7615 update
Build and Deploy / build (push) Successful in 2m10s
2026-01-04 21:52:28 +03:30
Admin 7611e8320b fix: Remove domain from insecure-registries, use IP only
Build and Deploy / build (push) Failing after 11s
2026-01-03 15:15:32 +00:00
Admin 7c89638181 fix: Update registry to IP address for CI/CD
Build and Deploy / build (push) Failing after 3m27s
2026-01-03 15:03:26 +00:00
masoodafar-web 68e6fed06f fix: update external registry URL in deployment workflows
Build and Deploy / build (push) Failing after 2m3s
2026-01-03 17:30:23 +03:30
masoodafar-web f027fd0b06 Refactor inventory commands and queries for improved clarity and consistency
Build and Deploy / build (push) Failing after 2m12s
- Updated ProductType enum values to be more descriptive (RegularProduct, DiscountProduct).
- Removed unnecessary Success and Message properties from response DTOs in AddStock and AdjustStock commands.
- Renamed Note property to Reason in AdjustStock and RecordLoss commands, and added ReferenceNumber.
- Changed InventoryItemId to Id in UpdateInventorySettingsCommand for consistency.
- Updated pagination properties in various queries (PageIndex to Page).
- Enhanced GetAllInventoryItemsQuery and GetLowStockItemsQuery to include sorting options.
- Updated InventoryProfile mappings to reflect changes in DTOs and Protobuf definitions.
- Adjusted Protobuf definitions to align with new property names and structures.
2026-01-03 15:38:22 +03:30
masoodafar-web 989f4a799a fix: specify full namespace for GetAllWarehousesRequest in GetAllWarehousesQueryHandler
Build and Deploy / build (push) Successful in 1m45s
2026-01-03 08:19:01 +03:30
masoodafar-web f6f943947c feat: Implement Inventory Management Commands and Queries
Build and Deploy / build (push) Successful in 1m46s
- Added CreateWarehouseCommand and CreateWarehouseCommandHandler for warehouse creation.
- Introduced RecordLossCommand and RecordLossCommandHandler to handle stock loss recording.
- Created UpdateInventorySettingsCommand and its handler for updating inventory settings.
- Implemented GetAllInventoryItemsQuery and its handler to retrieve inventory items with pagination.
- Added GetAllWarehousesQuery and handler for fetching all warehouses.
- Developed GetLowStockItemsQuery and handler to get items below a specified stock threshold.
- Implemented GetStockMovementsQuery and handler for retrieving stock movement records.
- Created mappings for inventory-related requests and responses in InventoryProfile.
- Developed InventoryService to handle gRPC requests for inventory operations.
- Added Protobuf definitions for inventory management services and messages.
2026-01-03 07:37:32 +03:30
masoodafar-web 0c63b3c83c feat: add ImagePath and ImageDocumentId to ManualPaymentModel and update related query handler
Build and Deploy / build (push) Successful in 1m47s
2026-01-02 02:37:10 +03:30
masoodafar-web 36b30e37b8 feat: enhance CreateManualPayment command with image document ID and update protobuf definitions
Build and Deploy / build (push) Successful in 1m50s
2026-01-02 02:13:51 +03:30
masoodafar-web 31df6d843a feat: add FileUploadDto and image file handling to CreateManualPayment command and protobuf definitions
Build and Deploy / build (push) Successful in 2m23s
2026-01-02 01:19:11 +03:30
masoodafar-web 441098a80b feat: add ImagePath property to CreateManualPayment command and update related protobuf definitions
Build and Deploy / build (push) Successful in 2m22s
2026-01-02 00:45:53 +03:30
masoodafar-web d0224d41b2 feat: Implement Discount Sales Report Query and Handler
Build and Deploy / build (push) Successful in 2m33s
- Added GetDiscountSalesReportQuery and GetDiscountSalesReportQueryHandler to fetch discount sales reports.
- Introduced DiscountSalesReportDto, SalesSummaryDto, and PeriodSalesDto for structured report data.

feat: Add Discount Product Image Management

- Created AddDiscountProductImageCommand and AddDiscountProductImageCommandHandler for image uploads.
- Implemented DeleteDiscountProductImageCommand and DeleteDiscountProductImageCommandHandler for image deletion.
- Added ReorderDiscountProductImagesCommand and ReorderDiscountProductImagesCommandHandler for image reordering.
- Developed UpdateDiscountProductImageCommand and UpdateDiscountProductImageCommandHandler for image updates.

feat: Enhance Product Tag Management

- Introduced DeleteProductTagCommand and DeleteProductTagCommandHandler for tag deletion.
- Added UpdateProductTagCommand and UpdateProductTagCommandHandler for updating product tags.
- Implemented GetAllProductTagsQuery and GetAllProductTagsQueryHandler for fetching all product tags.
- Created GetProductTagQuery and GetProductTagQueryHandler for retrieving specific product tags.

feat: Develop Web API Services for Discount and Product Management

- Implemented DiscountCategoryService for managing discount categories.
- Created DiscountOrderService for handling discount orders and related queries.
- Developed DiscountProductService for managing discount products and their images.
- Added DiscountShoppingCartService for managing shopping cart operations.
- Implemented ProductTagService for managing product tags and their associations.
- Created TagService for managing tags and their related operations.
2026-01-01 02:26:51 +03:30
115 changed files with 3035 additions and 234 deletions
+52 -53
View File
@@ -1,4 +1,4 @@
name: Build and Deploy
name: Build and Deploy to Kubernetes
on:
push:
@@ -6,85 +6,84 @@ on:
- kub-stage
env:
REGISTRY: gitea-svc:3000
REGISTRY: 194.5.195.53:30080
IMAGE_NAME: admin/backoffice-bff
K8S_SERVER: 194.5.195.53
jobs:
build:
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
echo "🚀 Starting Docker daemon..."
dockerd &
for i in $(seq 1 30); do
docker info >/dev/null 2>&1 && break || sleep 2
# 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/BackOffice.BFF.git .
git log -1 --format="%H %s"
- name: Publish Protobuf packages
run: |
echo "📦 Publishing Protobuf packages..."
docker run --rm -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"
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: |
cd src
docker build -f BackOffice.BFF.WebApi/Dockerfile \
-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 build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f src/BackOffice.BFF.WebApi/Dockerfile .
- 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/backoffice-bff || echo "Deployment doesn't exist yet"
# Wait for rollout to complete
kubectl rollout status deployment/backoffice-bff --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/backoffice-bff
kubectl rollout status deployment/backoffice-bff --timeout=180s
"
echo "Deployed!"
+5 -3
View File
@@ -7,7 +7,7 @@ on:
env:
REGISTRY: gitea-svc:3000
EXTERNAL_REGISTRY: git.foursat.afrino.co
EXTERNAL_REGISTRY: 194.5.195.53:30080
IMAGE_NAME: admin/backoffice-bff
jobs:
@@ -24,7 +24,9 @@ jobs:
- name: Install dependencies
run: |
apk add --no-cache git curl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
# Install kubectl with fixed version
KUBECTL_VERSION="v1.31.0"
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/
@@ -33,7 +35,7 @@ jobs:
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:32082", "gitea-svc:3000"]
}
DAEMON
mkdir -p ~/.docker
+56 -7
View File
@@ -1,22 +1,71 @@
# Plan: فعال‌سازی مرحله‌ای Handler های کامنت‌شده
**تاریخ:** 8 دسامبر 2025
**وضعیت:** در انتظار اجرا
**تعداد کل فایل‌های کامنت‌شده:** 37 فایل در 3 دسته
**وضعیت:** ✅ کامل شده
**آخرین به‌روزرسانی:** 1 ژانویه 2026
---
## خلاصه اجرایی
در `BackOffice.BFF.Application.csproj` سه دسته handler کامنت شدهاند:
در `BackOffice.BFF.Application.csproj` سه دسته handler کامنت شده بودند که همه فعال شدند:
1. **DiscountOrderCQ/** (18 فایل) - عدم تطابق Proto با CMS
2. **DiscountShoppingCartCQ/** (16 فایل) - عدم تطابق Proto
3. **CommissionCQ/Commands/ProcessWithdrawal/** (3 فایل) - مشکل تبدیل StringValue
1. **DiscountOrderCQ/** (18 فایل) - ✅ فعال شد
2. **DiscountShoppingCartCQ/** (16 فایل) - ✅ فعال شد و با CMS proto هماهنگ شد
3. **CommissionCQ/Commands/ProcessWithdrawal/** (3 فایل) - ✅ قبلاً فعال شده بود
**همچنین در BackOffice UI (1 ژانویه 2026):**
- فعال‌سازی همه سرویس‌ها در `ConfigureService.cs`
- ثبت gRPC Clients برای DiscountProduct, DiscountCategory, DiscountOrder, Tag, ProductTag, PublicMessage
- ثبت Application Services
---
## مرحله 1: ProcessWithdrawal (30 دقیقه - ساده ✅)
## تغییرات انجام‌شده (1 ژانویه 2026)
### BackOffice UI - فعال‌سازی فرانت‌اند
#### ConfigureService.cs
- فعال‌سازی using statements برای همه proto clients
- ثبت gRPC Clients در DI container
- ثبت Application Services (IDiscountProductService, IDiscountCategoryService, etc.)
#### صفحات فعال شده:
| Route | صفحه |
|-------|------|
| `/discount-products` | مدیریت محصولات تخفیفی |
| `/discount-categories` | مدیریت دسته‌بندی‌ها |
| `/discount-orders` | مدیریت سفارشات |
| `/tags` | مدیریت تگ‌ها |
| `/public-messages` | پیام‌های عمومی |
---
## تغییرات انجام‌شده (13 ژانویه 2025)
### DiscountShoppingCartCQ - اصلاحات Proto
#### RemoveFromCart
- `CartItemId``ProductId` (مطابق با proto)
#### UpdateCartItemCount
- `CartItemId``ProductId` (مطابق با proto)
#### GetUserCart Response
- حذف `UserId` از response
- `TotalDiscountedPrice``TotalDiscountAmount`
- `TotalSavings` → حذف شد
- اضافه شدن `FinalPrice`
#### CartItemDto
- حذف `Id`
- `DiscountedPrice``DiscountAmount`
- `AddedAt``Created`
- اضافه شدن `FinalPrice`, `ProductRemainingCount`
---
## مرحله 1: ProcessWithdrawal ✅ (قبلاً انجام شده)
### ویژگی بیزینسی
مدیریت درخواست‌های برداشت کمیسیون توسط ادمین (تایید/رد)
@@ -5,14 +5,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Exclude handlers that need proto fixes -->
<!-- All handlers now ENABLED with BFF Protobuf and Mapster profiles -->
<!-- ProcessWithdrawal, DiscountOrder, DiscountShoppingCart fixed and enabled -->
<ItemGroup>
<!-- DiscountShoppingCart - FrontOffice-only feature, not needed in BackOffice -->
<!-- این feature فقط برای مشتریان FrontOffice است. مدیریت سبد خرید توسط ادمین در آینده اضافه خواهد شد -->
<Compile Remove="DiscountShoppingCartCQ/**/*.cs" />
<!-- All handlers now ENABLED with BFF Protobuf and Mapster profiles -->
<!-- ProcessWithdrawal, DiscountOrder fixed and enabled -->
<!-- All excluded handlers have been enabled -->
</ItemGroup>
<ItemGroup>
@@ -24,6 +24,7 @@ using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using CMSMicroservice.Protobuf.Protos.AppVersion;
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.Common.Interfaces;
@@ -69,6 +70,9 @@ public interface IApplicationContractContext
// App Version Management
AppVersionContract.AppVersionContractClient AppVersions { get; }
// Inventory Management System
InventoryContract.InventoryContractClient Inventory { get; }
#endregion
}
@@ -14,6 +14,7 @@ public class CreateDiscountCategoryCommandValidator : AbstractValidator<CreateDi
.MaximumLength(200).WithMessage("عنوان دسته‌بندی نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.Description)
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد");
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.Description));
}
}
@@ -17,7 +17,8 @@ public class UpdateDiscountCategoryCommandValidator : AbstractValidator<UpdateDi
.MaximumLength(200).WithMessage("عنوان دسته‌بندی نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.Description)
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد");
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.Description));
RuleFor(x => x.ParentCategoryId)
.NotEqual(x => x.CategoryId).When(x => x.ParentCategoryId.HasValue)
@@ -25,7 +25,7 @@ public class GetDiscountCategoriesQueryHandler : IRequestHandler<GetDiscountCate
return new GetDiscountCategoriesResponseDto
{
Categories = response.Categories.Select(MapCategory).ToList()
Categories = response.Categories?.Select(MapCategory).ToList() ?? new List<DiscountCategoryTreeDto>()
};
}
@@ -36,14 +36,14 @@ public class GetDiscountCategoriesQueryHandler : IRequestHandler<GetDiscountCate
Id = node.Id,
Name = node.Name,
Title = node.Title,
Description = string.IsNullOrWhiteSpace(node.Description) ? node.Description : string.Empty,
ImagePath =string.IsNullOrWhiteSpace(node.ImagePath)? node.ImagePath : string.Empty,
ParentCategoryId = node.ParentCategoryId!=null? node.ParentCategoryId.Value : null,
Description = !string.IsNullOrWhiteSpace(node.Description) ? node.Description : string.Empty,
ImagePath = !string.IsNullOrWhiteSpace(node.ImagePath) ? node.ImagePath : string.Empty,
ParentCategoryId = node.ParentCategoryId != 0 ? node.ParentCategoryId : null,
SortOrder = node.SortOrder,
IsActive = node.IsActive,
ProductCount = node.ProductCount,
Created = DateTime.UtcNow, // Proto doesn't have created field
ChildCategories = node.Children.Select(MapCategory).ToList()
ChildCategories = node.Children?.Select(MapCategory).ToList() ?? new List<DiscountCategoryTreeDto>()
};
}
}
@@ -0,0 +1,50 @@
namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetAllDiscountOrders;
/// <summary>
/// کوئری دریافت همه سفارشات فروشگاه تخفیفی برای ادمین
/// </summary>
public class GetAllDiscountOrdersQuery : IRequest<GetAllDiscountOrdersResponseDto>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 20;
public long? UserId { get; set; }
public int? PaymentStatus { get; set; }
public int? 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<AdminOrderDto> Orders { 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 int PaymentStatus { get; set; }
public DateTime? PaymentDate { get; set; }
public int 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; }
}
@@ -0,0 +1,88 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using Google.Protobuf.WellKnownTypes;
namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetAllDiscountOrders;
public class GetAllDiscountOrdersQueryHandler : IRequestHandler<GetAllDiscountOrdersQuery, GetAllDiscountOrdersResponseDto>
{
private readonly IApplicationContractContext _context;
public GetAllDiscountOrdersQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetAllDiscountOrdersResponseDto> Handle(GetAllDiscountOrdersQuery request, CancellationToken cancellationToken)
{
var grpcRequest = new GetAllDiscountOrdersRequest
{
PageNumber = request.PageNumber,
PageSize = request.PageSize
};
if (request.UserId.HasValue)
grpcRequest.UserId = request.UserId.Value;
if (request.PaymentStatus.HasValue)
grpcRequest.PaymentStatus = request.PaymentStatus.Value;
if (request.DeliveryStatus.HasValue)
grpcRequest.DeliveryStatus = request.DeliveryStatus.Value;
if (!string.IsNullOrWhiteSpace(request.UserMobile))
grpcRequest.UserMobile = request.UserMobile;
if (!string.IsNullOrWhiteSpace(request.TrackingCode))
grpcRequest.TrackingCode = request.TrackingCode;
if (request.FromDate.HasValue)
grpcRequest.FromDate = Timestamp.FromDateTime(DateTime.SpecifyKind(request.FromDate.Value, DateTimeKind.Utc));
if (request.ToDate.HasValue)
grpcRequest.ToDate = Timestamp.FromDateTime(DateTime.SpecifyKind(request.ToDate.Value, DateTimeKind.Utc));
if (request.MinAmount.HasValue)
grpcRequest.MinAmount = request.MinAmount.Value;
if (request.MaxAmount.HasValue)
grpcRequest.MaxAmount = request.MaxAmount.Value;
var response = await _context.DiscountOrders.GetAllDiscountOrdersAsync(grpcRequest, cancellationToken: cancellationToken);
return new GetAllDiscountOrdersResponseDto
{
MetaData = new MetaData
{
CurrentPage = response.MetaData.CurrentPage,
TotalPage = response.MetaData.TotalPage,
PageSize = response.MetaData.PageSize,
TotalCount = response.MetaData.TotalCount,
HasPrevious = response.MetaData.HasPrevious,
HasNext = response.MetaData.HasNext
},
Orders = response.Models.Select(o => new AdminOrderDto
{
Id = o.Id,
UserId = o.UserId,
UserFullName = o.UserFullName,
UserMobile = o.UserMobile,
TotalAmount = o.TotalAmount,
DiscountBalanceUsed = o.DiscountBalanceUsed,
GatewayAmountPaid = o.GatewayAmountPaid,
VatAmount = o.VatAmount,
PaymentStatus = (int)o.PaymentStatus,
PaymentDate = o.PaymentDate?.ToDateTime(),
DeliveryStatus = (int)o.DeliveryStatus,
DeliveryDate = o.DeliveryDate?.ToDateTime(),
ShippingAddress = o.ShippingAddress,
ReceiverName = o.ReceiverName,
ReceiverMobile = o.ReceiverMobile,
TrackingCode = o.TrackingCode,
AdminNote = o.AdminNote,
Created = o.Created.ToDateTime(),
LastModified = o.LastModified?.ToDateTime(),
ItemsCount = o.ItemsCount
}).ToList()
};
}
}
@@ -0,0 +1,53 @@
namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetDiscountSalesReport;
public class GetDiscountSalesReportQuery : IRequest<DiscountSalesReportDto>
{
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public ReportPeriodType PeriodType { get; set; } = ReportPeriodType.Daily;
}
public enum ReportPeriodType
{
Summary = 0,
Daily = 1,
Weekly = 2,
Monthly = 3
}
public class DiscountSalesReportDto
{
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public ReportPeriodType PeriodType { get; set; }
public SalesSummaryDto Summary { get; set; } = new();
public List<PeriodSalesDto> Periods { get; set; } = new();
}
public class SalesSummaryDto
{
public int TotalOrders { get; set; }
public int SuccessfulOrders { get; set; }
public int PendingOrders { get; set; }
public int RejectedOrders { get; set; }
public decimal TotalRevenue { get; set; }
public decimal TotalDiscountUsed { get; set; }
public decimal TotalGatewayPayments { get; set; }
public decimal TotalVat { get; set; }
public int TotalItemsSold { get; set; }
public int UniqueCustomers { get; set; }
public decimal AverageOrderValue { get; set; }
}
public class PeriodSalesDto
{
public string PeriodLabel { get; set; } = string.Empty;
public DateTime PeriodStart { get; set; }
public DateTime PeriodEnd { get; set; }
public int OrderCount { get; set; }
public decimal Revenue { get; set; }
public decimal DiscountUsed { get; set; }
public decimal GatewayPayments { get; set; }
public decimal VatAmount { get; set; }
public int ItemsSold { get; set; }
}
@@ -0,0 +1,59 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using Google.Protobuf.WellKnownTypes;
namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetDiscountSalesReport;
public class GetDiscountSalesReportQueryHandler : IRequestHandler<GetDiscountSalesReportQuery, DiscountSalesReportDto>
{
private readonly IApplicationContractContext _context;
public GetDiscountSalesReportQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<DiscountSalesReportDto> Handle(GetDiscountSalesReportQuery request, CancellationToken cancellationToken)
{
var grpcRequest = new GetDiscountSalesReportRequest
{
FromDate = Timestamp.FromDateTime(DateTime.SpecifyKind(request.FromDate, DateTimeKind.Utc)),
ToDate = Timestamp.FromDateTime(DateTime.SpecifyKind(request.ToDate, DateTimeKind.Utc)),
ReportType = (SalesReportType)request.PeriodType
};
var response = await _context.DiscountOrders.GetDiscountSalesReportAsync(grpcRequest, cancellationToken: cancellationToken);
return new DiscountSalesReportDto
{
FromDate = request.FromDate,
ToDate = request.ToDate,
PeriodType = request.PeriodType,
Summary = new SalesSummaryDto
{
TotalOrders = response.Summary.TotalOrders,
SuccessfulOrders = response.Summary.CompletedOrders,
PendingOrders = response.Summary.PendingOrders,
RejectedOrders = response.Summary.CancelledOrders,
TotalRevenue = response.Summary.TotalSalesAmount,
TotalDiscountUsed = response.Summary.TotalDiscountUsed,
TotalGatewayPayments = response.Summary.TotalGatewayPaid,
TotalVat = response.Summary.TotalVatAmount,
TotalItemsSold = response.Summary.TotalProductsSold,
UniqueCustomers = response.Summary.UniqueCustomers,
AverageOrderValue = response.Summary.AverageOrderValue
},
Periods = response.Periods.Select(p => new PeriodSalesDto
{
PeriodLabel = p.PeriodLabel,
PeriodStart = p.PeriodStart.ToDateTime(),
PeriodEnd = p.PeriodEnd.ToDateTime(),
OrderCount = p.OrdersCount,
Revenue = p.TotalAmount,
DiscountUsed = p.DiscountUsed,
GatewayPayments = p.GatewayPaid,
VatAmount = 0, // Not in proto response
ItemsSold = 0 // Not in proto response
}).ToList()
};
}
}
@@ -0,0 +1,22 @@
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.AddDiscountProductImage;
public class AddDiscountProductImageCommand : IRequest<AddDiscountProductImageResponseDto>
{
public long DiscountProductId { get; set; }
public ImageFileInput? ImageFile { get; set; }
public ImageFileInput? ThumbnailFile { get; set; }
public string? Title { get; set; }
public string? AltText { get; set; }
}
public class ImageFileInput
{
public byte[] Data { get; set; } = [];
public string FileName { get; set; } = string.Empty;
public string MimeType { get; set; } = string.Empty;
}
public class AddDiscountProductImageResponseDto
{
public long ImageId { get; set; }
}
@@ -0,0 +1,67 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
using Google.Protobuf;
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.AddDiscountProductImage;
public class AddDiscountProductImageCommandHandler : IRequestHandler<AddDiscountProductImageCommand, AddDiscountProductImageResponseDto>
{
private readonly IApplicationContractContext _context;
public AddDiscountProductImageCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<AddDiscountProductImageResponseDto> Handle(AddDiscountProductImageCommand request, CancellationToken cancellationToken)
{
var addRequest = new AddDiscountProductImageRequest
{
DiscountProductId = request.DiscountProductId
};
if (!string.IsNullOrWhiteSpace(request.Title))
addRequest.Title = request.Title;
if (!string.IsNullOrWhiteSpace(request.AltText))
addRequest.AltText = request.AltText;
// Upload Image
if (request.ImageFile != null && request.ImageFile.Data != null && request.ImageFile.Data.Length > 0)
{
var imageFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
{
Directory = "Images/DiscountShop/Products/Gallery",
IsBase64 = false,
MIME = request.ImageFile.MimeType,
FileName = request.ImageFile.FileName,
File = ByteString.CopyFrom(request.ImageFile.Data)
}, cancellationToken: cancellationToken);
if (imageFileInfo != null && !string.IsNullOrWhiteSpace(imageFileInfo.File))
addRequest.ImagePath = imageFileInfo.File;
}
// Upload Thumbnail
if (request.ThumbnailFile != null && request.ThumbnailFile.Data != null && request.ThumbnailFile.Data.Length > 0)
{
var thumbnailFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
{
Directory = "Images/DiscountShop/Products/Gallery/Thumbnails",
IsBase64 = false,
MIME = request.ThumbnailFile.MimeType,
FileName = request.ThumbnailFile.FileName,
File = ByteString.CopyFrom(request.ThumbnailFile.Data)
}, cancellationToken: cancellationToken);
if (thumbnailFileInfo != null && !string.IsNullOrWhiteSpace(thumbnailFileInfo.File))
addRequest.ThumbnailPath = thumbnailFileInfo.File;
}
var response = await _context.DiscountProducts.AddDiscountProductImageAsync(addRequest, cancellationToken: cancellationToken);
return new AddDiscountProductImageResponseDto
{
ImageId = response.ImageId
};
}
}
@@ -17,9 +17,6 @@ public record CreateDiscountProductCommand : IRequest<CreateDiscountProductRespo
/// <summary>حداکثر درصد تخفیف (0-100)</summary>
public int MaxDiscountPercent { get; init; }
/// <summary>تعداد اولیه موجودی</summary>
public int InitialCount { get; init; }
/// <summary>ترتیب نمایش</summary>
public int SortOrder { get; init; }
@@ -17,11 +17,10 @@ public class CreateDiscountProductCommandHandler : IRequestHandler<CreateDiscoun
var createRequest = new CreateDiscountProductRequest
{
Title = request.Title,
ShortInfomation = request.ShortInformation,
FullInformation = request.FullInformation,
ShortInfomation = request.ShortInformation ?? string.Empty,
FullInformation = request.FullInformation ?? string.Empty,
Price = request.Price,
MaxDiscountPercent = request.MaxDiscountPercent,
InitialCount = request.InitialCount,
SortOrder = request.SortOrder,
IsActive = request.IsActive
};
@@ -9,11 +9,12 @@ public class CreateDiscountProductCommandValidator : AbstractValidator<CreateDis
.MaximumLength(200).WithMessage("عنوان محصول نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.ShortInformation)
.NotEmpty().WithMessage("اطلاعات کوتاه محصول الزامی است")
.MaximumLength(500).WithMessage("اطلاعات کوتاه نباید بیشتر از 500 کاراکتر باشد");
.MaximumLength(500).WithMessage("اطلاعات کوتاه نباید بیشتر از 500 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.ShortInformation));
RuleFor(x => x.FullInformation)
.NotEmpty().WithMessage("اطلاعات کامل محصول الزامی است");
.MaximumLength(10000).WithMessage("اطلاعات کامل نباید بیشتر از 10000 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.FullInformation));
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("قیمت محصول باید بیشتر از صفر باشد");
@@ -21,9 +22,6 @@ public class CreateDiscountProductCommandValidator : AbstractValidator<CreateDis
RuleFor(x => x.MaxDiscountPercent)
.InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد");
RuleFor(x => x.InitialCount)
.GreaterThanOrEqualTo(0).WithMessage("تعداد اولیه نمی‌تواند منفی باشد");
RuleFor(x => x.CategoryIds)
.NotEmpty().WithMessage("حداقل یک دسته‌بندی باید انتخاب شود");
}
@@ -0,0 +1,6 @@
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProductImage;
public class DeleteDiscountProductImageCommand : IRequest
{
public long Id { get; set; }
}
@@ -0,0 +1,23 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProductImage;
public class DeleteDiscountProductImageCommandHandler : IRequestHandler<DeleteDiscountProductImageCommand>
{
private readonly IApplicationContractContext _context;
public DeleteDiscountProductImageCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteDiscountProductImageCommand request, CancellationToken cancellationToken)
{
await _context.DiscountProducts.DeleteDiscountProductImageAsync(new DeleteDiscountProductImageRequest
{
Id = request.Id
}, cancellationToken: cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,7 @@
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.ReorderDiscountProductImages;
public class ReorderDiscountProductImagesCommand : IRequest
{
public long DiscountProductId { get; set; }
public List<long> ImageIds { get; set; } = new();
}
@@ -0,0 +1,26 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.ReorderDiscountProductImages;
public class ReorderDiscountProductImagesCommandHandler : IRequestHandler<ReorderDiscountProductImagesCommand>
{
private readonly IApplicationContractContext _context;
public ReorderDiscountProductImagesCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<Unit> Handle(ReorderDiscountProductImagesCommand request, CancellationToken cancellationToken)
{
var reorderRequest = new ReorderDiscountProductImagesRequest
{
DiscountProductId = request.DiscountProductId
};
reorderRequest.ImageIds.AddRange(request.ImageIds);
await _context.DiscountProducts.ReorderDiscountProductImagesAsync(reorderRequest, cancellationToken: cancellationToken);
return Unit.Value;
}
}
@@ -18,8 +18,8 @@ public class UpdateDiscountProductCommandHandler : IRequestHandler<UpdateDiscoun
{
ProductId = request.ProductId,
Title = request.Title,
ShortInfomation = request.ShortInformation,
FullInformation = request.FullInformation,
ShortInfomation = request.ShortInformation ?? string.Empty,
FullInformation = request.FullInformation ?? string.Empty,
Price = request.Price,
MaxDiscountPercent = request.MaxDiscountPercent,
SortOrder = request.SortOrder,
@@ -12,11 +12,12 @@ public class UpdateDiscountProductCommandValidator : AbstractValidator<UpdateDis
.MaximumLength(200).WithMessage("عنوان محصول نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.ShortInformation)
.NotEmpty().WithMessage("اطلاعات کوتاه محصول الزامی است")
.MaximumLength(500).WithMessage("اطلاعات کوتاه نباید بیشتر از 500 کاراکتر باشد");
.MaximumLength(500).WithMessage("اطلاعات کوتاه نباید بیشتر از 500 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.ShortInformation));
RuleFor(x => x.FullInformation)
.NotEmpty().WithMessage("اطلاعات کامل محصول الزامی است");
.MaximumLength(10000).WithMessage("اطلاعات کامل نباید بیشتر از 10000 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.FullInformation));
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("قیمت محصول باید بیشتر از صفر باشد");
@@ -0,0 +1,13 @@
using BackOffice.BFF.Application.DiscountProductCQ.Commands.AddDiscountProductImage;
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProductImage;
public class UpdateDiscountProductImageCommand : IRequest
{
public long Id { get; set; }
public ImageFileInput? ImageFile { get; set; }
public ImageFileInput? ThumbnailFile { get; set; }
public string? Title { get; set; }
public string? AltText { get; set; }
public bool IsActive { get; set; }
}
@@ -0,0 +1,65 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
using Google.Protobuf;
namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProductImage;
public class UpdateDiscountProductImageCommandHandler : IRequestHandler<UpdateDiscountProductImageCommand>
{
private readonly IApplicationContractContext _context;
public UpdateDiscountProductImageCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<Unit> Handle(UpdateDiscountProductImageCommand request, CancellationToken cancellationToken)
{
var updateRequest = new UpdateDiscountProductImageRequest
{
Id = request.Id,
IsActive = request.IsActive
};
if (!string.IsNullOrWhiteSpace(request.Title))
updateRequest.Title = request.Title;
if (!string.IsNullOrWhiteSpace(request.AltText))
updateRequest.AltText = request.AltText;
// Upload new Image if provided
if (request.ImageFile != null && request.ImageFile.Data != null && request.ImageFile.Data.Length > 0)
{
var imageFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
{
Directory = "Images/DiscountShop/Products/Gallery",
IsBase64 = false,
MIME = request.ImageFile.MimeType,
FileName = request.ImageFile.FileName,
File = ByteString.CopyFrom(request.ImageFile.Data)
}, cancellationToken: cancellationToken);
if (imageFileInfo != null && !string.IsNullOrWhiteSpace(imageFileInfo.File))
updateRequest.ImagePath = imageFileInfo.File;
}
// Upload new Thumbnail if provided
if (request.ThumbnailFile != null && request.ThumbnailFile.Data != null && request.ThumbnailFile.Data.Length > 0)
{
var thumbnailFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
{
Directory = "Images/DiscountShop/Products/Gallery/Thumbnails",
IsBase64 = false,
MIME = request.ThumbnailFile.MimeType,
FileName = request.ThumbnailFile.FileName,
File = ByteString.CopyFrom(request.ThumbnailFile.Data)
}, cancellationToken: cancellationToken);
if (thumbnailFileInfo != null && !string.IsNullOrWhiteSpace(thumbnailFileInfo.File))
updateRequest.ThumbnailPath = thumbnailFileInfo.File;
}
await _context.DiscountProducts.UpdateDiscountProductImageAsync(updateRequest, cancellationToken: cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,24 @@
namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductImages;
public class GetDiscountProductImagesQuery : IRequest<GetDiscountProductImagesResponseDto>
{
public long DiscountProductId { get; set; }
public bool OnlyActive { get; set; } = false; // Admin can see all
}
public class GetDiscountProductImagesResponseDto
{
public List<DiscountProductImageDto> Images { get; set; } = new();
}
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; }
public string? Title { get; set; }
public string? AltText { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; }
}
@@ -0,0 +1,39 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductImages;
public class GetDiscountProductImagesQueryHandler : IRequestHandler<GetDiscountProductImagesQuery, GetDiscountProductImagesResponseDto>
{
private readonly IApplicationContractContext _context;
public GetDiscountProductImagesQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetDiscountProductImagesResponseDto> Handle(GetDiscountProductImagesQuery request, CancellationToken cancellationToken)
{
var grpcRequest = new GetDiscountProductImagesRequest
{
DiscountProductId = request.DiscountProductId,
OnlyActive = request.OnlyActive
};
var response = await _context.DiscountProducts.GetDiscountProductImagesAsync(grpcRequest, cancellationToken: cancellationToken);
return new GetDiscountProductImagesResponseDto
{
Images = response.Images.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
}).ToList()
};
}
}
@@ -41,7 +41,7 @@ public class GetDiscountProductsQueryHandler : IRequestHandler<GetDiscountProduc
return new GetDiscountProductsResponseDto
{
MetaData = new MetaData
MetaData = response.MetaData != null ? new MetaData
{
CurrentPage = response.MetaData.CurrentPage,
TotalPage = response.MetaData.TotalPage,
@@ -49,8 +49,8 @@ public class GetDiscountProductsQueryHandler : IRequestHandler<GetDiscountProduc
TotalCount = response.MetaData.TotalCount,
HasPrevious = response.MetaData.HasPrevious,
HasNext = response.MetaData.HasNext
},
Products = response.Models.Select(p => new DiscountProductDto
} : new MetaData(),
Products = response.Models?.Select(p => new DiscountProductDto
{
Id = p.Id,
Title = p.Title,
@@ -62,8 +62,8 @@ public class GetDiscountProductsQueryHandler : IRequestHandler<GetDiscountProduc
RemainingCount = p.RemainingCount,
ViewCount = p.ViewCount,
IsActive = p.IsActive,
Created = p.Created.ToDateTime()
}).ToList()
Created = p.Created?.ToDateTime() ?? DateTime.MinValue
}).ToList() ?? new List<DiscountProductDto>()
};
}
}
@@ -5,6 +5,6 @@ public record RemoveFromCartCommand : IRequest
/// <summary>شناسه کاربر</summary>
public long UserId { get; init; }
/// <summary>شناسه آیتم سبد خرید</summary>
public long CartItemId { get; init; }
/// <summary>شناسه محصول</summary>
public long ProductId { get; init; }
}
@@ -17,7 +17,7 @@ public class RemoveFromCartCommandHandler : IRequestHandler<RemoveFromCartComman
new RemoveFromCartRequest
{
UserId = request.UserId,
CartItemId = request.CartItemId
ProductId = request.ProductId
},
cancellationToken: cancellationToken);
@@ -7,7 +7,7 @@ public class RemoveFromCartCommandValidator : AbstractValidator<RemoveFromCartCo
RuleFor(x => x.UserId)
.GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است");
RuleFor(x => x.CartItemId)
.GreaterThan(0).WithMessage("شناسه آیتم سبد خرید نامعتبر است");
RuleFor(x => x.ProductId)
.GreaterThan(0).WithMessage("شناسه محصول نامعتبر است");
}
}
@@ -5,8 +5,8 @@ public record UpdateCartItemCountCommand : IRequest
/// <summary>شناسه کاربر</summary>
public long UserId { get; init; }
/// <summary>شناسه آیتم سبد خرید</summary>
public long CartItemId { get; init; }
/// <summary>شناسه محصول</summary>
public long ProductId { get; init; }
/// <summary>تعداد جدید</summary>
public int NewCount { get; init; }
@@ -17,7 +17,7 @@ public class UpdateCartItemCountCommandHandler : IRequestHandler<UpdateCartItemC
new UpdateCartItemCountRequest
{
UserId = request.UserId,
CartItemId = request.CartItemId,
ProductId = request.ProductId,
NewCount = request.NewCount
},
cancellationToken: cancellationToken);
@@ -7,8 +7,8 @@ public class UpdateCartItemCountCommandValidator : AbstractValidator<UpdateCartI
RuleFor(x => x.UserId)
.GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است");
RuleFor(x => x.CartItemId)
.GreaterThan(0).WithMessage("شناسه آیتم سبد خرید نامعتبر است");
RuleFor(x => x.ProductId)
.GreaterThan(0).WithMessage("شناسه محصول نامعتبر است");
RuleFor(x => x.NewCount)
.GreaterThan(0).WithMessage("تعداد جدید باید بیشتر از صفر باشد");
@@ -19,13 +19,11 @@ public class GetUserCartQueryHandler : IRequestHandler<GetUserCartQuery, GetUser
return new GetUserCartResponseDto
{
UserId = response.UserId,
TotalPrice = response.TotalPrice,
TotalDiscountedPrice = response.TotalDiscountedPrice,
TotalSavings = response.TotalSavings,
TotalDiscountAmount = response.TotalDiscountAmount,
FinalPrice = response.FinalPrice,
Items = response.Items.Select(item => new CartItemDto
{
Id = item.Id,
ProductId = item.ProductId,
ProductTitle = item.ProductTitle,
ProductImagePath = item.ProductImagePath,
@@ -33,8 +31,10 @@ public class GetUserCartQueryHandler : IRequestHandler<GetUserCartQuery, GetUser
MaxDiscountPercent = item.MaxDiscountPercent,
Count = item.Count,
TotalPrice = item.TotalPrice,
DiscountedPrice = item.DiscountedPrice,
AddedAt = item.AddedAt.ToDateTime()
DiscountAmount = item.DiscountAmount,
FinalPrice = item.FinalPrice,
ProductRemainingCount = item.ProductRemainingCount,
Created = item.Created.ToDateTime()
}).ToList()
};
}
@@ -2,23 +2,28 @@ namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Queries.GetUserCart;
public class GetUserCartResponseDto
{
public long UserId { get; set; }
public List<CartItemDto> Items { get; set; } = new();
/// <summary>جمع کل قیمت (بدون تخفیف)</summary>
public long TotalPrice { get; set; }
public long TotalDiscountedPrice { get; set; }
public long TotalSavings { get; set; }
/// <summary>جمع تخفیف‌ها</summary>
public long TotalDiscountAmount { get; set; }
/// <summary>قیمت نهایی (با تخفیف)</summary>
public long FinalPrice { get; set; }
}
public class CartItemDto
{
public long Id { get; set; }
public long ProductId { get; set; }
public string ProductTitle { get; set; }
public string ProductImagePath { get; set; }
public string ProductTitle { get; set; } = string.Empty;
public string ProductImagePath { get; set; } = string.Empty;
public long UnitPrice { get; set; }
public int MaxDiscountPercent { get; set; }
public int Count { get; set; }
public long TotalPrice { get; set; }
public long DiscountedPrice { get; set; }
public DateTime AddedAt { get; set; }
/// <summary>مبلغ تخفیف</summary>
public long DiscountAmount { get; set; }
/// <summary>قیمت نهایی آیتم</summary>
public long FinalPrice { get; set; }
public int ProductRemainingCount { get; set; }
public DateTime Created { get; set; }
}
@@ -0,0 +1,17 @@
namespace BackOffice.BFF.Application.InventoryCQ.Commands.AddStock;
public record AddStockCommand : IRequest<AddStockResponseDto>
{
public long ProductId { get; init; }
public int ProductType { get; init; } // 1=RegularProduct, 2=DiscountProduct
public int Quantity { get; init; }
public string? ReferenceNumber { get; init; }
public string? Note { get; init; }
public long? WarehouseId { get; init; }
}
public class AddStockResponseDto
{
public long InventoryItemId { get; set; }
public int NewQuantity { get; set; }
}
@@ -0,0 +1,40 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.InventoryCQ.Commands.AddStock;
public class AddStockCommandHandler : IRequestHandler<AddStockCommand, AddStockResponseDto>
{
private readonly IApplicationContractContext _context;
public AddStockCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<AddStockResponseDto> Handle(AddStockCommand request, CancellationToken cancellationToken)
{
var protoRequest = new AddStockRequest
{
ProductId = request.ProductId,
ProductType = (ProductType)request.ProductType,
Quantity = request.Quantity
};
if (!string.IsNullOrWhiteSpace(request.ReferenceNumber))
protoRequest.ReferenceNumber = request.ReferenceNumber;
if (!string.IsNullOrWhiteSpace(request.Note))
protoRequest.Note = request.Note;
if (request.WarehouseId.HasValue)
protoRequest.WarehouseId = request.WarehouseId.Value;
var response = await _context.Inventory.AddStockAsync(protoRequest, cancellationToken: cancellationToken);
return new AddStockResponseDto
{
InventoryItemId = response.InventoryItemId,
NewQuantity = response.NewQuantity
};
}
}
@@ -0,0 +1,17 @@
namespace BackOffice.BFF.Application.InventoryCQ.Commands.AdjustStock;
public record AdjustStockCommand : IRequest<AdjustStockResponseDto>
{
public long ProductId { get; init; }
public int ProductType { get; init; } // 1=RegularProduct, 2=DiscountProduct
public int NewQuantity { get; init; }
public string? Reason { get; init; }
public string? ReferenceNumber { get; init; }
}
public class AdjustStockResponseDto
{
public int PreviousQuantity { get; set; }
public int NewQuantity { get; set; }
public int Difference { get; set; }
}
@@ -0,0 +1,38 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.InventoryCQ.Commands.AdjustStock;
public class AdjustStockCommandHandler : IRequestHandler<AdjustStockCommand, AdjustStockResponseDto>
{
private readonly IApplicationContractContext _context;
public AdjustStockCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<AdjustStockResponseDto> Handle(AdjustStockCommand request, CancellationToken cancellationToken)
{
var protoRequest = new AdjustStockRequest
{
ProductId = request.ProductId,
ProductType = (ProductType)request.ProductType,
NewQuantity = request.NewQuantity
};
if (!string.IsNullOrWhiteSpace(request.Reason))
protoRequest.Reason = request.Reason;
if (!string.IsNullOrWhiteSpace(request.ReferenceNumber))
protoRequest.ReferenceNumber = request.ReferenceNumber;
var response = await _context.Inventory.AdjustStockAsync(protoRequest, cancellationToken: cancellationToken);
return new AdjustStockResponseDto
{
PreviousQuantity = response.PreviousQuantity,
NewQuantity = response.NewQuantity,
Difference = response.Difference
};
}
}
@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.InventoryCQ.Commands.CreateWarehouse;
public record CreateWarehouseCommand : IRequest<long>
{
public string Name { get; init; } = string.Empty;
public string Code { get; init; } = string.Empty;
public string? Address { get; init; }
public bool IsDefault { get; init; }
public bool IsActive { get; init; } = true;
}
@@ -0,0 +1,29 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.InventoryCQ.Commands.CreateWarehouse;
public class CreateWarehouseCommandHandler : IRequestHandler<CreateWarehouseCommand, long>
{
private readonly IApplicationContractContext _context;
public CreateWarehouseCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<long> Handle(CreateWarehouseCommand request, CancellationToken cancellationToken)
{
var protoRequest = new CreateWarehouseRequest
{
Name = request.Name,
Code = request.Code,
IsDefault = request.IsDefault
};
if (!string.IsNullOrWhiteSpace(request.Address))
protoRequest.Address = request.Address;
var response = await _context.Inventory.CreateWarehouseAsync(protoRequest, cancellationToken: cancellationToken);
return response.Id;
}
}
@@ -0,0 +1,11 @@
namespace BackOffice.BFF.Application.InventoryCQ.Commands.RecordLoss;
public record RecordLossCommand : IRequest<bool>
{
public long ProductId { get; init; }
public int ProductType { get; init; } // 1=RegularProduct, 2=DiscountProduct
public int Quantity { get; init; }
public int LossType { get; init; } // 40=Loss, 41=Damaged, 42=Expired
public string? Reason { get; init; }
public string? ReferenceNumber { get; init; }
}
@@ -0,0 +1,33 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.InventoryCQ.Commands.RecordLoss;
public class RecordLossCommandHandler : IRequestHandler<RecordLossCommand, bool>
{
private readonly IApplicationContractContext _context;
public RecordLossCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<bool> Handle(RecordLossCommand request, CancellationToken cancellationToken)
{
var protoRequest = new RecordLossRequest
{
ProductId = request.ProductId,
ProductType = (ProductType)request.ProductType,
Quantity = request.Quantity,
LossType = (StockMovementType)request.LossType
};
if (!string.IsNullOrWhiteSpace(request.Reason))
protoRequest.Reason = request.Reason;
if (!string.IsNullOrWhiteSpace(request.ReferenceNumber))
protoRequest.ReferenceNumber = request.ReferenceNumber;
await _context.Inventory.RecordLossAsync(protoRequest, cancellationToken: cancellationToken);
return true;
}
}
@@ -0,0 +1,9 @@
namespace BackOffice.BFF.Application.InventoryCQ.Commands.UpdateInventorySettings;
public record UpdateInventorySettingsCommand : IRequest<bool>
{
public long Id { get; init; }
public int LowStockThreshold { get; init; }
public int ReorderPoint { get; init; }
public int MaxStockLevel { get; init; }
}
@@ -0,0 +1,27 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.InventoryCQ.Commands.UpdateInventorySettings;
public class UpdateInventorySettingsCommandHandler : IRequestHandler<UpdateInventorySettingsCommand, bool>
{
private readonly IApplicationContractContext _context;
public UpdateInventorySettingsCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<bool> Handle(UpdateInventorySettingsCommand request, CancellationToken cancellationToken)
{
var protoRequest = new UpdateInventorySettingsRequest
{
Id = request.Id,
LowStockThreshold = request.LowStockThreshold,
ReorderPoint = request.ReorderPoint,
MaxStockLevel = request.MaxStockLevel
};
await _context.Inventory.UpdateInventorySettingsAsync(protoRequest, cancellationToken: cancellationToken);
return true;
}
}
@@ -0,0 +1,41 @@
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetAllInventoryItems;
public record GetAllInventoryItemsQuery : IRequest<GetAllInventoryItemsResponseDto>
{
public int Page { get; init; } = 1;
public int PageSize { get; init; } = 20;
public long? WarehouseId { get; init; }
public int? ProductType { get; init; } // 1=RegularProduct, 2=DiscountProduct
public string? Search { get; init; }
public string? SortBy { get; init; }
public bool SortDesc { get; init; }
}
public class GetAllInventoryItemsResponseDto
{
public int TotalCount { get; set; }
public MetaData MetaData { get; set; } = new();
public List<InventoryItemDto> Items { get; set; } = new();
}
public class InventoryItemDto
{
public long Id { get; set; }
public long? ProductId { get; set; }
public long? DiscountProductId { get; set; }
public int ProductType { get; set; }
public string ProductTitle { get; set; } = string.Empty;
public long 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 ReorderPoint { get; set; }
public int MaxStockLevel { get; set; }
public DateTime? LastRestockedAt { get; set; }
public DateTime? LastSoldAt { get; set; }
public long WarehouseId { get; set; }
public string WarehouseName { get; set; } = string.Empty;
public bool IsLowStock { get; set; }
public DateTime? Created { get; set; }
}
@@ -0,0 +1,72 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
using Google.Protobuf.WellKnownTypes;
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetAllInventoryItems;
public class GetAllInventoryItemsQueryHandler : IRequestHandler<GetAllInventoryItemsQuery, GetAllInventoryItemsResponseDto>
{
private readonly IApplicationContractContext _context;
public GetAllInventoryItemsQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetAllInventoryItemsResponseDto> Handle(GetAllInventoryItemsQuery request, CancellationToken cancellationToken)
{
var protoRequest = new GetAllInventoryItemsRequest
{
Page = request.Page,
PageSize = request.PageSize
};
if (request.WarehouseId.HasValue)
protoRequest.WarehouseId = request.WarehouseId.Value;
if (request.ProductType.HasValue)
protoRequest.ProductType = (ProductType)request.ProductType.Value;
if (!string.IsNullOrWhiteSpace(request.Search))
protoRequest.Search = request.Search;
if (!string.IsNullOrWhiteSpace(request.SortBy))
protoRequest.SortBy = request.SortBy;
protoRequest.SortDesc = request.SortDesc;
var response = await _context.Inventory.GetAllInventoryItemsAsync(protoRequest, cancellationToken: cancellationToken);
return new GetAllInventoryItemsResponseDto
{
TotalCount = response.TotalCount,
MetaData = new MetaData
{
TotalCount = response.TotalCount,
PageSize = request.PageSize,
CurrentPage = request.Page,
TotalPage = (int)Math.Ceiling((double)response.TotalCount / request.PageSize)
},
Items = response.Items.Select(i => new InventoryItemDto
{
Id = i.Id,
ProductId = i.ProductId,
DiscountProductId = i.DiscountProductId,
ProductType = (int)i.ProductType,
ProductTitle = i.ProductTitle,
ProductPrice = i.ProductPrice,
Quantity = i.Quantity,
ReservedQuantity = i.ReservedQuantity,
AvailableQuantity = i.AvailableQuantity,
LowStockThreshold = i.LowStockThreshold,
ReorderPoint = i.ReorderPoint,
MaxStockLevel = i.MaxStockLevel,
LastRestockedAt = i.LastRestockedAt?.ToDateTime(),
LastSoldAt = i.LastSoldAt?.ToDateTime(),
WarehouseId = i.WarehouseId,
WarehouseName = i.WarehouseName,
IsLowStock = i.Quantity <= i.LowStockThreshold,
Created = i.Created?.ToDateTime()
}).ToList()
};
}
}
@@ -0,0 +1,24 @@
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetAllWarehouses;
public record GetAllWarehousesQuery : IRequest<GetAllWarehousesResponseDto>
{
public bool? IsActive { get; init; }
}
public class GetAllWarehousesResponseDto
{
public int TotalCount { get; set; }
public List<WarehouseDto> Warehouses { get; set; } = new();
}
public class WarehouseDto
{
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; }
public DateTime? Created { get; set; }
public DateTime? LastModified { get; set; }
}
@@ -0,0 +1,41 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetAllWarehouses;
public class GetAllWarehousesQueryHandler : IRequestHandler<GetAllWarehousesQuery, GetAllWarehousesResponseDto>
{
private readonly IApplicationContractContext _context;
public GetAllWarehousesQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetAllWarehousesResponseDto> Handle(GetAllWarehousesQuery request, CancellationToken cancellationToken)
{
var protoRequest = new CMSMicroservice.Protobuf.Protos.Inventory.GetAllWarehousesRequest();
if (request.IsActive.HasValue)
{
protoRequest.IsActive = request.IsActive.Value;
}
var response = await _context.Inventory.GetAllWarehousesAsync(protoRequest, cancellationToken: cancellationToken);
return new GetAllWarehousesResponseDto
{
TotalCount = response.TotalCount,
Warehouses = response.Warehouses.Select(w => new WarehouseDto
{
Id = w.Id,
Name = w.Name,
Code = w.Code,
Address = w.Address,
IsDefault = w.IsDefault,
IsActive = w.IsActive,
Created = w.Created?.ToDateTime(),
LastModified = w.LastModified?.ToDateTime()
}).ToList()
};
}
}
@@ -0,0 +1,17 @@
using BackOffice.BFF.Application.InventoryCQ.Queries.GetAllInventoryItems;
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetLowStockItems;
public record GetLowStockItemsQuery : IRequest<GetLowStockItemsResponseDto>
{
public long? WarehouseId { get; init; }
public int? ProductType { get; init; } // 1=RegularProduct, 2=DiscountProduct
public int Page { get; init; } = 1;
public int PageSize { get; init; } = 20;
}
public class GetLowStockItemsResponseDto
{
public int TotalCount { get; set; }
public List<InventoryItemDto> Items { get; set; } = new();
}
@@ -0,0 +1,54 @@
using BackOffice.BFF.Application.InventoryCQ.Queries.GetAllInventoryItems;
using CMSMicroservice.Protobuf.Protos.Inventory;
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetLowStockItems;
public class GetLowStockItemsQueryHandler : IRequestHandler<GetLowStockItemsQuery, GetLowStockItemsResponseDto>
{
private readonly IApplicationContractContext _context;
public GetLowStockItemsQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetLowStockItemsResponseDto> Handle(GetLowStockItemsQuery request, CancellationToken cancellationToken)
{
var protoRequest = new GetLowStockItemsRequest
{
Page = request.Page,
PageSize = request.PageSize
};
if (request.WarehouseId.HasValue)
protoRequest.WarehouseId = request.WarehouseId.Value;
if (request.ProductType.HasValue)
protoRequest.ProductType = (ProductType)request.ProductType.Value;
var response = await _context.Inventory.GetLowStockItemsAsync(protoRequest, cancellationToken: cancellationToken);
return new GetLowStockItemsResponseDto
{
TotalCount = response.TotalCount,
Items = response.Items.Select(i => new GetAllInventoryItems.InventoryItemDto
{
Id = i.Id,
ProductId = i.ProductId,
DiscountProductId = i.DiscountProductId,
ProductType = (int)i.ProductType,
ProductTitle = i.ProductTitle,
ProductPrice = i.ProductPrice,
Quantity = i.Quantity,
ReservedQuantity = i.ReservedQuantity,
AvailableQuantity = i.AvailableQuantity,
LowStockThreshold = i.LowStockThreshold,
ReorderPoint = i.ReorderPoint,
MaxStockLevel = i.MaxStockLevel,
WarehouseId = i.WarehouseId,
WarehouseName = i.WarehouseName,
IsLowStock = true
}).ToList()
};
}
}
@@ -0,0 +1,37 @@
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetStockMovements;
public record GetStockMovementsQuery : IRequest<GetStockMovementsResponseDto>
{
public long? InventoryItemId { get; init; }
public long? ProductId { get; init; }
public int? ProductType { get; init; }
public int? MovementType { get; init; }
public DateTime? FromDate { get; init; }
public DateTime? ToDate { get; init; }
public int Page { get; init; } = 1;
public int PageSize { get; init; } = 20;
}
public class GetStockMovementsResponseDto
{
public int TotalCount { get; set; }
public MetaData MetaData { get; set; } = new();
public List<StockMovementDto> Movements { get; set; } = new();
}
public class StockMovementDto
{
public long Id { get; set; }
public long InventoryItemId { get; set; }
public int 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; }
public DateTime Created { get; set; }
public string ProductTitle { get; set; } = string.Empty;
}
@@ -0,0 +1,89 @@
using CMSMicroservice.Protobuf.Protos.Inventory;
using Google.Protobuf.WellKnownTypes;
namespace BackOffice.BFF.Application.InventoryCQ.Queries.GetStockMovements;
public class GetStockMovementsQueryHandler : IRequestHandler<GetStockMovementsQuery, GetStockMovementsResponseDto>
{
private readonly IApplicationContractContext _context;
public GetStockMovementsQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetStockMovementsResponseDto> Handle(GetStockMovementsQuery request, CancellationToken cancellationToken)
{
var protoRequest = new GetStockMovementsRequest
{
Page = request.Page,
PageSize = request.PageSize
};
if (request.InventoryItemId.HasValue)
protoRequest.InventoryItemId = request.InventoryItemId.Value;
if (request.ProductId.HasValue)
protoRequest.ProductId = request.ProductId.Value;
if (request.ProductType.HasValue)
protoRequest.ProductType = (ProductType)request.ProductType.Value;
if (request.MovementType.HasValue)
protoRequest.MovementType = (StockMovementType)request.MovementType.Value;
if (request.FromDate.HasValue)
protoRequest.FromDate = Timestamp.FromDateTime(request.FromDate.Value.ToUniversalTime());
if (request.ToDate.HasValue)
protoRequest.ToDate = Timestamp.FromDateTime(request.ToDate.Value.ToUniversalTime());
var response = await _context.Inventory.GetStockMovementsAsync(protoRequest, cancellationToken: cancellationToken);
return new GetStockMovementsResponseDto
{
TotalCount = response.TotalCount,
MetaData = new MetaData
{
TotalCount = response.TotalCount,
PageSize = request.PageSize,
CurrentPage = request.Page,
TotalPage = (int)Math.Ceiling((double)response.TotalCount / request.PageSize)
},
Movements = response.Movements.Select(m => new StockMovementDto
{
Id = m.Id,
InventoryItemId = m.InventoryItemId,
MovementType = (int)m.MovementType,
Quantity = m.Quantity,
QuantityBefore = m.QuantityBefore,
QuantityAfter = m.QuantityAfter,
OrderId = m.OrderId,
DiscountOrderId = m.DiscountOrderId,
ReferenceNumber = string.IsNullOrEmpty(m.ReferenceNumber) ? null : m.ReferenceNumber,
Note = string.IsNullOrEmpty(m.Note) ? null : m.Note,
PerformedByUserId = m.PerformedByUserId,
Created = m.Created?.ToDateTime() ?? DateTime.MinValue,
ProductTitle = m.ProductTitle
}).ToList()
};
}
private static string GetMovementTypeName(StockMovementType type) => type switch
{
StockMovementType.InitialStock => "موجودی اولیه",
StockMovementType.Restock => "ورود کالا",
StockMovementType.Return => "برگشت از مشتری",
StockMovementType.Sale => "فروش",
StockMovementType.AdjustmentIncrease => "تعدیل افزایشی",
StockMovementType.AdjustmentDecrease => "تعدیل کاهشی",
StockMovementType.Reserved => "رزرو",
StockMovementType.Released => "آزادسازی رزرو",
StockMovementType.Loss => "مفقودی",
StockMovementType.Damaged => "ضایعات",
StockMovementType.Expired => "منقضی",
StockMovementType.TransferOut => "انتقال به انبار",
StockMovementType.TransferIn => "انتقال از انبار",
_ => "نامشخص"
};
}
@@ -7,6 +7,19 @@ public class CreateManualPaymentCommand : IRequest<CreateManualPaymentResponseDt
public int Type { get; set; }
public string Description { get; set; } = string.Empty;
public string? ReferenceNumber { get; set; }
public string? ImagePath { get; set; }
/// <summary>
/// Image file data for FMS upload
/// </summary>
public FileUploadDto? ImageFile { get; set; }
}
public class FileUploadDto
{
public byte[] File { get; set; } = Array.Empty<byte>();
public string FileName { get; set; } = string.Empty;
public string Mime { get; set; } = string.Empty;
}
public class CreateManualPaymentResponseDto
@@ -1,4 +1,5 @@
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using Google.Protobuf;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.CreateManualPayment;
@@ -37,6 +38,38 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
grpcRequest.ReferenceNumber = request.ReferenceNumber;
}
// Upload image to FMS if provided
if (request.ImageFile != null && request.ImageFile.File != null && request.ImageFile.File.Length > 0)
{
var fileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
{
Directory = "Images/ManualPayments",
IsBase64 = false,
MIME = request.ImageFile.Mime,
FileName = request.ImageFile.FileName,
File = ByteString.CopyFrom(request.ImageFile.File)
}, cancellationToken: cancellationToken);
if (fileInfo != null)
{
if (!string.IsNullOrWhiteSpace(fileInfo.File))
{
grpcRequest.ImagePath = fileInfo.File;
_logger.LogInformation("Image uploaded to FMS: {ImagePath}", fileInfo.File);
}
if (fileInfo.Id > 0)
{
grpcRequest.ImageDocumentId = fileInfo.Id;
_logger.LogInformation("Image document ID from FMS: {ImageDocumentId}", fileInfo.Id);
}
}
}
else if (!string.IsNullOrWhiteSpace(request.ImagePath))
{
grpcRequest.ImagePath = request.ImagePath;
}
var response = await _context.ManualPayments.CreateManualPaymentAsync(grpcRequest, cancellationToken: cancellationToken);
return new CreateManualPaymentResponseDto
@@ -1,4 +1,3 @@
using BackOffice.BFF.ManualPayment.Protobuf;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using Microsoft.Extensions.Logging;
@@ -26,7 +25,7 @@ public class GetManualPaymentsQueryHandler : IRequestHandler<GetManualPaymentsQu
request.Status,
request.Type);
var grpcRequest = new GetAllManualPaymentsRequest
var grpcRequest = new GetAllManualPaymentsRequest
{
PageNumber = request.PageNumber,
PageSize = request.PageSize,
@@ -44,23 +43,25 @@ public class GetManualPaymentsQueryHandler : IRequestHandler<GetManualPaymentsQu
{
Id = m.Id,
UserId = m.UserId,
UserFullName = m.UserFullName,
UserMobile = m.UserMobile,
UserFullName = m.UserFullName ?? string.Empty,
UserMobile = m.UserMobile ?? string.Empty,
Amount = m.Amount,
Type = m.Type.GetHashCode(),
TypeDisplay = m.TypeDisplay,
Description = m.Description,
ReferenceNumber = m.ReferenceNumber,
TypeDisplay = m.TypeDisplay ?? string.Empty,
Description = m.Description ?? string.Empty,
ReferenceNumber = m.ReferenceNumber ?? string.Empty,
Status = m.Status.GetHashCode(),
StatusDisplay = m.StatusDisplay,
StatusDisplay = m.StatusDisplay ?? string.Empty,
RequestedBy = m.RequestedBy,
RequestedByName = m.RequestedByName,
RequestedByName = m.RequestedByName ?? string.Empty,
ApprovedBy = m.ApprovedBy,
ApprovedByName = m.ApprovedByName,
ApprovedByName = m.ApprovedByName ?? string.Empty,
ApprovedAt = m.ApprovedAt?.ToDateTime(),
RejectionReason = m.RejectionReason,
RejectionReason = m.RejectionReason ?? string.Empty,
TransactionId = m.TransactionId,
Created = m.Created?.ToDateTime() ?? DateTime.UtcNow
Created = m.Created?.ToDateTime() ?? DateTime.UtcNow,
ImagePath = m.ImagePath,
ImageDocumentId = m.ImageDocumentId
})
.ToList()
?? new List<ManualPaymentModel>();
@@ -27,5 +27,7 @@ public class ManualPaymentModel
public string? RejectionReason { get; set; }
public long? TransactionId { get; set; }
public DateTime Created { get; set; }
public string? ImagePath { get; set; }
public long? ImageDocumentId { get; set; }
}
@@ -0,0 +1,6 @@
namespace BackOffice.BFF.Application.ProductTagCQ.Commands.DeleteProductTag;
public record DeleteProductTagCommand : IRequest<Unit>
{
public long Id { get; init; }
}
@@ -0,0 +1,25 @@
using CMSMicroservice.Protobuf.Protos.ProductTag;
namespace BackOffice.BFF.Application.ProductTagCQ.Commands.DeleteProductTag;
public class DeleteProductTagCommandHandler : IRequestHandler<DeleteProductTagCommand, Unit>
{
private readonly IApplicationContractContext _context;
public DeleteProductTagCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteProductTagCommand request, CancellationToken cancellationToken)
{
await _context.ProductTags.DeleteProductTagAsync(
new DeleteProductTagRequest
{
Id = request.Id
},
cancellationToken: cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,8 @@
namespace BackOffice.BFF.Application.ProductTagCQ.Commands.UpdateProductTag;
public record UpdateProductTagCommand : IRequest<Unit>
{
public long Id { get; init; }
public long ProductId { get; init; }
public long TagId { get; init; }
}
@@ -0,0 +1,27 @@
using CMSMicroservice.Protobuf.Protos.ProductTag;
namespace BackOffice.BFF.Application.ProductTagCQ.Commands.UpdateProductTag;
public class UpdateProductTagCommandHandler : IRequestHandler<UpdateProductTagCommand, Unit>
{
private readonly IApplicationContractContext _context;
public UpdateProductTagCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<Unit> Handle(UpdateProductTagCommand request, CancellationToken cancellationToken)
{
await _context.ProductTags.UpdateProductTagAsync(
new UpdateProductTagRequest
{
Id = request.Id,
ProductId = request.ProductId,
TagId = request.TagId
},
cancellationToken: cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,23 @@
namespace BackOffice.BFF.Application.ProductTagCQ.Queries.GetAllProductTags;
public record GetAllProductTagsQuery : IRequest<GetAllProductTagsResponseDto>
{
public int PageNumber { get; init; } = 1;
public int PageSize { get; init; } = 10;
public string? SortBy { get; init; }
public long? ProductId { get; init; }
public long? TagId { get; init; }
}
public record GetAllProductTagsResponseDto
{
public MetaData MetaData { get; init; } = default!;
public List<ProductTagItemDto> Items { get; init; } = new();
}
public record ProductTagItemDto
{
public long Id { get; init; }
public long ProductId { get; init; }
public long TagId { get; init; }
}
@@ -0,0 +1,51 @@
using CMSMicroservice.Protobuf.Protos.ProductTag;
using CmsPaginationState = CMSMicroservice.Protobuf.Protos.PaginationState;
namespace BackOffice.BFF.Application.ProductTagCQ.Queries.GetAllProductTags;
public class GetAllProductTagsQueryHandler : IRequestHandler<GetAllProductTagsQuery, GetAllProductTagsResponseDto>
{
private readonly IApplicationContractContext _context;
public GetAllProductTagsQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetAllProductTagsResponseDto> Handle(GetAllProductTagsQuery request, CancellationToken cancellationToken)
{
var response = await _context.ProductTags.GetAllProductTagByFilterAsync(
new GetAllProductTagByFilterRequest
{
PaginationState = new CmsPaginationState
{
PageNumber = request.PageNumber,
PageSize = request.PageSize
},
SortBy = request.SortBy ?? "",
Filter = new GetAllProductTagByFilterFilter
{
ProductId = request.ProductId ?? 0,
TagId = request.TagId ?? 0
}
},
cancellationToken: cancellationToken);
return new GetAllProductTagsResponseDto
{
MetaData = new MetaData
{
CurrentPage = response.MetaData.CurrentPage,
PageSize = response.MetaData.PageSize,
TotalCount = response.MetaData.TotalCount,
TotalPage = response.MetaData.TotalPage
},
Items = response.Models.Select(pt => new ProductTagItemDto
{
Id = pt.Id,
ProductId = pt.ProductId,
TagId = pt.TagId
}).ToList()
};
}
}
@@ -0,0 +1,13 @@
namespace BackOffice.BFF.Application.ProductTagCQ.Queries.GetProductTag;
public record GetProductTagQuery : IRequest<GetProductTagResponseDto>
{
public long Id { get; init; }
}
public record GetProductTagResponseDto
{
public long Id { get; init; }
public long ProductId { get; init; }
public long TagId { get; init; }
}
@@ -0,0 +1,30 @@
using CMSMicroservice.Protobuf.Protos.ProductTag;
namespace BackOffice.BFF.Application.ProductTagCQ.Queries.GetProductTag;
public class GetProductTagQueryHandler : IRequestHandler<GetProductTagQuery, GetProductTagResponseDto>
{
private readonly IApplicationContractContext _context;
public GetProductTagQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetProductTagResponseDto> Handle(GetProductTagQuery request, CancellationToken cancellationToken)
{
var response = await _context.ProductTags.GetProductTagAsync(
new GetProductTagRequest
{
Id = request.Id
},
cancellationToken: cancellationToken);
return new GetProductTagResponseDto
{
Id = response.Id,
ProductId = response.ProductId,
TagId = response.TagId
};
}
}
@@ -8,7 +8,6 @@ public record CreateNewProductsCommand : IRequest<CreateNewProductsResponseDto>
public long Price { get; init; }
public int Discount { get; init; }
public int Rate { get; init; }
public int RemainingCount { get; init; }
public ImageFileModel ImageFile { get; init; }
public ImageFileModel ThumbnailFile { get; init; }
// لیست شناسه دسته‌بندی‌های محصول
@@ -9,7 +9,6 @@ public record UpdateProductsCommand : IRequest<Unit>
public long Price { get; init; }
public int Discount { get; init; }
public int Rate { get; init; }
public int RemainingCount { get; init; }
public string ImagePath { get; init; }
public string ThumbnailPath { get; init; }
public ImageFileModel ImageFile { get; init; }
@@ -7,14 +7,14 @@
<ItemGroup>
<PackageReference Include="Afrino.FMSMicroservice.Protobuf" Version="0.0.122" />
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.162" />
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.167" />
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.54.0" />
</ItemGroup>
<!-- <ItemGroup>-->
<!-- <ProjectReference Include="..\..\..\..\FourSat\CMS\src\CMSMicroservice.Protobuf\CMSMicroservice.Protobuf.csproj" />-->
<!-- <ProjectReference Include="..\..\..\..\CMS\src\CMSMicroservice.Protobuf\CMSMicroservice.Protobuf.csproj" />-->
<!-- </ItemGroup>-->
@@ -25,6 +25,7 @@ using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using CMSMicroservice.Protobuf.Protos.AppVersion;
using CMSMicroservice.Protobuf.Protos.Inventory;
// BFF Protobuf contracts
@@ -97,5 +98,8 @@ public class ApplicationContractContext : IApplicationContractContext
// App Version Management
public AppVersionContract.AppVersionContractClient AppVersions => GetService<AppVersionContract.AppVersionContractClient>();
// Inventory Management System
public InventoryContract.InventoryContractClient Inventory => GetService<InventoryContract.InventoryContractClient>();
#endregion
}
@@ -42,6 +42,13 @@
<ProjectReference Include="..\Protobufs\BackOffice.BFF.Configuration.Protobuf\BackOffice.BFF.Configuration.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.NetworkMembership.Protobuf\BackOffice.BFF.NetworkMembership.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.PublicMessage.Protobuf\BackOffice.BFF.PublicMessage.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.DiscountProduct.Protobuf\BackOffice.BFF.DiscountProduct.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.DiscountOrder.Protobuf\BackOffice.BFF.DiscountOrder.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.DiscountCategory.Protobuf\BackOffice.BFF.DiscountCategory.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.DiscountShoppingCart.Protobuf\BackOffice.BFF.DiscountShoppingCart.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.Tag.Protobuf\BackOffice.BFF.Tag.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.ProductTag.Protobuf\BackOffice.BFF.ProductTag.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.Inventory.Protobuf\BackOffice.BFF.Inventory.Protobuf.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
@@ -0,0 +1,71 @@
using BackOffice.BFF.Application.DiscountCategoryCQ.Commands.CreateDiscountCategory;
using BackOffice.BFF.Application.DiscountCategoryCQ.Commands.UpdateDiscountCategory;
using BackOffice.BFF.Application.DiscountCategoryCQ.Commands.DeleteDiscountCategory;
using BackOffice.BFF.Application.DiscountCategoryCQ.Queries.GetDiscountCategories;
using BackOffice.BFF.DiscountCategory.Protobuf.Protos.DiscountCategory;
using Google.Protobuf.WellKnownTypes;
using Mapster;
using CreateFileModel = BackOffice.BFF.Application.DiscountCategoryCQ.Commands.CreateDiscountCategory.DiscountCategoryFileModel;
using UpdateFileModel = BackOffice.BFF.Application.DiscountCategoryCQ.Commands.UpdateDiscountCategory.DiscountCategoryFileModel;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class DiscountCategoryProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// Request to Command mappings
config.NewConfig<CreateDiscountCategoryRequest, CreateDiscountCategoryCommand>()
.Map(dest => dest.Name, src => src.Name)
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.Description, src => !string.IsNullOrWhiteSpace(src.Description) ? src.Description : null)
.Map(dest => dest.ParentCategoryId, src => src.ParentCategoryId > 0 ? src.ParentCategoryId : (long?)null)
.Map(dest => dest.SortOrder, src => src.SortOrder)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.ImageFile, src => (CreateFileModel)null);
config.NewConfig<UpdateDiscountCategoryRequest, UpdateDiscountCategoryCommand>()
.Map(dest => dest.CategoryId, src => src.CategoryId)
.Map(dest => dest.Name, src => src.Name)
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.Description, src => !string.IsNullOrWhiteSpace(src.Description) ? src.Description : null)
.Map(dest => dest.ParentCategoryId, src => src.ParentCategoryId > 0 ? src.ParentCategoryId : (long?)null)
.Map(dest => dest.SortOrder, src => src.SortOrder)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.ImageFile, src => (UpdateFileModel)null);
config.NewConfig<DeleteDiscountCategoryRequest, DeleteDiscountCategoryCommand>()
.Map(dest => dest.CategoryId, src => src.CategoryId);
// Map GetDiscountCategories response DTO to protobuf response
config.NewConfig<GetDiscountCategoriesResponseDto, GetDiscountCategoriesResponse>()
.MapWith(src => new GetDiscountCategoriesResponse
{
Categories = { src.Categories.Select(MapCategoryTree) }
});
}
private static DiscountCategoryDto MapCategoryTree(DiscountCategoryTreeDto src)
{
var dto = new DiscountCategoryDto
{
Id = src.Id,
Name = src.Name ?? string.Empty,
Title = src.Title ?? string.Empty,
Description = !string.IsNullOrWhiteSpace(src.Description) ? src.Description : null,
ImagePath = !string.IsNullOrWhiteSpace(src.ImagePath) ? src.ImagePath : null,
ParentCategoryId = src.ParentCategoryId,
SortOrder = src.SortOrder,
IsActive = src.IsActive,
ProductCount = src.ProductCount
};
// Recursively map children
if (src.ChildCategories != null && src.ChildCategories.Any())
{
dto.Children.AddRange(src.ChildCategories.Select(MapCategoryTree));
}
return dto;
}
}
@@ -0,0 +1,92 @@
using BackOffice.BFF.Application.DiscountProductCQ.Commands.CreateDiscountProduct;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProduct;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProduct;
using BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProducts;
using BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductById;
using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
using Mapster;
using CreateFileModel = BackOffice.BFF.Application.DiscountProductCQ.Commands.CreateDiscountProduct.DiscountProductFileModel;
using UpdateFileModel = BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProduct.DiscountProductFileModel;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class DiscountProductProfile : IRegister
{
public void Register(TypeAdapterConfig config)
{
// Request to Query/Command mappings
config.NewConfig<GetDiscountProductsRequest, GetDiscountProductsQuery>()
.Map(dest => dest.CategoryId, src => src.CategoryId)
.Map(dest => dest.SearchQuery, src => src.SearchQuery)
.Map(dest => dest.MinPrice, src => src.MinPrice)
.Map(dest => dest.MaxPrice, src => src.MaxPrice)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.InStock, src => src.InStock)
.Map(dest => dest.PageNumber, src => src.PageNumber)
.Map(dest => dest.PageSize, src => src.PageSize);
config.NewConfig<GetDiscountProductByIdRequest, GetDiscountProductByIdQuery>()
.Map(dest => dest.ProductId, src => src.ProductId);
config.NewConfig<CreateDiscountProductRequest, CreateDiscountProductCommand>()
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.ShortInformation, src => src.ShortInfomation)
.Map(dest => dest.FullInformation, src => src.FullInformation)
.Map(dest => dest.Price, src => src.Price)
.Map(dest => dest.MaxDiscountPercent, src => src.MaxDiscountPercent)
.Map(dest => dest.SortOrder, src => src.SortOrder)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.CategoryIds, src => src.CategoryIds)
.Map(dest => dest.ImageFile, src => src.ImageFile != null ? new CreateFileModel { File = src.ImageFile.File.ToByteArray(), FileName = src.ImageFile.FileName, Mime = src.ImageFile.Mime } : null)
.Map(dest => dest.ThumbnailFile, src => src.ThumbnailFile != null ? new CreateFileModel { File = src.ThumbnailFile.File.ToByteArray(), FileName = src.ThumbnailFile.FileName, Mime = src.ThumbnailFile.Mime } : null);
config.NewConfig<UpdateDiscountProductRequest, UpdateDiscountProductCommand>()
.Map(dest => dest.ProductId, src => src.ProductId)
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.ShortInformation, src => src.ShortInfomation)
.Map(dest => dest.FullInformation, src => src.FullInformation)
.Map(dest => dest.Price, src => src.Price)
.Map(dest => dest.MaxDiscountPercent, src => src.MaxDiscountPercent)
.Map(dest => dest.SortOrder, src => src.SortOrder)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.CategoryIds, src => src.CategoryIds)
.Map(dest => dest.ImageFile, src => src.ImageFile != null ? new UpdateFileModel { File = src.ImageFile.File.ToByteArray(), FileName = src.ImageFile.FileName, Mime = src.ImageFile.Mime } : null)
.Map(dest => dest.ThumbnailFile, src => src.ThumbnailFile != null ? new UpdateFileModel { File = src.ThumbnailFile.File.ToByteArray(), FileName = src.ThumbnailFile.FileName, Mime = src.ThumbnailFile.Mime } : null);
config.NewConfig<DeleteDiscountProductRequest, DeleteDiscountProductCommand>()
.Map(dest => dest.ProductId, src => src.ProductId);
// Response mappings
config.NewConfig<Application.DiscountProductCQ.Queries.GetDiscountProducts.GetDiscountProductsResponseDto, GetDiscountProductsResponse>()
.Map(dest => dest.MetaData, src => src.MetaData)
.Map(dest => dest.Models, src => src.Products);
config.NewConfig<Application.DiscountProductCQ.Queries.GetDiscountProducts.DiscountProductDto, BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct.DiscountProductDto>()
.Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.ShortInfomation, src => src.ShortInformation)
.Map(dest => dest.Price, src => src.Price)
.Map(dest => dest.MaxDiscountPercent, src => src.MaxDiscountPercent)
.Map(dest => dest.ImagePath, src => src.ImagePath)
.Map(dest => dest.ThumbnailPath, src => src.ThumbnailPath)
.Map(dest => dest.RemainingCount, src => src.RemainingCount)
.Map(dest => dest.ViewCount, src => src.ViewCount)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.Created, src => src.Created != DateTime.MinValue ? Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(src.Created.ToUniversalTime()) : null);
config.NewConfig<Application.DiscountProductCQ.Queries.GetDiscountProductById.GetDiscountProductByIdResponseDto, GetDiscountProductByIdResponse>()
.Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.ShortInfomation, src => src.ShortInformation)
.Map(dest => dest.FullInformation, src => src.FullInformation)
.Map(dest => dest.Price, src => src.Price)
.Map(dest => dest.MaxDiscountPercent, src => src.MaxDiscountPercent)
.Map(dest => dest.ImagePath, src => src.ImagePath)
.Map(dest => dest.ThumbnailPath, src => src.ThumbnailPath)
.Map(dest => dest.RemainingCount, src => src.RemainingCount)
.Map(dest => dest.ViewCount, src => src.ViewCount)
.Map(dest => dest.SortOrder, src => src.SortOrder)
.Map(dest => dest.IsActive, src => src.IsActive)
.Map(dest => dest.Created, src => src.Created != DateTime.MinValue ? Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(src.Created.ToUniversalTime()) : null);
}
}
@@ -0,0 +1,278 @@
using BackOffice.BFF.Application.Common.Models;
using BackOffice.BFF.Application.InventoryCQ.Commands.AddStock;
using BackOffice.BFF.Application.InventoryCQ.Commands.AdjustStock;
using BackOffice.BFF.Application.InventoryCQ.Commands.CreateWarehouse;
using BackOffice.BFF.Application.InventoryCQ.Commands.RecordLoss;
using BackOffice.BFF.Application.InventoryCQ.Commands.UpdateInventorySettings;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetAllInventoryItems;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetAllWarehouses;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetLowStockItems;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetStockMovements;
using Google.Protobuf.WellKnownTypes;
using BffProtos = Foursat.BackOffice.BFF.Inventory.Protos;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class InventoryProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// =============================================
// GetAllWarehouses
// =============================================
config.NewConfig<BffProtos.GetAllWarehousesRequest, GetAllWarehousesQuery>()
.MapWith(src => new GetAllWarehousesQuery
{
IsActive = src.IsActive != null ? src.IsActive.Value : null
});
config.NewConfig<GetAllWarehousesResponseDto, BffProtos.GetAllWarehousesResponse>()
.MapWith(src => new BffProtos.GetAllWarehousesResponse
{
TotalCount = src.TotalCount,
Warehouses = { src.Warehouses.Select(w => new BffProtos.WarehouseDto
{
Id = w.Id,
Name = w.Name ?? string.Empty,
Code = w.Code ?? string.Empty,
Address = w.Address ?? string.Empty,
IsDefault = w.IsDefault,
IsActive = w.IsActive,
Created = w.Created.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(w.Created.Value, DateTimeKind.Utc))
: null,
LastModified = w.LastModified.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(w.LastModified.Value, DateTimeKind.Utc))
: null
}) }
});
// =============================================
// CreateWarehouse
// =============================================
config.NewConfig<BffProtos.CreateWarehouseRequest, CreateWarehouseCommand>()
.MapWith(src => new CreateWarehouseCommand
{
Name = src.Name,
Code = src.Code,
Address = src.Address,
IsDefault = src.IsDefault,
IsActive = true
});
config.NewConfig<long, BffProtos.CreateWarehouseResponse>()
.MapWith(src => new BffProtos.CreateWarehouseResponse { Id = src });
// =============================================
// GetAllInventoryItems
// =============================================
config.NewConfig<BffProtos.GetAllInventoryItemsRequest, GetAllInventoryItemsQuery>()
.MapWith(src => new GetAllInventoryItemsQuery
{
Page = src.Page > 0 ? src.Page : 1,
PageSize = src.PageSize > 0 ? src.PageSize : 20,
WarehouseId = src.WarehouseId,
ProductType = src.ProductType != BffProtos.ProductType.Unspecified
? (int)src.ProductType
: null,
Search = string.IsNullOrWhiteSpace(src.Search) ? null : src.Search,
SortBy = string.IsNullOrWhiteSpace(src.SortBy) ? null : src.SortBy,
SortDesc = src.SortDesc
});
config.NewConfig<GetAllInventoryItemsResponseDto, BffProtos.GetAllInventoryItemsResponse>()
.MapWith(src => new BffProtos.GetAllInventoryItemsResponse
{
TotalCount = src.TotalCount,
MetaData = new BackOffice.BFF.Protobuf.Common.MetaData
{
CurrentPage = src.MetaData.CurrentPage,
PageSize = src.MetaData.PageSize,
TotalCount = src.MetaData.TotalCount,
TotalPage = src.MetaData.TotalPage
},
Items = { src.Items.Select(i => new BffProtos.InventoryItemDto
{
Id = i.Id,
ProductId = i.ProductId.HasValue ? i.ProductId.Value : null,
DiscountProductId = i.DiscountProductId.HasValue ? i.DiscountProductId.Value : null,
ProductType = (BffProtos.ProductType)i.ProductType,
ProductTitle = i.ProductTitle ?? string.Empty,
ProductPrice = i.ProductPrice,
Quantity = i.Quantity,
ReservedQuantity = i.ReservedQuantity,
AvailableQuantity = i.AvailableQuantity,
LowStockThreshold = i.LowStockThreshold,
ReorderPoint = i.ReorderPoint,
MaxStockLevel = i.MaxStockLevel,
LastRestockedAt = i.LastRestockedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(i.LastRestockedAt.Value, DateTimeKind.Utc))
: null,
LastSoldAt = i.LastSoldAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(i.LastSoldAt.Value, DateTimeKind.Utc))
: null,
WarehouseId = i.WarehouseId,
WarehouseName = i.WarehouseName ?? string.Empty,
IsLowStock = i.IsLowStock,
Created = i.Created.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(i.Created.Value, DateTimeKind.Utc))
: null
}) }
});
// =============================================
// GetLowStockItems
// =============================================
config.NewConfig<BffProtos.GetLowStockItemsRequest, GetLowStockItemsQuery>()
.MapWith(src => new GetLowStockItemsQuery
{
Page = src.Page > 0 ? src.Page : 1,
PageSize = src.PageSize > 0 ? src.PageSize : 20,
WarehouseId = src.WarehouseId,
ProductType = src.ProductType != BffProtos.ProductType.Unspecified
? (int)src.ProductType
: null
});
config.NewConfig<GetLowStockItemsResponseDto, BffProtos.GetLowStockItemsResponse>()
.MapWith(src => new BffProtos.GetLowStockItemsResponse
{
TotalCount = src.TotalCount,
Items = { src.Items.Select(i => new BffProtos.InventoryItemDto
{
Id = i.Id,
ProductId = i.ProductId.HasValue ? i.ProductId.Value : null,
DiscountProductId = i.DiscountProductId.HasValue ? i.DiscountProductId.Value : null,
ProductType = (BffProtos.ProductType)i.ProductType,
ProductTitle = i.ProductTitle ?? string.Empty,
ProductPrice = i.ProductPrice,
Quantity = i.Quantity,
ReservedQuantity = i.ReservedQuantity,
AvailableQuantity = i.AvailableQuantity,
LowStockThreshold = i.LowStockThreshold,
ReorderPoint = i.ReorderPoint,
MaxStockLevel = i.MaxStockLevel,
WarehouseId = i.WarehouseId,
WarehouseName = i.WarehouseName ?? string.Empty,
IsLowStock = i.IsLowStock
}) }
});
// =============================================
// GetStockMovements
// =============================================
config.NewConfig<BffProtos.GetStockMovementsRequest, GetStockMovementsQuery>()
.MapWith(src => new GetStockMovementsQuery
{
Page = src.Page > 0 ? src.Page : 1,
PageSize = src.PageSize > 0 ? src.PageSize : 20,
InventoryItemId = src.InventoryItemId,
ProductId = src.ProductId,
ProductType = src.ProductType != BffProtos.ProductType.Unspecified
? (int)src.ProductType
: null,
MovementType = src.MovementType != BffProtos.StockMovementType.MovementTypeUnspecified
? (int)src.MovementType
: null,
FromDate = src.FromDate != null ? src.FromDate.ToDateTime() : null,
ToDate = src.ToDate != null ? src.ToDate.ToDateTime() : null
});
config.NewConfig<GetStockMovementsResponseDto, BffProtos.GetStockMovementsResponse>()
.MapWith(src => new BffProtos.GetStockMovementsResponse
{
TotalCount = src.TotalCount,
MetaData = new BackOffice.BFF.Protobuf.Common.MetaData
{
CurrentPage = src.MetaData.CurrentPage,
PageSize = src.MetaData.PageSize,
TotalCount = src.MetaData.TotalCount,
TotalPage = src.MetaData.TotalPage
},
Movements = { src.Movements.Select(m => new BffProtos.StockMovementDto
{
Id = m.Id,
InventoryItemId = m.InventoryItemId,
MovementType = (BffProtos.StockMovementType)m.MovementType,
Quantity = m.Quantity,
QuantityBefore = m.QuantityBefore,
QuantityAfter = m.QuantityAfter,
OrderId = m.OrderId.HasValue ? m.OrderId.Value : null,
DiscountOrderId = m.DiscountOrderId.HasValue ? m.DiscountOrderId.Value : null,
ReferenceNumber = m.ReferenceNumber ?? string.Empty,
Note = m.Note ?? string.Empty,
PerformedByUserId = m.PerformedByUserId.HasValue ? m.PerformedByUserId.Value : null,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc)),
ProductTitle = m.ProductTitle ?? string.Empty
}) }
});
// =============================================
// AddStock
// =============================================
config.NewConfig<BffProtos.AddStockRequest, AddStockCommand>()
.MapWith(src => new AddStockCommand
{
ProductId = src.ProductId,
ProductType = (int)src.ProductType,
Quantity = src.Quantity,
ReferenceNumber = src.ReferenceNumber,
Note = src.Note,
WarehouseId = src.WarehouseId
});
config.NewConfig<AddStockResponseDto, BffProtos.AddStockResponse>()
.MapWith(src => new BffProtos.AddStockResponse
{
InventoryItemId = src.InventoryItemId,
NewQuantity = src.NewQuantity
});
// =============================================
// AdjustStock
// =============================================
config.NewConfig<BffProtos.AdjustStockRequest, AdjustStockCommand>()
.MapWith(src => new AdjustStockCommand
{
ProductId = src.ProductId,
ProductType = (int)src.ProductType,
NewQuantity = src.NewQuantity,
Reason = src.Reason,
ReferenceNumber = src.ReferenceNumber
});
config.NewConfig<AdjustStockResponseDto, BffProtos.AdjustStockResponse>()
.MapWith(src => new BffProtos.AdjustStockResponse
{
PreviousQuantity = src.PreviousQuantity,
NewQuantity = src.NewQuantity,
Difference = src.Difference
});
// =============================================
// RecordLoss
// =============================================
config.NewConfig<BffProtos.RecordLossRequest, RecordLossCommand>()
.MapWith(src => new RecordLossCommand
{
ProductId = src.ProductId,
ProductType = (int)src.ProductType,
Quantity = src.Quantity,
LossType = (int)src.LossType,
Reason = src.Reason,
ReferenceNumber = src.ReferenceNumber
});
// =============================================
// UpdateInventorySettings
// =============================================
config.NewConfig<BffProtos.UpdateInventorySettingsRequest, UpdateInventorySettingsCommand>()
.MapWith(src => new UpdateInventorySettingsCommand
{
Id = src.Id,
LowStockThreshold = src.LowStockThreshold,
ReorderPoint = src.ReorderPoint,
MaxStockLevel = src.MaxStockLevel
});
}
}
@@ -1,53 +1,26 @@
using BackOffice.BFF.Application.ManualPaymentCQ.Commands.CreateManualPayment;
using BackOffice.BFF.ManualPayment.Protobuf;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class ManualPaymentProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// GetManualPaymentsResponseDto -> GetManualPaymentsResponse
// config.NewConfig<GetManualPaymentsResponseDto, GetManualPaymentsResponse>()
// .MapWith(src => new GetManualPaymentsResponse
// {
// MetaData = new MetaData
// {
// CurrentPage = src.MetaData.PageNumber,
// PageSize = src.MetaData.PageSize,
// TotalCount = src.MetaData.TotalCount,
// TotalPage = (int)Math.Ceiling((double)src.MetaData.TotalCount / src.MetaData.PageSize)
// },
// Models = { (src.Models ?? Enumerable.Empty<ManualPaymentModel>())
// .Select(m => new ManualPaymentProtoModel
// {
// Id = m.Id,
// UserId = m.UserId,
// UserFullName = m.UserFullName ?? string.Empty,
// UserMobile = m.UserMobile ?? string.Empty,
// Amount = m.Amount,
// Type = m.Type,
// TypeDisplay = m.TypeDisplay ?? string.Empty,
// Description = m.Description ?? string.Empty,
// ReferenceNumber = m.ReferenceNumber ?? string.Empty,
// Status = m.Status,
// StatusDisplay = m.StatusDisplay ?? string.Empty,
// RequestedBy = m.RequestedBy,
// RequestedByName = m.RequestedByName ?? string.Empty,
// ApprovedBy = m.ApprovedBy ?? 0,
// ApprovedByName = m.ApprovedByName ?? string.Empty,
// ApprovedAt = m.ApprovedAt.HasValue
// ? Timestamp.FromDateTime(DateTime.SpecifyKind(m.ApprovedAt.Value, DateTimeKind.Utc))
// : null,
// RejectionReason = m.RejectionReason ?? string.Empty,
// TransactionId = m.TransactionId ?? 0,
// Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc))
// }) }
// });
// FileUploadModel -> FileUploadDto
config.NewConfig<FileUploadModel, FileUploadDto>()
.Map(dest => dest.File, src => src.File.ToByteArray())
.Map(dest => dest.FileName, src => src.FileName)
.Map(dest => dest.Mime, src => src.Mime);
// CreateManualPaymentResponseDto -> CreateManualPaymentResponse
// config.NewConfig<CreateManualPaymentResponseDto, CreateManualPaymentResponse>()
// .MapWith(src => new CreateManualPaymentResponse
// {
// Id = src.Id,
// TransactionId = src.TransactionId
// });
// CreateManualPaymentRequest -> CreateManualPaymentCommand
config.NewConfig<CreateManualPaymentRequest, CreateManualPaymentCommand>()
.Map(dest => dest.UserId, src => src.UserId)
.Map(dest => dest.Amount, src => src.Amount)
.Map(dest => dest.Type, src => (int)src.Type)
.Map(dest => dest.Description, src => src.Description)
.Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber)
.Map(dest => dest.ImagePath, src => src.ImagePath)
.Map(dest => dest.ImageFile, src => src.ImageFile);
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
FROM 194.5.195.53:32082/dotnet/aspnet:9.0 AS base
USER $APP_UID
WORKDIR /app
WORKDATA /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
FROM 194.5.195.53:32082/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["BackOffice.BFF.WebApi/NuGet.config", "NuGet.config"]
+6 -10
View File
@@ -1,18 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="FourSat" value="https://git.afrino.co/api/packages/FourSat/nuget/index.json" />
<add key="Afrino" value="https://git.afrino.co/api/packages/Afrino/nuget/index.json" />
<clear />
<!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
</packageSources>
<packageSourceCredentials>
<FourSat>
<add key="Username" value="masoud" />
<Nexus>
<add key="Username" value="admin" />
<add key="ClearTextPassword" value="87zH26nbqT" />
</FourSat>
<Afrino>
<add key="Username" value="systemuser" />
<add key="ClearTextPassword" value="sZSA7PTiv3pUSQZ" />
</Afrino>
</Nexus>
</packageSourceCredentials>
</configuration>
@@ -0,0 +1,38 @@
using BackOffice.BFF.DiscountCategory.Protobuf.Protos.DiscountCategory;
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.DiscountCategoryCQ.Commands.CreateDiscountCategory;
using BackOffice.BFF.Application.DiscountCategoryCQ.Commands.UpdateDiscountCategory;
using BackOffice.BFF.Application.DiscountCategoryCQ.Commands.DeleteDiscountCategory;
using BackOffice.BFF.Application.DiscountCategoryCQ.Queries.GetDiscountCategories;
namespace BackOffice.BFF.WebApi.Services;
public class DiscountCategoryService : DiscountCategoryContract.DiscountCategoryContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public DiscountCategoryService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
public override async Task<CreateDiscountCategoryResponse> CreateDiscountCategory(CreateDiscountCategoryRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateDiscountCategoryRequest, CreateDiscountCategoryCommand, CreateDiscountCategoryResponse>(request, context);
}
public override async Task<Empty> UpdateDiscountCategory(UpdateDiscountCategoryRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateDiscountCategoryRequest, UpdateDiscountCategoryCommand>(request, context);
}
public override async Task<Empty> DeleteDiscountCategory(DeleteDiscountCategoryRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteDiscountCategoryRequest, DeleteDiscountCategoryCommand>(request, context);
}
public override async Task<GetDiscountCategoriesResponse> GetDiscountCategories(GetDiscountCategoriesRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetDiscountCategoriesRequest, GetDiscountCategoriesQuery, GetDiscountCategoriesResponse>(request, context);
}
}
@@ -0,0 +1,59 @@
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.DiscountOrderCQ.Commands.PlaceOrder;
using BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment;
using BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus;
using BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetOrderById;
using BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetUserOrders;
using BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetAllDiscountOrders;
using BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetDiscountSalesReport;
namespace BackOffice.BFF.WebApi.Services;
public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public DiscountOrderService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
// Order Management
public override async Task<PlaceOrderResponse> PlaceOrder(PlaceOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<PlaceOrderRequest, PlaceOrderCommand, PlaceOrderResponse>(request, context);
}
public override async Task<CompleteOrderPaymentResponse> CompleteOrderPayment(CompleteOrderPaymentRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CompleteOrderPaymentRequest, CompleteOrderPaymentCommand, CompleteOrderPaymentResponse>(request, context);
}
public override async Task<UpdateOrderStatusResponse> UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateOrderStatusRequest, UpdateOrderStatusCommand, UpdateOrderStatusResponse>(request, context);
}
// Order Queries
public override async Task<GetOrderByIdResponse> GetOrderById(GetOrderByIdRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetOrderByIdRequest, GetOrderByIdQuery, GetOrderByIdResponse>(request, context);
}
public override async Task<GetUserOrdersResponse> GetUserOrders(GetUserOrdersRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetUserOrdersRequest, GetUserOrdersQuery, GetUserOrdersResponse>(request, context);
}
// Admin APIs
public override async Task<GetAllDiscountOrdersResponse> GetAllDiscountOrders(GetAllDiscountOrdersRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllDiscountOrdersRequest, GetAllDiscountOrdersQuery, GetAllDiscountOrdersResponse>(request, context);
}
public override async Task<GetDiscountSalesReportResponse> GetDiscountSalesReport(GetDiscountSalesReportRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetDiscountSalesReportRequest, GetDiscountSalesReportQuery, GetDiscountSalesReportResponse>(request, context);
}
}
@@ -0,0 +1,76 @@
using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.CreateDiscountProduct;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProduct;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProduct;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.AddDiscountProductImage;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProductImage;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProductImage;
using BackOffice.BFF.Application.DiscountProductCQ.Commands.ReorderDiscountProductImages;
using BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductById;
using BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProducts;
using BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductImages;
namespace BackOffice.BFF.WebApi.Services;
public class DiscountProductService : DiscountProductContract.DiscountProductContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public DiscountProductService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
// Product CRUD
public override async Task<CreateDiscountProductResponse> CreateDiscountProduct(CreateDiscountProductRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateDiscountProductRequest, CreateDiscountProductCommand, CreateDiscountProductResponse>(request, context);
}
public override async Task<Empty> UpdateDiscountProduct(UpdateDiscountProductRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateDiscountProductRequest, UpdateDiscountProductCommand>(request, context);
}
public override async Task<Empty> DeleteDiscountProduct(DeleteDiscountProductRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteDiscountProductRequest, DeleteDiscountProductCommand>(request, context);
}
public override async Task<GetDiscountProductByIdResponse> GetDiscountProductById(GetDiscountProductByIdRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetDiscountProductByIdRequest, GetDiscountProductByIdQuery, GetDiscountProductByIdResponse>(request, context);
}
public override async Task<GetDiscountProductsResponse> GetDiscountProducts(GetDiscountProductsRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetDiscountProductsRequest, GetDiscountProductsQuery, GetDiscountProductsResponse>(request, context);
}
// Product Image Gallery
public override async Task<AddDiscountProductImageResponse> AddDiscountProductImage(AddDiscountProductImageRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AddDiscountProductImageRequest, AddDiscountProductImageCommand, AddDiscountProductImageResponse>(request, context);
}
public override async Task<Empty> UpdateDiscountProductImage(UpdateDiscountProductImageRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateDiscountProductImageRequest, UpdateDiscountProductImageCommand>(request, context);
}
public override async Task<Empty> DeleteDiscountProductImage(DeleteDiscountProductImageRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteDiscountProductImageRequest, DeleteDiscountProductImageCommand>(request, context);
}
public override async Task<Empty> ReorderDiscountProductImages(ReorderDiscountProductImagesRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<ReorderDiscountProductImagesRequest, ReorderDiscountProductImagesCommand>(request, context);
}
public override async Task<GetDiscountProductImagesResponse> GetDiscountProductImages(GetDiscountProductImagesRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetDiscountProductImagesRequest, GetDiscountProductImagesQuery, GetDiscountProductImagesResponse>(request, context);
}
}
@@ -0,0 +1,44 @@
using BackOffice.BFF.DiscountShoppingCart.Protobuf.Protos.DiscountShoppingCart;
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.AddToCart;
using BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.RemoveFromCart;
using BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.UpdateCartItemCount;
using BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.ClearCart;
using BackOffice.BFF.Application.DiscountShoppingCartCQ.Queries.GetUserCart;
namespace BackOffice.BFF.WebApi.Services;
public class DiscountShoppingCartService : DiscountShoppingCartContract.DiscountShoppingCartContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public DiscountShoppingCartService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
public override async Task<AddToCartResponse> AddToCart(AddToCartRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AddToCartRequest, AddToCartCommand, AddToCartResponse>(request, context);
}
public override async Task<RemoveFromCartResponse> RemoveFromCart(RemoveFromCartRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<RemoveFromCartRequest, RemoveFromCartCommand, RemoveFromCartResponse>(request, context);
}
public override async Task<UpdateCartItemCountResponse> UpdateCartItemCount(UpdateCartItemCountRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateCartItemCountRequest, UpdateCartItemCountCommand, UpdateCartItemCountResponse>(request, context);
}
public override async Task<GetUserCartResponse> GetUserCart(GetUserCartRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetUserCartRequest, GetUserCartQuery, GetUserCartResponse>(request, context);
}
public override async Task<Empty> ClearCart(ClearCartRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<ClearCartRequest, ClearCartCommand>(request, context);
}
}
@@ -0,0 +1,104 @@
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.InventoryCQ.Commands.AddStock;
using BackOffice.BFF.Application.InventoryCQ.Commands.AdjustStock;
using BackOffice.BFF.Application.InventoryCQ.Commands.CreateWarehouse;
using BackOffice.BFF.Application.InventoryCQ.Commands.RecordLoss;
using BackOffice.BFF.Application.InventoryCQ.Commands.UpdateInventorySettings;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetAllInventoryItems;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetAllWarehouses;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetLowStockItems;
using BackOffice.BFF.Application.InventoryCQ.Queries.GetStockMovements;
using Foursat.BackOffice.BFF.Inventory.Protos;
namespace BackOffice.BFF.WebApi.Services;
public class InventoryService : InventoryBFFContract.InventoryBFFContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
public override async Task<GetAllWarehousesResponse> GetAllWarehouses(
GetAllWarehousesRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllWarehousesRequest, GetAllWarehousesQuery, GetAllWarehousesResponse>(
request,
context);
}
public override async Task<CreateWarehouseResponse> CreateWarehouse(
CreateWarehouseRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateWarehouseRequest, CreateWarehouseCommand, CreateWarehouseResponse>(
request,
context);
}
public override async Task<GetAllInventoryItemsResponse> GetAllInventoryItems(
GetAllInventoryItemsRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllInventoryItemsRequest, GetAllInventoryItemsQuery, GetAllInventoryItemsResponse>(
request,
context);
}
public override async Task<GetLowStockItemsResponse> GetLowStockItems(
GetLowStockItemsRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetLowStockItemsRequest, GetLowStockItemsQuery, GetLowStockItemsResponse>(
request,
context);
}
public override async Task<GetStockMovementsResponse> GetStockMovements(
GetStockMovementsRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetStockMovementsRequest, GetStockMovementsQuery, GetStockMovementsResponse>(
request,
context);
}
public override async Task<AddStockResponse> AddStock(
AddStockRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AddStockRequest, AddStockCommand, AddStockResponse>(
request,
context);
}
public override async Task<AdjustStockResponse> AdjustStock(
AdjustStockRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AdjustStockRequest, AdjustStockCommand, AdjustStockResponse>(
request,
context);
}
public override async Task<Empty> RecordLoss(
RecordLossRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<RecordLossRequest, RecordLossCommand>(
request,
context);
}
public override async Task<Empty> UpdateInventorySettings(
UpdateInventorySettingsRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateInventorySettingsRequest, UpdateInventorySettingsCommand>(
request,
context);
}
}
@@ -0,0 +1,44 @@
using BackOffice.BFF.ProductTag.Protobuf.Protos.ProductTag;
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.ProductTagCQ.Commands.AssignTagToProduct;
using BackOffice.BFF.Application.ProductTagCQ.Commands.UpdateProductTag;
using BackOffice.BFF.Application.ProductTagCQ.Commands.DeleteProductTag;
using BackOffice.BFF.Application.ProductTagCQ.Queries.GetProductTag;
using BackOffice.BFF.Application.ProductTagCQ.Queries.GetAllProductTags;
namespace BackOffice.BFF.WebApi.Services;
public class ProductTagService : ProductTagContract.ProductTagContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public ProductTagService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
public override async Task<CreateNewProductTagResponse> CreateNewProductTag(CreateNewProductTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateNewProductTagRequest, AssignTagToProductCommand, CreateNewProductTagResponse>(request, context);
}
public override async Task<Empty> UpdateProductTag(UpdateProductTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateProductTagRequest, UpdateProductTagCommand>(request, context);
}
public override async Task<Empty> DeleteProductTag(DeleteProductTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteProductTagRequest, DeleteProductTagCommand>(request, context);
}
public override async Task<GetProductTagResponse> GetProductTag(GetProductTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetProductTagRequest, GetProductTagQuery, GetProductTagResponse>(request, context);
}
public override async Task<GetAllProductTagByFilterResponse> GetAllProductTagByFilter(GetAllProductTagByFilterRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllProductTagByFilterRequest, GetAllProductTagsQuery, GetAllProductTagByFilterResponse>(request, context);
}
}
@@ -0,0 +1,50 @@
using BackOffice.BFF.Tag.Protobuf.Protos.Tag;
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.TagCQ.Commands.CreateTag;
using BackOffice.BFF.Application.TagCQ.Commands.UpdateTag;
using BackOffice.BFF.Application.TagCQ.Commands.DeleteTag;
using BackOffice.BFF.Application.TagCQ.Queries.GetTag;
using BackOffice.BFF.Application.TagCQ.Queries.GetAllTags;
using BackOffice.BFF.Application.TagCQ.Queries.GetProductsByTag;
namespace BackOffice.BFF.WebApi.Services;
public class TagService : TagContract.TagContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public TagService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
public override async Task<CreateNewTagResponse> CreateNewTag(CreateNewTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateNewTagRequest, CreateTagCommand, CreateNewTagResponse>(request, context);
}
public override async Task<Empty> UpdateTag(UpdateTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateTagRequest, UpdateTagCommand>(request, context);
}
public override async Task<Empty> DeleteTag(DeleteTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteTagRequest, DeleteTagCommand>(request, context);
}
public override async Task<GetTagResponse> GetTag(GetTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetTagRequest, GetTagQuery, GetTagResponse>(request, context);
}
public override async Task<GetAllTagByFilterResponse> GetAllTagByFilter(GetAllTagByFilterRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllTagByFilterRequest, GetAllTagsQuery, GetAllTagByFilterResponse>(request, context);
}
public override async Task<GetProductsByTagResponse> GetProductsByTag(GetProductsByTagRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetProductsByTagRequest, GetProductsByTagQuery, GetProductsByTagResponse>(request, context);
}
}
@@ -15,7 +15,7 @@
},
"Authentication": {
"Authority": "https://ids.domain.com/",
"Audience": "domain_api"
"Audience": "domain_api"
},
"Kavenegar": {
"Sender": "1000001110100",
+1 -1
View File
@@ -3,7 +3,7 @@
"JwtIssuer": "https://localhost",
"JwtAudience": "https://localhost",
"JwtExpiryInDays": 365,
"AllowedHosts": "*",
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
+15
View File
@@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.Configuratio
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.PublicMessage.Protobuf", "Protobufs\BackOffice.BFF.PublicMessage.Protobuf\BackOffice.BFF.PublicMessage.Protobuf.csproj", "{3454F4C0-A6C8-44BC-9389-6248518E3EA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackOffice.BFF.Inventory.Protobuf", "Protobufs\BackOffice.BFF.Inventory.Protobuf\BackOffice.BFF.Inventory.Protobuf.csproj", "{0FFE3E2F-4F78-4376-B373-51830C447EEE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -393,6 +395,18 @@ Global
{3454F4C0-A6C8-44BC-9389-6248518E3EA6}.Release|x64.Build.0 = Release|Any CPU
{3454F4C0-A6C8-44BC-9389-6248518E3EA6}.Release|x86.ActiveCfg = Release|Any CPU
{3454F4C0-A6C8-44BC-9389-6248518E3EA6}.Release|x86.Build.0 = Release|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Debug|x64.ActiveCfg = Debug|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Debug|x64.Build.0 = Debug|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Debug|x86.ActiveCfg = Debug|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Debug|x86.Build.0 = Debug|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Release|Any CPU.Build.0 = Release|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Release|x64.ActiveCfg = Release|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Release|x64.Build.0 = Release|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Release|x86.ActiveCfg = Release|Any CPU
{0FFE3E2F-4F78-4376-B373-51830C447EEE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -421,6 +435,7 @@ Global
{1F95197B-9118-4C19-9C1B-C0872AA8F412} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{FB2AAF65-F9DC-4315-979E-A77EC44C5FB1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{3454F4C0-A6C8-44BC-9389-6248518E3EA6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{0FFE3E2F-4F78-4376-B373-51830C447EEE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0AE1AB4A-3C91-4853-93C2-C2476E79F845}
@@ -31,7 +31,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -34,7 +34,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -36,7 +36,7 @@
</ItemGroup>
<Target Name="PushPackage" AfterTargets="Pack">
<Exec Command="dotnet nuget push $(OutputPath)..\$(PackageId).$(Version).nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate" />
<Exec Command="dotnet nuget push $(OutputPath)..\$(PackageId).$(Version).nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate" />
</Target>
</Project>
@@ -26,7 +26,7 @@
</ItemGroup>
<Target Name="PushPackage" AfterTargets="Pack">
<Exec Command="dotnet nuget push $(OutputPath)..\$(PackageId).$(Version).nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate"/>
<Exec Command="dotnet nuget push $(OutputPath)..\$(PackageId).$(Version).nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate"/>
</Target>
</Project>
@@ -36,7 +36,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.3</Version>
<Version>0.0.4</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\discountcategory.proto" ProtoRoot="Protos\" GrpcServices="Client" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
<Protobuf Include="Protos\discountcategory.proto" ProtoRoot="Protos\" GrpcServices="Both" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
</ItemGroup>
<ItemGroup>
@@ -33,7 +33,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.3</Version>
<Version>0.0.4</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\discountorder.proto" ProtoRoot="Protos\" GrpcServices="Client" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
<Protobuf Include="Protos\discountorder.proto" ProtoRoot="Protos\" GrpcServices="Both" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
</ItemGroup>
<ItemGroup>
@@ -33,7 +33,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -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)
@@ -175,3 +189,118 @@ message OrderSummaryDto
int32 items_count = 9;
google.protobuf.Timestamp created = 10;
}
// ===== 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;
}
@@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.3</Version>
<Version>0.0.4</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\discountproduct.proto" ProtoRoot="Protos\" GrpcServices="Client" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
<Protobuf Include="Protos\discountproduct.proto" ProtoRoot="Protos\" GrpcServices="Both" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
</ItemGroup>
<ItemGroup>
@@ -33,7 +33,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -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;
}
// Image File Model for upload
message ImageFileModel
{
bytes file = 1;
string mime = 2;
string file_name = 3;
}
@@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.3</Version>
<Version>0.0.4</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\discountshoppingcart.proto" ProtoRoot="Protos\" GrpcServices="Client" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
<Protobuf Include="Protos\discountshoppingcart.proto" ProtoRoot="Protos\" GrpcServices="Both" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
</ItemGroup>
<ItemGroup>
@@ -33,7 +33,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -28,7 +28,7 @@
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source https://git.afrino.co/api/packages/FourSat/nuget/index.json --api-key 061a5cb15517c6da39c16cfce8556c55ae104d0d --skip-duplicate
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<PackageId>Foursat.BackOffice.BFF.Inventory.Protobuf</PackageId>
<Version>0.0.1</Version>
<Authors>FourSat</Authors>
<Company>FourSat</Company>
<Product>Foursat.BackOffice.BFF.Inventory.Protobuf</Product>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
<PackageReference Include="Grpc.Core.Api" Version="2.54.0" />
<PackageReference Include="Grpc.Tools" Version="2.72.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.2.2" />
<PackageReference Include="Google.Api.CommonProtos" Version="2.10.0" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\inventory.proto" ProtoRoot="Protos\" GrpcServices="Both" AdditionalImportDirs="..\BackOffice.BFF.Common.Protobuf\Protos"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BackOffice.BFF.Common.Protobuf\BackOffice.BFF.Common.Protobuf.csproj" />
</ItemGroup>
<Target Name="PushToFourSat" AfterTargets="Pack">
<PropertyGroup>
<NugetPackagePath>$(PackageOutputPath)$(PackageId).$(Version).nupkg</NugetPackagePath>
<PushCommand>
dotnet nuget push **/*.nupkg --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json --api-key admin:87zH26nbqT --skip-duplicate
</PushCommand>
</PropertyGroup>
<Exec Command="$(PushCommand)" />
</Target>
</Project>

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