Создание дополнительного номера в карточке, помимо проектного и регистрационного
Создание дополнительного номера в карточке, помимо проектного и регистрационного¶
По умолчанию в типовом решении для различных типов документов (договор, входящий, исходящий и др.) присутствуют два типа номера NumberType: проектный (обычно выделяется при создании карточки) и регистрационный (выделяется после регистрации).
Каждый из типов номеров хранится в местоположении номера NumberLocation
- это группы из трёх полей Number, FullNumber, Sequence.
В типовом решении есть два местоположения: основное Primary
(поля Number, FullNumber, Sequence в секции DocumentCommonInfo) и вторичное Secondary
(поля SecondaryNumber, SecondaryFullNumber, SecondarySequence).
-
Пока выделен только проектный номер, то в обоих местоположениях содержится один и тот же номер.
-
Как только выделяется регистрационный номер - он записывается в местоположение
Primary
, в то время как в местоположенииSecondary
по-прежнему расположен проектный номер.
Это позволяет при дерегистрации восстановить проектный номер в местоположении Primary
. При этом контрол “Нумератор” связан с местоположением Primary
, и пользователь видит только “текущий” (регистрационный или проектный) номер.
В разрабатываемых решениях возможна ситуация, когда с карточкой должен быть ассоциирован дополнительный номер, который не является ни проектным, ни регистрационным. Например, это номер-идентификатор со сквозной нумерацией по всем типам карточек, или же это номер, по которому генерируется штрих-код, или любой другой дополнительный номер (номеров может быть несколько).
Допустим, требуется добавить один такой дополнительный номер, который будет доступен в новом контроле “Нумератор” для некоторых типов карточек. Для этого сгенерируйте уникальный идентификатор Guid, который будет связан с этим типом номера, и запишите его в статический класс с типами номеров в сборке Tessa.Extensions.Shared
.
using System;
using Tessa.Cards.Numbers;
namespace Tessa.Extensions.Shared.Numbers
{
public static class MyNumberTypes
{
public static readonly NumberType AdditionalNumber =
new NumberType(new Guid(...), nameof(AdditionalNumber));
public static void Register()
{
NumberTypeRegistry.Instance.Register(AdditionalNumber);
}
}
}
Теперь в той же сборке создайте класс-наследник типового класса DocumentNumberDirector
, в котором будут определены алгоритмы работы с новым типом номера.
-
Свойство
AdditionalNumberLocation
определяет местоположение номера, т.е. поля карточки, используемые для сохранения номера. -
Метод
TryGetNumberLocationCore
связывает тип номераMyNumberTypes.AdditionalNumber
и местоположениеAdditionalNumberLocation
. Поскольку номер всегда будет записан в отдельные поля карточки (по условию задачи), то метод возвращает именно это местоположение (если же номер будет сохраняться в разное время в разных полях, как для проектного номера в типовом решении, то алгоритм местоположения можно изменить). -
Метод
TryGetSequenceNameCore
позволяет сгенерировать уникальное имя последовательности с учётом плейсхолдеров, например, указав год из поля “Дата документа” плейсхолдером{yyyy}
-
Метод
GetFullNumberCore
аналогичным образом генерирует строку с номером, которая затем может использоваться для отображения в контроле и в представлениях, в файловых шаблонах и др. Плейсхолдер{0000n}
выводит сам номер, дополненный соответствующим количеством нулей.
using System.Threading;
using System.Threading.Tasks;
using Tessa.Cards.Numbers;
using Tessa.Extensions.Default.Shared.Numbers;
using Tessa.Extensions.Default.Shared.Workflow.KrProcess;
namespace Tessa.Extensions.Shared.Numbers
{
public class AdditionalNumberDirector : DocumentNumberDirector
{
public AdditionalNumberDirector(
IKrTypesCache typesCache,
INumberDependencies dependencies)
: base(typesCache, dependencies)
{
// при необходимости в конструкторе можно получить другие зависимости из Unity,
// например, ICardCache для чтения значений из карточки настроек
}
private INumberLocation secondaryLocation;
protected override INumberLocation SecondaryLocation =>
// укажите секцию и поля, в которых будет расположен номер
this.secondaryLocation ??= new CardNumberLocation(
"MyDocumentSection", "Number", "FullNumber", "Sequence", this);
protected override async ValueTask<INumberLocation> TryGetNumberLocationCoreAsync(
INumberContext context,
NumberTypeDescriptor numberType,
CancellationToken cancellationToken = default)
{
return numberType == MyNumberTypes.AdditionalNumber
? this.SecondaryLocation
: await base.TryGetNumberLocationCoreAsync(
context, numberType, cancellationToken).ConfigureAwait(false);
}
protected override async Task<string> TryGetSequenceNameCoreAsync(
INumberContext context,
NumberTypeDescriptor numberType,
CancellationToken cancellationToken = default)
{
if (numberType == MyNumberTypes.AdditionalNumber)
{
// определяем формат последовательности, его можно прочитать
// из каких-то карточек настроек, получив их из Unity
const string sequenceName = "ДопНомера-{yyyy}";
return await this.FormatSequenceNameAsync(
context, numberType, sequenceName, cancellationToken).ConfigureAwait(false);
}
return await base.TryGetSequenceNameCoreAsync(
context, numberType, cancellationToken).ConfigureAwait(false);
}
protected override async Task<string> GetFullNumberCoreAsync(
INumberContext context,
NumberTypeDescriptor numberType,
long number,
CancellationToken cancellationToken = default)
{
if (numberType == MyNumberTypes.AdditionalNumber)
{
// любой формат номера с плейсхолдерами, его также можно прочитать из карточки
const string numberFormat = "Номер-{0000n}";
return await this.FormatNumberAsync(
context, numberType, number, numberFormat, cancellationToken).ConfigureAwait(false);
}
return await base.GetFullNumberCoreAsync(
context, numberType, number, cancellationToken).ConfigureAwait(false);
}
}
}
Класс также может содержать методы, определяющие в какой момент происходят действия с этим номером. Например, чтобы выделить номер при первом сохранении карточки, допишите в класс следующий метод:
protected override async ValueTask<bool> IsAvailableCoreAsync(
INumberContext context,
NumberEventType eventType,
CancellationToken cancellationToken = default)
{
if (eventType == NumberEventTypes.SavingNewCard)
{
// разрешаем выделение номеров при сохранении, независимо от настроек типового,
// чтобы выделить наш новый номер
return true;
}
return await base.IsAvailableCoreAsync(context, eventType, cancellationToken);
}
protected override async Task<bool> OnSavingNewCardAsync(
INumberContext context,
CancellationToken cancellationToken = default)
{
bool reservedAdditionalNumber = false;
INumberObject number = await context.Composer
.GetNumberAsync(context, MyNumberTypes.AdditionalNumber, cancellationToken).ConfigureAwait(false);
if (number.IsEmpty())
{
// наш номер ещё не выделен из контрола, выделяем
INumberObject reservedNumber = await context.Builder
.ReserveAndCommitAtServerAsync(
context,
MyNumberTypes.AdditionalNumber,
cancellationToken: cancellationToken).ConfigureAwait(false);
reservedAdditionalNumber = !reservedNumber.IsEmpty();
}
// выполняем базовую реализацию, если хотим, чтобы сработали действия для типовых номеров;
// в IsAvailableCoreAsync мы не вызывали base.IsAvailableCoreAsync для этого события,
// поэтому проверим его здесь перед тем, как обратиться к base.OnSavingNewCardAsync
bool baseResult = await base.IsAvailableCoreAsync(context, context.EventType, cancellationToken).ConfigureAwait(false)
&& await base.OnSavingNewCardAsync(context, cancellationToken).ConfigureAwait(false);
// возвращаем true, если либо наша, либо базовая реализация что-либо сделала с номерами
return reservedAdditionalNumber || baseResult;
}
Код в типовом классе DocumentNumberDirector
служит примером использования API номеров. Найдите в нём места использования класса NumberTypes
с типовыми номерами, и по аналогии определите свою бизнес-логику для нового номера.
Класс, реализующий INumberDirector
, требуется зарегистрировать. Класс регистратора необходимо добавить в сборку Tessa.Extensions.Shared
, чтобы он функционировал и на клиенте, и на сервере (в web-клиенте будет использоваться серверная реализация).
Если зарегистрировать AdditionalNumberDirector
с указанием имени типа карточки (например, “Contract”), то он будет функционировать только для этого типа. Для нескольких типов можно вызвать регистрацию RegisterType для каждого из типов. Если ваша реализация INumberDirector должна работать по умолчанию для всех типов (вместо DocumentNumberDirector), то вызовите RegisterType без указания имени типа.
using System;
using Tessa.Cards.Numbers;
using Tessa.Platform;
using Unity;
using Unity.Lifetime;
namespace Tessa.Extensions.Shared.Numbers
{
[Registrator]
public sealed class Registrator : RegistratorBase
{
private static readonly Guid[] ids =
{
// Contract
new Guid("335f86a1-d009-012c-8b45-1f43c2382c2d")
};
public override void InitializeRegistration() => MyNumberTypes.Register();
public override void RegisterUnity()
{
this.UnityContainer
.RegisterType<AdditionalNumberDirector>(new ContainerControlledLifetimeManager());
}
public override void FinalizeRegistration()
{
var container = this.UnityContainer.TryResolve<INumberDirectorContainer>();
if (container is not null)
{
foreach (Guid cardTypeID in ids)
{
container.Register(cardTypeID, c => c.Resolve<AdditionalNumberDirector>());
}
}
}
}
}
Если номер требуется отображать и/или изменять в интерфейсе карточки, то добавьте в этот тип карточки контрол “Нумератор”.
Укажите ему следующие настройки:
-
Секция карточки (таблица, в которой содержатся поля номера, например, MyDocumentSection)
-
Номер - обычно FullNumber (строка)
-
Порядковый номер - обычно Number (число Int64)
-
Последовательность - обычно Sequence (строка)
-
Тип номера - та же строка, которая указана для типа в классе MyNumberTypes. У нас это “AdditionalNumber” (без кавычек).
-
Вы можете поставить флажок “Только для чтения”, если пользователь не должен иметь возможность редактировать поля номера, независимо от настроек правил доступа.