- CartService: единая корзина для Прайс-листа и Отправить - Прайс-лист: выбор позиции + кол-во + «Добавить в заказ», индикатор корзины - Настройки (диалог ⚙ в шапке): адрес API, тема (свет/тьма, применяется сразу), грузополучатель по умолчанию; сохранение в %APPDATA% - SettingsStore (Core) + AppConfig читает сохранённый API-адрес - тема применяется на старте Проверено вживую: добавление из Прайс-листа в корзину, диалог настроек. Реальная отправка заказа (POST) — собрана и связана, живой заказ по явному «да» (автозащита блокирует боевую транзакцию без подтверждения). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
107 lines
3.7 KiB
C#
107 lines
3.7 KiB
C#
using System;
|
||
using System.Collections.ObjectModel;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using Elfisa.Core;
|
||
|
||
namespace Elfisa.Avalonia.ViewModels;
|
||
|
||
/// <summary>
|
||
/// Отправить: поиск по прайсу → корзина (общая с Прайс-листом) → создание заказа.
|
||
/// ВНИМАНИЕ: отправка создаёт реальный заказ у поставщика.
|
||
/// </summary>
|
||
public partial class SendOrderViewModel : ViewModelBase
|
||
{
|
||
private readonly ApiClient _api = new();
|
||
private readonly CartService _cart = CartService.Instance;
|
||
|
||
public ObservableCollection<PriceItem> Results { get; } = new();
|
||
public ObservableCollection<CartItem> Cart => _cart.Items;
|
||
public ObservableCollection<BuyerLocationDto> Locations { get; } = new();
|
||
|
||
[ObservableProperty] private string _search = "";
|
||
[ObservableProperty] private PriceItem? _selectedResult;
|
||
[ObservableProperty] private BuyerLocationDto? _selectedLocation;
|
||
[ObservableProperty] private string _comment = "";
|
||
[ObservableProperty] private bool _busy;
|
||
[ObservableProperty] private string? _status;
|
||
[ObservableProperty] private decimal _cartTotal;
|
||
|
||
public SendOrderViewModel()
|
||
{
|
||
_api.SetToken(Session.Current.Token);
|
||
_cart.Changed += Recalc;
|
||
Recalc();
|
||
if (Session.Current.IsAuthenticated) _ = InitAsync();
|
||
}
|
||
|
||
private async Task InitAsync()
|
||
{
|
||
try
|
||
{
|
||
var locs = await _api.GetBuyerLocationsAsync();
|
||
Locations.Clear();
|
||
foreach (var l in locs) Locations.Add(l);
|
||
SelectedLocation = Locations.FirstOrDefault(l => l.IsDefault) ?? Locations.FirstOrDefault();
|
||
await SearchAsync();
|
||
}
|
||
catch (Exception ex) { Status = ex.Message; }
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task SearchAsync()
|
||
{
|
||
try
|
||
{
|
||
Busy = true; Status = null;
|
||
var resp = await _api.GetPriceSummaryAsync(Search, limit: 200);
|
||
Results.Clear();
|
||
foreach (var it in resp.Summary ?? new()) Results.Add(it);
|
||
}
|
||
catch (Exception ex) { Status = ex.Message; }
|
||
finally { Busy = false; }
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void AddToCart()
|
||
{
|
||
if (SelectedResult is null) { Status = "Выберите позицию в списке слева."; return; }
|
||
_cart.Add(SelectedResult);
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void RemoveFromCart(CartItem? item) => _cart.Remove(item!);
|
||
|
||
private void Recalc() => CartTotal = _cart.Total;
|
||
|
||
[RelayCommand]
|
||
private async Task SendAsync()
|
||
{
|
||
Status = null;
|
||
if (_cart.Count == 0) { Status = "Корзина пуста."; return; }
|
||
try
|
||
{
|
||
Busy = true;
|
||
var req = new BuyerOrderCreateRequest
|
||
{
|
||
LocationId = SelectedLocation?.BuyerLocationId,
|
||
Comment = string.IsNullOrWhiteSpace(Comment) ? null : Comment.Trim(),
|
||
Items = _cart.Items.Select(c => new BuyerOrderItemReq
|
||
{
|
||
SupplierPriceId = c.Source.SupplierPriceId ?? "",
|
||
Qty = (double)c.Qty,
|
||
ItemName = c.Source.DrugName,
|
||
Barcode = c.Source.Barcode
|
||
}).ToList()
|
||
};
|
||
var resp = await _api.CreateOrderAsync(req);
|
||
Status = $"✓ Заказ {resp.GlobalSign ?? resp.OrderId} создан: {resp.ItemsCount} поз., {resp.TotalAmount:N2}.";
|
||
_cart.Clear();
|
||
}
|
||
catch (Exception ex) { Status = "Ошибка отправки: " + ex.Message; }
|
||
finally { Busy = false; }
|
||
}
|
||
}
|