food-market/src/food-market.domain/Inventory/Loss.cs
nns 2ed9f04cb2 feat(migration): документный мигратор МойСклад (приёмки) + externalId на документах
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>
2026-10-09 13:53:52 +05:00

79 lines
3.5 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 foodmarket.Domain.Catalog;
using foodmarket.Domain.Common;
namespace foodmarket.Domain.Inventory;
public enum LossStatus
{
Draft = 0,
Posted = 1,
}
/// <summary>Причина списания. Из этого классификатора строятся ABC/XYZ-отчёты
/// и фискальная отчётность (порча/просрочка) в KZ. Не строй наценочную логику
/// поверх этих значений — используй ровно как business reason.</summary>
public enum LossReason
{
/// <summary>Брак (производственный/транспортный).</summary>
Defect = 0,
/// <summary>Просрочка / истёк срок годности.</summary>
Expired = 1,
/// <summary>Повреждение (механическое, при хранении/перевозке).</summary>
Damage = 2,
/// <summary>Недостача (выявлена инвентаризацией; для оформления свыше — отдельный документ инвентаризации).</summary>
Shortage = 3,
/// <summary>Прочая причина (в комментарии).</summary>
Other = 99,
}
/// <summary>Документ списания со склада: брак, просрочка, недостача, повреждение.
/// При проведении создаёт <see cref="StockMovement"/> с типом
/// <see cref="MovementType.WriteOff"/> и отрицательным Quantity.</summary>
public class Loss : TenantEntity
{
public string Number { get; set; } = "";
public DateTime Date { get; set; } = DateTime.UtcNow;
public LossStatus Status { get; set; } = LossStatus.Draft;
public Guid StoreId { get; set; }
public Store Store { get; set; } = null!;
public Guid CurrencyId { get; set; }
public Currency Currency { get; set; } = null!;
public LossReason Reason { get; set; } = LossReason.Other;
public string? Notes { get; set; }
/// <summary>Сумма по строкам = Σ Quantity·UnitCost. Балансовая стоимость списания.</summary>
public decimal Total { get; set; }
/// <summary>Если документ создан «на основании инвентаризации» (недостача) —
/// ссылка на неё. Иначе null (обычное списание брака/просрочки).</summary>
public Guid? InventoryDocId { get; set; }
public DateTime? PostedAt { get; set; }
public Guid? PostedByUserId { get; set; }
public ICollection<LossLine> Lines { get; set; } = new List<LossLine>();
/// <summary>Id сущности в МойСкладе — для идемпотентного переноса истории.</summary>
public string? MoySkladExternalId { get; set; }
}
public class LossLine : TenantEntity
{
public Guid LossId { get; set; }
public Loss Loss { get; set; } = null!;
public Guid ProductId { get; set; }
public Product Product { get; set; } = null!;
public decimal Quantity { get; set; }
/// <summary>Балансовая цена единицы (снимок Product.Cost на момент создания строки;
/// можно переопределить). Используется в Total и в отчётности.</summary>
public decimal UnitCost { get; set; }
public decimal LineTotal { get; set; }
public int SortOrder { get; set; }
}