elfisa-pharmacy/src/ElectronicPharmacy/Classes/DiscountResolver.cs
Magomed fd5bd18c66 Add per-pharmacy carts and client-side summed discount pricing.
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>
2026-07-30 14:07:47 +03:00

322 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}