Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 4m15s

This commit is contained in:
masoodafar-web
2026-09-04 07:26:01 +03:30
17 changed files with 228 additions and 93 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
## ##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files # User-specific files
*.rsuser *.rsuser
+1 -1
View File
@@ -1,4 +1,4 @@
# Docs moved to totalDoc # Docs moved to totalDoc
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation. See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
-1
View File
@@ -101,7 +101,6 @@
| 6.6 | `UserOrderContract.SubmitShopBuyOrder` | `UserOrderContract` | `/checkout-summary` | ثبت سفارش و پرداخت از کیف پول | | 6.6 | `UserOrderContract.SubmitShopBuyOrder` | `UserOrderContract` | `/checkout-summary` | ثبت سفارش و پرداخت از کیف پول |
| 6.7 | `OrderService.GetOrdersAsync` | `UserOrderContract.GetCustomerOrders` | `/orders` | لیست سفارش‌های کاربر | | 6.7 | `OrderService.GetOrdersAsync` | `UserOrderContract.GetCustomerOrders` | `/orders` | لیست سفارش‌های کاربر |
| 6.8 | `OrderService.GetOrderAsync` | `UserOrderContract.GetCustomerOrder` | `/order/{id}`, `/order-tracking/{id}` | جزئیات یک سفارش | | 6.8 | `OrderService.GetOrderAsync` | `UserOrderContract.GetCustomerOrder` | `/order/{id}`, `/order-tracking/{id}` | جزئیات یک سفارش |
| 6.9 | `OrderService.CalculateOrderPVAsync` | `UserOrderContract.CalculateOrderPV` | `/order/{id}` | محاسبه PV سفارش |
| 6.10 | `VATService.GetRateAsync` | `UserOrderContract` (via scope) | Store/Discount صفحات قیمت | نرخ مالیات بر ارزش افزوده (کش روزانه) | | 6.10 | `VATService.GetRateAsync` | `UserOrderContract` (via scope) | Store/Discount صفحات قیمت | نرخ مالیات بر ارزش افزوده (کش روزانه) |
| 6.11 | `WalletService.GetBalancesAsync` | `UserWalletContract` | `/checkout-summary` | بررسی موجودی برای پرداخت سفارش | | 6.11 | `WalletService.GetBalancesAsync` | `UserWalletContract` | `/checkout-summary` | بررسی موجودی برای پرداخت سفارش |
+2 -3
View File
@@ -10,9 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" /> <PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf --> <!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf (CI/CD packs → Nexus) -->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.204" /> <PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.211" />
<!-- <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
<PackageReference Include="MudBlazor" Version="8.14.0" /> <PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> <PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Mapster" Version="7.4.0" /> <PackageReference Include="Mapster" Version="7.4.0" />
@@ -22,9 +22,11 @@
else if (_addresses.Count == 0) else if (_addresses.Count == 0)
{ {
<MudAlert Severity="Severity.Warning"> <MudAlert Severity="Severity.Warning">
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید. هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید.
</MudAlert> </MudAlert>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton> <MudButton Class="mt-2" Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس</MudButton>
} }
else else
{ {
@@ -41,13 +43,25 @@
<MudText Typo="Typo.subtitle2">@address.Title</MudText> <MudText Typo="Typo.subtitle2">@address.Title</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText> <MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
</MudStack> </MudStack>
@if (address.IsDefault) <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
{ @if (address.IsDefault)
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیش‌فرض</MudChip> {
} <MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیش‌فرض</MudChip>
}
<span @onclick:stopPropagation="true">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
aria-label="ویرایش آدرس"
OnClick="@(() => OpenEditAddressDialog(address))" />
</span>
</MudStack>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
} }
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
</MudStack> </MudStack>
} }
</MudPaper> </MudPaper>
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Pages.Profile.Components;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using MudBlazor; using MudBlazor;
@@ -61,12 +62,14 @@ public partial class Checkout
else else
{ {
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
finally finally
{ {
@@ -75,6 +78,30 @@ public partial class Checkout
} }
} }
private async Task OpenAddAddressDialog()
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (result is { Canceled: false })
{
await AuthService.RefreshTokenAsync();
await LoadAddresses();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
{
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
{
{ x => x.Model, address }
});
var result = await dialog.Result;
if (result is { Canceled: false })
{
await LoadAddresses();
}
}
private async Task PlaceOrder() private async Task PlaceOrder()
{ {
if (!CanPlaceOrder || _selectedAddress is null) if (!CanPlaceOrder || _selectedAddress is null)
@@ -52,29 +52,47 @@
<div id="org-chart-container" class="org-chart-container"></div> <div id="org-chart-container" class="org-chart-container"></div>
} }
@* Stats Bar *@ @* Stats Bar — فقط آمار خود کاربر (نه نودهای درخت) *@
@if (_statistics != null && !_isLoading) @if (_statistics != null && !_isLoading)
{ {
<div class="org-chart-stats"> <div class="org-chart-stats">
<div class="stat-item"> <div class="org-chart-stats-row">
<span class="stat-label">کل اعضا:</span> <div class="stat-item">
<span class="stat-value">@_statistics.TotalMembers</span> <span class="stat-label">کل اعضا:</span>
<span class="stat-value">@_statistics.TotalMembers</span>
</div>
<div class="stat-item">
<span class="stat-label">سازمان چپ:</span>
<span class="stat-value left">@_statistics.LeftLegCount</span>
</div>
<div class="stat-item">
<span class="stat-label">سازمان راست:</span>
<span class="stat-value right">@_statistics.RightLegCount</span>
</div>
<div class="stat-item">
<span class="stat-label">عمق:</span>
<span class="stat-value">@_statistics.TreeDepth</span>
</div>
<div class="stat-item">
<span class="stat-label">پای ضعیف:</span>
<span class="stat-value">@(_statistics.WeakerLeg == "Left" ? "چپ" : "راست")</span>
</div>
</div> </div>
<div class="stat-item"> <div class="org-chart-stats-row leg-scores">
<span class="stat-label">سازمان چپ:</span> <div class="stat-item leg-score-gold">
<span class="stat-value left">@_statistics.LeftLegCount</span> <span class="stat-label">پکیج طلایی:</span>
<span class="stat-value left">چپ @_statistics.GoldLeftLegTotal</span>
<span class="stat-sep">·</span>
<span class="stat-value right">راست @_statistics.GoldRightLegTotal</span>
</div>
</div> </div>
<div class="stat-item"> <div class="org-chart-stats-row leg-scores">
<span class="stat-label">سازمان راست:</span> <div class="stat-item leg-score-silver">
<span class="stat-value right">@_statistics.RightLegCount</span> <span class="stat-label">پکیج نقره‌ای:</span>
</div> <span class="stat-value left">چپ @_statistics.SilverLeftLegTotal</span>
<div class="stat-item"> <span class="stat-sep">·</span>
<span class="stat-label">عمق:</span> <span class="stat-value right">راست @_statistics.SilverRightLegTotal</span>
<span class="stat-value">@_statistics.TreeDepth</span> </div>
</div>
<div class="stat-item">
<span class="stat-label">پای ضعیف:</span>
<span class="stat-value">@(_statistics.WeakerLeg == "Left" ? "چپ" : "راست")</span>
</div> </div>
</div> </div>
} }
@@ -1,3 +1,4 @@
using DateTimeConverterCL;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
@@ -108,7 +109,9 @@ public partial class OrganizationChart : IAsyncDisposable
isActive = n.IsActive, isActive = n.IsActive,
isClubActive = n.IsClubActive, isClubActive = n.IsClubActive,
activationWeekNumber = n.ActivationWeekNumber, activationWeekNumber = n.ActivationWeekNumber,
joinedAt = n.JoinedAt, registeredAt = n.JoinedAt.HasValue
? n.JoinedAt.Value.ToLocalTime().MiladiToJalali()
: "",
referralCode = n.ReferralCode, referralCode = n.ReferralCode,
packageName = n.PackageName ?? string.Empty packageName = n.PackageName ?? string.Empty
}).ToArray(); }).ToArray();
@@ -21,9 +21,11 @@
else if (_addresses.Count == 0) else if (_addresses.Count == 0)
{ {
<MudAlert Severity="Severity.Warning"> <MudAlert Severity="Severity.Warning">
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید. هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید.
</MudAlert> </MudAlert>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton> <MudButton Class="mt-2" Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس</MudButton>
} }
else else
{ {
@@ -40,13 +42,25 @@
<MudText Typo="Typo.subtitle2">@address.Title</MudText> <MudText Typo="Typo.subtitle2">@address.Title</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText> <MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
</MudStack> </MudStack>
@if (address.IsDefault) <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
{ @if (address.IsDefault)
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیش‌فرض</MudChip> {
} <MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیش‌فرض</MudChip>
}
<span @onclick:stopPropagation="true">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
aria-label="ویرایش آدرس"
OnClick="@(() => OpenEditAddressDialog(address))" />
</span>
</MudStack>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
} }
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
</MudStack> </MudStack>
} }
</MudPaper> </MudPaper>
@@ -1,5 +1,6 @@
using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserAddress;
using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserOrder;
using FrontOffice.Main.Pages.Profile.Components;
using FrontOffice.Main.Shared; using FrontOffice.Main.Shared;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -72,12 +73,14 @@ public partial class CheckoutSummary : ComponentBase
else else
{ {
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new(); _addresses = new();
_selectedAddress = null;
} }
finally finally
{ {
@@ -86,6 +89,30 @@ public partial class CheckoutSummary : ComponentBase
} }
} }
private async Task OpenAddAddressDialog()
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (result is { Canceled: false })
{
await AuthService.RefreshTokenAsync();
await LoadAddresses();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
{
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
{
{ x => x.Model, address }
});
var result = await dialog.Result;
if (result is { Canceled: false })
{
await LoadAddresses();
}
}
private async Task PlaceOrder() private async Task PlaceOrder()
{ {
if (!CanPlaceOrder || _selectedAddress is null) if (!CanPlaceOrder || _selectedAddress is null)
@@ -112,30 +112,6 @@ else
</MudStack> </MudStack>
} }
</MudStack> </MudStack>
@* نمایش PV سفارش *@
@if (_pvData != null && _pvData.TotalPv > 0)
{
<MudDivider Class="my-2" />
<MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Info" Size="Size.Small" />
<MudText Typo="Typo.subtitle2" Color="Color.Info">امتیاز فروش (PV)</MudText>
</MudStack>
@foreach (var product in _pvData.Products)
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.caption" Class="mud-text-secondary">@product.ProductTitle × @product.Quantity</MudText>
<MudText Typo="Typo.caption">@string.Format("{0:N0}", product.TotalPv) PV</MudText>
</MudStack>
}
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="fw-bold">جمع PV:</MudText>
<MudText Typo="Typo.body2" Color="Color.Info" Class="fw-bold">@string.Format("{0:N0}", _pvData.TotalPv) PV</MudText>
</MudStack>
</MudStack>
}
</MudStack> </MudStack>
</MudPaper> </MudPaper>
</MudContainer> </MudContainer>
@@ -12,17 +12,12 @@ public partial class OrderDetail : ComponentBase
[Parameter] public long id { get; set; } [Parameter] public long id { get; set; }
private GetUserOrderResponse? _order; private GetUserOrderResponse? _order;
private CalculateOrderPVResponse? _pvData;
private bool _loading; private bool _loading;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_loading = true; _loading = true;
_order = await OrderService.GetOrderAsync(id); _order = await OrderService.GetOrderAsync(id);
if (_order != null)
{
_pvData = await OrderService.CalculateOrderPVAsync(id);
}
_loading = false; _loading = false;
} }
@@ -52,4 +47,3 @@ public partial class OrderDetail : ComponentBase
private static string GetProductImageUrl(string? imageUrl) private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
} }
@@ -14,7 +14,7 @@ public class NetworkNodeDto
public NetworkNodeDto? RightChild { get; set; } public NetworkNodeDto? RightChild { get; set; }
public int Level { get; set; } public int Level { get; set; }
public bool IsActive { get; set; } = true; public bool IsActive { get; set; } = true;
public DateTime? JoinedAt { get; set; } public DateTime? JoinedAt { get; set; } // تاریخ ثبت‌نام (UserCreated)
public bool IsClubActive { get; set; } public bool IsClubActive { get; set; }
public string? ActivationWeekNumber { get; set; } public string? ActivationWeekNumber { get; set; }
/// <summary> /// <summary>
@@ -26,6 +26,18 @@ public class NetworkNodeDto
/// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای) /// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای)
/// </summary> /// </summary>
public string? PackageName { get; set; } public string? PackageName { get; set; }
/// <summary>تعداد اعضای فعال با پکیج طلایی جاری در پای چپ</summary>
public int GoldLeftLegTotal { get; set; }
/// <summary>تعداد اعضای فعال با پکیج طلایی جاری در پای راست</summary>
public int GoldRightLegTotal { get; set; }
/// <summary>تعداد اعضای فعال با پکیج نقره‌ای جاری در پای چپ</summary>
public int SilverLeftLegTotal { get; set; }
/// <summary>تعداد اعضای فعال با پکیج نقره‌ای جاری در پای راست</summary>
public int SilverRightLegTotal { get; set; }
} }
/// <summary> /// <summary>
@@ -53,6 +65,11 @@ public class FlatNetworkNodeDto
/// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای) /// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای)
/// </summary> /// </summary>
public string? PackageName { get; set; } public string? PackageName { get; set; }
public int GoldLeftLegTotal { get; set; }
public int GoldRightLegTotal { get; set; }
public int SilverLeftLegTotal { get; set; }
public int SilverRightLegTotal { get; set; }
} }
/// <summary> /// <summary>
@@ -92,7 +109,11 @@ public class NetworkTreeDto
ActivationWeekNumber = node.ActivationWeekNumber, ActivationWeekNumber = node.ActivationWeekNumber,
JoinedAt = node.JoinedAt, JoinedAt = node.JoinedAt,
ReferralCode = node.ReferralCode, ReferralCode = node.ReferralCode,
PackageName = node.PackageName PackageName = node.PackageName,
GoldLeftLegTotal = node.GoldLeftLegTotal,
GoldRightLegTotal = node.GoldRightLegTotal,
SilverLeftLegTotal = node.SilverLeftLegTotal,
SilverRightLegTotal = node.SilverRightLegTotal
}; };
result.Add(flatNode); result.Add(flatNode);
@@ -125,10 +146,19 @@ public class LastMemberDto
/// </summary> /// </summary>
public class NetworkStatisticsDto public class NetworkStatisticsDto
{ {
/// <summary>اعضای فعال باشگاه در پای چپ</summary>
public int LeftLegCount { get; set; } public int LeftLegCount { get; set; }
/// <summary>اعضای فعال باشگاه در پای راست</summary>
public int RightLegCount { get; set; } public int RightLegCount { get; set; }
/// <summary>LeftLegCount + RightLegCount</summary>
public int TotalMembers { get; set; } public int TotalMembers { get; set; }
public int TreeDepth { get; set; } public int TreeDepth { get; set; }
public string WeakerLeg { get; set; } = string.Empty; // "Left" or "Right" public string WeakerLeg { get; set; } = string.Empty; // "Left" or "Right"
public LastMemberDto? LastMember { get; set; } public LastMemberDto? LastMember { get; set; }
/// <summary>تعداد اعضای فعال با پکیج طلایی/نقره‌ای جاری در هر پا — نوار پایین شجره</summary>
public int GoldLeftLegTotal { get; set; }
public int GoldRightLegTotal { get; set; }
public int SilverLeftLegTotal { get; set; }
public int SilverRightLegTotal { get; set; }
} }
@@ -96,6 +96,10 @@ public class NetworkMembershipService
TotalMembers = response.TotalMembers, TotalMembers = response.TotalMembers,
TreeDepth = response.MaxDepth, TreeDepth = response.MaxDepth,
WeakerLeg = response.WeakerLeg, WeakerLeg = response.WeakerLeg,
GoldLeftLegTotal = response.GoldLeftLegTotal,
GoldRightLegTotal = response.GoldRightLegTotal,
SilverLeftLegTotal = response.SilverLeftLegTotal,
SilverRightLegTotal = response.SilverRightLegTotal,
LastMember = response.LastMember != null ? new LastMemberDto LastMember = response.LastMember != null ? new LastMemberDto
{ {
UserId = response.LastMember.UserId, UserId = response.LastMember.UserId,
@@ -138,8 +142,13 @@ public class NetworkMembershipService
IsClubActive = node.IsClubActive, IsClubActive = node.IsClubActive,
ReferralCode = node.ReferralCode, ReferralCode = node.ReferralCode,
PackageName = string.IsNullOrWhiteSpace(node.PackageName) ? null : node.PackageName, PackageName = string.IsNullOrWhiteSpace(node.PackageName) ? null : node.PackageName,
GoldLeftLegTotal = node.GoldLeftLegTotal,
GoldRightLegTotal = node.GoldRightLegTotal,
SilverLeftLegTotal = node.SilverLeftLegTotal,
SilverRightLegTotal = node.SilverRightLegTotal,
ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(), ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(),
JoinedAt = node.JoinedAt?.ToDateTime(), JoinedAt = node.UserCreated?.ToDateTime()
?? node.JoinedAt?.ToDateTime(),
LeftChild = MapNodeFromProto(node.LeftChild), LeftChild = MapNodeFromProto(node.LeftChild),
RightChild = MapNodeFromProto(node.RightChild) RightChild = MapNodeFromProto(node.RightChild)
}; };
@@ -97,22 +97,4 @@ public class OrderService
return 0.10; return 0.10;
} }
} }
/// <summary>
/// محاسبه PV سفارش
/// </summary>
public async Task<CalculateOrderPVResponse?> CalculateOrderPVAsync(long orderId)
{
try
{
return await _userOrderContractClient.CalculateOrderPVAsync(new CalculateOrderPVRequest
{
OrderId = orderId
});
}
catch
{
return null;
}
}
} }
+45 -3
View File
@@ -118,6 +118,16 @@
line-height: 1.2; line-height: 1.2;
} }
.node-registered-at {
font-size: 8px;
color: #6c757d;
margin-top: 1px;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.node-level-sm { .node-level-sm {
font-size: 9px; font-size: 9px;
color: #6c757d; color: #6c757d;
@@ -141,6 +151,7 @@
max-width: 120px; max-width: 120px;
} }
/* D3: تعداد پکیج طلایی/نقره‌ای فقط در نوار پایین شجره (.org-chart-stats) */
/* Referral Code - only for club active members */ /* Referral Code - only for club active members */
.node-referral { .node-referral {
display: flex; display: flex;
@@ -370,14 +381,27 @@
/* Stats Bar */ /* Stats Bar */
.org-chart-stats { .org-chart-stats {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 24px; gap: 8px;
padding: 12px 16px; padding: 12px 16px;
background: #f8f9fa; background: #f8f9fa;
border-top: 1px solid #e9ecef; border-top: 1px solid #e9ecef;
} }
.org-chart-stats-row {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 24px;
}
.org-chart-stats-row.leg-scores {
gap: 12px;
}
.stat-item { .stat-item {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -385,6 +409,21 @@
font-size: 13px; font-size: 13px;
} }
.stat-item.leg-score-gold .stat-label {
color: #b8860b;
font-weight: 600;
}
.stat-item.leg-score-silver .stat-label {
color: #607d8b;
font-weight: 600;
}
.stat-sep {
color: #adb5bd;
margin: 0 2px;
}
.stat-label { .stat-label {
color: #6c757d; color: #6c757d;
} }
@@ -448,10 +487,13 @@
} }
.org-chart-stats { .org-chart-stats {
flex-wrap: wrap; gap: 8px;
gap: 12px;
padding: 10px; padding: 10px;
} }
.org-chart-stats-row {
gap: 12px;
}
.stat-item { .stat-item {
font-size: 12px; font-size: 12px;
+3 -2
View File
@@ -38,7 +38,7 @@ window.OrgChart = {
.container('#' + containerId) .container('#' + containerId)
.data(data) .data(data)
.nodeWidth((d) => 140) .nodeWidth((d) => 140)
.nodeHeight((d) => 85) .nodeHeight((d) => 98)
.childrenMargin((d) => 50) .childrenMargin((d) => 50)
.compactMarginBetween((d) => 15) .compactMarginBetween((d) => 15)
.compactMarginPair((d) => 15) .compactMarginPair((d) => 15)
@@ -86,6 +86,7 @@ window.OrgChart = {
<div class="node-avatar-sm">${firstChar}</div> <div class="node-avatar-sm">${firstChar}</div>
<div class="node-info"> <div class="node-info">
<div class="node-name-sm">${data.fullName || 'بدون نام'}</div> <div class="node-name-sm">${data.fullName || 'بدون نام'}</div>
${data.registeredAt ? `<div class="node-registered-at">${data.registeredAt}</div>` : ''}
<div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div> <div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div>
${packageBadge} ${packageBadge}
${referralCodeHtml} ${referralCodeHtml}
@@ -296,7 +297,7 @@ window.OrgChart = {
isActive: node.isActive !== false, isActive: node.isActive !== false,
isClubActive: node.isClubActive || false, isClubActive: node.isClubActive || false,
activationWeekNumber: node.activationWeekNumber, activationWeekNumber: node.activationWeekNumber,
joinedAt: node.joinedAt registeredAt: node.registeredAt || node.joinedAt || ''
}; };
result.push(flatNode); result.push(flatNode);