diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.DataAccess/DataContext.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.DataAccess/DataContext.cs index 65b35c918..d74fb6674 100644 --- a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.DataAccess/DataContext.cs +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.DataAccess/DataContext.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using WalkingTec.Mvvm.BlazorDemo.Model; using WalkingTec.Mvvm.Core; using WalkingTec.Mvvm.Demo.Models; using WalkingTec.Mvvm.Demo.Models.Virus; @@ -40,6 +41,8 @@ public DataContext(DbContextOptions options) : base(options) { } public DbSet Patients { get; set; } public DbSet Reports { get; set; } + public DbSet WorkFlowDemo { get; set; } + public override async Task DataInit(object allModules, bool IsSpa) { var state = await base.DataInit(allModules, IsSpa); diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Model/WorkFlowDemo.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Model/WorkFlowDemo.cs new file mode 100644 index 000000000..8c0f56623 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Model/WorkFlowDemo.cs @@ -0,0 +1,49 @@ +/************************************************************************************* + * + * 文 件 名: WorkFlowDemo + * 描 述: + * + * 版 本: V0.1 + * 创 建 者: 粗制乱造 (QQ:195593710) + * 创建时间: 2024/3/21 18:24:36 + * ================================================== + * 历史更新记录 + * 版本:V 修改时间: 修改人: + * 修改内容: + * ================================================== +*************************************************************************************/ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Elsa.Builders; +using Microsoft.EntityFrameworkCore; +using WalkingTec.Mvvm.Core; + +namespace WalkingTec.Mvvm.BlazorDemo.Model +{ + /// + /// 流程测试 + /// + [Table("WorkFlowDemo")] + + [Display(Name = "流程测试")] + public class WorkFlowDemo : BasePoco, IPersistPoco, Core.IWorkflow + { + + [Display(Name = "_Model._Trains._IsValid")] + [Comment("是否有效")] + [Required(ErrorMessage = "Validate.{0}required")] + public bool IsValid { get; set; } = true; + + [Display(Name ="内容")] + [Comment("内容")] + [JsonConverter(typeof(JsonStringConverter))] + public string Content { get; set; } + + } +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Approve.razor b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Approve.razor new file mode 100644 index 000000000..fea3d34e9 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Approve.razor @@ -0,0 +1,85 @@ +@page "/WorkFlowDemo/Approve" +@using WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs; +@inherits BasePage + + + + + + + + + + + + + + + + + + + +@code { + + private WorkFlowDemoVM Model = new WorkFlowDemoVM(); + private ValidateForm vform { get; set; } + [Parameter] + public string id { get; set; } + [Parameter] + public string tag { get; set; } + + protected override async Task OnInitializedAsync() + { + if (id != "") + { + var rv = await WtmBlazor.Api.CallAPI($"/api/WorkFlowDemo/{id}"); + Model = rv.Data; + Model.Tag = tag; + } + await base.OnInitializedAsync(); + } + + private async Task Submit(EditContext context) + { + await PostsForm(vform, "/api/WorkFlowDemo/Approve", (s) => "Sys.OprationSuccess"); + } + + public void OnClose() + { + CloseDialog(); + } + + public void OnSubmit() + { + Model.ActionName = "提交"; + } + + public void OnAgree() + { + Model.ActionName = "同意"; + } + public void OnDeny() + { + Model.ActionName = "拒绝"; + } + public void OnBlack() + { + Model.ActionName = "回退"; + } + + + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Create.razor b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Create.razor new file mode 100644 index 000000000..e965398f7 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Create.razor @@ -0,0 +1,39 @@ +@page "/WorkFlowDemo/Create" +@using WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs; +@inherits BasePage + + + + + + + + + +@code { + + private WorkFlowDemoVM Model = new WorkFlowDemoVM(); + private ValidateForm vform { get; set; } + + + protected override async Task OnInitializedAsync() + { + + await base.OnInitializedAsync(); + } + + + private async Task Submit(EditContext context) + { + await PostsForm(vform, "/api/WorkFlowDemo/add", (s) => "Sys.OprationSuccess"); + } + + public void OnClose() + { + CloseDialog(); + } + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Details.razor b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Details.razor new file mode 100644 index 000000000..1113a8908 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Details.razor @@ -0,0 +1,44 @@ +@page "/WorkFlowDemo/Details/{id}" +@using WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs; +@inherits BasePage + + + + + + + + + + + + + + + + + + +@code { + + private WorkFlowDemoVM Model = null; + private ValidateForm vform { get; set; } + [Parameter] + public string id { get; set; } + + + protected override async Task OnInitializedAsync() + { + + var rv = await WtmBlazor.Api.CallAPI($"/api/WorkFlowDemo/{id}"); + Model = rv.Data; + } + + public void OnClose() + { + CloseDialog(); + } + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Edit.razor b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Edit.razor new file mode 100644 index 000000000..2bdd98091 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Edit.razor @@ -0,0 +1,42 @@ +@page "/WorkFlowDemo/Edit/{id}" +@using WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs; +@inherits BasePage + + + + + + + + + +@code { + + private WorkFlowDemoVM Model = null; + private ValidateForm vform { get; set; } + [Parameter] + public string id { get; set; } + + + protected override async Task OnInitializedAsync() + { + + var rv = await WtmBlazor.Api.CallAPI($"/api/WorkFlowDemo/{id}"); + Model = rv.Data; + await base.OnInitializedAsync(); + } + + private async Task Submit(EditContext context) + { + await PostsForm(vform, $"/api/WorkFlowDemo/edit", (s) => "Sys.OprationSuccess", method: HttpMethodEnum.PUT); + } + + public void OnClose() + { + CloseDialog(); + } + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Index.razor b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Index.razor new file mode 100644 index 000000000..25d4b7208 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/Pages/WorkFlowDemo/Index.razor @@ -0,0 +1,151 @@ +@page "/WorkFlowDemo" +@using WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs; +@inherits BasePage +@attribute [ActionDescription("流程测试", "WalkingTec.Mvvm.BlazorDemo.Controllers,WorkFlowDemo")] + + + + + + + + + + + + + + + + + + + @if (IsAccessable("/api/WorkFlowDemo/Add")) + { + + } + @if (IsAccessable("/api/WorkFlowDemo/BatchDelete")) + { + + + + } + + + +
+ @if (context.CanApprove.Equals("true")) + { + + } + + + @if (IsAccessable("/api/WorkFlowDemo/{id}")) + { + + } + @if (IsAccessable("/api/WorkFlowDemo/BatchDelete")) + { + + } +
+
+
+ +@code{ + + private WorkFlowDemoSearcher SearchModel = new WorkFlowDemoSearcher(); + private Table dataTable; + + + + protected override async Task OnInitializedAsync() + { + + await base.OnInitializedAsync(); + } + + private async Task OnApproveClick(WorkFlowDemo_View item) + { + if (await OpenDialog(@WtmBlazor.Localizer["Page.审批"], x => x.id == item.ID.ToString() && x.tag == item.Tag, isMax: false) == DialogResult.Yes) + { + await dataTable.QueryAsync(); + } + } + + private async Task> OnSearch(QueryPageOptions opts) + { + return await StartSearch("/api/WorkFlowDemo/Search", SearchModel, opts); + } + + private void DoSearch() + { + dataTable.QueryAsync(); + } + + private async Task OnCreateClick(IEnumerable items) + { + if (await OpenDialog(WtmBlazor.Localizer["Sys.Create"]) == DialogResult.Yes) + { + await dataTable.QueryAsync(); + } + } + + private async Task OnEditClick(WorkFlowDemo_View item) + { + if (await OpenDialog(WtmBlazor.Localizer["Sys.Edit"], x => x.id == item.ID.ToString()) == DialogResult.Yes) + { + await dataTable.QueryAsync(); + } + } + + private async Task OnDetailsClick(WorkFlowDemo_View item) + { + await OpenDialog
(WtmBlazor.Localizer["Sys.Details"], x => x.id == item.ID.ToString()); + } + + private async Task OnBatchDeleteClick() + { + if (dataTable.SelectedRows?.Any() == true) + { + await PostsData(dataTable.SelectedRows.Select(x => x.ID).ToList(), $"/api/WorkFlowDemo/batchdelete", (s) => WtmBlazor.Localizer["Sys.BatchDeleteSuccess", s]); + await dataTable.QueryAsync(); + } + else + { + await WtmBlazor.Toast.Information(WtmBlazor.Localizer["Sys.Info"], WtmBlazor.Localizer["Sys.SelectOneRowMin"]); + } + } + + private async Task OnDeleteClick(WorkFlowDemo_View item) + { + await PostsData(new List { item.ID.ToString() }, $"/api/WorkFlowDemo/batchdelete", (s) => "Sys.OprationSuccess"); + await dataTable.QueryAsync(); + } + + + private async Task OnExportClick(IEnumerable items) + { + if (dataTable.SelectedRows?.Any() == true) + { + await Download("/api/WorkFlowDemo/ExportExcelByIds", dataTable.SelectedRows.Select(x => x.ID.ToString()).ToList()); + } + else + { + await Download("/api/WorkFlowDemo/ExportExcel", SearchModel); + } + } + private async Task OnImportClick(IEnumerable items) + { + if (await OpenDialog(WtmBlazor.Localizer["Sys.Import"]) == DialogResult.Yes) + { + await dataTable.QueryAsync(); + } + } + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/WtmBlazorUtils/WTApproveHistory.razor b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/WtmBlazorUtils/WTApproveHistory.razor new file mode 100644 index 000000000..66fcf200e --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Shared/WtmBlazorUtils/WTApproveHistory.razor @@ -0,0 +1,61 @@ +@namespace WtmBlazorUtils +@inherits WTComponent +@inject WtmBlazorContext WtmBlazor +@using System.Linq.Expressions; +@using WalkingTec.Mvvm.Core.WorkFlow + + +@code{ + + [Parameter] + public object Model { get; set; } + public string FlowName{ get; set; } + private List timelineItems{get;set;} + + protected override async Task OnParametersSetAsync() + { + if (Model != null) + { + var vm = (Model as IBaseCRUDVM); + if (vm != null) + { + if (vm.Entity.HasID()) + { + var query = $"flowname={FlowName}&entitytype={vm.Entity.GetType().FullName}&entityid={vm.Entity.GetID()}"; + var list = await WtmBlazor.Api.CallAPI>($"/_workflowapi/gettimeline?{query}", HttpMethodEnum.GET, new Dictionary()); + timelineItems = new List(); + foreach (var item in list.Data) + { + var ti = new TimelineItem + { + Content = item.Time, + Description = item.Message + }; + + if (item.Action == "ύ") + { + ti.Color = Color.Dark; + } + + if(item.Action == "ͬ") + { + ti.Color = Color.Success; + } + if (item.Action == "") + { + ti.Color = Color.Warning; + } + if(item.Action == "ܾ") + { + ti.Color = Color.Danger; + } + timelineItems.Add(ti); + } + + } + } + } + + } + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Test/WorkFlowDemoApiTest.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Test/WorkFlowDemoApiTest.cs new file mode 100644 index 000000000..d16681ffd --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.Test/WorkFlowDemoApiTest.cs @@ -0,0 +1,140 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using WalkingTec.Mvvm.Core; +using WalkingTec.Mvvm.BlazorDemo.Controllers; +using WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs; +using WalkingTec.Mvvm.BlazorDemo.Model; +using WalkingTec.Mvvm.BlazorDemo.DataAccess; + + +namespace WalkingTec.Mvvm.BlazorDemo.Test +{ + [TestClass] + public class WorkFlowDemoApiTest + { + private WorkFlowDemoController _controller; + private string _seed; + + public WorkFlowDemoApiTest() + { + _seed = Guid.NewGuid().ToString(); + _controller = MockController.CreateApi(new DataContext(_seed, DBTypeEnum.Memory), "user"); + } + + [TestMethod] + public void SearchTest() + { + ContentResult rv = _controller.Search(new WorkFlowDemoSearcher()) as ContentResult; + Assert.IsTrue(string.IsNullOrEmpty(rv.Content)==false); + } + + [TestMethod] + public void CreateTest() + { + WorkFlowDemoVM vm = _controller.Wtm.CreateVM(); + WorkFlowDemo v = new WorkFlowDemo(); + + v.Content = "ItZjUkcEqd2Nj2E"; + vm.Entity = v; + var rv = _controller.Add(vm); + Assert.IsInstanceOfType(rv, typeof(OkObjectResult)); + + using (var context = new DataContext(_seed, DBTypeEnum.Memory)) + { + var data = context.Set().Find(v.ID); + + Assert.AreEqual(data.Content, "ItZjUkcEqd2Nj2E"); + Assert.AreEqual(data.CreateBy, "user"); + Assert.IsTrue(DateTime.Now.Subtract(data.CreateTime.Value).Seconds < 10); + } + } + + [TestMethod] + public void EditTest() + { + WorkFlowDemo v = new WorkFlowDemo(); + using (var context = new DataContext(_seed, DBTypeEnum.Memory)) + { + + v.Content = "ItZjUkcEqd2Nj2E"; + context.Set().Add(v); + context.SaveChanges(); + } + + WorkFlowDemoVM vm = _controller.Wtm.CreateVM(); + var oldID = v.ID; + v = new WorkFlowDemo(); + v.ID = oldID; + + v.Content = "sE"; + vm.Entity = v; + vm.FC = new Dictionary(); + + vm.FC.Add("Entity.Content", ""); + var rv = _controller.Edit(vm); + Assert.IsInstanceOfType(rv, typeof(OkObjectResult)); + + using (var context = new DataContext(_seed, DBTypeEnum.Memory)) + { + var data = context.Set().Find(v.ID); + + Assert.AreEqual(data.Content, "sE"); + Assert.AreEqual(data.UpdateBy, "user"); + Assert.IsTrue(DateTime.Now.Subtract(data.UpdateTime.Value).Seconds < 10); + } + + } + + [TestMethod] + public void GetTest() + { + WorkFlowDemo v = new WorkFlowDemo(); + using (var context = new DataContext(_seed, DBTypeEnum.Memory)) + { + + v.Content = "ItZjUkcEqd2Nj2E"; + context.Set().Add(v); + context.SaveChanges(); + } + var rv = _controller.Get(v.ID.ToString()); + Assert.IsNotNull(rv); + } + + [TestMethod] + public void BatchDeleteTest() + { + WorkFlowDemo v1 = new WorkFlowDemo(); + WorkFlowDemo v2 = new WorkFlowDemo(); + using (var context = new DataContext(_seed, DBTypeEnum.Memory)) + { + + v1.Content = "ItZjUkcEqd2Nj2E"; + v2.Content = "sE"; + context.Set().Add(v1); + context.Set().Add(v2); + context.SaveChanges(); + } + + var rv = _controller.BatchDelete(new string[] { v1.ID.ToString(), v2.ID.ToString() }); + Assert.IsInstanceOfType(rv, typeof(OkObjectResult)); + + using (var context = new DataContext(_seed, DBTypeEnum.Memory)) + { + var data1 = context.Set().Find(v1.ID); + var data2 = context.Set().Find(v2.ID); + Assert.AreEqual(data1, null); + Assert.AreEqual(data2, null); + } + + rv = _controller.BatchDelete(new string[] {}); + Assert.IsInstanceOfType(rv, typeof(OkResult)); + + } + + + } +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WalkingTec.Mvvm.BlazorDemo.ViewModel.csproj b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WalkingTec.Mvvm.BlazorDemo.ViewModel.csproj index fab03ff16..5fb2d7abb 100644 --- a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WalkingTec.Mvvm.BlazorDemo.ViewModel.csproj +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WalkingTec.Mvvm.BlazorDemo.ViewModel.csproj @@ -16,4 +16,8 @@ + + + + diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoBatchVM.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoBatchVM.cs new file mode 100644 index 000000000..f606ed8b0 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoBatchVM.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using WalkingTec.Mvvm.Core; +using WalkingTec.Mvvm.Core.Extensions; +using WalkingTec.Mvvm.BlazorDemo.Model; + + +namespace WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs +{ + public partial class WorkFlowDemoBatchVM : BaseBatchVM + { + public WorkFlowDemoBatchVM() + { + ListVM = new WorkFlowDemoListVM(); + LinkedVM = new WorkFlowDemo_BatchEdit(); + } + + } + + /// + /// Class to define batch edit fields + /// + public class WorkFlowDemo_BatchEdit : BaseVM + { + + protected override void InitVM() + { + } + + } + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoImportVM.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoImportVM.cs new file mode 100644 index 000000000..9e9022884 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoImportVM.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using WalkingTec.Mvvm.Core; +using WalkingTec.Mvvm.Core.Extensions; +using WalkingTec.Mvvm.BlazorDemo.Model; + + +namespace WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs +{ + public partial class WorkFlowDemoTemplateVM : BaseTemplateVM + { + + protected override void InitVM() + { + } + + } + + public class WorkFlowDemoImportVM : BaseImportVM + { + + } + +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoListVM.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoListVM.cs new file mode 100644 index 000000000..9c0833225 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoListVM.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WalkingTec.Mvvm.Core; +using WalkingTec.Mvvm.Core.Extensions; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using WalkingTec.Mvvm.BlazorDemo.Model; + + +namespace WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs +{ + public partial class WorkFlowDemoListVM : BasePagedListVM + { + + protected override IEnumerable> InitGridHeader() + { + return new List>{ + this.MakeGridHeader(x => x.Content), + this.MakeGridHeader(x => "CanApprove").SetHide().SetFormat((a, b) => + { + return a.CanApprove; + }), + this.MakeGridHeader(x => "Tag").SetHide().SetFormat((a, b) => + { + return a.Tag; + }), + this.MakeGridHeaderAction(width: 200) + }; + } + + public override IOrderedQueryable GetSearchQuery() + { + var query = DC.Set() + .CheckContain(Searcher.Content, x=>x.Content) + .Select(x => new WorkFlowDemo_View + { + ID = x.ID, + Content = x.Content, + }) + .OrderBy(x => x.ID); + return query; + } + + public override void AfterDoSearcher() + { + if (Wtm.LoginUserInfo != null) + { + var ids = GetMyApproves(); + foreach (var item in EntityList) + { + var m = ids.Find(x => x.ModelID == item.GetID().ToString()); + if (m != null) + { + item.Tag = m.Tag; + item.CanApprove = "true"; + } + } + } + } + + } + + public class WorkFlowDemo_View : WorkFlowDemo{ + public string CanApprove { get; set; } = "false"; + + public string Tag { get; set; } + } +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoSearcher.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoSearcher.cs new file mode 100644 index 000000000..b643d5f4c --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoSearcher.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using WalkingTec.Mvvm.Core; +using WalkingTec.Mvvm.Core.Extensions; +using WalkingTec.Mvvm.BlazorDemo.Model; + + +namespace WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs +{ + public partial class WorkFlowDemoSearcher : BaseSearcher + { + [Display(Name = "内容")] + public String Content { get; set; } + + protected override void InitVM() + { + } + + } +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoVM.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoVM.cs new file mode 100644 index 000000000..46cff0f91 --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo.ViewModel/WorkFlowDemoVMs/WorkFlowDemoVM.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; +using WalkingTec.Mvvm.Core; +using WalkingTec.Mvvm.Core.Extensions; +using WalkingTec.Mvvm.BlazorDemo.Model; + + +namespace WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs +{ + public partial class WorkFlowDemoVM : BaseCRUDVM + { + + public WorkFlowDemoVM() + { + } + + protected override void InitVM() + { + } + + public override void DoAdd() + { + base.DoAdd(); + } + + public override void DoEdit(bool updateAllFields = false) + { + base.DoEdit(updateAllFields); + } + + public override void DoDelete() + { + base.DoDelete(); + } + } +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo/Controllers/WorkFlowDemoController.cs b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo/Controllers/WorkFlowDemoController.cs new file mode 100644 index 000000000..7bc78744f --- /dev/null +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo/Controllers/WorkFlowDemoController.cs @@ -0,0 +1,200 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using WalkingTec.Mvvm.Core; +using WalkingTec.Mvvm.Core.Extensions; +using WalkingTec.Mvvm.Mvc; +using WalkingTec.Mvvm.BlazorDemo.ViewModel.WorkFlowDemoVMs; +using WalkingTec.Mvvm.BlazorDemo.Model; +using System.Threading.Tasks; + + +namespace WalkingTec.Mvvm.BlazorDemo.Controllers +{ + + [AuthorizeJwtWithCookie] + [ActionDescription("流程测试")] + [ApiController] + [Route("api/WorkFlowDemo")] + public partial class WorkFlowDemoController : BaseApiController + { + [ActionDescription("Sys.Search")] + [HttpPost("Search")] + public IActionResult Search(WorkFlowDemoSearcher searcher) + { + if (ModelState.IsValid) + { + var vm = Wtm.CreateVM(); + vm.Searcher = searcher; + return Content(vm.GetJson(enumToString: false)); + } + else + { + return BadRequest(ModelState.GetErrorJson()); + } + } + + [ActionDescription("Sys.Get")] + [HttpGet("{id}")] + public WorkFlowDemoVM Get(string id) + { + var vm = Wtm.CreateVM(id); + return vm; + } + + [ActionDescription("Sys.Create")] + [HttpPost("Add")] + public IActionResult Add(WorkFlowDemoVM vm) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorJson()); + } + else + { + vm.DoAdd(); + + if (ModelState.IsValid) + { + var _ = vm.StartWorkflowAsync().Result; + } + + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorJson()); + } + else + { + return Ok(vm.Entity); + } + } + + } + + [ActionDescription("Sys.Edit")] + [HttpPut("Edit")] + public IActionResult Edit(WorkFlowDemoVM vm) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorJson()); + } + else + { + vm.DoEdit(false); + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorJson()); + } + else + { + return Ok(vm.Entity); + } + } + } + + [HttpPost("BatchDelete")] + [ActionDescription("Sys.Delete")] + public IActionResult BatchDelete(string[] ids) + { + var vm = Wtm.CreateVM(); + if (ids != null && ids.Count() > 0) + { + vm.Ids = ids; + } + else + { + return Ok(); + } + if (!ModelState.IsValid || !vm.DoBatchDelete()) + { + return BadRequest(ModelState.GetErrorJson()); + } + else + { + return Ok(ids.Count()); + } + } + + + [ActionDescription("Sys.Export")] + [HttpPost("ExportExcel")] + public IActionResult ExportExcel(WorkFlowDemoSearcher searcher) + { + var vm = Wtm.CreateVM(); + vm.Searcher = searcher; + vm.SearcherMode = ListVMSearchModeEnum.Export; + return vm.GetExportData(); + } + + [ActionDescription("Sys.CheckExport")] + [HttpPost("ExportExcelByIds")] + public IActionResult ExportExcelByIds(string[] ids) + { + var vm = Wtm.CreateVM(); + if (ids != null && ids.Count() > 0) + { + vm.Ids = new List(ids); + vm.SearcherMode = ListVMSearchModeEnum.CheckExport; + } + return vm.GetExportData(); + } + + [ActionDescription("Sys.DownloadTemplate")] + [HttpGet("GetExcelTemplate")] + public IActionResult GetExcelTemplate() + { + var vm = Wtm.CreateVM(); + var qs = new Dictionary(); + foreach (var item in Request.Query.Keys) + { + qs.Add(item, Request.Query[item]); + } + vm.SetParms(qs); + var data = vm.GenerateTemplate(out string fileName); + return File(data, "application/vnd.ms-excel", fileName); + } + + [ActionDescription("Sys.Import")] + [HttpPost("Import")] + public ActionResult Import(WorkFlowDemoImportVM vm) + { + if (vm!=null && (vm.ErrorListVM.EntityList.Count > 0 || !vm.BatchSaveData())) + { + return BadRequest(vm.GetErrorJson()); + } + else + { + return Ok(vm?.EntityList?.Count ?? 0); + } + } + + [ActionDescription("Sys.Approve")] + [HttpPost("[action]")] + public async Task Approve(WorkFlowDemoVM vm) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorJson()); + } + else + { + await vm.DoEditAsync(false); + if (ModelState.IsValid) + { + await vm.ContinueWorkflowAsync(vm.ActionName, vm.Remark, null, vm.Tag); + } + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorJson()); + } + else + { + return Ok(vm.Entity); + } + } + } + + } +} diff --git a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo/appsettings.json b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo/appsettings.json index 3bffbd4b0..9da4b842c 100644 --- a/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo/appsettings.json +++ b/demo/WalkingTec.Mvvm.BlazorDemo/WalkingTec.Mvvm.BlazorDemo/appsettings.json @@ -22,10 +22,16 @@ }, "Connections": [ + //{ + // "Key": "default", + // "Value": "Server=(localdb)\\mssqllocaldb;Database=blazor;Trusted_Connection=True;MultipleActiveResultSets=true", + // "DBType": "sqlserver" //DataBase, you can choose mysql,sqlserver,pgsql,sqlite,oracle + //} { "Key": "default", - "Value": "Server=(localdb)\\mssqllocaldb;Database=blazor;Trusted_Connection=True;MultipleActiveResultSets=true", - "DBType": "sqlserver" //DataBase, you can choose mysql,sqlserver,pgsql,sqlite,oracle + "Value": "Data Source=..\\..\\..\\..\\Demo.sqlite", + "DbContext": "DataContext", + "DBType": "sqlite" //DataBase, you can choose mysql,sqlserver,pgsql,sqlite,oracle } ], "DetailedErrors": true, @@ -39,7 +45,7 @@ "IsFilePublic": true, "ErrorHandler": "/_Framework/Error", "Languages": "zh,en", - "BlazorMode": "server", // server or wasm + "BlazorMode": "wasm", // server or wasm "CorsOptions": { "EnableAll": true //所有方法是否默认跨域 }, diff --git a/src/WalkingTec.Mvvm.Core/BaseCRUDVM.cs b/src/WalkingTec.Mvvm.Core/BaseCRUDVM.cs index d5566ffcd..f850ef6f2 100644 --- a/src/WalkingTec.Mvvm.Core/BaseCRUDVM.cs +++ b/src/WalkingTec.Mvvm.Core/BaseCRUDVM.cs @@ -1430,7 +1430,26 @@ public virtual async Task ContinueWorkflowAsync(string action li.Groups = Wtm.LoginUserInfo.Groups; li.Roles = Wtm.LoginUserInfo.Roles; li.TenantCode = Wtm.LoginUserInfo.CurrentTenant; - var query = new WorkflowsQuery(nameof(WtmApproveActivity), new WtmApproveBookmark(Wtm.LoginUserInfo.ITCode, string.IsNullOrEmpty(flowName)?typeof(TModel).FullName:flowName, tag, Entity.GetID().ToString()), null, null, null, Wtm.LoginUserInfo.CurrentTenant); + + var bookmark = new WtmApproveBookmark(Wtm.LoginUserInfo.ITCode, string.IsNullOrEmpty(flowName) ? typeof(TModel).FullName : flowName, tag, Entity.GetID().ToString()); + + var input = new WorkflowInput { Input = new WtmApproveInput { Action = actionName, CurrentUser = li, Remark = remark } }; + + var backQuery = new WorkflowsQuery(nameof(BackApproveActivity), bookmark, null, null, null, Wtm.LoginUserInfo.CurrentTenant); + { + var flows = await lp.FindWorkflowsAsync(backQuery); + foreach (var flow in flows) + { + if (flow.WorkflowInstance == null) + { + var result = await lp.ExecutePendingWorkflowAsync(flow, input); + return result; + } + } + } + + + var query = new WorkflowsQuery(nameof(WtmApproveActivity), bookmark, null, null, null, Wtm.LoginUserInfo.CurrentTenant); if (query != null) { var flows = await lp.FindWorkflowsAsync(query); @@ -1438,7 +1457,7 @@ public virtual async Task ContinueWorkflowAsync(string action { if (item.WorkflowInstance == null) { - var result = await lp.ExecutePendingWorkflowAsync(item, new WorkflowInput { Input = new WtmApproveInput { Action = actionName, CurrentUser = li, Remark = remark } }); + var result = await lp.ExecutePendingWorkflowAsync(item, input); return result; } } @@ -1455,7 +1474,7 @@ public virtual async Task ContinueWorkflowAsync(string action { if (item.WorkflowInstance == null) { - var result = await lp.ExecutePendingWorkflowAsync(item, new WorkflowInput { Input = new WtmApproveInput { Action = actionName, CurrentUser = li, Remark = remark } }); + var result = await lp.ExecutePendingWorkflowAsync(item, input); return result; } } @@ -1476,7 +1495,7 @@ public virtual async Task ContinueWorkflowAsync(string action { if (item.WorkflowInstance == null) { - var result = await lp.ExecutePendingWorkflowAsync(item, new WorkflowInput { Input = new WtmApproveInput { Action = actionName, CurrentUser = li, Remark = remark } }); + var result = await lp.ExecutePendingWorkflowAsync(item, input); return result; } } diff --git a/src/WalkingTec.Mvvm.Core/BaseVM.cs b/src/WalkingTec.Mvvm.Core/BaseVM.cs index f691a3510..9ccb83861 100644 --- a/src/WalkingTec.Mvvm.Core/BaseVM.cs +++ b/src/WalkingTec.Mvvm.Core/BaseVM.cs @@ -214,6 +214,7 @@ public string CreatorAssembly [Display(Name = "_Admin.Remark")] public string Remark { get; set; } public string ActionName { get; set; } + public string Tag { get; set; } #endregion #region Event diff --git a/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveActivity.cs b/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveActivity.cs index 6729a79c3..f6be4a666 100644 --- a/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveActivity.cs +++ b/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveActivity.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Xml.Linq; using Elsa.ActivityResults; using Elsa.Attributes; using Elsa.Design; @@ -15,9 +16,12 @@ namespace WalkingTec.Mvvm.Core.WorkFlow [Trigger( Category = "工作流", DisplayName = "审批", - Description = "审批流程", Outcomes = new[] { "同意", "拒绝" })] + Description = "审批流程", Outcomes = new[] { "回退", "同意", "拒绝" })] public class WtmApproveActivity : Elsa.Services.Activity, IActivityPropertyOptionsProvider { + + + [ActivityInput( UIHint = ActivityInputUIHints.MultiText, DefaultSyntax = "Json", @@ -72,9 +76,12 @@ public class WtmApproveActivity : Elsa.Services.Activity, IActivityPropertyOptio [ActivityInput(IsBrowsable = false, Label = "备注")] public string Remark { get; set; } - [ActivityInput(IsBrowsable = false, Label = "审批人")] + [ActivityInput(IsBrowsable = false, Label = "审批人(明细)")] public string ApprovedBy { get; set; } + [ActivityInput(IsBrowsable = false, Label = "审批人")] + public string Approved { get; set; } + [ActivityInput( UIHint = ActivityInputUIHints.SingleLine, Label = "标签")] @@ -142,7 +149,10 @@ public object GetOptions(PropertyInfo property) protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { - var entity = context.GetWorkflowContext(); + + var ardata = context.WorkFlowActivityRecord(); + + var query = ""; ApproveUsersFullText = new List(); List users = new List(); @@ -306,15 +316,40 @@ protected override IActivityExecutionResult OnExecute(ActivityExecutionContext c } } catch { } + + context.JournalData.Add("ApproveUsersFullText", ApproveUsersFullText ?? []); + + context.WorkFlowApproveRecord(new WorkFlowApproveRecord() + { + ApproveUsersFullText = ApproveUsersFullText ?? [] + }); + return Suspend(); } protected override IActivityExecutionResult OnResume(ActivityExecutionContext context) { - WtmApproveInput input = context.Input as WtmApproveInput; - Remark = input?.Remark; - ApprovedBy = input.CurrentUser.Name + "(" + input.CurrentUser.ITCode.ToLower() + ")"; - if (input?.Action == "同意" || input?.Action == "拒绝") + + var ardata = context.WorkFlowActivityRecord(); + + if (context.Input != null) + { + WtmApproveInput input = context.Input as WtmApproveInput; + //记录当前流程信息 + context.WorkFlowActivityRecord(input); + ardata = context.WorkFlowActivityRecord(); + } + + + var remark = ardata?.Remark; + + var ar = context.WorkFlowApproveRecord(); + + ar.Approved = ardata.ITCode; + ar.ApprovedBy = ardata.Name + "(" + ardata.ITCode.ToLower() + ")"; + + + if (ardata?.Action == "同意" || ardata?.Action == "拒绝" || ardata?.Action == "回退") { var exist = _wtm.DC.Set().IgnoreQueryFilters() .Where(x => @@ -323,28 +358,35 @@ protected override IActivityExecutionResult OnResume(ActivityExecutionContext co && x.TenantCode == context.WorkflowInstance.TenantId).ToList(); _wtm.DC.Set().RemoveRange(exist); _wtm.DC.SaveChanges(); - try - { - var notify = _wtm.ServiceProvider.GetRequiredService(); - if (notify != null) - { - ApproveInfo info = new ApproveInfo - { - ApprovedBy = input.CurrentUser, - EntityId = context.ContextId, - EntityType = context.WorkflowExecutionContext.WorkflowContext?.GetType(), - FlowInstanceId = context.WorkflowInstance.Id, - FlowName = context.WorkflowExecutionContext.WorkflowBlueprint.Name, - SubmitBy = context.WorkflowInstance.Variables.Get("Submitter")?.ToString(), - TagName = this.Tag, - }; - notify.OnResume(info); - } - } - catch { } - return Outcome(input?.Action); + //try + //{ + // var notify = _wtm.ServiceProvider.GetService(); + // if (notify != null) + // { + // ApproveInfo info = new ApproveInfo + // { + // ApprovedBy = input.CurrentUser, + // EntityId = context.ContextId, + // EntityType = context.WorkflowExecutionContext.WorkflowContext?.GetType(), + // FlowInstanceId = context.WorkflowInstance.Id, + // FlowName = context.WorkflowExecutionContext.WorkflowBlueprint.Name, + // SubmitBy = context.WorkflowInstance.Variables.Get("Submitter")?.ToString(), + // TagName = this.Tag, + // }; + // notify.OnResume(info); + // } + //} + //catch { } + + //写入Elsa日志 + context.JournalData.Add("Remark", remark); + context.JournalData.Add("ApprovedBy", ar.Approved); + context.JournalData.Add("ApproveUsersFullText", ar.ApproveUsersFullText ?? []); + //写入流程审批记录 + context.WorkFlowApproveRecord(ar); + return Outcome(ardata?.Action); } else { diff --git a/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveTimeLine.cs b/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveTimeLine.cs index ed7d07b9c..84af21af5 100644 --- a/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveTimeLine.cs +++ b/src/WalkingTec.Mvvm.Core/WorkFlow/ApproveTimeLine.cs @@ -27,9 +27,11 @@ public string Message break; case "同意": case "拒绝": + case "回退": rv = this.Approved + this.Action; break; case "_start": + case "提交": rv = this.Approved + "提交审批"; break; case "_finish": diff --git a/src/WalkingTec.Mvvm.Core/WorkFlow/BackApproveActivity.cs b/src/WalkingTec.Mvvm.Core/WorkFlow/BackApproveActivity.cs new file mode 100644 index 000000000..21cf05658 --- /dev/null +++ b/src/WalkingTec.Mvvm.Core/WorkFlow/BackApproveActivity.cs @@ -0,0 +1,154 @@ +/************************************************************************************* + * + * 文 件 名: BackApproveActivity + * 描 述: + * + * 版 本: V0.1 + * 创 建 者: 粗制乱造 (QQ:195593710) + * 创建时间: 2024/3/16 17:11:18 + * ================================================== + * 历史更新记录 + * 版本:V 修改时间: 修改人: + * 修改内容: + * ================================================== +*************************************************************************************/ +using Elsa.ActivityResults; +using Elsa.Attributes; +using Elsa.Metadata; +using Elsa.Services.Models; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace WalkingTec.Mvvm.Core.WorkFlow +{ + [Trigger( + Category = "工作流", + DisplayName = "审批(回退)", + Description = "审批流程中用于回退的中间节点,一般用于开始和不需要向上回退的节点。", + Outcomes = new[] { "提交" })] + public class BackApproveActivity : Elsa.Services.Activity, IActivityPropertyOptionsProvider + { + + private WTMContext _wtm; + public BackApproveActivity(WTMContext wtm) + { + _wtm = wtm; + } + + public object GetOptions(PropertyInfo property) + { + return null; + } + + protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) + { + var ardata = context.WorkFlowActivityRecord(); + + //获取上一节点数据,如果全为空则为起始节点 + if (context.Input == null && ardata == null) + { + string submitter=context.WorkflowInstance.Variables.Get("Submitter")?.ToString(); + + context.WorkFlowApproveRecord(new WorkFlowApproveRecord() + { + Approved = submitter, + ApproveUsersFullText = [submitter], + ApprovedBy=submitter + }); + + return Outcome("提交"); + } + + + if (context.Input != null) + { + WtmApproveInput input = context.Input as WtmApproveInput; + //记录当前流程信息 + context.WorkFlowActivityRecord(input); + ardata = context.WorkFlowActivityRecord(); + } + + //记录提交人信息,用于后续回退做准备 + if (ardata?.Action == "提交" || ardata?.Action == "同意") + { + context.WorkFlowApproveRecord(new WorkFlowApproveRecord() + { + ApproveUsersFullText = [ardata.Name], + Approved = ardata.ITCode, + ApprovedBy = ardata.Name + "(" + ardata.ITCode.ToLower() + ")" + }); + } + + if (ardata.Action != "回退") + { + return Outcome("提交"); + } + + var ar = context.WorkFlowApproveRecord(); + var approved = ar.Approved; + if (approved != null) + { + _wtm.DC.Set().Add(new FrameworkWorkflow + { + ActivityId = context.ActivityId, + WorkflowId = context.WorkflowInstance.Id, + UserCode = approved, + WorkflowName = context.WorkflowExecutionContext.WorkflowBlueprint.Name, + ModelID = context.ContextId, + ModelType = context.WorkflowExecutionContext.WorkflowContext?.GetType()?.FullName, + TenantCode = context.WorkflowInstance.TenantId, + Submitter = context.WorkflowInstance.Variables.Get("Submitter")?.ToString(), + StartTime = DateTime.Now, + Tag = "提交" + }); + _wtm.DC.SaveChanges(); + + context.JournalData.Add("ApproveUsersFullText", ar.ApproveUsersFullText ?? [approved]); + } + + return Suspend(); + } + + + + protected override IActivityExecutionResult OnResume(ActivityExecutionContext context) + { + var ardata = context.WorkFlowActivityRecord(); + + if (context.Input != null) + { + WtmApproveInput input = context.Input as WtmApproveInput; + //记录当前流程信息 + context.WorkFlowActivityRecord(input); + ardata = context.WorkFlowActivityRecord(); + } + + if (ardata?.Action == "提交") + { + var exist = _wtm.DC.Set().IgnoreQueryFilters() + .Where(x => + x.WorkflowId == context.WorkflowInstance.Id + && x.ActivityId == context.ActivityId + && x.TenantCode == context.WorkflowInstance.TenantId).ToList(); + _wtm.DC.Set().RemoveRange(exist); + _wtm.DC.SaveChanges(); + + + var record = context.WorkFlowApproveRecord(); + + //写入Elsa日志 + context.JournalData.Add("ApprovedBy", record.ApprovedBy); + context.JournalData.Add("ApproveUsersFullText", record.ApproveUsersFullText ?? []); + + return Outcome(ardata?.Action); + } + + return Suspend(); + } + } +} diff --git a/src/WalkingTec.Mvvm.Core/WorkFlow/BookMark.cs b/src/WalkingTec.Mvvm.Core/WorkFlow/BookMark.cs index 0b9496745..5149954c1 100644 --- a/src/WalkingTec.Mvvm.Core/WorkFlow/BookMark.cs +++ b/src/WalkingTec.Mvvm.Core/WorkFlow/BookMark.cs @@ -78,4 +78,31 @@ public override async ValueTask> GetBookmarksAsync(B } } + + public class BackApproveBookmarkProvider : BookmarkProvider + { + public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) + { + List rv = new List(); + + var name = context.ActivityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint.Name ?? ""; + var model = context.ActivityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint.ContextOptions?.ContextType?.FullName; + var id = context.ActivityExecutionContext.WorkflowExecutionContext.ContextId?.ToString() ?? ""; + var tag = "提交"; + + var data = context.ActivityExecutionContext.WorkFlowApproveRecord(); + + if (data != null && data.Approved != null) + { + rv.Add(Result(new WtmApproveBookmark(data.Approved, name, tag, id))); + + if (!string.IsNullOrEmpty(model)) + { + rv.Add(Result(new WtmApproveBookmark(data.Approved, model, tag, id))); + } + } + + return rv; + } + } } diff --git a/src/WalkingTec.Mvvm.Core/WorkFlow/WorkFlowExtension.cs b/src/WalkingTec.Mvvm.Core/WorkFlow/WorkFlowExtension.cs new file mode 100644 index 000000000..730319216 --- /dev/null +++ b/src/WalkingTec.Mvvm.Core/WorkFlow/WorkFlowExtension.cs @@ -0,0 +1,93 @@ +/************************************************************************************* + * + * 文 件 名: WorkFlowExtension + * 描 述: + * + * 版 本: V0.1 + * 创 建 者: 粗制乱造 (QQ:195593710) + * 创建时间: 2024/3/18 8:20:17 + * ================================================== + * 历史更新记录 + * 版本:V 修改时间: 修改人: + * 修改内容: + * ================================================== +*************************************************************************************/ +using Elsa.Services.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WalkingTec.Mvvm.Core.WorkFlow +{ + public static class WorkFlowExtension + { + + /// + /// 记录流程动作 + /// + /// + /// + /// + public static void WorkFlowApproveRecord(this ActivityExecutionContext context, WorkFlowApproveRecord recode) + { + //获取流程记录 + var data=context.WorkflowInstance.Variables.Get>("WorkFlowApproveRecord"); + if (data == null) + { + data = new(); + } + + if (data.ContainsKey(context.ActivityId)) + { + data[context.ActivityId] = recode; + } + else + { + data.Add(context.ActivityId, recode); + } + context.WorkflowInstance.Variables.Set("WorkFlowApproveRecord", data); + } + + /// + /// 读取流程审批记录 + /// + /// + /// + public static WorkFlowApproveRecord WorkFlowApproveRecord(this ActivityExecutionContext context) + { + WorkFlowApproveRecord record = null; + var data = context.WorkflowInstance.Variables.Get>("WorkFlowApproveRecord"); + if (data != null&&data.ContainsKey(context.ActivityId)) + { + record=data[context.ActivityId]; + } + return record; + } + + //写入当前流程数据 + public static void WorkFlowActivityRecord(this ActivityExecutionContext context, WtmApproveInput input) + { + if (input != null) + { + + context.WorkflowInstance.Variables.Set("WorkFlowActivityRecord", new WorkFlowActivityRecord() { + ActivityId = context.ActivityId, + Action = input.Action, + Name = input.CurrentUser.Name, + Remark = input.Remark, + ITCode = input.CurrentUser.ITCode, + UserId = input.CurrentUser.UserId, + TenantCode = input.CurrentUser.TenantCode, + }); + } + } + + //获取当前流程数据 + public static WorkFlowActivityRecord WorkFlowActivityRecord(this ActivityExecutionContext context) + { + return context.WorkflowInstance.Variables.Get("WorkFlowActivityRecord"); + } + } +} diff --git a/src/WalkingTec.Mvvm.Core/WorkFlow/WorkFlowRecord.cs b/src/WalkingTec.Mvvm.Core/WorkFlow/WorkFlowRecord.cs new file mode 100644 index 000000000..c0c7236f7 --- /dev/null +++ b/src/WalkingTec.Mvvm.Core/WorkFlow/WorkFlowRecord.cs @@ -0,0 +1,78 @@ +/************************************************************************************* + * + * 文 件 名: WorkFlowApproveRecord + * 描 述: + * + * 版 本: V0.1 + * 创 建 者: 粗制乱造 (QQ:195593710) + * 创建时间: 2024/3/18 8:31:58 + * ================================================== + * 历史更新记录 + * 版本:V 修改时间: 修改人: + * 修改内容: + * ================================================== +*************************************************************************************/ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WalkingTec.Mvvm.Core.WorkFlow +{ + /// + /// 流程审批记录 + /// + public class WorkFlowApproveRecord + { + /// + /// 审批人 + /// + public string Approved { get; set; } + /// + /// 下一步审批 + /// + public List ApproveUsersFullText { get; set; } = []; + /// + /// 审批信息 + /// + public string ApprovedBy { get; set; } + + } + + /// + /// 工作流节点记录 + /// + public class WorkFlowActivityRecord + { + //流程ID + public string ActivityId { get; set; } + /// + /// 流程动作 + /// + public string Action { get; set; } + + /// + /// 用户ID + /// + public string UserId { get; set; } + /// + /// 用户账号 + /// + public string ITCode { get; set; } + + /// + /// 租(客)户代码 + /// + public string TenantCode { get; set; } + + /// + /// 用户名称 + /// + public string Name { get; set; } + /// + /// 审批信息 + /// + public string Remark { get; set; } + } +} diff --git a/src/WalkingTec.Mvvm.Mvc/Helper/FrameworkServiceExtension.cs b/src/WalkingTec.Mvvm.Mvc/Helper/FrameworkServiceExtension.cs index f17fd0250..179cf5892 100644 --- a/src/WalkingTec.Mvvm.Mvc/Helper/FrameworkServiceExtension.cs +++ b/src/WalkingTec.Mvvm.Mvc/Helper/FrameworkServiceExtension.cs @@ -640,6 +640,7 @@ public static IServiceCollection AddWtmWorkflow(this IServiceCollection services elsa .AddConsoleActivities() .AddActivity() + .AddActivity() .AddJavaScriptActivities() .AddHttpActivities(x=> { if (conf.Domains.ContainsKey("server")) { @@ -690,7 +691,7 @@ public static IServiceCollection AddWtmWorkflow(this IServiceCollection services } } services.AddBookmarkProvider(); - + services.AddBookmarkProvider(); return services; } diff --git a/src/WalkingTec.Mvvm.Mvc/_WorkflowApiController.cs b/src/WalkingTec.Mvvm.Mvc/_WorkflowApiController.cs index c21e0798c..8d8448149 100644 --- a/src/WalkingTec.Mvvm.Mvc/_WorkflowApiController.cs +++ b/src/WalkingTec.Mvvm.Mvc/_WorkflowApiController.cs @@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using NetBox.Extensions; +using Newtonsoft.Json.Linq; using NodaTime; using NPOI.SS.Formula.Functions; using Open.Linq.AsyncExtensions; @@ -149,33 +150,34 @@ public async Task GetTimeLine(string flowname,string entitytype,s var specification = new Elsa.Persistence.Specifications.WorkflowExecutionLogRecords.WorkflowInstanceIdSpecification(workflowId); var orderBy = OrderBySpecification.OrderBy(x => x.Timestamp); var records = await log.FindManyAsync(specification, orderBy).ToList(); - var rv = records.Where(x => x.ActivityType == nameof(WtmApproveActivity) && x.EventName != "Executing" && x.EventName != "Resuming" && x.EventName != "Suspended") + var rv = records.Where(x => (x.ActivityType == nameof(WtmApproveActivity) || x.ActivityType == nameof(BackApproveActivity)) && x.EventName != "Executing" && x.EventName != "Resuming" && x.EventName != "Suspended") .Select(x => - new ApproveTimeLine - { - Id = x.ActivityId, - Time = x.Timestamp.InZone(DateTimeZoneProviders.Tzdb.GetSystemDefault()).ToString("yyyy-MM-dd HH:mm:ss", null), - Action = x.EventName == "Executed" ? "等待审批" : x.Data.ContainsKey("Outcomes")?x.Data["Outcomes"].Values().FirstOrDefault():"", - Remark = "", - Approvers = "", - Approved = "" - } + new ApproveTimeLine + { + Id = x.ActivityId, + Time = x.Timestamp.InZone(DateTimeZoneProviders.Tzdb.GetSystemDefault()).ToString("yyyy-MM-dd HH:mm:ss", null), + Action = x.EventName == "Executed" ? "等待审批" : x.Data.ContainsKey("Outcomes") ? x.Data["Outcomes"].Values().FirstOrDefault() : "", + Remark = x?.Data.ContainsKey("Remark") ?? false ? x.Data["Remark"].Value() ?? "" : "", + Approvers = x?.Data.ContainsKey("ApproveUsersFullText") ?? false ? x.Data["ApproveUsersFullText"].Values()?.ToSepratedString() ?? "" : "", + Approved = x?.Data.ContainsKey("ApprovedBy") ?? false ? x.Data["ApprovedBy"].Value() ?? "" : "", + } ).ToList(); - rv = rv.Where(x => string.IsNullOrEmpty(x.Action) == false).ToList(); - foreach (var record in rv) - { - var ad = instance.ActivityData[record.Id]; - object approved, remark, approvers; - ad.TryGetValue(nameof(WtmApproveActivity.ApprovedBy), out approved); - ad.TryGetValue(nameof(WtmApproveActivity.Remark), out remark); - ad.TryGetValue(nameof(WtmApproveActivity.ApproveUsersFullText), out approvers); - record.Approved = (approved as string) ?? ""; - record.Approvers = (approvers as List)?.ToSepratedString() ?? ""; - if (record.Action != "等待审批") - { - record.Remark = (remark as string) ?? ""; - } - } + rv = rv.Where(x => string.IsNullOrEmpty(x.Action) == false && string.IsNullOrEmpty(x.Approvers) == false).ToList(); + //foreach (var record in rv) + //{ + // var ad = instance.ActivityData[record.Id]; + // object approved, remark, approvers; + // ad.TryGetValue(nameof(WtmApproveActivity.ApprovedBy), out approved); + // ad.TryGetValue(nameof(WtmApproveActivity.Remark), out remark); + // ad.TryGetValue(nameof(WtmApproveActivity.ApproveUsersFullText), out approvers); + // record.Approved = (approved as string) ?? ""; + // record.Approvers = (approvers as List)?.ToSepratedString() ?? ""; + // if (record.Action != "等待审批") + // { + // record.Remark = (remark as string) ?? ""; + // } + //} + ApproveTimeLine first = new ApproveTimeLine { Action = "_start",