������� �� Avalonia 2.0 (�����-���������, ����������, �������� ��������) #1
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -29,6 +29,9 @@
|
|||||||
<setting name="IntDefaultPercentMarkup" serializeAs="String">
|
<setting name="IntDefaultPercentMarkup" serializeAs="String">
|
||||||
<value>0</value>
|
<value>0</value>
|
||||||
</setting>
|
</setting>
|
||||||
|
<setting name="ClientMarkupPercent" serializeAs="String">
|
||||||
|
<value>0</value>
|
||||||
|
</setting>
|
||||||
<setting name="stringLogin" serializeAs="String">
|
<setting name="stringLogin" serializeAs="String">
|
||||||
<value />
|
<value />
|
||||||
</setting>
|
</setting>
|
||||||
@ -38,6 +41,21 @@
|
|||||||
<setting name="stringToken" serializeAs="String">
|
<setting name="stringToken" serializeAs="String">
|
||||||
<value />
|
<value />
|
||||||
</setting>
|
</setting>
|
||||||
|
<setting name="InvoiceExportPath" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="UpdateCheckBaseUrl" serializeAs="String">
|
||||||
|
<value>https://git.24pharmdata.ru</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="UpdateRepo" serializeAs="String">
|
||||||
|
<value>pharmdata/elfisa-pharmacy</value>
|
||||||
|
</setting>
|
||||||
|
<setting name="UpdateApiToken" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="UpgradeSettings" serializeAs="String">
|
||||||
|
<value>True</value>
|
||||||
|
</setting>
|
||||||
</Электронная_Фармация.Properties.Settings>
|
</Электронная_Фармация.Properties.Settings>
|
||||||
</userSettings>
|
</userSettings>
|
||||||
<runtime>
|
<runtime>
|
||||||
|
|||||||
Binary file not shown.
@ -374,6 +374,26 @@ namespace Электронная_Фармация.Classes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отчёт покупателя: позиции его заказов за период. Сервер скоупит по JWT покупателя.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<BuyerReportLineDto>> GetBuyerReportAsync(DateTime from, DateTime to)
|
||||||
|
{
|
||||||
|
EnsureAuthenticated();
|
||||||
|
var path = $"/api/buyer/report?date_from={from:yyyy-MM-dd}&date_to={to:yyyy-MM-dd}";
|
||||||
|
var responseBody = await SendAuthorizedGetAsync(path, "формирование отчёта");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = JsonSerializer.Deserialize<BuyerReportResponse>(responseBody, JsonOptions);
|
||||||
|
return response?.Lines ?? new List<BuyerReportLineDto>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppDebugLog.Error("ApiClient", "Не удалось разобрать отчёт", ex);
|
||||||
|
throw new HttpRequestException($"Некорректный ответ сервера при формировании отчёта: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<BuyerLocationDto>> GetBuyerLocationsAsync()
|
public async Task<List<BuyerLocationDto>> GetBuyerLocationsAsync()
|
||||||
{
|
{
|
||||||
EnsureAuthenticated();
|
EnsureAuthenticated();
|
||||||
@ -758,6 +778,48 @@ namespace Электронная_Фармация.Classes
|
|||||||
public List<BuyerOrderListItemDto> Orders { get; set; }
|
public List<BuyerOrderListItemDto> Orders { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class BuyerReportResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("lines")]
|
||||||
|
public List<BuyerReportLineDto> Lines { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BuyerReportLineDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("order_id")]
|
||||||
|
public string OrderID { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("global_sign")]
|
||||||
|
public string GlobalSign { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("order_date")]
|
||||||
|
public DateTime? OrderDate { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("status")]
|
||||||
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("supplier")]
|
||||||
|
public string Supplier { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("location")]
|
||||||
|
public string Location { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("item_name")]
|
||||||
|
public string ItemName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("item_code")]
|
||||||
|
public string ItemCode { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("qty")]
|
||||||
|
public decimal Qty { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("unit_price")]
|
||||||
|
public decimal UnitPrice { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("sum")]
|
||||||
|
public decimal Sum { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class BuyerLocationDto
|
public sealed class BuyerLocationDto
|
||||||
{
|
{
|
||||||
[JsonPropertyName("buyer_location_id")]
|
[JsonPropertyName("buyer_location_id")]
|
||||||
|
|||||||
@ -248,6 +248,9 @@
|
|||||||
<Compile Include="UserControls\UCRefusals.cs">
|
<Compile Include="UserControls\UCRefusals.cs">
|
||||||
<SubType>UserControl</SubType>
|
<SubType>UserControl</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="UserControls\UCReports.cs">
|
||||||
|
<SubType>UserControl</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="UserControls\UCPriceList.cs">
|
<Compile Include="UserControls\UCPriceList.cs">
|
||||||
<SubType>UserControl</SubType>
|
<SubType>UserControl</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
@ -61,6 +61,7 @@ namespace Электронная_Фармация
|
|||||||
new SidebarItem { Key = "price", Text = "Прайс-лист", IconGlyph = "\uE14C" },
|
new SidebarItem { Key = "price", Text = "Прайс-лист", IconGlyph = "\uE14C" },
|
||||||
new SidebarItem { Key = "orders", Text = "Заказы", IconGlyph = "\uE7BF" },
|
new SidebarItem { Key = "orders", Text = "Заказы", IconGlyph = "\uE7BF" },
|
||||||
new SidebarItem { Key = "invoices", Text = "Накладные", IconGlyph = "\uE8A5" },
|
new SidebarItem { Key = "invoices", Text = "Накладные", IconGlyph = "\uE8A5" },
|
||||||
|
new SidebarItem { Key = "reports", Text = "Отчёты", IconGlyph = "" },
|
||||||
new SidebarItem { Key = "upload", Text = "Загрузить", IconGlyph = "\uE896" },
|
new SidebarItem { Key = "upload", Text = "Загрузить", IconGlyph = "\uE896" },
|
||||||
new SidebarItem { Key = "send", Text = "Отправить", IconGlyph = "\uE725" },
|
new SidebarItem { Key = "send", Text = "Отправить", IconGlyph = "\uE725" },
|
||||||
new SidebarItem { Key = "refusals", Text = "Отказы", IconGlyph = "\uE7BA" },
|
new SidebarItem { Key = "refusals", Text = "Отказы", IconGlyph = "\uE7BA" },
|
||||||
@ -321,6 +322,9 @@ namespace Электронная_Фармация
|
|||||||
case "invoices":
|
case "invoices":
|
||||||
OpenInvoicesTab();
|
OpenInvoicesTab();
|
||||||
break;
|
break;
|
||||||
|
case "reports":
|
||||||
|
OpenReportsTab();
|
||||||
|
break;
|
||||||
case "upload":
|
case "upload":
|
||||||
tsBtnDownloadData_Click(this, EventArgs.Empty);
|
tsBtnDownloadData_Click(this, EventArgs.Empty);
|
||||||
_appHeader.SetActive("upload");
|
_appHeader.SetActive("upload");
|
||||||
@ -499,6 +503,12 @@ namespace Электронная_Фармация
|
|||||||
OpenDocumentTab("Отказы", "refusals", refusals, allowDuplicate: false);
|
OpenDocumentTab("Отказы", "refusals", refusals, allowDuplicate: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OpenReportsTab()
|
||||||
|
{
|
||||||
|
var reports = new UCReports();
|
||||||
|
OpenDocumentTab("Отчёты", "reports", reports, allowDuplicate: false);
|
||||||
|
}
|
||||||
|
|
||||||
partial void ShowShellLoading(string message)
|
partial void ShowShellLoading(string message)
|
||||||
{
|
{
|
||||||
_loadingOverlay?.Show(message);
|
_loadingOverlay?.Show(message);
|
||||||
|
|||||||
@ -32,6 +32,6 @@ using System.Runtime.InteropServices;
|
|||||||
// пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
// пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ "*", пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ:
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ "*", пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.0.25.0")]
|
[assembly: AssemblyVersion("1.0.26.0")]
|
||||||
[assembly: AssemblyFileVersion("1.0.25.0")]
|
[assembly: AssemblyFileVersion("1.0.26.0")]
|
||||||
|
|
||||||
|
|||||||
182
src/ElectronicPharmacy/UserControls/UCReports.cs
Normal file
182
src/ElectronicPharmacy/UserControls/UCReports.cs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Электронная_Фармация.Classes;
|
||||||
|
using Электронная_Фармация.Properties;
|
||||||
|
|
||||||
|
namespace Электронная_Фармация.UserControls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Раздел «Отчёты» покупателя. Данные тянутся с сервера (/api/buyer/report),
|
||||||
|
/// который скоупит их по JWT покупателя — клиент видит ТОЛЬКО свои заказы.
|
||||||
|
/// Все 4 отчёта агрегируются на клиенте из одного набора строк.
|
||||||
|
/// </summary>
|
||||||
|
public class UCReports : UserControl
|
||||||
|
{
|
||||||
|
private readonly DateTimePicker _dtFrom;
|
||||||
|
private readonly DateTimePicker _dtTo;
|
||||||
|
private readonly ComboBox _cboReport;
|
||||||
|
private readonly Button _btnBuild;
|
||||||
|
private readonly Label _lblTotals;
|
||||||
|
private readonly DataGridView _grid;
|
||||||
|
private List<BuyerReportLineDto> _lines = new List<BuyerReportLineDto>();
|
||||||
|
|
||||||
|
public UCReports()
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill;
|
||||||
|
|
||||||
|
var top = new Panel { Dock = DockStyle.Top, Height = 46 };
|
||||||
|
|
||||||
|
var lblFrom = new Label { Text = "С", AutoSize = true, Left = 10, Top = 15 };
|
||||||
|
_dtFrom = new DateTimePicker { Format = DateTimePickerFormat.Short, Width = 110, Left = 30, Top = 11, Value = DateTime.Today.AddMonths(-1) };
|
||||||
|
var lblTo = new Label { Text = "по", AutoSize = true, Left = 146, Top = 15 };
|
||||||
|
_dtTo = new DateTimePicker { Format = DateTimePickerFormat.Short, Width = 110, Left = 172, Top = 11, Value = DateTime.Today };
|
||||||
|
_cboReport = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 190, Left = 292, Top = 11 };
|
||||||
|
_cboReport.Items.AddRange(new object[] { "Мои заказы", "По поставщикам", "По товарам", "По аптекам" });
|
||||||
|
_cboReport.SelectedIndex = 0;
|
||||||
|
_btnBuild = new Button { Text = "Сформировать", Left = 492, Top = 10, Width = 120, Height = 26 };
|
||||||
|
_lblTotals = new Label { AutoSize = true, Left = 624, Top = 15, Text = "" };
|
||||||
|
|
||||||
|
top.Controls.AddRange(new Control[] { lblFrom, _dtFrom, lblTo, _dtTo, _cboReport, _btnBuild, _lblTotals });
|
||||||
|
|
||||||
|
_grid = new DataGridView
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ReadOnly = true,
|
||||||
|
AllowUserToAddRows = false,
|
||||||
|
AllowUserToDeleteRows = false,
|
||||||
|
AllowUserToResizeRows = false,
|
||||||
|
RowHeadersVisible = false,
|
||||||
|
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
||||||
|
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
|
||||||
|
BorderStyle = BorderStyle.None
|
||||||
|
};
|
||||||
|
|
||||||
|
Controls.Add(_grid);
|
||||||
|
Controls.Add(top);
|
||||||
|
|
||||||
|
_btnBuild.Click += async (_, __) => await LoadAsync();
|
||||||
|
_cboReport.SelectedIndexChanged += (_, __) => Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_btnBuild.Enabled = false;
|
||||||
|
var token = Settings.Default.stringToken;
|
||||||
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
|
{
|
||||||
|
ToastNotification.ShowError("Не найден токен авторизации. Войдите в систему.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = new ApiClient();
|
||||||
|
client.SetToken(token);
|
||||||
|
_lines = await client.GetBuyerReportAsync(_dtFrom.Value.Date, _dtTo.Value.Date)
|
||||||
|
?? new List<BuyerReportLineDto>();
|
||||||
|
Render();
|
||||||
|
|
||||||
|
if (_lines.Count == 0)
|
||||||
|
{
|
||||||
|
ToastNotification.ShowCustom("За выбранный период заказов нет.", Color.DarkOrange, Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppDebugLog.Error("UCReports", "Ошибка формирования отчёта", ex);
|
||||||
|
ToastNotification.ShowError("Ошибка отчёта: " + ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_btnBuild.Enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Render()
|
||||||
|
{
|
||||||
|
var dt = new DataTable();
|
||||||
|
|
||||||
|
switch (_cboReport.SelectedIndex)
|
||||||
|
{
|
||||||
|
case 0: // Мои заказы (по заказу)
|
||||||
|
dt.Columns.Add("Дата", typeof(string));
|
||||||
|
dt.Columns.Add("Номер", typeof(string));
|
||||||
|
dt.Columns.Add("Поставщик", typeof(string));
|
||||||
|
dt.Columns.Add("Аптека", typeof(string));
|
||||||
|
dt.Columns.Add("Позиций", typeof(int));
|
||||||
|
dt.Columns.Add("Сумма", typeof(decimal));
|
||||||
|
dt.Columns.Add("Статус", typeof(string));
|
||||||
|
foreach (var g in _lines.GroupBy(l => l.OrderID)
|
||||||
|
.OrderByDescending(x => x.First().OrderDate ?? DateTime.MinValue))
|
||||||
|
{
|
||||||
|
var f = g.First();
|
||||||
|
var suppliers = string.Join(", ", g.Select(x => x.Supplier)
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct());
|
||||||
|
dt.Rows.Add(
|
||||||
|
f.OrderDate?.ToLocalTime().ToString("dd.MM.yyyy") ?? "",
|
||||||
|
f.GlobalSign,
|
||||||
|
suppliers,
|
||||||
|
f.Location,
|
||||||
|
g.Count(),
|
||||||
|
g.Sum(x => x.Sum),
|
||||||
|
f.Status);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // По поставщикам
|
||||||
|
dt.Columns.Add("Поставщик", typeof(string));
|
||||||
|
dt.Columns.Add("Заказов", typeof(int));
|
||||||
|
dt.Columns.Add("Позиций", typeof(int));
|
||||||
|
dt.Columns.Add("Сумма", typeof(decimal));
|
||||||
|
foreach (var g in _lines.GroupBy(l => string.IsNullOrWhiteSpace(l.Supplier) ? "(не указан)" : l.Supplier)
|
||||||
|
.OrderByDescending(x => x.Sum(y => y.Sum)))
|
||||||
|
{
|
||||||
|
dt.Rows.Add(g.Key, g.Select(x => x.OrderID).Distinct().Count(), g.Count(), g.Sum(x => x.Sum));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // По товарам
|
||||||
|
dt.Columns.Add("Товар", typeof(string));
|
||||||
|
dt.Columns.Add("Код", typeof(string));
|
||||||
|
dt.Columns.Add("Кол-во", typeof(decimal));
|
||||||
|
dt.Columns.Add("Сумма", typeof(decimal));
|
||||||
|
foreach (var g in _lines.GroupBy(l => ((l.ItemName ?? "").Trim() + "|" + (l.ItemCode ?? "")))
|
||||||
|
.OrderByDescending(x => x.Sum(y => y.Sum)))
|
||||||
|
{
|
||||||
|
var f = g.First();
|
||||||
|
dt.Rows.Add(f.ItemName, f.ItemCode, g.Sum(x => x.Qty), g.Sum(x => x.Sum));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // По аптекам (грузополучателям)
|
||||||
|
dt.Columns.Add("Аптека", typeof(string));
|
||||||
|
dt.Columns.Add("Заказов", typeof(int));
|
||||||
|
dt.Columns.Add("Позиций", typeof(int));
|
||||||
|
dt.Columns.Add("Сумма", typeof(decimal));
|
||||||
|
foreach (var g in _lines.GroupBy(l => string.IsNullOrWhiteSpace(l.Location) ? "(не указана)" : l.Location)
|
||||||
|
.OrderByDescending(x => x.Sum(y => y.Sum)))
|
||||||
|
{
|
||||||
|
dt.Rows.Add(g.Key, g.Select(x => x.OrderID).Distinct().Count(), g.Count(), g.Sum(x => x.Sum));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_grid.DataSource = dt;
|
||||||
|
foreach (DataGridViewColumn c in _grid.Columns)
|
||||||
|
{
|
||||||
|
if (c.Name == "Сумма") c.DefaultCellStyle.Format = "N2";
|
||||||
|
if (c.Name == "Кол-во") c.DefaultCellStyle.Format = "N3";
|
||||||
|
if (c.Name == "Позиций" || c.Name == "Заказов") c.Width = 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
int orders = _lines.Select(l => l.OrderID).Distinct().Count();
|
||||||
|
decimal totalSum = _lines.Sum(l => l.Sum);
|
||||||
|
_lblTotals.Text = $"Заказов: {orders} Позиций: {_lines.Count} Сумма: {totalSum:N2}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user