diff --git a/src/Elfisa.Avalonia/ViewModels/SendOrderViewModel.cs b/src/Elfisa.Avalonia/ViewModels/SendOrderViewModel.cs index bae2be9..4493bfd 100644 --- a/src/Elfisa.Avalonia/ViewModels/SendOrderViewModel.cs +++ b/src/Elfisa.Avalonia/ViewModels/SendOrderViewModel.cs @@ -9,98 +9,119 @@ using Elfisa.Core; namespace Elfisa.Avalonia.ViewModels; /// -/// Отправить: поиск по прайсу → корзина (общая с Прайс-листом) → создание заказа. -/// ВНИМАНИЕ: отправка создаёт реальный заказ у поставщика. +/// Отправить — отправка НЕОТПРАВЛЕННЫХ накладных (черновиков заказов из раздела +/// «Заказы»). Список готовых накладных, кнопка «Отправить» (создаёт реальный заказ +/// у поставщика). Без поиска по прайсу и наименований — только сами накладные. /// public partial class SendOrderViewModel : ViewModelBase { private readonly ApiClient _api = new(); - private readonly CartService _cart = CartService.Instance; - public ObservableCollection Results { get; } = new(); - public ObservableCollection Cart => _cart.Items; - public ObservableCollection Locations { get; } = new(); + /// Неотправленные накладные (черновики). + public ObservableCollection Drafts { get; } = new(); - [ObservableProperty] private string _search = ""; - [ObservableProperty] private PriceItem? _selectedResult; - [ObservableProperty] private BuyerLocationDto? _selectedLocation; - [ObservableProperty] private string _comment = ""; + [ObservableProperty] private DraftOrder? _selected; [ObservableProperty] private bool _busy; [ObservableProperty] private string? _status; - [ObservableProperty] private decimal _cartTotal; + [ObservableProperty] private decimal _totalSum; + [ObservableProperty] private bool _isEmpty = true; + [ObservableProperty] private int _count; 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; } + Load(); } [RelayCommand] - private async Task SearchAsync() + private void Refresh() => Load(); + + private void Load() { - 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; } + Drafts.Clear(); + foreach (var d in DraftStore.All.OrderByDescending(x => x.CreatedAt)) + Drafts.Add(d); + TotalSum = Drafts.Sum(d => d.Total); + Count = Drafts.Count; + IsEmpty = Drafts.Count == 0; + if (Selected is null || !Drafts.Contains(Selected)) + Selected = Drafts.FirstOrDefault(); } + /// Отправить одну накладную. [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() + private async Task SendAsync(DraftOrder? draft) { + var d = draft ?? Selected; + if (d is null) { Status = "Выберите накладную для отправки."; return; } 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(); + var resp = await CreateFromDraft(d); + DraftStore.Remove(d.Id); + Status = $"✓ Накладная {resp.GlobalSign ?? resp.OrderId} отправлена ({resp.ItemsCount} поз., {resp.TotalAmount:N2})."; } catch (Exception ex) { Status = "Ошибка отправки: " + ex.Message; } - finally { Busy = false; } + finally { Busy = false; Load(); } + } + + /// Отправить все неотправленные накладные. + [RelayCommand] + private async Task SendAllAsync() + { + if (Drafts.Count == 0) { Status = "Нет неотправленных накладных."; return; } + Status = null; + int ok = 0; + string? lastErr = null; + try + { + Busy = true; + foreach (var d in DraftStore.All.ToList()) + { + try + { + await CreateFromDraft(d); + DraftStore.Remove(d.Id); + ok++; + } + catch (Exception ex) { lastErr = ex.Message; } + } + } + finally { Busy = false; Load(); } + + Status = lastErr is null + ? $"✓ Отправлено накладных: {ok}." + : $"Отправлено: {ok}. Не удалось часть — {lastErr}"; + } + + /// Удалить неотправленную накладную (не отправляя). + [RelayCommand] + private void Delete(DraftOrder? draft) + { + var d = draft ?? Selected; + if (d is null) return; + DraftStore.Remove(d.Id); + Load(); + Status = "Накладная удалена."; + } + + private async Task CreateFromDraft(DraftOrder d) + { + var req = new BuyerOrderCreateRequest + { + // Номер EX резервировался при сохранении черновика — используем его. + GlobalSign = d.Number.StartsWith("EX-", StringComparison.OrdinalIgnoreCase) ? d.Number : null, + LocationId = d.LocationId, + Comment = d.Comment, + Items = d.Items.Select(i => new BuyerOrderItemReq + { + SupplierPriceId = i.SupplierPriceId, + Qty = (double)i.Qty, + ItemName = i.Name, + Barcode = i.Barcode + }).ToList() + }; + return await _api.CreateOrderAsync(req); } } diff --git a/src/Elfisa.Avalonia/Views/SendOrderView.axaml b/src/Elfisa.Avalonia/Views/SendOrderView.axaml index 3af0ce7..f8d1c8f 100644 --- a/src/Elfisa.Avalonia/Views/SendOrderView.axaml +++ b/src/Elfisa.Avalonia/Views/SendOrderView.axaml @@ -1,16 +1,19 @@ - - - - + + + + +