- при «Сохранить заказ» резервируется настоящий номер через 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>
166 lines
6.1 KiB
C#
166 lines
6.1 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;
|
||
|
||
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 = "Черновик удалён.";
|
||
}
|
||
}
|