elfisa-pharmacy/src/Elfisa.Avalonia/ViewModels/OrdersViewModel.cs
Exest cd2fb57bca Avalonia: черновики получают реальный номер EX-####### (как в WinForms)
- при «Сохранить заказ» резервируется настоящий номер через
  GET /api/buyer/global-sign/next (fallback ЧРН-… если офлайн)
- при отправке черновика этот EX-номер передаётся в заказ (global_sign),
  заказ сохраняет тот же номер
- ApiClient.NextGlobalSignAsync, BuyerOrderCreateRequest.GlobalSign
- DraftStore: номер задаёт вызывающий

Проверено: черновик = EX-0000018, статус «Не отправлен».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-08 11:54:43 +03:00

166 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
public sealed class OrderItemRow
{
public string Name { get; init; } = "";
public string Code { get; init; } = "";
public decimal Qty { get; init; }
public decimal Price { get; init; }
public decimal Sum { get; init; }
public string? SupplierPriceId { get; init; }
public string? Barcode { get; init; }
}
public sealed class OrderSummary
{
public string Date { get; init; } = "";
public string Number { get; init; } = "";
public string Supplier { get; init; } = "";
public string Location { get; init; } = "";
public string Status { get; init; } = "";
public int ItemsCount { get; init; }
public decimal Sum { get; init; }
public ObservableCollection<OrderItemRow> Items { get; init; } = new();
public bool IsDraft { get; init; }
public string? DraftId { get; init; }
public string? LocationId { get; init; }
public string? Comment { get; init; }
}
/// <summary>
/// Заказы: локальные черновики (статус «Не отправлен») + серверные заказы (из /api/buyer/report).
/// Черновик можно отправить (создать реальный заказ) или удалить.
/// </summary>
public partial class OrdersViewModel : ViewModelBase
{
private readonly ApiClient _api = new();
public ObservableCollection<OrderSummary> Orders { get; } = new();
[ObservableProperty] private OrderSummary? _selectedOrder;
[ObservableProperty] private bool _busy;
[ObservableProperty] private string? _status;
public OrdersViewModel()
{
_api.SetToken(Session.Current.Token);
if (Session.Current.IsAuthenticated) _ = LoadAsync();
}
[RelayCommand]
private async Task LoadAsync()
{
Status = null;
try
{
Busy = true;
Orders.Clear();
// 1) Локальные черновики — вверху, статус «Не отправлен»
foreach (var d in DraftStore.All)
{
var items = new ObservableCollection<OrderItemRow>(
d.Items.Select(i => new OrderItemRow
{
Name = i.Name, Qty = i.Qty, Price = i.Price, Sum = i.LineSum,
SupplierPriceId = i.SupplierPriceId, Barcode = i.Barcode
}));
Orders.Add(new OrderSummary
{
Date = d.CreatedAt.ToString("dd.MM.yyyy"),
Number = d.Number,
Supplier = string.Join(", ", d.Items.Select(i => i.Supplier).Where(s => !string.IsNullOrWhiteSpace(s)).Distinct()),
Location = d.LocationAddress ?? "",
Status = "Не отправлен",
ItemsCount = d.ItemsCount,
Sum = d.Total,
Items = items,
IsDraft = true,
DraftId = d.Id,
LocationId = d.LocationId,
Comment = d.Comment
});
}
// 2) Серверные заказы
var to = DateTime.Today;
var from = to.AddMonths(-24);
var lines = await _api.GetBuyerReportAsync(from, to);
foreach (var g in lines.GroupBy(l => l.OrderId)
.OrderByDescending(x => x.First().OrderDate ?? DateTime.MinValue))
{
var f = g.First();
var items = new ObservableCollection<OrderItemRow>(
g.Select(l => new OrderItemRow
{
Name = l.ItemName ?? "", Code = l.ItemCode ?? "",
Qty = l.Qty, Price = l.UnitPrice, Sum = l.Sum
}));
Orders.Add(new OrderSummary
{
Date = f.OrderDate?.ToLocalTime().ToString("dd.MM.yyyy") ?? "",
Number = f.GlobalSign ?? "",
Supplier = string.Join(", ", g.Select(x => x.Supplier).Where(s => !string.IsNullOrWhiteSpace(s)).Distinct()),
Location = f.Location ?? "",
Status = f.Status ?? "",
ItemsCount = g.Count(),
Sum = g.Sum(x => x.Sum),
Items = items
});
}
SelectedOrder = Orders.FirstOrDefault();
}
catch (Exception ex) { Status = ex.Message; }
finally { Busy = false; }
}
[RelayCommand]
private async Task SendDraftAsync()
{
if (SelectedOrder is not { IsDraft: true } d) return;
Status = null;
try
{
Busy = true;
var req = new BuyerOrderCreateRequest
{
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()
};
var resp = await _api.CreateOrderAsync(req);
if (d.DraftId is { } id) DraftStore.Remove(id);
Status = $"✓ Заказ {resp.GlobalSign ?? resp.OrderId} отправлен ({resp.ItemsCount} поз., {resp.TotalAmount:N2}).";
await LoadAsync();
}
catch (Exception ex) { Status = "Ошибка отправки: " + ex.Message; }
finally { Busy = false; }
}
[RelayCommand]
private async Task DeleteDraftAsync()
{
if (SelectedOrder is not { IsDraft: true } d || d.DraftId is not { } id) return;
DraftStore.Remove(id);
await LoadAsync();
Status = "Черновик удалён.";
}
}