diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs index b3b8579e..2685c557 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs @@ -9,6 +9,7 @@ public class WorkflowDto public string ApprovalStatusFieldName { get; set; } public string ApprovalDescriptionFieldName { get; set; } public bool ApprovalIsFilterUserName { get; set; } + public bool ApprovalIsResetWorkflow { get; set; } public List Criteria { get; set; } = []; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs index c90045aa..019e4986 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs @@ -11,5 +11,6 @@ public interface IListFormWorkflowAppService : IApplicationService Task DeleteCriteriaAsync(string id); Task ResetDemoAsync(string listFormCode = null); Task StartAsync(StartWorkflowDto input); + Task ResetAsync(StartWorkflowDto input); Task DecisionAsync(DecisionWorkflowDto input); } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs index 9e013f92..b23826a0 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs @@ -318,6 +318,65 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA return result; } + [HttpPost("reset")] + public async Task ResetAsync(StartWorkflowDto input) + { + if (input.Keys?.Length > 1) + { + return await RunForEachKeyAsync(input.Keys, keys => ResetSingleAsync(input.ListFormCode, keys)); + } + + return await ResetSingleAsync(input.ListFormCode, input.Keys); + } + + private async Task ResetSingleAsync(string listFormCode, object[] keys) + { + var context = await CreateRunContextAsync(listFormCode, keys); + if (!context.Workflow.ApprovalIsResetWorkflow) + { + throw new UserFriendlyException("Bu liste formu için workflow sıfırlama tanımlı değil."); + } + + var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start") + ?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı."); + + var previousStatus = GetRowValue(context.Row, context.Workflow.ApprovalStatusFieldName)?.ToString(); + + var clear = new Dictionary + { + [context.Workflow.ApprovalStatusFieldName] = null + }; + if (!context.Workflow.ApprovalUserFieldName.IsNullOrWhiteSpace()) + { + clear[context.Workflow.ApprovalUserFieldName] = null; + } + if (!context.Workflow.ApprovalDateFieldName.IsNullOrWhiteSpace()) + { + clear[context.Workflow.ApprovalDateFieldName] = null; + } + if (!context.Workflow.ApprovalDescriptionFieldName.IsNullOrWhiteSpace()) + { + clear[context.Workflow.ApprovalDescriptionFieldName] = null; + } + + await UpdateRowAsync(context, clear); + MergeRowValues(context.Row, clear); + + context.WorkflowNoteRows.Add(("Reset By: ", ResolveCurrentUserDisplayName())); + if (!previousStatus.IsNullOrWhiteSpace()) + { + context.WorkflowNoteRows.Add(("Reset Step: ", previousStatus)); + } + + var result = await RunUntilWaitAsync(context, start); + await InsertWorkflowNoteAsync( + context, + $"Workflow Reset: {start.Title}", + BuildWorkflowNoteContent(context.WorkflowNoteRows)); + + return result; + } + [HttpPost("decision")] public async Task DecisionAsync(DecisionWorkflowDto input) { diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 7342f3b0..5f121fd9 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -3792,6 +3792,24 @@ "en": "Workflow has already been started for the selected record", "tr": "Seçili kayıt icin workflow zaten başlamış." }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.WorkflowReset", + "en": "Workflow Reset", + "tr": "İş Akışı Sıfırla" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.WorkflowResetMessage", + "en": "All approvals of the selected {0} record(s) will be removed and the workflow will start over. Do you confirm?", + "tr": "Seçili {0} kaydın tüm onayları kaldırılacak ve iş akışı baştan başlatılacak. Onaylıyor musunuz?" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.WorkflowNotStarted", + "en": "Workflow has not been started for the selected record", + "tr": "Seçili kayıt için workflow başlatılmamış." + }, { "resourceName": "Platform", "key": "SeciliKayitlarSilmekIstiyormusunuz", @@ -20034,6 +20052,12 @@ "en": "Filter User Name?", "tr": "Kullanıcı Adı Filtresin mi?" }, + { + "resourceName": "Platform", + "key": "ListForms.ListFormEdit.Workflow.ApprovalIsResetWorkflow", + "en": "Workflow Reset?", + "tr": "İş Akışı Sıfırlama?" + }, { "resourceName": "Platform", "key": "ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName", diff --git a/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs b/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs index 9febf405..b454d21b 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs @@ -10,6 +10,7 @@ public class Workflow : ValueObject public string ApprovalStatusFieldName { get; set; } public string ApprovalDescriptionFieldName { get; set; } public bool ApprovalIsFilterUserName { get; set; } + public bool ApprovalIsResetWorkflow { get; set; } protected override IEnumerable GetAtomicValues() { @@ -18,6 +19,7 @@ public class Workflow : ValueObject yield return ApprovalStatusFieldName; yield return ApprovalDescriptionFieldName; yield return ApprovalIsFilterUserName; + yield return ApprovalIsResetWorkflow; } } diff --git a/ui/src/proxy/form/models.ts b/ui/src/proxy/form/models.ts index 7a07b56c..7713d6c7 100644 --- a/ui/src/proxy/form/models.ts +++ b/ui/src/proxy/form/models.ts @@ -930,6 +930,7 @@ export interface WorkflowDto { approvalStatusFieldName: string approvalDescriptionFieldName: string approvalIsFilterUserName: boolean + approvalIsResetWorkflow: boolean criteria: ListFormWorkflowCriteriaDto[] } diff --git a/ui/src/services/workflow.service.ts b/ui/src/services/workflow.service.ts index bc39e238..510d7426 100644 --- a/ui/src/services/workflow.service.ts +++ b/ui/src/services/workflow.service.ts @@ -101,6 +101,16 @@ export const workflowService = { return response.data }, + async resetWorkflow(listFormCode: string, keys: unknown[]) { + const response = await apiService.fetchData({ + method: 'POST', + url: `${baseUrl}/reset`, + data: { listFormCode, keys }, + }) + + return response.data + }, + async decideWorkflow( listFormCode: string, keys: unknown[], diff --git a/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx b/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx index 47dd98d2..65e49502 100644 --- a/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx @@ -318,6 +318,7 @@ export function FormTabWorkflow( approvalDateFieldName: string(), approvalDescriptionFieldName: string(), approvalIsFilterUserName: bool(), + approvalIsResetWorkflow: bool(), }), }) @@ -340,7 +341,7 @@ export function FormTabWorkflow(
-
+
+ + + +
{workflowItems.length > 0 && (
diff --git a/ui/src/views/admin/listForm/wizard/WizardStep6.tsx b/ui/src/views/admin/listForm/wizard/WizardStep6.tsx index 22aca92e..5dc32ed6 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep6.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep6.tsx @@ -300,7 +300,7 @@ function WizardStep6({ -
+
{[ [ 'approvalUserFieldName', @@ -381,6 +381,26 @@ function WizardStep6({ )} + + + {({ field, form }: FieldProps) => ( + { + form.setFieldValue(field.name, checked) + onWorkflowChange({ + ...values, + approvalIsResetWorkflow: checked, + criteria, + }) + }} + /> + )} + +
diff --git a/ui/src/views/list/toolbarOrder.ts b/ui/src/views/list/toolbarOrder.ts index af8ba569..a1b635ad 100644 --- a/ui/src/views/list/toolbarOrder.ts +++ b/ui/src/views/list/toolbarOrder.ts @@ -5,6 +5,7 @@ const orderedToolbarItemNames = [ 'addRowButton', 'refreshButton', 'openDynamicGridReport', + 'workflowReset', 'workflowStart', 'expandAllButton', 'collapseAllButton', diff --git a/ui/src/views/list/useToolbar.tsx b/ui/src/views/list/useToolbar.tsx index e075e327..a6aaa714 100644 --- a/ui/src/views/list/useToolbar.tsx +++ b/ui/src/views/list/useToolbar.tsx @@ -154,6 +154,85 @@ const useToolbar = ({ approvalCriteria.length > 0 && grdOpt.updateServiceAddress ) { + if (workflowOptions.approvalIsResetWorkflow) { + items.push({ + widget: 'dxButton', + name: 'workflowReset', + location: 'after', + locateInMenu: 'auto', + showText: 'always', + options: { + icon: 'revert', + text: toToolbarText(translate('::ListForms.ListForm.WorkflowReset')), + hint: translate('::ListForms.ListForm.WorkflowReset'), + visible: true, + disabled: true, + onClick: async () => { + const selectedRowKeys = (await Promise.resolve( + getSelectedRowKeys() as any, + )) as unknown[] + if (!selectedRowKeys?.length) { + toast.push( + + {translate('::ListForms.ListForm.SelectRecord')} + , + { placement: 'bottom-end' }, + ) + return + } + + const selectedRows = ((await Promise.resolve(getSelectedRowsData() as any)) || + []) as Record[] + if ( + selectedRows.length === 0 || + selectedRows.some((row) => isWorkflowNotStarted(row, workflowOptions)) + ) { + toast.push( + + {translate('::ListForms.ListForm.WorkflowNotStarted')} + , + { placement: 'bottom-end' }, + ) + return + } + + if ( + !grdOpt.keyFieldName || + selectedRows.some( + (row) => + row[grdOpt.keyFieldName!] === undefined || row[grdOpt.keyFieldName!] === null, + ) + ) { + toast.push( + + Liste formu anahtar alanı seçili kayıtlarda bulunamadı. + , + { placement: 'bottom-end' }, + ) + return + } + + const keys = selectedRows.map((row) => row[grdOpt.keyFieldName!]) + + setToolbarModalData({ + open: true, + content: ( + setToolbarModalData(undefined)} + onCompleted={() => { + refreshData() + setToolbarModalData(undefined) + }} + /> + ), + }) + }, + }, + }) + } + items.push({ widget: 'dxButton', name: 'workflowStart', @@ -716,6 +795,14 @@ export function isWorkflowToolbarItemDisabled( ) } + if (itemName === 'workflowReset') { + return ( + !workflowOptions.approvalIsResetWorkflow || + selectedRowsData.length === 0 || + selectedRowsData.some((row) => isWorkflowNotStarted(row, workflowOptions)) + ) + } + if (!itemName.startsWith('workflowApproval_')) { return undefined } @@ -778,6 +865,22 @@ export function updateWorkflowApprovalToolbarItems( } } + const workflowResetItemIndex = toolbarOptions.items + .map((item: any) => item.name) + .indexOf('workflowReset') + + if (workflowResetItemIndex >= 0) { + const resetEnabled = + workflowOptions.approvalIsResetWorkflow === true && + selectedRowsData.length > 0 && + selectedRowsData.every((row) => !isWorkflowNotStarted(row, workflowOptions)) + const resetOptionPath = `toolbar.items[${workflowResetItemIndex}].options.disabled` + const nextResetDisabled = !resetEnabled + if (component.option(resetOptionPath) !== nextResetDisabled) { + component.option(resetOptionPath, nextResetDisabled) + } + } + const currentUserIdentities = getCurrentUserWorkflowIdentities(currentUser) approvalCriteria.forEach((criteria) => { @@ -808,6 +911,66 @@ export function updateWorkflowApprovalToolbarItems( }) } +function WorkflowResetConfirmDialog({ + keys, + listFormCode, + onCancel, + onCompleted, +}: { + keys: unknown[] + listFormCode: string + onCancel: () => void + onCompleted: () => void +}) { + const { translate } = useLocalization() + const [submitting, setSubmitting] = useState(false) + + const reset = async () => { + setSubmitting(true) + try { + const results = await Promise.all( + keys.map((key) => workflowService.resetWorkflow(listFormCode, [key])), + ) + showWorkflowToastMessages(results) + onCompleted() + } catch (error: any) { + toast.push( + + {error?.response?.data?.error?.message || + error?.response?.data?.message || + error?.message || + 'Workflow sifirlanamadi.'} + , + { placement: 'bottom-end' }, + ) + } finally { + setSubmitting(false) + } + } + + return ( + <> +
{translate('::ListForms.ListForm.WorkflowReset')}
+

+ {translate('::ListForms.ListForm.WorkflowResetMessage', { 0: keys.length })} +

+
+ + +
+ + ) +} + function WorkflowApprovalDecisionDialog({ criteriaTitle, keys,