From d10763c4ea499de54a7aa2665b9fa36cceef6d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sun, 26 Oct 2025 23:27:01 +0300 Subject: [PATCH] Hr Expense --- .../Seeds/HostData.json | 210 ++++++- .../{ListFormsSeeder.cs => ListFormSeeder.cs} | 569 +++++++++++++++++- .../Enums/TableNameEnum.cs | 3 +- .../PlatformConsts.cs | 1 + .../TableNameResolver.cs | 1 + .../Kurs.Platform.Domain/Data/SeedConsts.cs | 1 + .../Entities/Tenant/Hr/Employee.cs | 1 + .../Entities/Tenant/Hr/Expense.cs | 28 + .../Queries/QueryHelper.cs | 43 +- .../EntityFrameworkCore/PlatformDbContext.cs | 23 + ....cs => 20251026200120_Initial.Designer.cs} | 122 +++- ...9_Initial.cs => 20251026200120_Initial.cs} | 65 ++ .../PlatformDbContextModelSnapshot.cs | 120 ++++ .../Tenants/Seeds/TenantData.json | 38 ++ .../Tenants/TenantDataSeeder.cs | 42 +- .../Tenants/TenantSeederDto.cs | 15 + .../assets/styles/components/_menu-item.css | 2 +- ui/src/mocks/mockIntranet.ts | 46 +- .../admin/role-management/RolesPermission.tsx | 6 +- ui/src/views/list/Grid.tsx | 1 + 20 files changed, 1289 insertions(+), 48 deletions(-) rename api/src/Kurs.Platform.DbMigrator/Seeds/{ListFormsSeeder.cs => ListFormSeeder.cs} (98%) create mode 100644 api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Expense.cs rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20251025204259_Initial.Designer.cs => 20251026200120_Initial.Designer.cs} (98%) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20251025204259_Initial.cs => 20251026200120_Initial.cs} (98%) diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/HostData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/HostData.json index 8e04e434..e8c51d17 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/HostData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/HostData.json @@ -2205,6 +2205,12 @@ "en": "Update", "tr": "Değiştir" }, + { + "resourceName": "Platform", + "key": "Widget", + "en": "Widget", + "tr": "Widget" + }, { "resourceName": "Platform", "key": "Manager", @@ -7827,6 +7833,12 @@ "tr": "Eğitimler", "en": "Trainings" }, + { + "resourceName": "Platform", + "key": "App.Hr.Expense", + "tr": "Harcama Talepleri", + "en": "Expense Requests" + }, { "resourceName": "Platform", "key": "App.Intranet.Meal", @@ -15412,11 +15424,21 @@ "RequiredPermissionName": "App.Hr.Overtime", "IsDisabled": false }, + { + "ParentCode": "App.Hr", + "Code": "App.Hr.Expense", + "DisplayName": "App.Hr.Expense", + "Order": 10, + "Url": "/admin/list/list-expense", + "Icon": "FaFileInvoiceDollar", + "RequiredPermissionName": "App.Hr.Expense", + "IsDisabled": false + }, { "ParentCode": "App.Hr", "Code": "App.Hr.Payroll", "DisplayName": "App.Hr.Payroll", - "Order": 10, + "Order": 11, "Url": "/admin/list/list-payroll", "Icon": "FcMoneyTransfer", "RequiredPermissionName": "App.Hr.Payroll", @@ -15426,7 +15448,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Template360", "DisplayName": "App.Hr.Template360", - "Order": 11, + "Order": 12, "Url": "/admin/list/list-template360", "Icon": "FcInspection", "RequiredPermissionName": "App.Hr.Template360", @@ -15436,7 +15458,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Performance360", "DisplayName": "App.Hr.Performance360", - "Order": 12, + "Order": 13, "Url": "/admin/list/list-performance360", "Icon": "FcSurvey", "RequiredPermissionName": "App.Hr.Performance360", @@ -15446,7 +15468,7 @@ "ParentCode": "App.Hr", "Code": "App.Hr.Training", "DisplayName": "App.Hr.Training", - "Order": 13, + "Order": 14, "Url": "/admin/list/list-training", "Icon": "FcGraduationCap", "RequiredPermissionName": "App.Hr.Training", @@ -20012,6 +20034,15 @@ "MultiTenancySide": 3, "MenuGroup": "Erp|Kurs" }, + { + "GroupName": "App.Administration", + "Name": "App.Files.Widget", + "ParentName": "App.Files", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp|Kurs" + }, { "GroupName": "App.Administration", "Name": "App.Definitions.Sector", @@ -23864,6 +23895,15 @@ "MultiTenancySide": 3, "MenuGroup": "Erp" }, + { + "GroupName": "App.Projects", + "Name": "App.Projects.Tasks.Widget", + "ParentName": "App.Projects.Tasks", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, { "GroupName": "App.Projects", "Name": "App.Projects.Phases", @@ -24242,6 +24282,15 @@ "MultiTenancySide": 3, "MenuGroup": "Erp" }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Employee.Widget", + "ParentName": "App.Hr.Employee", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, { "GroupName": "App.Hr", "Name": "App.Hr.Department", @@ -24620,6 +24669,15 @@ "MultiTenancySide": 3, "MenuGroup": "Erp" }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Leave.Widget", + "ParentName": "App.Hr.Leave", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, { "GroupName": "App.Hr", "Name": "App.Hr.Overtime", @@ -24683,6 +24741,87 @@ "MultiTenancySide": 3, "MenuGroup": "Erp" }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Overtime.Widget", + "ParentName": "App.Hr.Overtime", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense", + "ParentName": null, + "DisplayName": "App.Hr.Expense", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense.Create", + "ParentName": "App.Hr.Expense", + "DisplayName": "Create", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense.Update", + "ParentName": "App.Hr.Expense", + "DisplayName": "Update", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense.Delete", + "ParentName": "App.Hr.Expense", + "DisplayName": "Delete", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense.Export", + "ParentName": "App.Hr.Expense", + "DisplayName": "Export", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense.Import", + "ParentName": "App.Hr.Expense", + "DisplayName": "Import", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense.Activity", + "ParentName": "App.Hr.Expense", + "DisplayName": "Activity", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Expense.Widget", + "ParentName": "App.Hr.Expense", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, { "GroupName": "App.Hr", "Name": "App.Hr.Payroll", @@ -24809,6 +24948,15 @@ "MultiTenancySide": 3, "MenuGroup": "Erp" }, + { + "GroupName": "App.Hr", + "Name": "App.Hr.Training.Widget", + "ParentName": "App.Hr.Training", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp" + }, { "GroupName": "App.Hr", "Name": "App.Hr.CostCenter", @@ -25187,6 +25335,15 @@ "MultiTenancySide": 3, "MenuGroup": "Kurs" }, + { + "GroupName": "App.Intranet", + "Name": "App.Intranet.Events.Event.Widget", + "ParentName": "App.Intranet.Events.Event", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Kurs" + }, { "GroupName": "App.Intranet", "Name": "App.Intranet.Meal", @@ -25250,6 +25407,15 @@ "MultiTenancySide": 3, "MenuGroup": "Kurs" }, + { + "GroupName": "App.Intranet", + "Name": "App.Intranet.Meal.Widget", + "ParentName": "App.Intranet.Meal", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Kurs" + }, { "GroupName": "App.Intranet", "Name": "App.Intranet.Reservation", @@ -25313,6 +25479,15 @@ "MultiTenancySide": 3, "MenuGroup": "Kurs" }, + { + "GroupName": "App.Intranet", + "Name": "App.Intranet.Reservation.Widget", + "ParentName": "App.Intranet.Reservation", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Kurs" + }, { "GroupName": "App.Intranet", "Name": "App.Intranet.ShuttleRoute", @@ -25376,6 +25551,15 @@ "MultiTenancySide": 3, "MenuGroup": "Kurs" }, + { + "GroupName": "App.Intranet", + "Name": "App.Intranet.ShuttleRoute.Widget", + "ParentName": "App.Intranet.ShuttleRoute", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Kurs" + }, { "GroupName": "App.Intranet", "Name": "App.Intranet.Announcement", @@ -25439,6 +25623,15 @@ "MultiTenancySide": 3, "MenuGroup": "Kurs" }, + { + "GroupName": "App.Intranet", + "Name": "App.Intranet.Announcement.Widget", + "ParentName": "App.Intranet.Announcement", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Kurs" + }, { "GroupName": "App.Intranet", "Name": "App.Intranet.Visitor", @@ -25502,6 +25695,15 @@ "MultiTenancySide": 3, "MenuGroup": "Kurs" }, + { + "GroupName": "App.Intranet", + "Name": "App.Intranet.Visitor.Widget", + "ParentName": "App.Intranet.Visitor", + "DisplayName": "Widget", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Kurs" + }, { "GroupName": "App.Crm", "Name": "App.Crm.Customer", diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormsSeeder.cs b/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder.cs similarity index 98% rename from api/src/Kurs.Platform.DbMigrator/Seeds/ListFormsSeeder.cs rename to api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder.cs index 8358fc43..438012e2 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormsSeeder.cs +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder.cs @@ -21,7 +21,7 @@ using AppCodes = Kurs.Platform.Data.Seeds.SeedConsts.AppCodes; namespace Kurs.Platform.Data.Seeds; -public class ListFormsSeeder : IDataSeedContributor, ITransientDependency +public class ListFormSeeder : IDataSeedContributor, ITransientDependency { private readonly IRepository _listFormRepository; private readonly IRepository _listFormFieldRepository; @@ -29,7 +29,7 @@ public class ListFormsSeeder : IDataSeedContributor, ITransientDependency private readonly IdentityRoleManager _identityRoleManager; private readonly IConfiguration _configuration; - public ListFormsSeeder( + public ListFormSeeder( IRepository listFormRepository, IRepository listFormFieldRepository, IdentityUserManager userManager, @@ -40884,6 +40884,571 @@ public class ListFormsSeeder : IDataSeedContributor, ITransientDependency } #endregion + #region Expense Request + if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == ListFormCodes.Lists.Expense)) + { + var listFormExpense = await _listFormRepository.InsertAsync( + new ListForm() + { + ListFormType = ListFormTypeEnum.List, + IsSubForm = true, + LayoutJson = JsonSerializer.Serialize(new LayoutDto() + { + Grid = true, + Card = true, + Pivot = true, + Chart = true, + DefaultLayout = "grid", + CardLayoutColumn = 3 + }), + CultureName = LanguageCodes.En, + ListFormCode = ListFormCodes.Lists.Expense, + Name = AppCodes.Hr.Expense, + Title = AppCodes.Hr.Expense, + DataSourceCode = SeedConsts.DataSources.DefaultCode, + IsTenant = true, + IsBranch = false, + IsOrganizationUnit = false, + Description = AppCodes.Hr.Expense, + SelectCommandType = SelectCommandTypeEnum.Table, + SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Expense)), + KeyFieldName = "Id", + KeyFieldDbSourceType = DbType.Guid, + DefaultFilter = "\"IsDeleted\" = 'false'", + SortMode = GridOptions.SortModeSingle, + FilterRowJson = JsonSerializer.Serialize(new GridFilterRowDto { Visible = true }), + HeaderFilterJson = JsonSerializer.Serialize(new { Visible = true }), + SearchPanelJson = JsonSerializer.Serialize(new { Visible = true }), + GroupPanelJson = JsonSerializer.Serialize(new { Visible = true }), + SelectionJson = JsonSerializer.Serialize(new SelectionDto + { + Mode = GridOptions.SelectionModeSingle, + AllowSelectAll = false + }), + ColumnOptionJson = JsonSerializer.Serialize(new + { + ColumnFixingEnabled = true, + ColumnAutoWidth = true, + ColumnChooserEnabled = true, + AllowColumnResizing = true, + AllowColumnReordering = true, + ColumnResizingMode = "widget", + }), + PermissionJson = JsonSerializer.Serialize(new PermissionCrudDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + D = AppCodes.Hr.Expense + ".Delete", + E = AppCodes.Hr.Expense + ".Export", + I = AppCodes.Hr.Expense + ".Import", + A = AppCodes.Hr.Expense + ".Activity", + }), + DeleteCommand = $"UPDATE \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.Expense))}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id", + DeleteFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] { + new() { + FieldName = "DeleterId", + FieldDbType = DbType.Guid, + Value = "@USERID", + CustomValueType = FieldCustomValueTypeEnum.CustomKey }, + new() { + FieldName = "Id", + FieldDbType = DbType.Guid, + Value = "@ID", + CustomValueType = FieldCustomValueTypeEnum.CustomKey } + }), + PagerOptionJson = JsonSerializer.Serialize(new GridPagerOptionDto + { + Visible = true, + AllowedPageSizes = "10,20,50,100", + ShowPageSizeSelector = true, + ShowNavigationButtons = true, + ShowInfo = false, + InfoText = "Page {0} of {1} ({2} items)", + DisplayMode = GridColumnOptions.PagerDisplayModeAdaptive, + ScrollingMode = GridColumnOptions.ScrollingModeStandard, + LoadPanelEnabled = "auto", + LoadPanelText = "Loading..." + }), + EditingOptionJson = JsonSerializer.Serialize(new GridEditingDto + { + Popup = new GridEditingPopupDto() + { + Title = "Expnse Request Form", + Width = 500, + Height = 450 + }, + AllowDeleting = true, + AllowAdding = true, + AllowUpdating = true, + SendOnlyChangedFormValuesUpdate = false, + }), + InsertFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] { + new() { + FieldName = "CreationTime", + FieldDbType = DbType.DateTime, + Value = "@NOW", + CustomValueType = FieldCustomValueTypeEnum.CustomKey }, + new() { + FieldName = "CreatorId", + FieldDbType = DbType.Guid, + Value = "@USERID", + CustomValueType = FieldCustomValueTypeEnum.CustomKey }, + new() { + FieldName = "IsDeleted", + FieldDbType = DbType.Boolean, + Value = "false", + CustomValueType = FieldCustomValueTypeEnum.Value } + }), + EditingFormJson = JsonSerializer.Serialize(new List() + { + new() { + Order=1, ColCount=1, ColSpan=2, ItemType="group", Items = + [ + new EditingFormItemDto { Order = 1, DataField = "EmployeeId", IsRequired = true, EditorType2 = EditorTypes.dxSelectBox }, + new EditingFormItemDto { Order = 2, DataField = "Category", IsRequired = true, EditorType2 = EditorTypes.dxSelectBox }, + new EditingFormItemDto { Order = 3, DataField = "Amount", IsRequired = true, EditorType2 = EditorTypes.dxNumberBox }, + new EditingFormItemDto { Order = 4, DataField = "CurrencyId", IsRequired = true, EditorType2 = EditorTypes.dxSelectBox }, + new EditingFormItemDto { Order = 5, DataField = "RequestDate", IsRequired = true, EditorType2 = EditorTypes.dxDateBox }, + new EditingFormItemDto { Order = 6, DataField = "Description", EditorType2 = EditorTypes.dxTextArea }, + new EditingFormItemDto { Order = 7, DataField = "Project", EditorType2 = EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 8, DataField = "Status", IsRequired = true, EditorType2 = EditorTypes.dxSelectBox }, + new EditingFormItemDto { Order = 9, DataField = "Notes", EditorType2 = EditorTypes.dxTextArea }, + ] + } + }), + FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] + { + new() { + FieldName = "RequestDate", + FieldDbType = DbType.Date, + Value = "@NOW", + CustomValueType = FieldCustomValueTypeEnum.CustomKey } + }), + CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] { + new() { + Hint = "Accept", + Text ="Accept", + UrlTarget="_blank", + AuthName = AppCodes.Hr.Expense + ".Update", + Url="/admin/list/list-employees/@Id" + }, + new() { + Hint = "Reject", + Text ="Reject", + UrlTarget="_blank", + AuthName = AppCodes.Hr.Expense + ".Update", + Url="/admin/list/list-employees/@Id" + }, + }), + } + ); + + #region Expense Request Fields + await _listFormFieldRepository.InsertManyAsync([ + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Guid, + FieldName = "Id", + Width = 100, + ListOrderNo = 1, + Visible = false, + IsActive = true, + IsDeleted = false, + SortIndex = 0, + ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] { + new ValidationRuleDto() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } + }), + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Intranet.Announcement + ".Create", + R = AppCodes.Intranet.Announcement, + U = AppCodes.Intranet.Announcement + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Guid, + FieldName = "EmployeeId", + Width = 100, + ListOrderNo = 2, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] { + new ValidationRuleDto() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } + }), + LookupJson = JsonSerializer.Serialize(new LookupDto + { + DataSourceType = UiLookupDataSourceTypeEnum.Query, + DisplayExpr = "name", + ValueExpr = "key", + LookupQuery = LookUpQueryValues.EmployeeValues + }), + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Category", + Width = 100, + ListOrderNo = 3, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] { + new ValidationRuleDto() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } + }), + LookupJson = JsonSerializer.Serialize(new LookupDto + { + DataSourceType = UiLookupDataSourceTypeEnum.StaticData, + DisplayExpr = "name", + ValueExpr = "key", + LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { + new () { Key= "travel", Name= "Travel" }, + new () { Key= "meal", Name= "Meal" }, + new () { Key= "accommodation", Name= "Accommodation" }, + }), + }), + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Decimal, + FieldName = "Amount", + Width = 100, + ListOrderNo = 4, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] { + new ValidationRuleDto() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } + }), + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Guid, + FieldName = "CurrencyId", + Width = 100, + ListOrderNo = 5, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + LookupJson = JsonSerializer.Serialize(new LookupDto + { + DataSourceType = UiLookupDataSourceTypeEnum.Query, + DisplayExpr = "name", + ValueExpr = "key", + LookupQuery = LookUpQueryValues.CurrencyValues + }), + ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] { + new ValidationRuleDto() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } + }), + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Date, + FieldName = "RequestDate", + Width = 100, + ListOrderNo = 6, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] { + new ValidationRuleDto() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } + }), + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Description", + Width = 250, + ListOrderNo = 7, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Project", + Width = 100, + ListOrderNo = 8, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Status", + Width = 100, + ListOrderNo = 9, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] { + new ValidationRuleDto() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } + }), + LookupJson = JsonSerializer.Serialize(new LookupDto + { + DataSourceType = UiLookupDataSourceTypeEnum.StaticData, + DisplayExpr = "name", + ValueExpr = "key", + LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { + new () { Key= "pending", Name= "Pending" }, + new () { Key= "approved", Name= "Approved" }, + new () { Key= "rejected", Name= "Rejected" }, + }), + }), + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Notes", + Width = 200, + ListOrderNo = 10, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + new() { + ListFormCode = listFormExpense.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "RejectionReason", + Width = 200, + ListOrderNo = 11, + Visible = true, + IsActive = true, + IsDeleted = false, + AllowSearch = true, + ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto + { + AllowReordering = true, + }), + PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto + { + C = AppCodes.Hr.Expense + ".Create", + R = AppCodes.Hr.Expense, + U = AppCodes.Hr.Expense + ".Update", + E = true, + I = true, + Deny = false + }), + PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto + { + IsPivot = true + }) + }, + ]); + #endregion + } + #endregion + #endregion } } diff --git a/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs index c0348d90..db3c41cd 100644 --- a/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs +++ b/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs @@ -122,5 +122,6 @@ public enum TableNameEnum Reservation, ShuttleRoute, Announcement, - Visitor + Visitor, + Expense } \ No newline at end of file diff --git a/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs index 7c8c7773..cc7d4f84 100644 --- a/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs @@ -533,6 +533,7 @@ public static class PlatformConsts public const string ShuttleRoute = "list-shuttleroute"; public const string Announcement = "list-announcement"; public const string Visitor = "list-visitor"; + public const string Expense = "list-expense"; } } diff --git a/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs index 6835110d..adcc843a 100644 --- a/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs +++ b/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs @@ -130,6 +130,7 @@ public static class TableNameResolver { nameof(TableNameEnum.PayrollItem), (PlatformConsts.TablePrefix.TenantByName, MenuPrefix.Hr) }, { nameof(TableNameEnum.Template360), (PlatformConsts.TablePrefix.TenantByName, MenuPrefix.Hr) }, { nameof(TableNameEnum.Performance360), (PlatformConsts.TablePrefix.TenantByName, MenuPrefix.Hr) }, + { nameof(TableNameEnum.Expense), (PlatformConsts.TablePrefix.TenantByName, MenuPrefix.Hr) }, // Intranet { nameof(TableNameEnum.EventCategory), (PlatformConsts.TablePrefix.TenantByName, MenuPrefix.Intranet) }, diff --git a/api/src/Kurs.Platform.Domain/Data/SeedConsts.cs b/api/src/Kurs.Platform.Domain/Data/SeedConsts.cs index f8e5b907..ccb7feca 100644 --- a/api/src/Kurs.Platform.Domain/Data/SeedConsts.cs +++ b/api/src/Kurs.Platform.Domain/Data/SeedConsts.cs @@ -472,6 +472,7 @@ public static class SeedConsts public const string CostCenter = Default + ".CostCenter"; public const string Leave = Default + ".Leave"; public const string Overtime = Default + ".Overtime"; + public const string Expense = Default + ".Expense"; public const string Payroll = Default + ".Payroll"; public const string Template360 = Default + ".Template360"; public const string Performance360 = Default + ".Performance360"; diff --git a/api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Employee.cs b/api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Employee.cs index 52d92aba..95c078d9 100644 --- a/api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Employee.cs +++ b/api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Employee.cs @@ -72,4 +72,5 @@ public class Employee : FullAuditedEntity, IMultiTenant public ICollection Visitors { get; set; } public ICollection Reservations { get; set; } public ICollection Certificates { get; set; } + public ICollection ExpenseRequests { get; set; } } diff --git a/api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Expense.cs b/api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Expense.cs new file mode 100644 index 00000000..d343d412 --- /dev/null +++ b/api/src/Kurs.Platform.Domain/Entities/Tenant/Hr/Expense.cs @@ -0,0 +1,28 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.MultiTenancy; + +namespace Kurs.Platform.Entities; + +public class Expense : FullAuditedEntity, IMultiTenant +{ + public Guid? TenantId { get; set; } + + public Guid? EmployeeId { get; set; } + public Employee Employee { get; set; } + + public string Category { get; set; } + public decimal Amount { get; set; } + public Guid? CurrencyId { get; set; } + public Currency Currency { get; set; } + + public DateTime RequestDate { get; set; } + public string Description { get; set; } + public string Project { get; set; } + public string Status { get; set; } + public Guid? ApproverId { get; set; } + public Employee Approver { get; set; } + public DateTime? ApprovalDate { get; set; } + public string RejectionReason { get; set; } + public string Notes { get; set; } +} diff --git a/api/src/Kurs.Platform.Domain/Queries/QueryHelper.cs b/api/src/Kurs.Platform.Domain/Queries/QueryHelper.cs index ede06c78..bf0156ce 100644 --- a/api/src/Kurs.Platform.Domain/Queries/QueryHelper.cs +++ b/api/src/Kurs.Platform.Domain/Queries/QueryHelper.cs @@ -65,17 +65,48 @@ public class QueryHelper var s = a as string ?? a.ToString(); if (string.IsNullOrWhiteSpace(s)) return (DateTimeOffset?)null; - // İlk olarak TryParseExact ile kısa format "yyyy-MM-dd" kontrolü - if (DateTimeOffset.TryParseExact(s, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var dto)) - return dto.ToUniversalTime(); + // 🔹 Kabul edilen olası tarih formatları (tüm dillerde yaygın) + var possibleFormats = new[] + { + "yyyy-MM-dd", + "dd.MM.yyyy", + "dd/MM/yyyy", + "MM/dd/yyyy", + "yyyy/MM/dd", + "yyyy-MM-ddTHH:mm:ss", + "yyyy-MM-ddTHH:mm:ss.fff", + "yyyy-MM-ddTHH:mm:ssZ", + "yyyy-MM-dd HH:mm:ss", + "dd.MM.yyyy HH:mm:ss", + "dd/MM/yyyy HH:mm:ss", + "MM/dd/yyyy HH:mm:ss" + }; - // Genel parse denemesi - if (DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out dto)) + // 🔹 1. Önce ISO / Invariant formatları dene + if (DateTimeOffset.TryParseExact(s, possibleFormats, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, + out var dto)) + { return dto.ToUniversalTime(); + } - // Başarısızsa null dön + // 🔹 2. Eğer olmadıysa kültür bağımlı genel parse denemesi + if (DateTimeOffset.TryParse(s, CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out dto)) + { + return dto.ToUniversalTime(); + } + + // 🔹 3. Son çare: system culture veya ISO fallback + if (DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dto)) + { + return dto.ToUniversalTime(); + } + + // 🔹 Parse edilemezse null return (DateTimeOffset?)null; }).ToArray(); + value = isArray ? dateTimeValues : dateTimeValues[0]; break; case DbType.Int16: diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 891d0b3c..2acbd12a 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -183,6 +183,7 @@ public class PlatformDbContext : public DbSet ShuttleRoutes { get; set; } public DbSet Announcements { get; set; } public DbSet Visitors { get; set; } + public DbSet ExpenseRequests { get; set; } #endregion @@ -2000,5 +2001,27 @@ public class PlatformDbContext : .HasPrincipalKey(e => e.Id) .OnDelete(DeleteBehavior.Restrict); }); + + builder.Entity(b => + { + b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Expense)), Prefix.DbSchema); + b.ConfigureByConvention(); + + b.Property(x => x.EmployeeId).IsRequired(); + b.Property(x => x.Category).IsRequired().HasMaxLength(50); + b.Property(x => x.Amount).IsRequired().HasPrecision(18, 2); + b.Property(x => x.RequestDate).IsRequired(); + b.Property(x => x.Description).HasMaxLength(1000); + b.Property(x => x.Project).HasMaxLength(200); + b.Property(x => x.Status).IsRequired().HasMaxLength(20); + b.Property(x => x.RejectionReason).HasMaxLength(300); + b.Property(x => x.Notes).HasMaxLength(1000); + + b.HasOne(x => x.Employee) + .WithMany(e => e.ExpenseRequests) + .HasForeignKey(x => x.EmployeeId) + .HasPrincipalKey(e => e.Id) + .OnDelete(DeleteBehavior.Restrict); + }); } } diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251025204259_Initial.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251026200120_Initial.Designer.cs similarity index 98% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251025204259_Initial.Designer.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251026200120_Initial.Designer.cs index dc938f26..e09f49ec 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251025204259_Initial.Designer.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251026200120_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Kurs.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20251025204259_Initial")] + [Migration("20251026200120_Initial")] partial class Initial { /// @@ -4009,6 +4009,101 @@ namespace Kurs.Platform.Migrations b.ToTable("T_Net_EventType", (string)null); }); + modelBuilder.Entity("Kurs.Platform.Entities.Expense", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ApprovalDate") + .HasColumnType("datetime2"); + + b.Property("ApproverId") + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("CurrencyId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Project") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RejectionReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RequestDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApproverId"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("T_Hr_Expense", (string)null); + }); + modelBuilder.Entity("Kurs.Platform.Entities.GlobalSearch", b => { b.Property("Id") @@ -10969,6 +11064,29 @@ namespace Kurs.Platform.Migrations b.Navigation("Event"); }); + modelBuilder.Entity("Kurs.Platform.Entities.Expense", b => + { + b.HasOne("Kurs.Platform.Entities.Employee", "Approver") + .WithMany() + .HasForeignKey("ApproverId"); + + b.HasOne("Kurs.Platform.Entities.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId"); + + b.HasOne("Kurs.Platform.Entities.Employee", "Employee") + .WithMany("ExpenseRequests") + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Currency"); + + b.Navigation("Employee"); + }); + modelBuilder.Entity("Kurs.Platform.Entities.JobPosition", b => { b.HasOne("Kurs.Platform.Entities.Currency", "Currency") @@ -11470,6 +11588,8 @@ namespace Kurs.Platform.Migrations b.Navigation("Certificates"); + b.Navigation("ExpenseRequests"); + b.Navigation("Leaves"); b.Navigation("Overtimes"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251025204259_Initial.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251026200120_Initial.cs similarity index 98% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251025204259_Initial.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251026200120_Initial.cs index d1e4688c..a226473a 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251025204259_Initial.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251026200120_Initial.cs @@ -4119,6 +4119,53 @@ namespace Kurs.Platform.Migrations principalColumn: "Id"); }); + migrationBuilder.CreateTable( + name: "T_Hr_Expense", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: true), + EmployeeId = table.Column(type: "uniqueidentifier", nullable: false), + Category = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Amount = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + CurrencyId = table.Column(type: "uniqueidentifier", nullable: true), + RequestDate = table.Column(type: "datetime2", nullable: false), + Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + Project = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Status = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + ApproverId = table.Column(type: "uniqueidentifier", nullable: true), + ApprovalDate = table.Column(type: "datetime2", nullable: true), + RejectionReason = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + Notes = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_T_Hr_Expense", x => x.Id); + table.ForeignKey( + name: "FK_T_Hr_Expense_P_Sas_Currency_CurrencyId", + column: x => x.CurrencyId, + principalTable: "P_Sas_Currency", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_T_Hr_Expense_T_Hr_Employee_ApproverId", + column: x => x.ApproverId, + principalTable: "T_Hr_Employee", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_T_Hr_Expense_T_Hr_Employee_EmployeeId", + column: x => x.EmployeeId, + principalTable: "T_Hr_Employee", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateTable( name: "T_Hr_Leave", columns: table => new @@ -4946,6 +4993,21 @@ namespace Kurs.Platform.Migrations table: "T_Hr_Employee", column: "ManagerId"); + migrationBuilder.CreateIndex( + name: "IX_T_Hr_Expense_ApproverId", + table: "T_Hr_Expense", + column: "ApproverId"); + + migrationBuilder.CreateIndex( + name: "IX_T_Hr_Expense_CurrencyId", + table: "T_Hr_Expense", + column: "CurrencyId"); + + migrationBuilder.CreateIndex( + name: "IX_T_Hr_Expense_EmployeeId", + table: "T_Hr_Expense", + column: "EmployeeId"); + migrationBuilder.CreateIndex( name: "IX_T_Hr_JobPosition_CurrencyId", table: "T_Hr_JobPosition", @@ -5338,6 +5400,9 @@ namespace Kurs.Platform.Migrations migrationBuilder.DropTable( name: "T_Crd_QuestionTag"); + migrationBuilder.DropTable( + name: "T_Hr_Expense"); + migrationBuilder.DropTable( name: "T_Hr_Leave"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index f6000256..5e6ce1b8 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -4006,6 +4006,101 @@ namespace Kurs.Platform.Migrations b.ToTable("T_Net_EventType", (string)null); }); + modelBuilder.Entity("Kurs.Platform.Entities.Expense", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ApprovalDate") + .HasColumnType("datetime2"); + + b.Property("ApproverId") + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("CurrencyId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Project") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RejectionReason") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("RequestDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("ApproverId"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("T_Hr_Expense", (string)null); + }); + modelBuilder.Entity("Kurs.Platform.Entities.GlobalSearch", b => { b.Property("Id") @@ -10966,6 +11061,29 @@ namespace Kurs.Platform.Migrations b.Navigation("Event"); }); + modelBuilder.Entity("Kurs.Platform.Entities.Expense", b => + { + b.HasOne("Kurs.Platform.Entities.Employee", "Approver") + .WithMany() + .HasForeignKey("ApproverId"); + + b.HasOne("Kurs.Platform.Entities.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId"); + + b.HasOne("Kurs.Platform.Entities.Employee", "Employee") + .WithMany("ExpenseRequests") + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Currency"); + + b.Navigation("Employee"); + }); + modelBuilder.Entity("Kurs.Platform.Entities.JobPosition", b => { b.HasOne("Kurs.Platform.Entities.Currency", "Currency") @@ -11467,6 +11585,8 @@ namespace Kurs.Platform.Migrations b.Navigation("Certificates"); + b.Navigation("ExpenseRequests"); + b.Navigation("Leaves"); b.Navigation("Overtimes"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json index 4c827309..260a39c6 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json @@ -3671,5 +3671,43 @@ "purpose": "Teknik Sunum", "status": "checked-out" } + ], + "ExpenseRequests": [ + { + "employeeCode": "EMP-001", + "category": "travel", + "amount": 850, + "currencyCode": "TRY", + "requestDate": "08-10-2024", + "description": "Ankara ofis ziyareti - uçak bileti", + "project": "Intranet v2", + "status": "approved", + "approverCode": "EMP-004", + "approvalDate": "10-10-2024" + }, + { + "employeeCode": "EMP-002", + "category": "meal", + "amount": 320, + "currencyCode": "TRY", + "requestDate": "07-10-2024", + "description": "Müşteri toplantısı - öğle yemeği", + "project": null, + "status": "pending", + "approverCode": null, + "approvalDate": null + }, + { + "employeeCode": "EMP-003", + "category": "accommodation", + "amount": 1200, + "currencyCode": "TRY", + "requestDate": "04-10-2024", + "description": "İzmir workshop - otel konaklaması (2 gece)", + "project": "UX Workshop", + "status": "approved", + "approverCode": "EMP-005", + "approvalDate": "05-10-2024" + } ] } \ No newline at end of file diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs index becfd162..6ffca2ab 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs @@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore; using System.Linq; using System.Collections.Generic; using Volo.Abp.Identity; +using Azure.Core; namespace Kurs.Platform.Data.Seeds; @@ -78,6 +79,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency private readonly IRepository _shuttleRouteRepository; private readonly IRepository _announcementRepository; private readonly IRepository _visitorRepository; + private readonly IRepository _expenseRepository; public TenantDataSeeder( IRepository repositoryUser, @@ -139,7 +141,8 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency IRepository reservationRepository, IRepository shuttleRouteRepository, IRepository announcementRepository, - IRepository visitorRepository + IRepository visitorRepository, + IRepository expenseRepository ) { _repositoryUser = repositoryUser; @@ -202,6 +205,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency _shuttleRouteRepository = shuttleRouteRepository; _announcementRepository = announcementRepository; _visitorRepository = visitorRepository; + _expenseRepository = expenseRepository; } private static IConfigurationRoot BuildConfiguration() @@ -1190,7 +1194,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency Status = item.Status, DepartmentId = department != null ? department.Id : null, TargetEmployees = string.Join("|", targetEmployees.Select(e => e.Id)) - }, autoSave: true); + }); } foreach (var item in items.Trainings) @@ -1213,7 +1217,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency Status = item.Status, Location = item.Location, Thumbnail = item.Thumbnail - }, autoSave: true); + }); } foreach (var item in items.Reservations) @@ -1234,7 +1238,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency Participants = item.Participants, Notes = item.Notes, Status = item.Status - }, autoSave: true); + }); } foreach (var item in items.ShuttleRoutes) @@ -1252,7 +1256,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency Capacity = item.Capacity, Available = item.Available, Type = item.Type - }, autoSave: true); + }); } foreach (var item in items.Announcements) @@ -1283,7 +1287,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency IsPinned = item.IsPinned, ViewCount = item.ViewCount, Departments = string.Join("|", targetDepartments.Select(d => d.Id)), - }, autoSave: true); + }); } @@ -1306,7 +1310,31 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency CheckOut = item.CheckOut, EmployeeId = employee != null ? employee.Id : null, Status = item.Status - }, autoSave: true); + }); + } + + foreach (var item in items.ExpenseRequests) + { + var employee = await _employeeRepository.FirstOrDefaultAsync(x => x.Code == item.EmployeeCode); + var exists = await _expenseRepository.AnyAsync(x => x.EmployeeId == employee.Id && x.RequestDate == item.RequestDate); + if (exists) + continue; + var approver = await _employeeRepository.FirstOrDefaultAsync(x => x.Code == item.ApproverCode); + var currency = await _currencyRepository.FirstOrDefaultAsync(x => x.Code == item.CurrencyCode); + + await _expenseRepository.InsertAsync(new Expense + { + EmployeeId = employee != null ? employee.Id : null, + Category = item.Category, + Amount = item.Amount, + CurrencyId = currency != null ? currency.Id : null, + RequestDate = item.RequestDate, + Description = item.Description, + Project = item.Project, + Status = item.Status, + ApproverId = approver != null ? approver.Id : null, + ApprovalDate = item.ApprovalDate + }); } } } diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs index be5a18db..942f36c3 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs @@ -67,6 +67,21 @@ public class TenantSeederDto public List ShuttleRoutes { get; set; } public List Announcements { get; set; } public List Visitors { get; set; } + public List ExpenseRequests { get; set; } +} + +public class ExpenseRequestSeedDto +{ + public string EmployeeCode { get; set; } + public string Category { get; set; } + public decimal Amount { get; set; } + public string CurrencyCode { get; set; } + public DateTime RequestDate { get; set; } + public string Description { get; set; } + public string Project { get; set; } + public string Status { get; set; } + public string ApproverCode { get; set; } + public DateTime? ApprovalDate { get; set; } } public class VisitorSeedDto diff --git a/ui/src/assets/styles/components/_menu-item.css b/ui/src/assets/styles/components/_menu-item.css index fea97f11..1cf7f936 100644 --- a/ui/src/assets/styles/components/_menu-item.css +++ b/ui/src/assets/styles/components/_menu-item.css @@ -1,5 +1,5 @@ .menu-item { - @apply cursor-pointer font-medium rounded-md flex items-center w-full whitespace-nowrap gap-x-1 px-1.5; + @apply cursor-pointer font-medium rounded-md flex items-center w-full whitespace-nowrap px-1.5; &.menu-item-light { @apply text-gray-600; diff --git a/ui/src/mocks/mockIntranet.ts b/ui/src/mocks/mockIntranet.ts index 605f9ac8..64e90a56 100644 --- a/ui/src/mocks/mockIntranet.ts +++ b/ui/src/mocks/mockIntranet.ts @@ -86,9 +86,9 @@ export const mockSurveys: Survey[] = [ 2: 'Kötü', 3: 'Orta', 4: 'İyi', - 5: 'Çok İyi' - } - } + 5: 'Çok İyi', + }, + }, }, { id: 'q2', @@ -102,8 +102,8 @@ export const mockSurveys: Survey[] = [ { id: 'opt2', text: 'İnsan Kaynakları', order: 2 }, { id: 'opt3', text: 'Finans', order: 3 }, { id: 'opt4', text: 'Satış', order: 4 }, - { id: 'opt5', text: 'Pazarlama', order: 5 } - ] + { id: 'opt5', text: 'Pazarlama', order: 5 }, + ], }, { id: 'q3', @@ -111,7 +111,7 @@ export const mockSurveys: Survey[] = [ questionText: 'Görüş ve önerileriniz', type: 'textarea', order: 3, - isRequired: false + isRequired: false, }, { id: 'q4', @@ -119,8 +119,8 @@ export const mockSurveys: Survey[] = [ questionText: 'Çalışma ortamından memnun musunuz?', type: 'yes-no', order: 4, - isRequired: true - } + isRequired: true, + }, ], responses: 45, targetAudience: ['Tüm Çalışanlar'], @@ -147,8 +147,8 @@ export const mockSurveys: Survey[] = [ { id: 'opt7', text: 'Node.js / Backend', order: 2 }, { id: 'opt8', text: 'Database / SQL', order: 3 }, { id: 'opt9', text: 'DevOps / Cloud', order: 4 }, - { id: 'opt10', text: 'Mobile Development', order: 5 } - ] + { id: 'opt10', text: 'Mobile Development', order: 5 }, + ], }, { id: 'q6', @@ -160,8 +160,8 @@ export const mockSurveys: Survey[] = [ options: [ { id: 'opt11', text: 'Online Eğitim', order: 1 }, { id: 'opt12', text: 'Yüz Yüze Eğitim', order: 2 }, - { id: 'opt13', text: 'Hibrit (Karma)', order: 3 } - ] + { id: 'opt13', text: 'Hibrit (Karma)', order: 3 }, + ], }, { id: 'q7', @@ -176,10 +176,10 @@ export const mockSurveys: Survey[] = [ labels: { 1: '1 saat', 5: '5 saat', - 10: '10+ saat' - } - } - } + 10: '10+ saat', + }, + }, + }, ], responses: 28, targetAudience: ['Yazılım Geliştirme', 'Ürün Yönetimi'], @@ -209,9 +209,9 @@ export const mockSurveys: Survey[] = [ 2: 'Kötü', 3: 'Orta', 4: 'İyi', - 5: 'Mükemmel' - } - } + 5: 'Mükemmel', + }, + }, }, { id: 'q9', @@ -219,7 +219,7 @@ export const mockSurveys: Survey[] = [ questionText: 'Hangi yemekleri daha sık görmek istiyorsunuz?', type: 'textarea', order: 2, - isRequired: false + isRequired: false, }, { id: 'q10', @@ -227,8 +227,8 @@ export const mockSurveys: Survey[] = [ questionText: 'Servis hızından memnun musunuz?', type: 'yes-no', order: 3, - isRequired: true - } + isRequired: true, + }, ], responses: 62, targetAudience: ['Tüm Çalışanlar'], @@ -1065,4 +1065,4 @@ export const mockExpenseRequests: ExpenseRequest[] = [ approvalDate: new Date('2024-10-15T09:00:00'), creationTime: new Date('2024-10-14T22:00:00'), }, -] \ No newline at end of file +] diff --git a/ui/src/views/admin/role-management/RolesPermission.tsx b/ui/src/views/admin/role-management/RolesPermission.tsx index f4f98d73..88a5bef8 100644 --- a/ui/src/views/admin/role-management/RolesPermission.tsx +++ b/ui/src/views/admin/role-management/RolesPermission.tsx @@ -258,12 +258,12 @@ function RolesPermission({ onClose={onDialogClose} onRequestClose={onDialogClose} > -
+
{translate('::Permission')} - {name}
-
+
-
+
{translate('AbpPermissionManagement::SelectAllInAllTabs')} diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index 9e6bfa49..906b92a5 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -270,6 +270,7 @@ const Grid = (props: GridProps) => { // Grid'den gelen columnFormat'ları kullanarak default değerleri set et if (colFormat.defaultValue != null) { + console.log('Setting default value for', colFormat.fieldName, colFormat.defaultValue) e.data[colFormat.fieldName] = colFormat.defaultValue }