Part 2 (документы) — проверенный шаблон на приёмках: - Phase41: MoySkladExternalId у counterparties/stores/units/retail_points/employees и документов (supplies/enters/losses/inventories/supplier_returns/retail_sales/ cash_shifts/money_transactions) + индексы. Свойства на доменных сущностях. - MoySkladClient.StreamDocumentsAsync: документы в хронологии (order=moment,asc), expand позиций/связей (limit=100), обработка 429/сбоев. - MoySkladDocumentImportService.ImportSuppliesAsync: идемпотентно по msId, резюмируемо (skip перенесённых), moment МСК→UTC, суммы из тиынов, поставщик по externalId контрагента, товары по externalId, проведение → движения остатков (StockService). Журнал ошибок. - Импорт контрагентов теперь пишет MoySkladExternalId (нужно для ссылок в документах). - Эндпоинт POST /api/admin/moysklad/import-supplies (фон, job). Остальные типы — по шаблону. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
3.1 KiB
C#
77 lines
3.1 KiB
C#
using foodmarket.Domain.Catalog;
|
|
using foodmarket.Domain.Common;
|
|
|
|
namespace foodmarket.Domain.Purchases;
|
|
|
|
public enum SupplyStatus
|
|
{
|
|
Draft = 0,
|
|
Posted = 1,
|
|
}
|
|
|
|
public class Supply : TenantEntity, IVersionedEntity
|
|
{
|
|
/// <summary>Postgres `xmin` — optimistic concurrency token. См. IVersionedEntity.</summary>
|
|
public uint Xmin { get; set; }
|
|
|
|
/// <summary>Human-readable document number, unique per organization (e.g. "П-2026-000001").</summary>
|
|
public string Number { get; set; } = "";
|
|
|
|
public DateTime Date { get; set; } = DateTime.UtcNow;
|
|
public SupplyStatus Status { get; set; } = SupplyStatus.Draft;
|
|
|
|
public Guid SupplierId { get; set; }
|
|
public Counterparty Supplier { get; set; } = null!;
|
|
|
|
public Guid StoreId { get; set; }
|
|
public Store Store { get; set; } = null!;
|
|
|
|
public Guid CurrencyId { get; set; }
|
|
public Currency Currency { get; set; } = null!;
|
|
|
|
public string? Notes { get; set; }
|
|
|
|
/// <summary>Sum of line totals. Computed on save.</summary>
|
|
public decimal Total { get; set; }
|
|
|
|
public DateTime? PostedAt { get; set; }
|
|
public Guid? PostedByUserId { get; set; }
|
|
|
|
/// <summary>Сколько из <see cref="Total"/> оплачено — сумма всех
|
|
/// проведённых <c>MoneyTransactionLink.LinkedSum</c>, где
|
|
/// <c>SupplyId</c>=этой приёмки. Пересчитывается в <c>MoneyService</c>
|
|
/// при проведении/отмене платежа. Инвариант: <c>0 ≤ PayedSum ≤ Total</c>.
|
|
/// Статус оплаты получаем сравнением: PayedSum==0 — не оплачена,
|
|
/// 0<PayedSum<Total — частично, PayedSum>=Total — оплачена.</summary>
|
|
public decimal PayedSum { get; set; }
|
|
|
|
/// <summary>Id сущности в МойСкладе — для идемпотентного переноса истории.</summary>
|
|
public string? MoySkladExternalId { get; set; }
|
|
|
|
public ICollection<SupplyLine> Lines { get; set; } = new List<SupplyLine>();
|
|
}
|
|
|
|
public class SupplyLine : TenantEntity
|
|
{
|
|
public Guid SupplyId { get; set; }
|
|
public Supply Supply { get; set; } = null!;
|
|
|
|
public Guid ProductId { get; set; }
|
|
public Product Product { get; set; } = null!;
|
|
|
|
public decimal Quantity { get; set; }
|
|
public decimal UnitPrice { get; set; }
|
|
public decimal LineTotal { get; set; }
|
|
|
|
public int SortOrder { get; set; }
|
|
|
|
/// <summary>Если true — пользователь вручную задал розничную цену для
|
|
/// этой строки (через UI приёмки). При Posting автонаценка по Group.MarkupPercent
|
|
/// для этой строки пропускается.</summary>
|
|
public bool RetailPriceManuallyOverridden { get; set; }
|
|
|
|
/// <summary>Розничная цена, которую пользователь вписал в колонке «Розничная»
|
|
/// строки приёмки. Применяется к Product.Prices[default] при Posting.</summary>
|
|
public decimal? RetailPriceOverride { get; set; }
|
|
}
|