Server now sums price-list, region and buyer markups; desktop must not re-apply discounts. Co-authored-by: Cursor <cursoragent@cursor.com>
746 lines
31 KiB
C#
746 lines
31 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Data.SQLite;
|
||
using System.Drawing;
|
||
using System.Net.Http;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using Электронная_Фармация.Classes;
|
||
using Электронная_Фармация.Properties;
|
||
|
||
namespace Электронная_Фармация.HelpForms
|
||
{
|
||
public partial class HF_DownloadDataFromServer : Form
|
||
{
|
||
// Доли общего прогресса 0..100.
|
||
private const int DownloadPhaseEnd = 45;
|
||
private const int BuildPhaseEnd = 55;
|
||
private const int SavePhaseEnd = 100;
|
||
|
||
public HF_DownloadDataFromServer()
|
||
{
|
||
InitializeComponent();
|
||
}
|
||
|
||
// Чтобы при обновлении прайса не терять корзины других аптек,
|
||
// чистим TempOrderItems только для активного грузополучателя.
|
||
public string ActiveConsigneeName { get; set; } = string.Empty;
|
||
|
||
private readonly string _login = Settings.Default.stringLogin ?? string.Empty;
|
||
private readonly string _password = Settings.Default.stringPassword ?? string.Empty;
|
||
private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString;
|
||
private bool _isDownloading;
|
||
|
||
private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
|
||
{
|
||
UiThemeHelper.ApplyToControlTree(this);
|
||
ConfigureLogView();
|
||
btnCloseThis.Enabled = false;
|
||
dataGridView1.Visible = false;
|
||
rtxtDebug.Visible = true;
|
||
rtxtDebug.ReadOnly = true;
|
||
_isDownloading = true;
|
||
SetOverallProgress(0, "Подключение к серверу...");
|
||
|
||
var client = new ApiClient();
|
||
AppendStatus($"Сервер: {client.BaseUrl}");
|
||
AppendStatus($"Лог: {AppDebugLog.CurrentLogFilePath}");
|
||
AppDebugLog.Info("Download", $"Старт загрузки прайса. Сервер={client.BaseUrl}");
|
||
|
||
try
|
||
{
|
||
SetOverallProgress(1, "Авторизация...");
|
||
await EnsureAuthenticatedAsync(client);
|
||
|
||
SetOverallProgress(3, "Загрузка прайса с сервера...");
|
||
AppendStatus("Загружаю сводный прайс...");
|
||
var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client);
|
||
int totalItems = allSuppliersSummary.Summary?.Count ?? 0;
|
||
|
||
// Наценки прайса/региона/покупателя считает сервер.
|
||
// Десктоп сохраняет цену из API как есть, без локального пересчёта.
|
||
AppendStatus("Цена берётся с сервера без локального пересчёта наценок");
|
||
|
||
AppendStatus($"Получено {totalItems} позиций");
|
||
SetOverallProgress(DownloadPhaseEnd, $"Получено {totalItems} / {totalItems} позиций (100%)");
|
||
|
||
SetOverallProgress(DownloadPhaseEnd, "Подготовка таблицы...");
|
||
var buildProgress = new Progress<CountProgress>(p =>
|
||
{
|
||
int phasePercent = MapPhasePercent(p.Current, p.Total, DownloadPhaseEnd, BuildPhaseEnd);
|
||
SetOverallProgress(
|
||
phasePercent,
|
||
$"Обработка: {p.Current} / {p.Total} ({PercentOf(p.Current, p.Total)}%)");
|
||
});
|
||
var tablePrice = await Task.Run(() => BuildPriceTable(allSuppliersSummary, buildProgress));
|
||
|
||
SetOverallProgress(BuildPhaseEnd, "Сохранение в локальную базу...");
|
||
var saveProgress = new Progress<CountProgress>(p =>
|
||
{
|
||
int phasePercent = MapPhasePercent(p.Current, p.Total, BuildPhaseEnd, SavePhaseEnd);
|
||
SetOverallProgress(
|
||
phasePercent,
|
||
$"Сохранение: {p.Current} / {p.Total} ({PercentOf(p.Current, p.Total)}%)");
|
||
});
|
||
await Task.Run(() => SavePriceTable(tablePrice, saveProgress));
|
||
|
||
SetOverallProgress(100, $"Готово: {tablePrice.Rows.Count} / {tablePrice.Rows.Count} (100%)");
|
||
DialogResult = DialogResult.OK;
|
||
AppendStatus($"Готово. Сохранено {tablePrice.Rows.Count} позиций в локальную базу.");
|
||
ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");
|
||
|
||
Settings.Default.LastPriceUpdateUtc = DateTime.UtcNow;
|
||
Settings.Default.LastPriceReminderUtc = DateTime.MinValue;
|
||
Settings.Default.Save();
|
||
}
|
||
catch (UnauthorizedAccessException ex)
|
||
{
|
||
SetOverallProgress(0, "Ошибка авторизации");
|
||
AppendStatus($"Ошибка авторизации: {ex.Message}");
|
||
AppDebugLog.Error("Download", "Ошибка авторизации при загрузке прайса", ex);
|
||
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
|
||
}
|
||
catch (HttpRequestException ex)
|
||
{
|
||
SetOverallProgress(0, "Ошибка сети");
|
||
AppendStatus($"Ошибка сети: {ex.Message}");
|
||
AppDebugLog.Error("Download", "Ошибка сети при загрузке прайса", ex);
|
||
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetOverallProgress(0, "Ошибка загрузки");
|
||
AppendStatus($"Ошибка загрузки прайса: {ex.Message}");
|
||
AppDebugLog.Error("Download", "Неожиданная ошибка при загрузке прайса", ex);
|
||
ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
_isDownloading = false;
|
||
btnCloseThis.Enabled = true;
|
||
}
|
||
}
|
||
|
||
private async Task EnsureAuthenticatedAsync(ApiClient client)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(Settings.Default.stringToken))
|
||
{
|
||
client.SetToken(Settings.Default.stringToken);
|
||
AppendStatus("Используется сохранённый токен...");
|
||
return;
|
||
}
|
||
|
||
await LoginAndSaveTokenAsync(client);
|
||
}
|
||
|
||
private async Task LoginAndSaveTokenAsync(ApiClient client)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_login) || string.IsNullOrWhiteSpace(_password))
|
||
{
|
||
throw new UnauthorizedAccessException(
|
||
"Не заданы логин и пароль. Выполните авторизацию в меню «Регистрация».");
|
||
}
|
||
|
||
AppendStatus("Выполняю авторизацию...");
|
||
var token = await client.LoginAsync(_login, _password);
|
||
Settings.Default.stringToken = token;
|
||
Settings.Default.Save();
|
||
AppendStatus("Авторизация успешно пройдена");
|
||
}
|
||
|
||
private async Task<PriceSummaryResponse> GetFullPriceSummaryPagedAsync(ApiClient client)
|
||
{
|
||
var regionId = string.IsNullOrWhiteSpace(Settings.Default.RegionId)
|
||
? null
|
||
: Settings.Default.RegionId;
|
||
|
||
const int supplierPageLimit = 100000;
|
||
const int discoveryLimit = 50000;
|
||
|
||
var combined = new PriceSummaryResponse { Summary = new List<PriceSummaryItem>() };
|
||
var transferProgress = new Progress<HttpTransferProgress>(ReportTransferProgress);
|
||
|
||
var supplierIds = new List<string>(ConsigneeHelper.GetKnownSupplierIds());
|
||
if (supplierIds.Count == 0)
|
||
{
|
||
AppendStatus("Первая загрузка: определяю список поставщиков...");
|
||
SetOverallProgress(3, "Определение поставщиков...");
|
||
var discovery = await GetPriceSummaryWithAuthRetryAsync(
|
||
client,
|
||
supplierId: null,
|
||
regionId: regionId,
|
||
limit: discoveryLimit,
|
||
offset: 0,
|
||
transferProgress: transferProgress);
|
||
|
||
if (discovery?.MarkupPercent.HasValue == true)
|
||
{
|
||
combined.MarkupPercent = discovery.MarkupPercent;
|
||
}
|
||
|
||
var discovered = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
if (discovery?.Summary != null)
|
||
{
|
||
foreach (var item in discovery.Summary)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(item.SupplierId))
|
||
{
|
||
discovered.Add(item.SupplierId.Trim());
|
||
}
|
||
}
|
||
}
|
||
|
||
supplierIds.AddRange(discovered);
|
||
AppendStatus($"Найдено поставщиков: {supplierIds.Count}");
|
||
}
|
||
|
||
if (supplierIds.Count == 0)
|
||
{
|
||
AppendStatus("Поставщики не найдены, пробую полный запрос...");
|
||
var fallback = await GetPriceSummaryWithAuthRetryAsync(
|
||
client,
|
||
supplierId: null,
|
||
regionId: regionId,
|
||
limit: 200000,
|
||
offset: 0,
|
||
transferProgress: transferProgress);
|
||
if (fallback?.MarkupPercent.HasValue == true)
|
||
{
|
||
combined.MarkupPercent = fallback.MarkupPercent;
|
||
}
|
||
|
||
if (fallback?.Summary != null)
|
||
{
|
||
combined.Summary.AddRange(fallback.Summary);
|
||
}
|
||
|
||
return combined;
|
||
}
|
||
|
||
int totalSuppliers = supplierIds.Count;
|
||
for (int i = 0; i < totalSuppliers; i++)
|
||
{
|
||
var supplierId = supplierIds[i];
|
||
int supplierProgressStart = 3 + (int)((DownloadPhaseEnd - 3) * ((double)i / totalSuppliers));
|
||
int supplierProgressEnd = 3 + (int)((DownloadPhaseEnd - 3) * ((double)(i + 1) / totalSuppliers));
|
||
SetOverallProgress(
|
||
supplierProgressStart,
|
||
$"Поставщик {i + 1}/{totalSuppliers}...");
|
||
AppendStatus($"Загрузка поставщика {i + 1}/{totalSuppliers}: {supplierId}");
|
||
|
||
PriceSummaryResponse chunk;
|
||
try
|
||
{
|
||
chunk = await GetPriceSummaryWithAuthRetryAsync(
|
||
client,
|
||
supplierId: supplierId,
|
||
regionId: regionId,
|
||
limit: supplierPageLimit,
|
||
offset: 0,
|
||
transferProgress: transferProgress);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppendStatus($"Ошибка поставщика {supplierId}: {ex.Message}");
|
||
AppDebugLog.Error("Download", $"Ошибка загрузки прайса поставщика {supplierId}", ex);
|
||
continue;
|
||
}
|
||
|
||
if (i == 0 && chunk?.MarkupPercent.HasValue == true && !combined.MarkupPercent.HasValue)
|
||
{
|
||
combined.MarkupPercent = chunk.MarkupPercent;
|
||
}
|
||
|
||
int got = chunk?.Summary?.Count ?? 0;
|
||
if (got > 0)
|
||
{
|
||
combined.Summary.AddRange(chunk.Summary);
|
||
}
|
||
|
||
SetOverallProgress(
|
||
Math.Max(supplierProgressStart, supplierProgressEnd - 1),
|
||
$"Поставщик {i + 1}/{totalSuppliers}: +{got}, всего {combined.Summary.Count}");
|
||
AppendStatus($"Поставщик {i + 1}/{totalSuppliers}: получено {got}, всего {combined.Summary.Count}");
|
||
}
|
||
|
||
// Лёгкий discovery новых поставщиков, которых ещё не было в локальной базе.
|
||
try
|
||
{
|
||
AppendStatus("Проверка новых поставщиков...");
|
||
var probe = await GetPriceSummaryWithAuthRetryAsync(
|
||
client,
|
||
supplierId: null,
|
||
regionId: regionId,
|
||
limit: 5000,
|
||
offset: 0,
|
||
transferProgress: null);
|
||
var known = new HashSet<string>(supplierIds, StringComparer.OrdinalIgnoreCase);
|
||
var newcomers = new List<string>();
|
||
if (probe?.Summary != null)
|
||
{
|
||
foreach (var item in probe.Summary)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(item.SupplierId) && known.Add(item.SupplierId.Trim()))
|
||
{
|
||
newcomers.Add(item.SupplierId.Trim());
|
||
}
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < newcomers.Count; i++)
|
||
{
|
||
var supplierId = newcomers[i];
|
||
AppendStatus($"Новый поставщик {i + 1}/{newcomers.Count}: {supplierId}");
|
||
var chunk = await GetPriceSummaryWithAuthRetryAsync(
|
||
client,
|
||
supplierId: supplierId,
|
||
regionId: regionId,
|
||
limit: supplierPageLimit,
|
||
offset: 0,
|
||
transferProgress: transferProgress);
|
||
int got = chunk?.Summary?.Count ?? 0;
|
||
if (got > 0)
|
||
{
|
||
combined.Summary.AddRange(chunk.Summary);
|
||
}
|
||
|
||
AppendStatus($"Новый поставщик: +{got}, всего {combined.Summary.Count}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppendStatus($"Проверка новых поставщиков пропущена: {ex.Message}");
|
||
AppDebugLog.Error("Download", "Ошибка discovery новых поставщиков", ex);
|
||
}
|
||
|
||
return combined;
|
||
}
|
||
|
||
private async Task<PriceSummaryResponse> GetPriceSummaryWithAuthRetryAsync(
|
||
ApiClient client,
|
||
string supplierId,
|
||
string regionId,
|
||
int limit,
|
||
int offset,
|
||
IProgress<HttpTransferProgress> transferProgress)
|
||
{
|
||
try
|
||
{
|
||
return await client.GetPriceSummaryAsync(
|
||
supplierId: supplierId,
|
||
regionId: regionId,
|
||
limit: limit,
|
||
offset: offset,
|
||
transferProgress: transferProgress);
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
AppendStatus("Токен недействителен, повторная авторизация...");
|
||
SetOverallProgress(2, "Повторная авторизация...");
|
||
Settings.Default.stringToken = string.Empty;
|
||
Settings.Default.Save();
|
||
client.SetToken(null);
|
||
await LoginAndSaveTokenAsync(client);
|
||
return await client.GetPriceSummaryAsync(
|
||
supplierId: supplierId,
|
||
regionId: regionId,
|
||
limit: limit,
|
||
offset: offset,
|
||
transferProgress: transferProgress);
|
||
}
|
||
}
|
||
|
||
private void ReportTransferProgress(HttpTransferProgress transfer)
|
||
{
|
||
if (transfer.TotalBytes.HasValue && transfer.TotalBytes.Value > 0)
|
||
{
|
||
int phasePercent = MapPhasePercent(transfer.BytesReceived, transfer.TotalBytes.Value, 3, DownloadPhaseEnd);
|
||
SetOverallProgress(
|
||
phasePercent,
|
||
$"Скачивание: {FormatBytes(transfer.BytesReceived)} / {FormatBytes(transfer.TotalBytes.Value)} ({transfer.Percent}%)");
|
||
}
|
||
else
|
||
{
|
||
SetOverallProgress(
|
||
Math.Min(DownloadPhaseEnd - 1, 5),
|
||
$"Скачивание: получено {FormatBytes(transfer.BytesReceived)}...");
|
||
}
|
||
}
|
||
|
||
private static DataTable BuildPriceTable(
|
||
PriceSummaryResponse allSuppliersSummary,
|
||
IProgress<CountProgress> progress = null)
|
||
{
|
||
var tablePrice = new DataTable();
|
||
tablePrice.Columns.Add("supplier_price_id");
|
||
tablePrice.Columns.Add("guid_es");
|
||
tablePrice.Columns.Add("es_code");
|
||
tablePrice.Columns.Add("supplier_id");
|
||
tablePrice.Columns.Add("DrugName");
|
||
tablePrice.Columns.Add("SupplierName");
|
||
tablePrice.Columns.Add("Price");
|
||
tablePrice.Columns.Add("Quantity");
|
||
tablePrice.Columns.Add(new DataColumn
|
||
{
|
||
ColumnName = "ExpiryPeriod",
|
||
DataType = typeof(string),
|
||
AllowDBNull = true
|
||
});
|
||
tablePrice.Columns.Add(new DataColumn
|
||
{
|
||
ColumnName = "Description",
|
||
DataType = typeof(string),
|
||
AllowDBNull = true
|
||
});
|
||
tablePrice.Columns.Add("Zakaz");
|
||
tablePrice.Columns.Add("SummaZakaza");
|
||
tablePrice.Columns.Add(new DataColumn
|
||
{
|
||
ColumnName = "TradeName",
|
||
DataType = typeof(string),
|
||
AllowDBNull = true
|
||
});
|
||
tablePrice.Columns.Add(new DataColumn
|
||
{
|
||
ColumnName = "Dosage",
|
||
DataType = typeof(string),
|
||
AllowDBNull = true
|
||
});
|
||
tablePrice.Columns.Add(new DataColumn
|
||
{
|
||
ColumnName = "MarkupPercent",
|
||
DataType = typeof(decimal),
|
||
AllowDBNull = true
|
||
});
|
||
|
||
if (allSuppliersSummary?.Summary == null)
|
||
{
|
||
return tablePrice;
|
||
}
|
||
|
||
int total = allSuppliersSummary.Summary.Count;
|
||
int current = 0;
|
||
foreach (var item in allSuppliersSummary.Summary)
|
||
{
|
||
var row = tablePrice.NewRow();
|
||
row["supplier_price_id"] = item.SupplierPriceID ?? string.Empty;
|
||
row["guid_es"] = item.GuidEs ?? string.Empty;
|
||
row["es_code"] = item.EsCode?.ToString() ?? "0";
|
||
row["supplier_id"] = item.SupplierId ?? string.Empty;
|
||
row["DrugName"] = item.DrugName ?? string.Empty;
|
||
row["SupplierName"] = item.SupplierName ?? string.Empty;
|
||
|
||
// Цена уже итоговая с сервера — локально не пересчитываем.
|
||
var serverPrice = item.Price ?? 0m;
|
||
row["Price"] = serverPrice.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||
row["Quantity"] = item.Quantity?.ToString() ?? string.Empty;
|
||
row["ExpiryPeriod"] = item.ExpiryPeriod == null ? (object)DBNull.Value : item.ExpiryPeriod;
|
||
row["Description"] = item.Description == null ? (object)DBNull.Value : item.Description;
|
||
row["Zakaz"] = string.Empty;
|
||
row["SummaZakaza"] = string.Empty;
|
||
row["TradeName"] = string.IsNullOrWhiteSpace(item.TradeName) ? (object)DBNull.Value : item.TradeName.Trim();
|
||
row["Dosage"] = string.IsNullOrWhiteSpace(item.Dosage) ? (object)DBNull.Value : item.Dosage.Trim();
|
||
row["MarkupPercent"] = item.MarkupPercent.HasValue && item.MarkupPercent.Value > 0m
|
||
? item.MarkupPercent.Value
|
||
: 0m;
|
||
tablePrice.Rows.Add(row);
|
||
|
||
current++;
|
||
if (progress != null && (current % 500 == 0 || current == total))
|
||
{
|
||
progress.Report(new CountProgress(current, total));
|
||
}
|
||
}
|
||
|
||
return tablePrice;
|
||
}
|
||
|
||
private void SavePriceTable(DataTable tablePrice, IProgress<CountProgress> progress = null)
|
||
{
|
||
const string commandToDelete = "delete from [PriceList]";
|
||
var commandToDeleteTempOrder = string.IsNullOrWhiteSpace(ActiveConsigneeName)
|
||
? "delete from [TempOrderItems]"
|
||
: "delete from [TempOrderItems] where [ConsigneeName] = @consignee";
|
||
const string commandToInsert = @"INSERT INTO [PriceList] (
|
||
[guid_es],
|
||
[es_code],
|
||
[supplier_id],
|
||
[DrugName],
|
||
[SupplierName],
|
||
[Price],
|
||
[Quantity],
|
||
[ExpiryPeriod],
|
||
[Description],
|
||
[Zakaz],
|
||
[SummaZakaza],
|
||
[supplier_price_id],
|
||
[TradeName],
|
||
[Dosage],
|
||
[MarkupPercent]
|
||
)
|
||
VALUES
|
||
(
|
||
@guid_es,
|
||
@es_code,
|
||
@supplier_id,
|
||
@DrugName,
|
||
@SupplierName,
|
||
@Price,
|
||
@Quantity,
|
||
@ExpiryPeriod,
|
||
@Description,
|
||
@Zakaz,
|
||
@SummaZakaza,
|
||
@supplier_price_id,
|
||
@TradeName,
|
||
@Dosage,
|
||
@MarkupPercent
|
||
)";
|
||
|
||
int total = tablePrice.Rows.Count;
|
||
using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb))
|
||
{
|
||
sqliteCon.Open();
|
||
|
||
using (var cmdDeleteBeforeInsert = new SQLiteCommand(commandToDelete, sqliteCon))
|
||
using (var cmdDeleteTempOrder = new SQLiteCommand(commandToDeleteTempOrder, sqliteCon))
|
||
{
|
||
cmdDeleteBeforeInsert.ExecuteNonQuery();
|
||
|
||
if (!string.IsNullOrWhiteSpace(ActiveConsigneeName) &&
|
||
commandToDeleteTempOrder.Contains("@consignee"))
|
||
{
|
||
cmdDeleteTempOrder.Parameters.AddWithValue("@consignee", ActiveConsigneeName.Trim());
|
||
}
|
||
|
||
cmdDeleteTempOrder.ExecuteNonQuery();
|
||
}
|
||
|
||
using (var transaction = sqliteCon.BeginTransaction())
|
||
using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
|
||
{
|
||
var pGuidEs = cmd.Parameters.Add("@guid_es", DbType.String);
|
||
var pEsCode = cmd.Parameters.Add("@es_code", DbType.String);
|
||
var pSupplierId = cmd.Parameters.Add("@supplier_id", DbType.String);
|
||
var pDrugName = cmd.Parameters.Add("@DrugName", DbType.String);
|
||
var pSupplierName = cmd.Parameters.Add("@SupplierName", DbType.String);
|
||
var pPrice = cmd.Parameters.Add("@Price", DbType.String);
|
||
var pQuantity = cmd.Parameters.Add("@Quantity", DbType.String);
|
||
var pExpiry = cmd.Parameters.Add("@ExpiryPeriod", DbType.String);
|
||
var pDescription = cmd.Parameters.Add("@Description", DbType.String);
|
||
var pZakaz = cmd.Parameters.Add("@Zakaz", DbType.String);
|
||
var pSumma = cmd.Parameters.Add("@SummaZakaza", DbType.String);
|
||
var pSupplierPriceId = cmd.Parameters.Add("@supplier_price_id", DbType.String);
|
||
var pTradeName = cmd.Parameters.Add("@TradeName", DbType.String);
|
||
var pDosage = cmd.Parameters.Add("@Dosage", DbType.String);
|
||
var pMarkup = cmd.Parameters.Add("@MarkupPercent", DbType.Object);
|
||
|
||
int saved = 0;
|
||
bool hasTradeName = tablePrice.Columns.Contains("TradeName");
|
||
bool hasDosage = tablePrice.Columns.Contains("Dosage");
|
||
bool hasMarkup = tablePrice.Columns.Contains("MarkupPercent");
|
||
|
||
foreach (DataRow row in tablePrice.Rows)
|
||
{
|
||
pGuidEs.Value = row["guid_es"] ?? DBNull.Value;
|
||
pEsCode.Value = row["es_code"] ?? DBNull.Value;
|
||
pSupplierId.Value = row["supplier_id"] ?? DBNull.Value;
|
||
pDrugName.Value = row["DrugName"] ?? DBNull.Value;
|
||
pSupplierName.Value = row["SupplierName"] ?? DBNull.Value;
|
||
pPrice.Value = row["Price"] ?? DBNull.Value;
|
||
pQuantity.Value = row["Quantity"] ?? DBNull.Value;
|
||
pExpiry.Value = row["ExpiryPeriod"] ?? DBNull.Value;
|
||
pDescription.Value = row["Description"] ?? DBNull.Value;
|
||
pZakaz.Value = row["Zakaz"] ?? DBNull.Value;
|
||
pSumma.Value = row["SummaZakaza"] ?? DBNull.Value;
|
||
pSupplierPriceId.Value = row["supplier_price_id"] ?? DBNull.Value;
|
||
pTradeName.Value = hasTradeName ? (row["TradeName"] ?? DBNull.Value) : DBNull.Value;
|
||
pDosage.Value = hasDosage ? (row["Dosage"] ?? DBNull.Value) : DBNull.Value;
|
||
pMarkup.Value = hasMarkup ? (row["MarkupPercent"] ?? DBNull.Value) : DBNull.Value;
|
||
cmd.ExecuteNonQuery();
|
||
|
||
saved++;
|
||
if (progress != null && (saved % 250 == 0 || saved == total))
|
||
{
|
||
progress.Report(new CountProgress(saved, total));
|
||
}
|
||
}
|
||
|
||
transaction.Commit();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static int MapPhasePercent(long current, long total, int phaseStart, int phaseEnd)
|
||
{
|
||
if (total <= 0)
|
||
{
|
||
return phaseStart;
|
||
}
|
||
|
||
double ratio = Math.Max(0.0, Math.Min(1.0, (double)current / total));
|
||
return phaseStart + (int)((phaseEnd - phaseStart) * ratio);
|
||
}
|
||
|
||
private static int PercentOf(long current, long total)
|
||
{
|
||
if (total <= 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
return (int)Math.Max(0, Math.Min(100, (100.0 * current) / total));
|
||
}
|
||
|
||
private static string FormatBytes(long bytes)
|
||
{
|
||
if (bytes < 1024)
|
||
{
|
||
return $"{bytes} Б";
|
||
}
|
||
|
||
if (bytes < 1024 * 1024)
|
||
{
|
||
return $"{bytes / 1024.0:0.0} КБ";
|
||
}
|
||
|
||
return $"{bytes / (1024.0 * 1024.0):0.00} МБ";
|
||
}
|
||
|
||
private void SetOverallProgress(int percent, string text)
|
||
{
|
||
if (IsDisposed)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (InvokeRequired)
|
||
{
|
||
BeginInvoke(new Action<int, string>(SetOverallProgress), percent, text);
|
||
return;
|
||
}
|
||
|
||
if (progressDownload.Style != ProgressBarStyle.Continuous)
|
||
{
|
||
progressDownload.Style = ProgressBarStyle.Continuous;
|
||
}
|
||
|
||
progressDownload.Minimum = 0;
|
||
progressDownload.Maximum = 100;
|
||
progressDownload.Value = Math.Max(0, Math.Min(100, percent));
|
||
lblProgress.Text = FormatProgressText(percent, text);
|
||
lblProgress.Refresh();
|
||
progressDownload.Refresh();
|
||
}
|
||
|
||
private void ConfigureLogView()
|
||
{
|
||
rtxtDebug.ReadOnly = true;
|
||
rtxtDebug.WordWrap = true;
|
||
rtxtDebug.ScrollBars = RichTextBoxScrollBars.Vertical;
|
||
rtxtDebug.HideSelection = false;
|
||
rtxtDebug.DetectUrls = false;
|
||
rtxtDebug.BorderStyle = BorderStyle.FixedSingle;
|
||
if (rtxtDebug.Font.Name != "Consolas")
|
||
{
|
||
rtxtDebug.Font = new Font("Consolas", 9f);
|
||
}
|
||
|
||
lblProgress.AutoEllipsis = false;
|
||
lblProgress.UseMnemonic = false;
|
||
}
|
||
|
||
private static string FormatProgressText(int percent, string text)
|
||
{
|
||
return WrapLongText($"{Math.Max(0, Math.Min(100, percent))}% — {text}", 78, 2);
|
||
}
|
||
|
||
private static string WrapLongText(string text, int maxLineLength, int maxLines)
|
||
{
|
||
if (string.IsNullOrEmpty(text) || text.Length <= maxLineLength)
|
||
{
|
||
return text ?? string.Empty;
|
||
}
|
||
|
||
var lines = new List<string>();
|
||
var remaining = text.Trim();
|
||
while (!string.IsNullOrEmpty(remaining) && lines.Count < maxLines)
|
||
{
|
||
if (remaining.Length <= maxLineLength)
|
||
{
|
||
lines.Add(remaining);
|
||
break;
|
||
}
|
||
|
||
var breakIndex = remaining.LastIndexOf(' ', Math.Min(maxLineLength, remaining.Length - 1));
|
||
if (breakIndex <= 0)
|
||
{
|
||
breakIndex = maxLineLength;
|
||
}
|
||
|
||
lines.Add(remaining.Substring(0, breakIndex).TrimEnd());
|
||
remaining = remaining.Substring(breakIndex).TrimStart();
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(remaining) && lines.Count >= maxLines)
|
||
{
|
||
var lastLine = lines[lines.Count - 1];
|
||
if (lastLine.Length > maxLineLength - 3)
|
||
{
|
||
lastLine = lastLine.Substring(0, Math.Max(0, maxLineLength - 3)).TrimEnd();
|
||
}
|
||
|
||
lines[lines.Count - 1] = lastLine + "...";
|
||
}
|
||
|
||
return string.Join(Environment.NewLine, lines);
|
||
}
|
||
|
||
private void AppendStatus(string message)
|
||
{
|
||
if (InvokeRequired)
|
||
{
|
||
BeginInvoke(new Action<string>(AppendStatus), message);
|
||
return;
|
||
}
|
||
|
||
var wrapped = WrapLongText(message ?? string.Empty, 96, int.MaxValue);
|
||
rtxtDebug.AppendText(wrapped + Environment.NewLine);
|
||
rtxtDebug.SelectionStart = rtxtDebug.TextLength;
|
||
rtxtDebug.SelectionLength = 0;
|
||
rtxtDebug.ScrollToCaret();
|
||
rtxtDebug.Refresh();
|
||
AppDebugLog.Info("Download", message);
|
||
}
|
||
|
||
private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e)
|
||
{
|
||
if (_isDownloading)
|
||
{
|
||
e.Cancel = true;
|
||
}
|
||
}
|
||
|
||
private void btnCloseThis_Click(object sender, EventArgs e)
|
||
{
|
||
Close();
|
||
}
|
||
|
||
private sealed class CountProgress
|
||
{
|
||
public CountProgress(int current, int total)
|
||
{
|
||
Current = current;
|
||
Total = total;
|
||
}
|
||
|
||
public int Current { get; }
|
||
public int Total { get; }
|
||
}
|
||
}
|
||
}
|
||
|
||
|