Перейти к содержанию

Расширения для работы с файлами в задании

Расширения для работы с файлами в задании

По умолчанию в задании не сохраняются прикрепленные файлы, но расширения позволяют реализовать их сохранение в основной карточке, либо в ее файловом сателлите. Пример последнего можно увидеть в расширениях типового решения TaskFilesExampleGetExtension, TaskFilesExampleStoreExtension и TaskEnableAttachFilesExampleUIExtension для desktop- и web-клиента.

Создайте серверное расширение, которое при сохранении карточки перенесет файлы, относящиеся к заданиям, из файлового контейнера основной карточки в файловый сателлит:

using System.Threading.Tasks; using Tessa.Cards; using Tessa.Cards.Extensions; using Tessa.Extensions.Platform.Server.Cards; using Tessa.Platform;

namespace Tessa.Extensions.Default.Server.Cards { /// <summary> /// Пример расширения, которое сохраняет файлы из заданий карточки в карточку - файловый сателлит. /// </summary> public class TaskFilesExampleStoreExtension : CardStoreExtension { private ICardRepository cardRepository;

public TaskFilesExampleStoreExtension(ICardRepository cardRepository) { Check.ArgumentNotNull(cardRepository, nameof(cardRepository));

this.cardRepository = cardRepository; }

public override async Task BeforeRequest(ICardStoreExtensionContext context) { // Получаем карточку из запроса. var card = context.Request.Card; if (card.TypeID == CardHelper.FileSatelliteTypeID) { return; }

Card fileSatellite = null;

// Перебираем файлы в файловом контейнере для того, чтобы сохранить те, что относятся к заданиям, в файловый сателлит. for (int i = card.Files.Count - 1; i >= 0; i--) { var file = card.Files[i]; // Если у файла не определен TaskID, переходим к следующему. if (file.TaskID is null) { continue; }

// Если до сих пор не извлечен файловый сателлит для главной карточки, извлекаем его. if (fileSatellite is null) { fileSatellite = await FileSatelliteHelper.GetFileSatelliteAsync( context, this.cardRepository);

// Если мы не можем получить файловый сателлит, то выходим из метода. if (fileSatellite is null) { return; }

context.Request.ForceTransaction = true; }

// Добавляем файл в файловый сателлит. fileSatellite.Files.Add(file);

// Настраиваем маппинг сохранения контента файла в карточку файлового сателлита if (file.State != CardFileState.Deleted) { var fileMapping = context.Request.FileMapping.Add(); fileMapping.CardID = fileSatellite.ID; fileMapping.FileID = file.RowID; fileMapping.VersionRowID = file.VersionRowID; fileMapping.SourceFileID = file.RowID; fileMapping.StoreSource = file.StoreSource; }

// Из главной карточки удаляем информацию по файлу. card.Files.RemoveAt(i); } }

public override Task BeforeCommitTransaction(ICardStoreExtensionContext context) { if (context.ValidationResult.IsSuccessful()) { // Сохраняем файловый сателлит. return FileSatelliteHelper.StoreFileSatelliteAsync(context, this.cardRepository); }

return Task.CompletedTask; } } }

Зарегистрируйте серверное расширение на сохранение карточки следующим образом:

[Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() { this.UnityContainer .RegisterType<TaskFilesExampleStoreExtension>(new ContainerControlledLifetimeManager()) ; }

public override void RegisterExtensions(IExtensionContainer extensionContainer) { extensionContainer .RegisterExtension<ICardStoreExtension, TaskFilesExampleStoreExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform, 1) .WithUnity(this.UnityContainer)) ; } }

Important

Данное расширение должно быть зарегистрировано с порядком AfterPlatform, т.к. обязательно должно выполняться после платформенного расширения, определяющего место хранения содержимого файла по умолчанию.

При чтении карточки с заданиями с сервера необходимо переносить файлы из файлового сателлита в карточки заданий, чтобы они стали доступны пользователю. Этой цели послужит серверное расширение, которое будет запускаться каждый раз при чтении карточки:

using System.Linq; using System.Threading.Tasks; using Tessa.Cards; using Tessa.Cards.Extensions; using Tessa.Extensions.Platform.Server.Cards; using Tessa.Platform; using Tessa.Platform.Collections;

namespace Tessa.Extensions.Default.Server.Cards { /// <summary> /// Пример расширения, которое переносит файлы из карточки - файлового сателлита в карточки задач. /// </summary> public class TaskFilesExampleGetExtension : CardGetExtension { #region Fields

private ICardRepository cardRepository;

#endregion

#region Constructors

public TaskFilesExampleGetExtension(ICardRepository cardRepository) { Check.ArgumentNotNull(cardRepository, nameof(cardRepository));

this.cardRepository = cardRepository; }

#endregion

#region Base Overrides

/// <inheritdoc/> public override async Task AfterRequest(ICardGetExtensionContext context) { // Получаем карточку из ответа. var card = context.Response.Card;

// Если в карточке нет задач, выходим из метода. if (!card.Tasks.Any()) { return; }

// Извлекаем файловый сателлит для главной карточки. var fileSatellite = await FileSatelliteHelper.GetFileSatelliteAsync (this.cardRepository, card.ID, context.ValidationResult, false);

// Если мы не можем получить файловый сателлит, то выходим из метода. if (fileSatellite is null) { return; }

// Получаем и затем перебираем список файлов, что относятся к задачам. var taskFiles = fileSatellite.Files;

foreach (var taskFile in taskFiles) { if (taskFile.TaskID is null || !card.Tasks.TryFirst(x => x.RowID == taskFile.TaskID, out var task)) { continue; }

// Добавляем в файловый контейнер файл, который уже в UI-расширении будет перенесен в файловый контейнер задачи. var addedTaskFile = task.Card.Files.Add(taskFile);

// Добавляем информацию о версиях файла. var response = await this.cardRepository.GetFileVersionsAsync(new CardGetFileVersionsRequest() { FileID = addedTaskFile.RowID, CardID = fileSatellite.ID, FileName = addedTaskFile.Name }, context.CancellationToken);

addedTaskFile.Versions.Clear(); addedTaskFile.Versions.AddRange(response.FileVersions);

// В информации о файле указываем в качестве внешнего источника файловый сателлит. addedTaskFile.ExternalSource = new CardFileContentSource { CardID = fileSatellite.ID, FileID = addedTaskFile.RowID, VersionRowID = addedTaskFile.VersionRowID, Source = addedTaskFile.StoreSource, CardTypeID = CardHelper.FileSatelliteTypeID };

// Для карточки задачи, к которой прикреплен файл, выставим Version > 0. // Это необходимо для того, чтобы изменить способ сохранения карточки с CardStoreMode.Insert на CardStoreMode.Update, // и таким образом обеспечить работу логики, извлекающей по запросу список версий для прикрепленных файлов. if (task.Card.Version == 0) { task.Card.Version = 1; } } }

#endregion } }

Зарегистрируйте серверное расширение на загрузку карточки следующим образом:

[Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() { this.UnityContainer .RegisterType<TaskFilesExampleGetExtension>(new ContainerControlledLifetimeManager()) ; }

public override void RegisterExtensions(IExtensionContainer extensionContainer) { extensionContainer .RegisterExtension<ICardGetExtension, TaskFilesExampleGetExtension>(x => x .WithOrder(ExtensionStage.BeforePlatform, 1) .WithUnity(this.UnityContainer)) ; } }

Теперь создайте UI-расширение для desktop-клиента, которое вызывается на все запросы сохранения карточки.

Данное расширение на клиентской стороне при сохранении карточки переносит файлы из заданий в файловый контейнер основной карточки:

using System.Linq; using System.Threading.Tasks; using Tessa.Cards; using Tessa.Files; using Tessa.UI.Cards; using Tessa.UI.Cards.Forms; using Tessa.UI.Cards.Tasks;

namespace Tessa.Extensions.Default.Client.UI { /// <summary> /// Пример расширения UI, которое позволяет добавлять файлы в задачи карточки. /// </summary> public sealed class TaskEnableAttachFilesExampleUIExtension : CardUIExtension { #region Base Overrides

///<inheritdoc/> public override async Task Saving(ICardUIExtensionContext context) { // Если в форме с карточкой нет задач, то ничего не делаем. if (context.Model.MainForm is not DefaultFormTabWithTasksViewModel mainForm || !mainForm.Tasks.Any()) { return; }

// Перебираем файлы в карточках задач главной формы. foreach (var taskViewModel in mainForm.Tasks.OfType<TaskViewModel>()) { await taskViewModel.TaskModel.FileContainer.Files.EnsureAllContentModifiedAsync(context.CancellationToken).ConfigureAwait(false); foreach (var fileCard in taskViewModel.TaskModel.CardTask.Card.Files) {

// Отправляем в контейнер только измененные файлы, либо те, у которых была добавлена или снята электронная подпись. if (fileCard.State != CardFileState.None || fileCard.Flags != CardFileFlags.None || (fileCard.Card.Sections.TryGetValue(CardSignatureHelper.SectionName, out CardSection signatures) && signatures.Rows.Any(x => x.State != CardRowState.None))) { if (taskViewModel.TaskModel.FileContainer.TryGetFile(fileCard.RowID) is { } file) { // Добавляем файл в контейнер главной карточки, если он есть. context.FileContainer.Files.Add(file); }

// Добавляем файл в главную карточку. var newFileCard = context.Card.Files.Add(fileCard);

// Записываем в поле TaskID идентификатор текущей задачи. newFileCard.TaskID = taskViewModel.TaskModel.CardTask.Card.ID; } } } }

#endregion } }

Зарегистрируйте UI-расширение следующим образом:

[Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterExtensions(IExtensionContainer extensionContainer) { extensionContainer .RegisterExtension<ICardUIExtension, TaskEnableAttachFilesExampleUIExtension>(x => x .WithOrder(ExtensionStage.BeforePlatform, 1) .WithSingleton()) ; } }

Для web-клиента потребуется создать и зарегистрировать собственное расширение:

import { ICardUIExtensionContext, CardUIExtension } from 'tessa/ui/cards'; import { DefaultFormTabWithTasksViewModel } from 'tessa/ui/cards/forms';

export class TaskEnableAttachFilesExampleUIExtension extends CardUIExtension { public async saving(context: ICardUIExtensionContext): Promise<void> { // Если в форме с карточкой нет задач, то ничего не делаем. if ( !(context.model.mainForm instanceof DefaultFormTabWithTasksViewModel) || !context.model.mainForm.tasks.length ) { return; } const form = context.model.mainForm; // Перебираем файлы в карточках задач главной формы. for await (const task of form.tasks) { await task.taskModel.fileContainer.ensureAllContentModified(); // Отправляем в контейнер только измененные файлы, либо те, у которых была добавлена или снята электронная подпись. const filesCount = task.taskModel.card.files.length; for (let i = 0; i < filesCount; i++) { const cardFile = task.taskModel.card.files[i]; if (cardFile.hasChanges()) { cardFile.taskId = task.id; context.storeRequest.card.files.add(cardFile); const file = task.taskModel.fileContainer.files.find(x => x.id === cardFile.rowId)!; if (file) { await file.lastVersion.ensureContentDownloaded(); await context.fileContainer.addFile(file); } } } } } }

Регистрация производится следующим образом:

import { ExtensionContainer, ExtensionStage } from 'tessa/extensions'; import { TaskEnableAttachFilesExampleUIExtension } from './taskEnableAttachFilesExampleUIExtension';

ExtensionContainer.instance.registerExtension({ extension: TaskEnableAttachFilesExampleUIExtension, singleton: true, stage: ExtensionStage.BeforePlatform });

Back to top