������� �� Avalonia 2.0 (�����-���������, ����������, �������� ��������) #1
@ -15,6 +15,8 @@ public sealed class OrderItemRow
|
||||
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
|
||||
@ -27,12 +29,16 @@ public sealed class OrderSummary
|
||||
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 (он работает);
|
||||
/// после деплоя фикса /api/buyer/orders можно перейти на «родной» эндпоинт
|
||||
/// (даст ещё PlacedAt, Comment, отмену/размещение).
|
||||
/// Заказы: локальные черновики (статус «Не отправлен») + серверные заказы (из /api/buyer/report).
|
||||
/// Черновик можно отправить (создать реальный заказ) или удалить.
|
||||
/// </summary>
|
||||
public partial class OrdersViewModel : ViewModelBase
|
||||
{
|
||||
@ -57,11 +63,38 @@ public partial class OrdersViewModel : ViewModelBase
|
||||
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);
|
||||
|
||||
Orders.Clear();
|
||||
foreach (var g in lines.GroupBy(l => l.OrderId)
|
||||
.OrderByDescending(x => x.First().OrderDate ?? DateTime.MinValue))
|
||||
{
|
||||
@ -69,19 +102,14 @@ public partial class OrdersViewModel : ViewModelBase
|
||||
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
|
||||
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()),
|
||||
Supplier = string.Join(", ", g.Select(x => x.Supplier).Where(s => !string.IsNullOrWhiteSpace(s)).Distinct()),
|
||||
Location = f.Location ?? "",
|
||||
Status = f.Status ?? "",
|
||||
ItemsCount = g.Count(),
|
||||
@ -91,9 +119,46 @@ public partial class OrdersViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
SelectedOrder = Orders.FirstOrDefault();
|
||||
if (Orders.Count == 0) Status = "Заказов за последние 2 года нет.";
|
||||
}
|
||||
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
|
||||
{
|
||||
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 = "Черновик удалён.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@ -7,32 +8,57 @@ using Elfisa.Core;
|
||||
|
||||
namespace Elfisa.Avalonia.ViewModels;
|
||||
|
||||
/// <summary>Прайс-лист (сводный прайс) + добавление позиций в заказ (общая корзина).</summary>
|
||||
/// <summary>
|
||||
/// Прайс-лист + сборка заказа: слева каталог, справа «Мой заказ» (общая корзина).
|
||||
/// «Сохранить заказ» кладёт черновик в «Заказы» (статус «Не отправлен»).
|
||||
/// </summary>
|
||||
public partial class PriceListViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ApiClient _api = new();
|
||||
private readonly CartService _cart = CartService.Instance;
|
||||
|
||||
public ObservableCollection<PriceItem> Items { get; } = new();
|
||||
public ObservableCollection<CartItem> Cart => _cart.Items;
|
||||
public ObservableCollection<BuyerLocationDto> Locations { get; } = new();
|
||||
|
||||
[ObservableProperty] private string _search = "";
|
||||
[ObservableProperty] private PriceItem? _selectedItem;
|
||||
[ObservableProperty] private decimal _qty = 1;
|
||||
[ObservableProperty] private BuyerLocationDto? _selectedLocation;
|
||||
[ObservableProperty] private string _comment = "";
|
||||
[ObservableProperty] private bool _busy;
|
||||
[ObservableProperty] private string? _status;
|
||||
[ObservableProperty] private int _total;
|
||||
[ObservableProperty] private string _cartInfo = "";
|
||||
[ObservableProperty] private decimal _cartTotal;
|
||||
|
||||
public PriceListViewModel()
|
||||
{
|
||||
_api.SetToken(Session.Current.Token);
|
||||
_cart.Changed += UpdateCartInfo;
|
||||
UpdateCartInfo();
|
||||
if (Session.Current.IsAuthenticated) _ = SearchAsync();
|
||||
_cart.Changed += Recalc;
|
||||
Recalc();
|
||||
if (Session.Current.IsAuthenticated)
|
||||
{
|
||||
_ = SearchAsync();
|
||||
_ = LoadLocationsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCartInfo() =>
|
||||
CartInfo = _cart.Count == 0 ? "Корзина пуста" : $"В заказе: {_cart.Count} поз. · {_cart.Total:N2}";
|
||||
private void Recalc() => CartTotal = _cart.Total;
|
||||
|
||||
private async Task LoadLocationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var locs = await _api.GetBuyerLocationsAsync();
|
||||
Locations.Clear();
|
||||
foreach (var l in locs) Locations.Add(l);
|
||||
var savedId = SettingsStore.Current.DefaultLocationId;
|
||||
SelectedLocation = Locations.FirstOrDefault(l => l.BuyerLocationId == savedId)
|
||||
?? Locations.FirstOrDefault(l => l.IsDefault)
|
||||
?? Locations.FirstOrDefault();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SearchAsync()
|
||||
@ -45,8 +71,6 @@ public partial class PriceListViewModel : ViewModelBase
|
||||
Items.Clear();
|
||||
foreach (var it in resp.Summary ?? new()) Items.Add(it);
|
||||
Total = resp.TotalDrugs;
|
||||
Status = $"Показано {Items.Count} из {Total} позиций"
|
||||
+ (string.IsNullOrWhiteSpace(Search) ? "" : $" по запросу «{Search.Trim()}»");
|
||||
}
|
||||
catch (Exception ex) { Status = ex.Message; }
|
||||
finally { Busy = false; }
|
||||
@ -56,8 +80,36 @@ public partial class PriceListViewModel : ViewModelBase
|
||||
private void AddToOrder()
|
||||
{
|
||||
if (SelectedItem is null) { Status = "Выберите позицию в списке."; return; }
|
||||
var q = Qty <= 0 ? 1 : Qty;
|
||||
_cart.Add(SelectedItem, q);
|
||||
Status = $"Добавлено в заказ: {SelectedItem.DrugName} × {q:N0}";
|
||||
_cart.Add(SelectedItem, Qty <= 0 ? 1 : Qty);
|
||||
Status = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveFromCart(CartItem? item) => _cart.Remove(item!);
|
||||
|
||||
[RelayCommand]
|
||||
private void SaveDraft()
|
||||
{
|
||||
if (_cart.Count == 0) { Status = "Заказ пуст — добавьте позиции."; return; }
|
||||
|
||||
var draft = new DraftOrder
|
||||
{
|
||||
LocationId = SelectedLocation?.BuyerLocationId,
|
||||
LocationAddress = SelectedLocation?.Address,
|
||||
Comment = string.IsNullOrWhiteSpace(Comment) ? null : Comment.Trim(),
|
||||
Items = _cart.Items.Select(c => new DraftItem
|
||||
{
|
||||
SupplierPriceId = c.Source.SupplierPriceId ?? "",
|
||||
Name = c.Name,
|
||||
Supplier = c.Supplier,
|
||||
Barcode = c.Source.Barcode,
|
||||
Price = c.Price,
|
||||
Qty = c.Qty
|
||||
}).ToList()
|
||||
};
|
||||
var saved = DraftStore.Add(draft);
|
||||
_cart.Clear();
|
||||
Comment = "";
|
||||
Status = $"Заказ сохранён как черновик {saved.Number} → раздел «Заказы» (не отправлен).";
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,14 +43,22 @@
|
||||
<TextBlock Text="{Binding SelectedOrder.Location}" Classes="muted" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<Border DockPanel.Dock="Bottom" Margin="0,12,0,0" Padding="0,10,0,0"
|
||||
BorderThickness="0,1,0,0" BorderBrush="{DynamicResource Border}">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Итого" FontWeight="SemiBold" Foreground="{DynamicResource Text}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding SelectedOrder.Sum, StringFormat='{}{0:N2}'}"
|
||||
FontWeight="SemiBold" Foreground="{DynamicResource Accent}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<StackPanel DockPanel.Dock="Bottom" Spacing="10" Margin="0,12,0,0">
|
||||
<Border Padding="0,10,0,0" BorderThickness="0,1,0,0" BorderBrush="{DynamicResource Border}">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Итого" FontWeight="SemiBold" Foreground="{DynamicResource Text}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding SelectedOrder.Sum, StringFormat='{}{0:N2}'}"
|
||||
FontWeight="SemiBold" Foreground="{DynamicResource Accent}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- Действия для черновика (не отправленного заказа) -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" IsVisible="{Binding SelectedOrder.IsDraft, FallbackValue=False}">
|
||||
<Button Classes="primary" Content="Отправить заказ" Command="{Binding SendDraftCommand}" IsEnabled="{Binding !Busy}"/>
|
||||
<Button Content="Удалить" Command="{Binding DeleteDraftCommand}"
|
||||
Background="Transparent" Foreground="{DynamicResource Muted}"
|
||||
BorderBrush="{DynamicResource Border}" BorderThickness="1" CornerRadius="8" Padding="16,9" Cursor="Hand"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<DataGrid ItemsSource="{Binding SelectedOrder.Items}" x:CompileBindings="False"
|
||||
AutoGenerateColumns="False" IsReadOnly="True" HeadersVisibility="Column"
|
||||
|
||||
@ -9,45 +9,91 @@
|
||||
<Grid DockPanel.Dock="Top" Margin="0,0,0,14">
|
||||
<StackPanel Spacing="2" VerticalAlignment="Center">
|
||||
<TextBlock Text="Прайс-лист" Classes="h1"/>
|
||||
<TextBlock Text="Каталог доступных позиций и цены" Classes="muted"/>
|
||||
<TextBlock Text="Каталог позиций — собери заказ и сохрани" Classes="muted"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Border DockPanel.Dock="Top" Classes="card" Margin="0,0,0,12" Padding="16">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<TextBox Text="{Binding Search}" Width="440"
|
||||
Watermark="поиск по наименованию (от 2 символов)…"/>
|
||||
<Button Classes="primary" Content="Найти" Command="{Binding SearchCommand}" IsEnabled="{Binding !Busy}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border DockPanel.Dock="Top" Margin="0,0,0,10" Padding="10,7" CornerRadius="6"
|
||||
Background="{DynamicResource AccentSoft}"
|
||||
IsVisible="{Binding Status, Converter={x:Static conv:ObjectConverters.IsNotNull}}">
|
||||
<TextBlock Text="{Binding Status}" Foreground="{DynamicResource AccentPressed}" FontSize="12.5"/>
|
||||
<TextBlock Text="{Binding Status}" Foreground="{DynamicResource AccentPressed}" TextWrapping="Wrap" FontSize="12.5"/>
|
||||
</Border>
|
||||
|
||||
<Border DockPanel.Dock="Bottom" Classes="card" Margin="0,10,0,0" Padding="14">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
|
||||
<TextBlock Text="Кол-во" Classes="label"/>
|
||||
<NumericUpDown Value="{Binding Qty}" Minimum="1" Maximum="100000" Increment="1" FormatString="0" Width="130"/>
|
||||
<Button Classes="primary" Content="Добавить в заказ ▸" Command="{Binding AddToOrderCommand}"/>
|
||||
<TextBlock Text="{Binding CartInfo}" Classes="muted" VerticalAlignment="Center" Margin="10,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Grid ColumnDefinitions="*,16,430">
|
||||
|
||||
<Border Classes="card" Padding="0" ClipToBounds="True">
|
||||
<DataGrid ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" x:CompileBindings="False"
|
||||
AutoGenerateColumns="False" IsReadOnly="True" HeadersVisibility="Column"
|
||||
CanUserResizeColumns="True" CanUserSortColumns="False">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Наименование" Width="*" Binding="{Binding DrugName}"/>
|
||||
<DataGridTextColumn Header="Форма" Width="150" Binding="{Binding CureForm}"/>
|
||||
<DataGridTextColumn Header="Производитель" Width="220" Binding="{Binding Manufacturer}"/>
|
||||
<DataGridTextColumn Header="Поставщик" Width="130" Binding="{Binding SupplierName}"/>
|
||||
<DataGridTextColumn Header="Цена" Width="110" Binding="{Binding Price, StringFormat='{}{0:N2}'}" CellStyleClasses="num"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
<!-- Каталог -->
|
||||
<Border Grid.Column="0" Classes="card" Padding="0" ClipToBounds="True">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="10" Margin="14">
|
||||
<TextBox Text="{Binding Search}" Width="330" Watermark="поиск по наименованию (от 2 символов)…"/>
|
||||
<Button Classes="primary" Content="Найти" Command="{Binding SearchCommand}" IsEnabled="{Binding !Busy}"/>
|
||||
</StackPanel>
|
||||
<Border DockPanel.Dock="Bottom" Margin="14" Padding="0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<TextBlock Text="Кол-во" Classes="label"/>
|
||||
<NumericUpDown Value="{Binding Qty}" Minimum="1" Maximum="100000" Increment="1" FormatString="0" Width="120"/>
|
||||
<Button Classes="primary" Content="Добавить в заказ ▸" Command="{Binding AddToOrderCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<DataGrid ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" x:CompileBindings="False"
|
||||
AutoGenerateColumns="False" IsReadOnly="True" HeadersVisibility="Column"
|
||||
CanUserResizeColumns="True" CanUserSortColumns="False">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Наименование" Width="*" Binding="{Binding DrugName}"/>
|
||||
<DataGridTextColumn Header="Производитель" Width="180" Binding="{Binding Manufacturer}"/>
|
||||
<DataGridTextColumn Header="Цена" Width="100" Binding="{Binding Price, StringFormat='{}{0:N2}'}" CellStyleClasses="num"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Мой заказ -->
|
||||
<Border Grid.Column="2" Classes="card" Padding="16">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Top" Text="Мой заказ" Classes="h2" Margin="0,0,0,10"/>
|
||||
|
||||
<StackPanel DockPanel.Dock="Bottom" Spacing="10" Margin="0,12,0,0">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Грузополучатель" Classes="muted"/>
|
||||
<ComboBox ItemsSource="{Binding Locations}" SelectedItem="{Binding SelectedLocation}" HorizontalAlignment="Stretch"/>
|
||||
</StackPanel>
|
||||
<TextBox Text="{Binding Comment}" Watermark="комментарий (необязательно)"/>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Итого" FontWeight="SemiBold" VerticalAlignment="Center" Foreground="{DynamicResource Text}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding CartTotal, StringFormat='{}{0:N2}'}" FontWeight="Bold" FontSize="16" Foreground="{DynamicResource Accent}"/>
|
||||
</Grid>
|
||||
<Button Classes="primary" Content="Сохранить заказ" HorizontalAlignment="Stretch" Command="{Binding SaveDraftCommand}"/>
|
||||
<TextBlock Text="Черновик появится в «Заказы» со статусом «Не отправлен»." Classes="muted" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Cart}" x:CompileBindings="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:CartItem">
|
||||
<Border Padding="10" Margin="0,0,0,8" CornerRadius="9"
|
||||
Background="{DynamicResource RowAlt}" BorderThickness="1" BorderBrush="{DynamicResource Border}">
|
||||
<DockPanel>
|
||||
<Button DockPanel.Dock="Right" Content="✕" Width="30" Height="30" Padding="0"
|
||||
VerticalAlignment="Top" Background="Transparent" Foreground="{DynamicResource Muted}" Cursor="Hand"
|
||||
Command="{Binding $parent[ItemsControl].DataContext.RemoveFromCartCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="{Binding Name}" Foreground="{DynamicResource Text}" TextWrapping="Wrap" MaxLines="2" TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Text="{Binding Supplier}" Classes="muted"/>
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
|
||||
<NumericUpDown Grid.Column="0" Value="{Binding Qty}" Minimum="1" Maximum="100000" Increment="1" FormatString="0" Width="120"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Price, StringFormat='× {0:N2}'}" Classes="muted" VerticalAlignment="Center" Margin="10,0"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding LineSum, StringFormat='{}{0:N2}'}" FontWeight="SemiBold" Foreground="{DynamicResource Text}" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
||||
77
src/Elfisa.Core/DraftStore.cs
Normal file
77
src/Elfisa.Core/DraftStore.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Elfisa.Core;
|
||||
|
||||
public sealed class DraftItem
|
||||
{
|
||||
public string SupplierPriceId { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public string Supplier { get; set; } = "";
|
||||
public string? Barcode { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Qty { get; set; } = 1;
|
||||
public decimal LineSum => Price * Qty;
|
||||
}
|
||||
|
||||
public sealed class DraftOrder
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
public string Number { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||
public string? LocationId { get; set; }
|
||||
public string? LocationAddress { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public List<DraftItem> Items { get; set; } = new();
|
||||
public decimal Total => Items.Sum(i => i.LineSum);
|
||||
public int ItemsCount => Items.Count;
|
||||
}
|
||||
|
||||
/// <summary>Локальные черновики заказов (%APPDATA%\ElfisaAvalonia\drafts.json).</summary>
|
||||
public static class DraftStore
|
||||
{
|
||||
private sealed class Box { public int Seq { get; set; } public List<DraftOrder> Drafts { get; set; } = new(); }
|
||||
|
||||
private static readonly string Dir =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ElfisaAvalonia");
|
||||
private static readonly string File = Path.Combine(Dir, "drafts.json");
|
||||
|
||||
private static Box _box = Load();
|
||||
|
||||
public static IReadOnlyList<DraftOrder> All => _box.Drafts;
|
||||
|
||||
private static Box Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(File))
|
||||
return JsonSerializer.Deserialize<Box>(System.IO.File.ReadAllText(File)) ?? new Box();
|
||||
}
|
||||
catch { }
|
||||
return new Box();
|
||||
}
|
||||
|
||||
private static void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Dir);
|
||||
System.IO.File.WriteAllText(File, JsonSerializer.Serialize(_box, new JsonSerializerOptions { WriteIndented = true }));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static DraftOrder Add(DraftOrder draft)
|
||||
{
|
||||
_box.Seq++;
|
||||
draft.Number = $"ЧРН-{_box.Seq:0000}";
|
||||
_box.Drafts.Insert(0, draft);
|
||||
Save();
|
||||
return draft;
|
||||
}
|
||||
|
||||
public static void Remove(string id)
|
||||
{
|
||||
_box.Drafts.RemoveAll(d => d.Id == id);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user