2.0.1: «Мои прайсы» в Прайс-листе + подключённые прайсы в «Загрузить»
- Прайс-лист: дропдаун «Мои прайсы» (подключённые прайсы + «Все прайсы»), выбор фильтрует каталог по price_list_id; один прайс — выбирается сам - Загрузить: показывает список подключённых прайсов и качает их - Core: GetBuyerPriceListsAsync + priceListId в GetPriceSummaryAsync Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
028c588d2a
commit
f98978a5d2
@ -3,7 +3,7 @@
|
||||
|
||||
#define MyAppName "Электронная Фармация"
|
||||
#define MyAppBrand "ЭльФиСА"
|
||||
#define MyAppVersion "2.0.0"
|
||||
#define MyAppVersion "2.0.1"
|
||||
#define MyAppPublisher "PharmData"
|
||||
#define MyAppURL "https://cdn.24pharmdata.ru"
|
||||
#define MyAppSupportURL "https://24pharmdata.ru"
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<Version>2.0.0</Version>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
<FileVersion>2.0.0.0</FileVersion>
|
||||
<Version>2.0.1</Version>
|
||||
<AssemblyVersion>2.0.1.0</AssemblyVersion>
|
||||
<FileVersion>2.0.1.0</FileVersion>
|
||||
<Product>ЭльФиСА — Электронная Фармация</Product>
|
||||
<Company>PharmData</Company>
|
||||
<!-- Кросс-платформенная публикация задаётся флагами CLI (RID + self-contained). -->
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
@ -8,15 +9,19 @@ using Elfisa.Core;
|
||||
|
||||
namespace Elfisa.Avalonia.ViewModels;
|
||||
|
||||
/// <summary>«Загрузить» — скачивание актуального прайса с сервера в память.</summary>
|
||||
/// <summary>«Загрузить» — скачивание подключённых прайсов с сервера в память.</summary>
|
||||
public partial class LoadPriceViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ApiClient _api = new();
|
||||
|
||||
/// <summary>Прайсы, подключённые покупателю (какие именно скачиваем).</summary>
|
||||
public ObservableCollection<BuyerPriceListDto> PriceLists { get; } = new();
|
||||
|
||||
[ObservableProperty] private bool _busy;
|
||||
[ObservableProperty] private double _progress; // 0..100
|
||||
[ObservableProperty] private string _progressText = "";
|
||||
[ObservableProperty] private string? _result;
|
||||
[ObservableProperty] private string _priceListsInfo = "Загрузка списка прайсов…";
|
||||
|
||||
public string LastLoaded =>
|
||||
PriceCache.LoadedAt is { } dt
|
||||
@ -26,6 +31,23 @@ public partial class LoadPriceViewModel : ViewModelBase
|
||||
public LoadPriceViewModel()
|
||||
{
|
||||
_api.SetToken(Session.Current.Token);
|
||||
if (Session.Current.IsAuthenticated)
|
||||
_ = LoadPriceListsAsync();
|
||||
}
|
||||
|
||||
/// <summary>Тянет список подключённых прайсов — чтобы было видно, что именно скачиваем.</summary>
|
||||
private async Task LoadPriceListsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var lists = await _api.GetBuyerPriceListsAsync();
|
||||
PriceLists.Clear();
|
||||
foreach (var pl in lists) PriceLists.Add(pl);
|
||||
PriceListsInfo = lists.Count == 0
|
||||
? "Прайсы пока не подключены — обратитесь к менеджеру."
|
||||
: $"Подключено прайсов: {lists.Count}";
|
||||
}
|
||||
catch { PriceListsInfo = "Не удалось получить список подключённых прайсов."; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@ -49,8 +49,11 @@ public partial class PriceListViewModel : ViewModelBase
|
||||
|
||||
public ObservableCollection<CartItem> Cart => _cart.Items;
|
||||
public ObservableCollection<BuyerLocationDto> Locations { get; } = new();
|
||||
public ObservableCollection<BuyerPriceListDto> MyPriceLists { get; } = new();
|
||||
|
||||
[ObservableProperty] private string _search = "";
|
||||
[ObservableProperty] private BuyerPriceListDto? _selectedPriceList;
|
||||
[ObservableProperty] private bool _hasMultiplePriceLists;
|
||||
[ObservableProperty] private string? _selectedBaseName;
|
||||
[ObservableProperty] private string? _selectedMg;
|
||||
[ObservableProperty] private OfferRow? _selectedOffer;
|
||||
@ -68,11 +71,39 @@ public partial class PriceListViewModel : ViewModelBase
|
||||
Recalc();
|
||||
if (Session.Current.IsAuthenticated)
|
||||
{
|
||||
_ = SearchAsync();
|
||||
_ = LoadPriceListsAsync(); // грузит «Мои прайсы» → ставит выбор → запускает поиск
|
||||
_ = LoadLocationsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _suppressPriceListSearch;
|
||||
|
||||
/// <summary>Загружает подключённые покупателю прайсы в дропдаун «Мои прайсы».</summary>
|
||||
private async Task LoadPriceListsAsync()
|
||||
{
|
||||
List<BuyerPriceListDto> lists;
|
||||
try { lists = await _api.GetBuyerPriceListsAsync(); }
|
||||
catch { lists = new(); }
|
||||
|
||||
MyPriceLists.Clear();
|
||||
MyPriceLists.Add(new BuyerPriceListDto { PriceListId = null, Name = "Все прайсы" });
|
||||
foreach (var pl in lists) MyPriceLists.Add(pl);
|
||||
HasMultiplePriceLists = lists.Count > 1;
|
||||
|
||||
_suppressPriceListSearch = true;
|
||||
// Один подключён — выбираем его; несколько или ни одного — «Все прайсы».
|
||||
SelectedPriceList = lists.Count == 1 ? MyPriceLists[1] : MyPriceLists[0];
|
||||
_suppressPriceListSearch = false;
|
||||
|
||||
await SearchAsync();
|
||||
}
|
||||
|
||||
partial void OnSelectedPriceListChanged(BuyerPriceListDto? value)
|
||||
{
|
||||
if (_suppressPriceListSearch) return;
|
||||
_ = SearchAsync();
|
||||
}
|
||||
|
||||
private void Recalc() => CartTotal = _cart.Total;
|
||||
|
||||
private async Task LoadLocationsAsync()
|
||||
@ -97,7 +128,7 @@ public partial class PriceListViewModel : ViewModelBase
|
||||
try
|
||||
{
|
||||
Busy = true;
|
||||
var resp = await _api.GetPriceSummaryAsync(Search, limit: 300);
|
||||
var resp = await _api.GetPriceSummaryAsync(Search, limit: 300, priceListId: SelectedPriceList?.PriceListId);
|
||||
RebuildIndex(resp.Summary ?? new());
|
||||
Total = resp.TotalDrugs;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:Elfisa.Avalonia.ViewModels"
|
||||
xmlns:conv="using:Avalonia.Data.Converters"
|
||||
xmlns:core="using:Elfisa.Core"
|
||||
x:Class="Elfisa.Avalonia.Views.LoadPriceView"
|
||||
x:DataType="vm:LoadPriceViewModel">
|
||||
|
||||
@ -20,6 +21,27 @@
|
||||
|
||||
<TextBlock Text="{Binding LastLoaded}" Classes="muted" HorizontalAlignment="Center" TextAlignment="Center"/>
|
||||
|
||||
<!-- Подключённые прайсы — что именно скачиваем -->
|
||||
<Border Background="{DynamicResource RowAlt}" CornerRadius="10" Padding="16,12" Width="380"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Border}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding PriceListsInfo}" FontWeight="SemiBold" Foreground="{DynamicResource Text}"/>
|
||||
<ItemsControl ItemsSource="{Binding PriceLists}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="core:BuyerPriceListDto">
|
||||
<Grid ColumnDefinitions="Auto,*" Margin="0,3">
|
||||
<TextBlock Grid.Column="0" Text="• " Foreground="{DynamicResource Accent}" FontWeight="Bold"/>
|
||||
<StackPanel Grid.Column="1" Spacing="1">
|
||||
<TextBlock Text="{Binding Name}" Foreground="{DynamicResource Text}" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding SupplierName}" Classes="muted" FontSize="12"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Button Classes="primary" Content="Загрузить прайс" HorizontalAlignment="Center"
|
||||
Padding="28,12" Command="{Binding LoadCommand}" IsEnabled="{Binding !Busy}"/>
|
||||
|
||||
|
||||
@ -6,11 +6,20 @@
|
||||
x:DataType="vm:PriceListViewModel">
|
||||
|
||||
<DockPanel Margin="24">
|
||||
<Grid DockPanel.Dock="Top" Margin="0,0,0,14">
|
||||
<StackPanel Spacing="2" VerticalAlignment="Center">
|
||||
<Grid DockPanel.Dock="Top" Margin="0,0,0,14" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Spacing="2" VerticalAlignment="Center">
|
||||
<TextBlock Text="Прайс-лист" Classes="h1"/>
|
||||
<TextBlock Text="Каталог позиций — собери заказ и сохрани" Classes="muted"/>
|
||||
</StackPanel>
|
||||
<!-- Мои прайсы: видно, по какому прайсу заказываем; если подключено несколько — можно выбрать -->
|
||||
<Border Grid.Column="1" VerticalAlignment="Center" Padding="12,8" CornerRadius="10"
|
||||
Background="{DynamicResource Panel}" BorderThickness="1" BorderBrush="{DynamicResource Border}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<TextBlock Text="Мои прайсы" Classes="label" VerticalAlignment="Center"/>
|
||||
<ComboBox ItemsSource="{Binding MyPriceLists}" SelectedItem="{Binding SelectedPriceList}"
|
||||
MinWidth="240" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Border DockPanel.Dock="Top" Margin="0,0,0,10" Padding="10,7" CornerRadius="6"
|
||||
|
||||
@ -98,16 +98,27 @@ public sealed class ApiClient
|
||||
}
|
||||
|
||||
/// <summary>Сводный прайс (то же, что грузит WinForms-десктоп). Поиск по q (от 2 символов).</summary>
|
||||
public async Task<PriceSummaryResponse> GetPriceSummaryAsync(string? q = null, int limit = 200, int offset = 0)
|
||||
public async Task<PriceSummaryResponse> GetPriceSummaryAsync(string? q = null, int limit = 200, int offset = 0, string? priceListId = null)
|
||||
{
|
||||
EnsureAuth();
|
||||
var path = $"/api/supplier-prices/summary?limit={limit}&offset={offset}";
|
||||
if (!string.IsNullOrWhiteSpace(q) && q.Trim().Length >= 2)
|
||||
path += "&q=" + Uri.EscapeDataString(q.Trim());
|
||||
if (!string.IsNullOrWhiteSpace(priceListId))
|
||||
path += "&price_list_id=" + Uri.EscapeDataString(priceListId);
|
||||
var text = await AuthorizedGetAsync(path, "загрузку прайса").ConfigureAwait(false);
|
||||
return JsonSerializer.Deserialize<PriceSummaryResponse>(text, Json) ?? new PriceSummaryResponse();
|
||||
}
|
||||
|
||||
/// <summary>Прайс-листы, подключённые текущему покупателю (для выбора «Мои прайсы»).</summary>
|
||||
public async Task<List<BuyerPriceListDto>> GetBuyerPriceListsAsync()
|
||||
{
|
||||
EnsureAuth();
|
||||
var text = await AuthorizedGetAsync("/api/buyer/price-lists", "загрузку прайсов").ConfigureAwait(false);
|
||||
try { return JsonSerializer.Deserialize<List<BuyerPriceListDto>>(text, Json) ?? new(); }
|
||||
catch { return new(); }
|
||||
}
|
||||
|
||||
/// <summary>Создаёт заказ (Placed). ВНИМАНИЕ: создаёт реальный заказ у поставщика.</summary>
|
||||
public async Task<BuyerOrderResponse> CreateOrderAsync(BuyerOrderCreateRequest request)
|
||||
{
|
||||
|
||||
@ -82,6 +82,19 @@ public sealed class BuyerLocationDto
|
||||
public override string ToString() => Address ?? "";
|
||||
}
|
||||
|
||||
/// <summary>Подключённый покупателю прайс-лист («Мои прайсы»).</summary>
|
||||
public sealed class BuyerPriceListDto
|
||||
{
|
||||
[JsonPropertyName("price_list_id")] public string? PriceListId { get; set; }
|
||||
[JsonPropertyName("name")] public string? Name { get; set; }
|
||||
[JsonPropertyName("supplier_name")] public string? SupplierName { get; set; }
|
||||
[JsonPropertyName("markup_pct")] public double MarkupPct { get; set; }
|
||||
[JsonPropertyName("default_markup_pct")] public double DefaultMarkupPct { get; set; }
|
||||
public override string ToString() =>
|
||||
string.IsNullOrWhiteSpace(SupplierName) ? (Name ?? "Прайс")
|
||||
: $"{Name} · {SupplierName}";
|
||||
}
|
||||
|
||||
public sealed class BuyerOrderItemReq
|
||||
{
|
||||
[JsonPropertyName("supplier_price_id")] public string SupplierPriceId { get; set; } = "";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user