Apply available buyer/price-list/region discounts to summary.price on download, keep consignee carts separate, and harden SQLite startup/migrations. Co-authored-by: Cursor <cursoragent@cursor.com>
322 lines
12 KiB
C#
322 lines
12 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using Электронная_Фармация.Properties;
|
||
|
||
namespace Электронная_Фармация.Classes
|
||
{
|
||
/// <summary>
|
||
/// Собирает доступные скидки покупателя/прайса/региона и вешает на позицию то, что пришло.
|
||
/// Отсутствующий источник = 0.
|
||
/// </summary>
|
||
public static class DiscountResolver
|
||
{
|
||
public static async Task<DiscountLookup> LoadAsync(ApiClient client, string loginHint)
|
||
{
|
||
var lookup = new DiscountLookup();
|
||
if (client == null)
|
||
{
|
||
return lookup;
|
||
}
|
||
|
||
var regionId = (Settings.Default.RegionId ?? string.Empty).Trim();
|
||
lookup.RegionId = regionId;
|
||
|
||
string buyerId = null;
|
||
try
|
||
{
|
||
var buyers = await client.GetBuyersAsync();
|
||
buyerId = ResolveBuyerId(buyers, loginHint);
|
||
lookup.BuyerId = buyerId;
|
||
AppDebugLog.Info(
|
||
"Discount",
|
||
string.IsNullOrWhiteSpace(buyerId)
|
||
? "buyer_id не найден — клиентская скидка будет 0"
|
||
: $"buyer_id={buyerId}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppDebugLog.Warning("Discount", $"Не удалось загрузить покупателей: {ex.Message}");
|
||
}
|
||
|
||
List<BuyerPriceListAssignmentDto> assignments = null;
|
||
if (!string.IsNullOrWhiteSpace(buyerId))
|
||
{
|
||
try
|
||
{
|
||
assignments = await client.GetBuyerPriceListsAsync(buyerId);
|
||
AppDebugLog.Info("Discount", $"Назначений прайсов покупателя: {assignments?.Count ?? 0}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppDebugLog.Warning("Discount", $"Не удалось загрузить buyer price-lists: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
List<PriceListDto> priceLists = null;
|
||
try
|
||
{
|
||
priceLists = await client.GetPriceListsAsync();
|
||
AppDebugLog.Info("Discount", $"Прайс-листов: {priceLists?.Count ?? 0}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppDebugLog.Warning("Discount", $"Не удалось загрузить price-lists: {ex.Message}");
|
||
}
|
||
|
||
EnrichAssignmentsFromPriceLists(assignments, priceLists);
|
||
|
||
if (assignments != null)
|
||
{
|
||
foreach (var a in assignments)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(a?.PriceListId))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var plId = a.PriceListId.Trim();
|
||
lookup.ByPriceListId[plId] = new ItemDiscountParts
|
||
{
|
||
PriceListId = plId,
|
||
ClientDiscountPct = PositiveOrZero(a.MarkupPct),
|
||
PriceListDiscountPct = PositiveOrZero(a.DefaultMarkupPct),
|
||
RegionDiscountPct = 0m
|
||
};
|
||
|
||
IndexSupplierKeys(lookup, a.SupplierId, a.SupplierName, plId);
|
||
}
|
||
}
|
||
|
||
if (priceLists != null)
|
||
{
|
||
foreach (var pl in priceLists)
|
||
{
|
||
var plId = pl?.ResolvedId;
|
||
if (string.IsNullOrWhiteSpace(plId))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (!lookup.ByPriceListId.TryGetValue(plId, out var parts))
|
||
{
|
||
parts = new ItemDiscountParts { PriceListId = plId };
|
||
lookup.ByPriceListId[plId] = parts;
|
||
}
|
||
|
||
if (parts.PriceListDiscountPct <= 0m && pl.DefaultMarkupPct.HasValue && pl.DefaultMarkupPct.Value > 0m)
|
||
{
|
||
parts.PriceListDiscountPct = pl.DefaultMarkupPct.Value;
|
||
}
|
||
|
||
IndexSupplierKeys(lookup, pl.SupplierId, pl.SupplierName, plId);
|
||
}
|
||
}
|
||
|
||
foreach (var plId in lookup.ByPriceListId.Keys.ToList())
|
||
{
|
||
try
|
||
{
|
||
var regions = await client.GetPriceListRegionsAsync(plId);
|
||
var regionPct = ResolveRegionDiscount(regions, regionId);
|
||
lookup.ByPriceListId[plId].RegionDiscountPct = regionPct;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppDebugLog.Warning("Discount", $"Регионы для прайса {plId}: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
AppDebugLog.Info(
|
||
"Discount",
|
||
$"Готово: прайсов={lookup.ByPriceListId.Count}, привязок по поставщику={lookup.BySupplierKey.Count}, region={regionId}");
|
||
return lookup;
|
||
}
|
||
|
||
public static ItemDiscountParts ResolveForItem(DiscountLookup lookup, PriceSummaryItem item)
|
||
{
|
||
if (lookup == null || item == null)
|
||
{
|
||
return new ItemDiscountParts();
|
||
}
|
||
|
||
var priceListId = (item.PriceListId ?? string.Empty).Trim();
|
||
if (!string.IsNullOrWhiteSpace(priceListId) &&
|
||
lookup.ByPriceListId.TryGetValue(priceListId, out var byId))
|
||
{
|
||
return Clone(byId);
|
||
}
|
||
|
||
var supplierId = (item.SupplierId ?? string.Empty).Trim();
|
||
if (!string.IsNullOrWhiteSpace(supplierId) &&
|
||
lookup.BySupplierKey.TryGetValue(NormalizeKey(supplierId), out var plFromSupplier) &&
|
||
lookup.ByPriceListId.TryGetValue(plFromSupplier, out var bySupplier))
|
||
{
|
||
return Clone(bySupplier);
|
||
}
|
||
|
||
var supplierName = (item.SupplierName ?? string.Empty).Trim();
|
||
if (!string.IsNullOrWhiteSpace(supplierName) &&
|
||
lookup.BySupplierKey.TryGetValue(NormalizeKey(supplierName), out var plFromName) &&
|
||
lookup.ByPriceListId.TryGetValue(plFromName, out var byName))
|
||
{
|
||
return Clone(byName);
|
||
}
|
||
|
||
return new ItemDiscountParts();
|
||
}
|
||
|
||
private static string ResolveBuyerId(List<BuyerDto> buyers, string loginHint)
|
||
{
|
||
if (buyers == null || buyers.Count == 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var login = (loginHint ?? string.Empty).Trim();
|
||
if (!string.IsNullOrWhiteSpace(login))
|
||
{
|
||
var matched = buyers.FirstOrDefault(b =>
|
||
string.Equals((b.Username ?? string.Empty).Trim(), login, StringComparison.OrdinalIgnoreCase) ||
|
||
string.Equals((b.Login ?? string.Empty).Trim(), login, StringComparison.OrdinalIgnoreCase) ||
|
||
string.Equals((b.Name ?? string.Empty).Trim(), login, StringComparison.OrdinalIgnoreCase));
|
||
if (!string.IsNullOrWhiteSpace(matched?.ResolvedId))
|
||
{
|
||
return matched.ResolvedId;
|
||
}
|
||
}
|
||
|
||
if (buyers.Count == 1 && !string.IsNullOrWhiteSpace(buyers[0].ResolvedId))
|
||
{
|
||
return buyers[0].ResolvedId;
|
||
}
|
||
|
||
return buyers
|
||
.Select(b => b.ResolvedId)
|
||
.FirstOrDefault(id => !string.IsNullOrWhiteSpace(id));
|
||
}
|
||
|
||
private static void EnrichAssignmentsFromPriceLists(
|
||
List<BuyerPriceListAssignmentDto> assignments,
|
||
List<PriceListDto> priceLists)
|
||
{
|
||
if (assignments == null || priceLists == null || priceLists.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var byId = priceLists
|
||
.Where(p => !string.IsNullOrWhiteSpace(p?.ResolvedId))
|
||
.GroupBy(p => p.ResolvedId, StringComparer.OrdinalIgnoreCase)
|
||
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||
|
||
foreach (var a in assignments)
|
||
{
|
||
if (a == null || string.IsNullOrWhiteSpace(a.PriceListId))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (!byId.TryGetValue(a.PriceListId.Trim(), out var pl))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(a.SupplierId))
|
||
{
|
||
a.SupplierId = pl.SupplierId;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(a.SupplierName))
|
||
{
|
||
a.SupplierName = pl.SupplierName;
|
||
}
|
||
|
||
if ((!a.DefaultMarkupPct.HasValue || a.DefaultMarkupPct.Value <= 0m) &&
|
||
pl.DefaultMarkupPct.HasValue)
|
||
{
|
||
a.DefaultMarkupPct = pl.DefaultMarkupPct;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static decimal ResolveRegionDiscount(List<PriceListRegionDto> regions, string regionId)
|
||
{
|
||
if (regions == null || regions.Count == 0)
|
||
{
|
||
return 0m;
|
||
}
|
||
|
||
PriceListRegionDto match = null;
|
||
if (!string.IsNullOrWhiteSpace(regionId))
|
||
{
|
||
match = regions.FirstOrDefault(r =>
|
||
string.Equals((r.RegionId ?? string.Empty).Trim(), regionId, StringComparison.OrdinalIgnoreCase) &&
|
||
(r.InWork == null || r.InWork.Value));
|
||
}
|
||
|
||
if (match == null)
|
||
{
|
||
match = regions.FirstOrDefault(r => r.InWork == true) ?? regions.FirstOrDefault();
|
||
}
|
||
|
||
return PositiveOrZero(match?.MarkupPct);
|
||
}
|
||
|
||
private static void IndexSupplierKeys(DiscountLookup lookup, string supplierId, string supplierName, string priceListId)
|
||
{
|
||
if (lookup == null || string.IsNullOrWhiteSpace(priceListId))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(supplierId))
|
||
{
|
||
lookup.BySupplierKey[NormalizeKey(supplierId)] = priceListId;
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(supplierName))
|
||
{
|
||
lookup.BySupplierKey[NormalizeKey(supplierName)] = priceListId;
|
||
}
|
||
}
|
||
|
||
private static ItemDiscountParts Clone(ItemDiscountParts source)
|
||
{
|
||
if (source == null)
|
||
{
|
||
return new ItemDiscountParts();
|
||
}
|
||
|
||
return new ItemDiscountParts
|
||
{
|
||
PriceListId = source.PriceListId,
|
||
PriceListDiscountPct = source.PriceListDiscountPct,
|
||
RegionDiscountPct = source.RegionDiscountPct,
|
||
ClientDiscountPct = source.ClientDiscountPct
|
||
};
|
||
}
|
||
|
||
private static decimal PositiveOrZero(decimal? value)
|
||
{
|
||
return value.HasValue && value.Value > 0m ? value.Value : 0m;
|
||
}
|
||
|
||
private static string NormalizeKey(string value)
|
||
{
|
||
return (value ?? string.Empty).Trim().ToLowerInvariant();
|
||
}
|
||
}
|
||
|
||
public sealed class DiscountLookup
|
||
{
|
||
public string BuyerId { get; set; }
|
||
public string RegionId { get; set; }
|
||
public Dictionary<string, ItemDiscountParts> ByPriceListId { get; } =
|
||
new Dictionary<string, ItemDiscountParts>(StringComparer.OrdinalIgnoreCase);
|
||
public Dictionary<string, string> BySupplierKey { get; } =
|
||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||
}
|
||
}
|