My Profile düzenlemesi

This commit is contained in:
Sedat ÖZTÜRK 2026-10-05 17:11:06 +03:00
parent 10abf9c08d
commit 2d59f7116a
20 changed files with 971 additions and 47 deletions

View file

@ -1273,10 +1273,10 @@ soneklerini alır.
| **Public › Blog Category / Posts** | `App.BlogManagement.Category` · `.Posts` | `Sas_H_BlogCategory` · `Sas_H_BlogPost` | host | Çok dilli içerik (`ContentTr`/`ContentEn`), slug, yayın durumu, sayaç alanları; public `/blog` bu tablolardan okur, yayınlanmış yazılar sitemap'e girer. | | **Public › Blog Category / Posts** | `App.BlogManagement.Category` · `.Posts` | `Sas_H_BlogCategory` · `Sas_H_BlogPost` | host | Çok dilli içerik (`ContentTr`/`ContentEn`), slug, yayın durumu, sayaç alanları; public `/blog` bu tablolardan okur, yayınlanmış yazılar sitemap'e girer. |
| **Public › Demos** | `App.Demos` | `Sas_H_Demo` | host | Public `/demo` formundan gelen talepler — public form → tablo akışının örneği. | | **Public › Demos** | `App.Demos` | `Sas_H_Demo` | host | Public `/demo` formundan gelen talepler — public form → tablo akışının örneği. |
| **Tenants** | `AbpTenantManagement.Tenants` | `AbpTenants` | host | Kiracı kaydı, bağlantı dizesi diyaloğu (`TenantsConnectionString`), alt form olarak şubeler; yazma `list-form-dynamic-api/tenant-*`. Ekranların `IsTenant` davranışının kaynağı. | | **Tenants** | `AbpTenantManagement.Tenants` | `AbpTenants` | host | Kiracı kaydı, bağlantı dizesi diyaloğu (`TenantsConnectionString`), alt form olarak şubeler; yazma `list-form-dynamic-api/tenant-*`. Ekranların `IsTenant` davranışının kaynağı. |
| **Branches** | `App.Branches` | `Sas_T_Branch` | tenant | Şube listesi. Ekranı `IsBranch = true` yaparsan filtre bu tablodan gelir; `BranchSeed` diyaloğu şube verisini kurar. | | **Branches** | `App.Branches` | `Sas_T_Branch` | tenant | Şube listesi. Ekranı `IsBranch = true` yaparsan filtre bu tablodan gelir; `BranchSeed` diyaloğu şube verisini kurar; `App.Branches.Update` ister ve kiracı yalnızca kendi şubesini kurabilir. |
| **Setting Definitions** | `App.SettingDefinitions` | `Sas_H_SettingDefinition` | host | Yeni uygulama ayarı: `Code`, `MainGroupKey`/`SubGroupKey`, `DataType`, `SelectOptions` (`List` tipinde değer → etiket; etiket dil anahtarıdır, Ayarlar ekranı `::` önekiyle çevirir, anahtar yoksa metin olduğu gibi görünür), `DefaultValue`, `Providers`, `RequiredPermissionName`, `IsEncrypted`, `IsVisibleToClients`. Hazır tanımlar `data/App.SettingDefinitions.json`'dan gelir. Ayarlanabilir olması gereken değeri koda gömme. | | **Setting Definitions** | `App.SettingDefinitions` | `Sas_H_SettingDefinition` | host | Yeni uygulama ayarı: `Code`, `MainGroupKey`/`SubGroupKey`, `DataType`, `SelectOptions` (`List` tipinde değer → etiket; etiket dil anahtarıdır, Ayarlar ekranı `::` önekiyle çevirir, anahtar yoksa metin olduğu gibi görünür), `DefaultValue`, `Providers`, `RequiredPermissionName`, `IsEncrypted`, `IsVisibleToClients`. Hazır tanımlar `data/App.SettingDefinitions.json`'dan gelir. Ayarlanabilir olması gereken değeri koda gömme. |
| **Languages › Language / Language Text** | `App.Languages.Language` · `.LanguageText` | `Sas_H_Language` · `Sas_H_LanguageKey` · `Sas_H_LanguageText` | host | Dil anahtarları. Üretilen her ekranın EN+TR metinleri buraya yazılır; ekran `data/App.Languages.{Language,LanguageText}.json` dosyalarına senkronlanır. Metin insert'i eksik `LanguageKey`'i de üretir, aynı kültür+kaynak+anahtar varsa eklemez. | | **Languages › Language / Language Text** | `App.Languages.Language` · `.LanguageText` | `Sas_H_Language` · `Sas_H_LanguageKey` · `Sas_H_LanguageText` | host | Dil anahtarları. Üretilen her ekranın EN+TR metinleri buraya yazılır; ekran `data/App.Languages.{Language,LanguageText}.json` dosyalarına senkronlanır. Metin insert'i eksik `LanguageKey`'i de üretir, aynı kültür+kaynak+anahtar varsa eklemez. |
| **Data Source** | `App.DataSource` | `Sas_H_DataSource` | host | `Code`, `DataSourceType`, `ConnectionString`. **İki tür kayıt bir arada:** elle tanımlanan host/harici bağlantılar ve `Code = tenant adı` olan otomatik tenant bağlantıları (Tenants ekranındaki bağlantı diyaloğu yönetir, elle dokunma). Varsayılan kaydı `HostDataSeeder` appsettings'teki bağlantı cümlesinden kurar. Çözüm kuralı `lowcode` §9.3.3. Bağlantı dizesi koda/JSON'a yazılmaz. | | **Data Source** | `App.DataSource` | `Sas_H_DataSource` | host | `Code`, `DataSourceType`, `ConnectionString`. Tipli API (`/api/app/data-source`) bağlantı cümlesini hiçbir zaman döndürmez; okuma açıktır (açılır listeler), yazma `App.DataSource.Create/Update/Delete` ister. **İki tür kayıt bir arada:** elle tanımlanan host/harici bağlantılar ve `Code = tenant adı` olan otomatik tenant bağlantıları (Tenants ekranındaki bağlantı diyaloğu yönetir, elle dokunma). Varsayılan kaydı `HostDataSeeder` appsettings'teki bağlantı cümlesinden kurar. Çözüm kuralı `lowcode` §9.3.3. Bağlantı dizesi koda/JSON'a yazılmaz. |
| **Background Workers › Recurring Jobs / Jobs** | `App.BackgroundWorkers.RecurringJobs` · `.Jobs` | `Sas_H_BackgroundWorker` · `Sas_H_BackgroundWorker_JobFlow` | host | Cron'lu işler ve çalıştırma geçmişi (`Tenant`, `ExecutionDate`, `Duration`, `Status`, `FailureReason`, `ExceptionDetails`). `WorkerType`/`Scope` değerleri §11'de; her kapsam ayrı bir Hangfire işidir, `Tenant` kolonu hangi kiracıda çalıştığını gösterir. Hazır işler (oturum temizliği, bildirim, yedek, şema sözlüğü) `data/App.BackgroundWorkers.RecurringJobs.json`'dan gelir. Ayrıntı: `lowcode` §9.10, `README.md` §14. | | **Background Workers › Recurring Jobs / Jobs** | `App.BackgroundWorkers.RecurringJobs` · `.Jobs` | `Sas_H_BackgroundWorker` · `Sas_H_BackgroundWorker_JobFlow` | host | Cron'lu işler ve çalıştırma geçmişi (`Tenant`, `ExecutionDate`, `Duration`, `Status`, `FailureReason`, `ExceptionDetails`). `WorkerType`/`Scope` değerleri §11'de; her kapsam ayrı bir Hangfire işidir, `Tenant` kolonu hangi kiracıda çalıştığını gösterir. Hazır işler (oturum temizliği, bildirim, yedek, şema sözlüğü) `data/App.BackgroundWorkers.RecurringJobs.json`'dan gelir. Ayrıntı: `lowcode` §9.10, `README.md` §14. |
| **Menus › Module / Routes / Menu / Manager** | `App.Menus.Module` · `.Routes` · `.Menu` · `.Manager` (`/admin/menuManager`) | `Sas_H_Module` · `Sas_H_Route` · `Sas_H_Menu` | host | Menü ve route veritabanından gelir; üretilen ekranın menü kaydı buradan açılır. `Menu` ekranı ağaç düzeninin referansıdır. Seed: `data/App.Menus.{Routes,Module,Menu}.json` — `Routes` dosyasına yalnızca var olan satırın `Seo*` alanları için dokunulur. | | **Menus › Module / Routes / Menu / Manager** | `App.Menus.Module` · `.Routes` · `.Menu` · `.Manager` (`/admin/menuManager`) | `Sas_H_Module` · `Sas_H_Route` · `Sas_H_Menu` | host | Menü ve route veritabanından gelir; üretilen ekranın menü kaydı buradan açılır. `Menu` ekranı ağaç düzeninin referansıdır. Seed: `data/App.Menus.{Routes,Module,Menu}.json` — `Routes` dosyasına yalnızca var olan satırın `Seo*` alanları için dokunulur. |
| **Developer Kit › Dev Requests** | `App.DevRequests` | `Sas_H_DevRequest` | host | **Kanban/Todo düzeninin** referansı (`DefaultLayout: todo`): `Status`, `Priority`, `SortOrder`, `Tags`, `Assignees`, `SubTasks`, `Timesheets`; detayda `Description` yanında `Analysis` sekmesi (`AnalysisExpr`). MCP geliştirme döngüsünün kuyruğudur (`Backlog` → `Analysis` MCP analiz eder, `Ready to Start`'a kullanıcı alır; `Ready to Start` → `Testing`, `Staging`'e kullanıcı alır; araç çubuğunda DB Migrate butonu vardır — `UiEvalService.ApiDbMigrate()`, önce onay sorar, `App.Setup.Migrate`; yalnızca `start_dev_request` başlatır; açıklamadaki base64 görseller MCP'ye resim içeriği olarak gider, analiz `save_dev_request_analysis` ile yazılır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir — `Description` talebin yazıldığı hâliyle kalır; `lowcode.instructions.md` §0.11–§0.12). | | **Developer Kit › Dev Requests** | `App.DevRequests` | `Sas_H_DevRequest` | host | **Kanban/Todo düzeninin** referansı (`DefaultLayout: todo`): `Status`, `Priority`, `SortOrder`, `Tags`, `Assignees`, `SubTasks`, `Timesheets`; detayda `Description` yanında `Analysis` sekmesi (`AnalysisExpr`). MCP geliştirme döngüsünün kuyruğudur (`Backlog` → `Analysis` MCP analiz eder, `Ready to Start`'a kullanıcı alır; `Ready to Start` → `Testing`, `Staging`'e kullanıcı alır; araç çubuğunda DB Migrate butonu vardır — `UiEvalService.ApiDbMigrate()`, önce onay sorar, `App.Setup.Migrate`; yalnızca `start_dev_request` başlatır; açıklamadaki base64 görseller MCP'ye resim içeriği olarak gider, analiz `save_dev_request_analysis` ile yazılır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir — `Description` talebin yazıldığı hâliyle kalır; `lowcode.instructions.md` §0.11–§0.12). |
@ -1319,7 +1319,7 @@ ekranları ABP tablolarıdır, Licence/Dev Request host tablolarını okur.
| **Identity › Permission Groups / Permissions** | `Abp.Identity.PermissionGroups` · `Abp.Identity.Permissions` | `AbpPermissionGroups` · `AbpPermissions` | Yetki ağacı (tree düzeni). Yeni ekranın yetkileri burada görünür; `Modules` kolonu yetkiyi modüllere bağlar (pipe ile çoklu), `Order` kardeşler arası sıradır. Seed: `data/Abp.Identity.Permission{Groups,s}.json`. | | **Identity › Permission Groups / Permissions** | `Abp.Identity.PermissionGroups` · `Abp.Identity.Permissions` | `AbpPermissionGroups` · `AbpPermissions` | Yetki ağacı (tree düzeni). Yeni ekranın yetkileri burada görünür; `Modules` kolonu yetkiyi modüllere bağlar (pipe ile çoklu), `Order` kardeşler arası sıradır. Seed: `data/Abp.Identity.Permission{Groups,s}.json`. |
| **Identity › Claim Type** | `App.IdentityManagement.ClaimType` | `AbpClaimTypes` | Kullanıcıya ek alan (claim) tanımı: `ValueType`, `Regex`, `Required`, `IsStatic`. | | **Identity › Claim Type** | `App.IdentityManagement.ClaimType` | `AbpClaimTypes` | Kullanıcıya ek alan (claim) tanımı: `ValueType`, `Regex`, `Required`, `IsStatic`. |
| **Identity › Roles** | `AbpIdentity.Roles` | `AbpRoles` | Rol yönetimi + `RolesPermission` diyaloğu; yeni ekranın yetkisi role buradan verilir. Yazma `list-form-dynamic-api/role-*`. | | **Identity › Roles** | `AbpIdentity.Roles` | `AbpRoles` | Rol yönetimi + `RolesPermission` diyaloğu; yeni ekranın yetkisi role buradan verilir. Yazma `list-form-dynamic-api/role-*`. |
| **Identity › Users** | `AbpIdentity.Users` · detay `/admin/users/detail/:userId` | `AbpUsers` | Kullanıcı yönetimi + `UsersPermission` diyaloğu, **KPI widget'ları** ve `list-form-dynamic-api/user-*` ile devredilen yazma. Kullanıcı `WorkHour`, `DepartmentId`, `JobPositionId`, `Gender` (`Male`/`Female`, `App.StaticLookup.*`) ve `HomeAddress` alanlarını liste ve formda taşır; kişisel ve kimlik bilgileri (`IdentityNumber`, `BirthDate`, `EducationLevel`, `Nationality`, `SskNo`…) detay ekranındadır. Detay sekmeleri karışmasın diye gruplanmıştır: **General** (iletişim + ikinci e-posta/telefon, ek bilgi, avatar, askerlik `MilitaryStatus`/`MilitaryPostponeEndDate`, engellilik `IsDisabled`/`DisabilityRate`/`DisabilityReport` — rapor base64, 5 MB), **Identity** (nüfus cüzdanı, doğum ve aile, uyruk Türkiye değilse yabancı uyruk `Passport*`/`WorkPermit*`), **Work** (organizasyon `RegistrationNo`/`SskNo`/`ShiftId`/`PdksCardNo`, sözleşme `EmploymentType`/`ContractType`/`ContractEndDate`/`ProbationEndDate`/`SeniorityStartDate`, çıkış `TerminationDate`/`TerminationReasonId`/`TerminationNote`, sürücü belgesi `DrivingLicense*` — sınıflar `|` ile, iletişim kanalları; altında iş deneyimi), **Education & Skills** (en yüksek `EducationLevel` + eğitim/dil/yetenek/belge alt listeleri), **Family / Emergency**, **Financial** (`BankName`, `Iban`, `SalaryType`, `GrossSalary`, `SalaryCurrency` + yan haklar), Health, Permissions, Lockout, Claims. Deneme süresi işe girişten en fazla dört ay sonra biter (`UpdateUserAsync` ve ekran doğrular). Ücret alanları `UserInfoViewModel`'de taşınmaz — o model intranet doğum günü listesine de akar; `GET {id}/financial` / `PUT financial` uçlarından okunur, yetki detayınkiyle aynıdır (`AbpIdentity.Users.Update`). Aynı sebeple `DisabilityReport` yalnızca `GetByIdAsync`'te doldurulur. `ShiftId`, `TerminationReasonId`, `SalaryCurrency` seçenekleri ekranda `list-form-select/lookup` ile bu ListForm'un alan lookup'larından okunur (`useUserFieldLookup`). Detaydaki **Sağlık Kartı** sekmesi `BloodType` ile soy geçmişini (`{Mother,Father,Sibling,Child}HealthStatus` — `Alive`/`Deceased`, `App.StaticLookup.*` — ve `…HealthNote`) ve öz geçmişini (`ChronicDisease`, `TetanusVaccination`, `HepatitisVaccination`, `OtherVaccination`) taşır; sekme dört gruptur (Soy/Öz Geçmişi, Laboratuvar - Fizik Muayene `Lab*`/`Exam*`, Anamnez `Complaint*`/`Diagnosis*` + 3-10. sorular, Sonuç `FitForWork*`/`ShiftWorkFit`/`NightWorkFit`). Sonuçtaki hekim (`FitnessDoctorId`, `ShiftNightDoctorId`) wizard tablosu `Hr_T_Doctor`'dan `LookupQueryValues.TenantDoctorValues` ile seçilir; diploma ve işyeri hekimliği belgesi hekimden gösterilir, kullanıcıya yazılmaz. Bu alanların hepsi listede gizli alandır; arayüz `user-management/HealthCardTab.tsx`. Kullanıcı seçen her lookup bu bilgileri `AbpUsers`'tan okur ve kopya kolon aynı ad/tip/değer kümesiyle açılır (`lowcode.instructions.md` §0.12 madde 5). Yeni bir kullanıcı alanı = `PlatformConsts.AbpIdentity.User` + `AbpIdentityUserExtensions` + EF eşlemesi ve migration + `UserInfoViewModel` (detay/profil) + `CreateUpdateUserInput` ve `user-insert`/`user-update` (liste) + `ListFormSeeder_Administration` alanı + gerekiyorsa yönetici varsayılanı (`PlatformConsts.AbpIdentity.User.Admin*DefaultValue` + `PlatformIdentityDataSeeder`; host ve tenant Seed sekmesinin yöneticisi buradan oluşur). | | **Identity › Users** | `AbpIdentity.Users` · detay `/admin/users/detail/:userId` | `AbpUsers` | Kullanıcı yönetimi + `UsersPermission` diyaloğu (detay ve kullanıcı bilgisi kaydı `AbpIdentity.Users.Update`, "Yetkili kullanıcılar" `App.Listforms.Listform.Update`, oturum sonlandırma `…IdentityManagement.Sessions` ister; istemciden gelen şube kimlikleri kiracının kendi şubelerine süzülür), **KPI widget'ları** ve `list-form-dynamic-api/user-*` ile devredilen yazma. Kullanıcı `WorkHour`, `DepartmentId`, `JobPositionId`, `Gender` (`Male`/`Female`, `App.StaticLookup.*`) ve `HomeAddress` alanlarını liste ve formda taşır; kişisel ve kimlik bilgileri (`IdentityNumber`, `BirthDate`, `EducationLevel`, `Nationality`, `SskNo`…) detay ekranındadır. Detay sekmeleri karışmasın diye gruplanmıştır: **General** (iletişim + ikinci e-posta/telefon, ek bilgi, avatar, askerlik `MilitaryStatus`/`MilitaryPostponeEndDate`, engellilik `IsDisabled`/`DisabilityRate`/`DisabilityReport` — rapor base64, 5 MB), **Identity** (nüfus cüzdanı, doğum ve aile, uyruk Türkiye değilse yabancı uyruk `Passport*`/`WorkPermit*`), **Work** (organizasyon `RegistrationNo`/`SskNo`/`ShiftId`/`PdksCardNo`, sözleşme `EmploymentType`/`ContractType`/`ContractEndDate`/`ProbationEndDate`/`SeniorityStartDate`, çıkış `TerminationDate`/`TerminationReasonId`/`TerminationNote`, sürücü belgesi `DrivingLicense*` — sınıflar `|` ile, iletişim kanalları; altında iş deneyimi), **Education & Skills** (en yüksek `EducationLevel` + eğitim/dil/yetenek/belge alt listeleri), **Family / Emergency**, **Financial** (`BankName`, `Iban`, `SalaryType`, `GrossSalary`, `SalaryCurrency` + yan haklar), Health, Permissions, Lockout, Claims. Deneme süresi işe girişten en fazla dört ay sonra biter (`UpdateUserAsync` ve ekran doğrular). Ücret alanları `UserInfoViewModel`'de taşınmaz — o model intranet doğum günü listesine de akar; `GET {id}/financial` / `PUT financial` uçlarından okunur, yetki detayınkiyle aynıdır (`AbpIdentity.Users.Update`). Aynı sebeple `DisabilityReport` yalnızca `GetByIdAsync`'te doldurulur. `ShiftId`, `TerminationReasonId`, `SalaryCurrency` seçenekleri ekranda `list-form-select/lookup` ile bu ListForm'un alan lookup'larından okunur (`useUserFieldLookup`). Detaydaki **Sağlık Kartı** sekmesi `BloodType` ile soy geçmişini (`{Mother,Father,Sibling,Child}HealthStatus` — `Alive`/`Deceased`, `App.StaticLookup.*` — ve `…HealthNote`) ve öz geçmişini (`ChronicDisease`, `TetanusVaccination`, `HepatitisVaccination`, `OtherVaccination`) taşır; sekme dört gruptur (Soy/Öz Geçmişi, Laboratuvar - Fizik Muayene `Lab*`/`Exam*`, Anamnez `Complaint*`/`Diagnosis*` + 3-10. sorular, Sonuç `FitForWork*`/`ShiftWorkFit`/`NightWorkFit`). Sonuçtaki hekim (`FitnessDoctorId`, `ShiftNightDoctorId`) wizard tablosu `Hr_T_Doctor`'dan `LookupQueryValues.TenantDoctorValues` ile seçilir; diploma ve işyeri hekimliği belgesi hekimden gösterilir, kullanıcıya yazılmaz. Bu alanların hepsi listede gizli alandır; arayüz `user-management/HealthCardTab.tsx`. Kullanıcı seçen her lookup bu bilgileri `AbpUsers`'tan okur ve kopya kolon aynı ad/tip/değer kümesiyle açılır (`lowcode.instructions.md` §0.12 madde 5). Yeni bir kullanıcı alanı = `PlatformConsts.AbpIdentity.User` + `AbpIdentityUserExtensions` + EF eşlemesi ve migration + `UserInfoViewModel` (detay/profil) + `CreateUpdateUserInput` ve `user-insert`/`user-update` (liste) + `ListFormSeeder_Administration` alanı + gerekiyorsa yönetici varsayılanı (`PlatformConsts.AbpIdentity.User.Admin*DefaultValue` + `PlatformIdentityDataSeeder`; host ve tenant Seed sekmesinin yöneticisi buradan oluşur). |
| **Identity › Users › Work Experience** | `App.IdentityManagement.UserWorkExperience` (menüsüz alt liste) | `Adm_T_UserWorkExperience` | Kullanıcının daha önce çalıştığı yerler: `CompanyName`, `SectorId` (Sektörler lookup'ı, `LookupQueryValues.TenantSectorValues`), `StartDate`, `EndDate`, `JobDescription` (HtmlEditor). Entity `IMultiTenant`; kullanıcı detayının Work sekmesinde `PlatformViewHost` ile `UserId` filtresiyle gömülüdür, yeni satır `UserId`'yi filtreden alır. Kendi yetkisi yoktur: okuma `AbpIdentity.Users`, ekleme/güncelleme/silme detay sayfasının yetkisi `AbpIdentity.Users.Update`. | | **Identity › Users › Work Experience** | `App.IdentityManagement.UserWorkExperience` (menüsüz alt liste) | `Adm_T_UserWorkExperience` | Kullanıcının daha önce çalıştığı yerler: `CompanyName`, `SectorId` (Sektörler lookup'ı, `LookupQueryValues.TenantSectorValues`), `StartDate`, `EndDate`, `JobDescription` (HtmlEditor). Entity `IMultiTenant`; kullanıcı detayının Work sekmesinde `PlatformViewHost` ile `UserId` filtresiyle gömülüdür, yeni satır `UserId`'yi filtreden alır. Kendi yetkisi yoktur: okuma `AbpIdentity.Users`, ekleme/güncelleme/silme detay sayfasının yetkisi `AbpIdentity.Users.Update`. |
| **Identity › Users › Health Card lists** | `App.IdentityManagement.UserPeriodicExamination` · `.UserPatientProtocol` · `.UserSickLeave` (menüsüz alt listeler) | `Adm_T_UserPeriodicExamination` · `Adm_T_UserPatientProtocol` · `Adm_T_UserSickLeave` | Periyodik muayene (hekim `Hr_T_Doctor`'dan, diploma no seçimde `EditorScript.Copy` ile kopyalanır), hasta protokol defteri (`ProtocolType`/`ProtocolResult` statik lookup) ve istirahat listesi (`Days` tarihlerden `EditorScript.Days` ile hesaplanır). Entity'ler `IMultiTenant`; Sağlık Kartı sekmesinde (`HealthCardTab.tsx`) `UserId` filtresiyle gömülüdür. Yetki Work Experience ile aynıdır: okuma `AbpIdentity.Users`, ekleme/güncelleme/silme `AbpIdentity.Users.Update`. | | **Identity › Users › Health Card lists** | `App.IdentityManagement.UserPeriodicExamination` · `.UserPatientProtocol` · `.UserSickLeave` (menüsüz alt listeler) | `Adm_T_UserPeriodicExamination` · `Adm_T_UserPatientProtocol` · `Adm_T_UserSickLeave` | Periyodik muayene (hekim `Hr_T_Doctor`'dan, diploma no seçimde `EditorScript.Copy` ile kopyalanır), hasta protokol defteri (`ProtocolType`/`ProtocolResult` statik lookup) ve istirahat listesi (`Days` tarihlerden `EditorScript.Days` ile hesaplanır). Entity'ler `IMultiTenant`; Sağlık Kartı sekmesinde (`HealthCardTab.tsx`) `UserId` filtresiyle gömülüdür. Yetki Work Experience ile aynıdır: okuma `AbpIdentity.Users`, ekleme/güncelleme/silme `AbpIdentity.Users.Update`. |
| **Identity › Users › Education & Skills lists** | `App.IdentityManagement.UserEducation` · `.UserLanguage` · `.UserSkill` · `.UserCertificate` (menüsüz alt listeler) | `Adm_T_UserEducation` · `Adm_T_UserLanguage` · `Adm_T_UserSkill` · `Adm_T_UserCertificate` | Eğitim geçmişi (`EducationLevel` kullanıcınınkiyle aynı değerler, `SchoolName`, `Department`, `StartDate`, `EndDate`, `IsGraduated`), yabancı dil (`SkillId` → `Languages` tipindeki `Hr_T_Skill`, `SkillLevelId` dilin tipine ait `Hr_T_SkillLevel`, cascade), yetenek (`SkillId` → `Languages` dışındaki tipler, `Level` 1-5 statik lookup, check constraint) ve yeterlilik belgesi (`CertificateId` → wizard tablosu `Hr_T_Certificate`, EF ilişkisi yok; `CertificateNo`, `IssuedBy`, `IssueDate`, `ExpiryDate`). Lookup'lar `LookupQueryValues.Tenant{LanguageSkill,SkillLevelBySkill,NonLanguageSkill,Certificate}Values`. `EducationSkillsTab.tsx`; yetki Work Experience ile aynıdır. | | **Identity › Users › Education & Skills lists** | `App.IdentityManagement.UserEducation` · `.UserLanguage` · `.UserSkill` · `.UserCertificate` (menüsüz alt listeler) | `Adm_T_UserEducation` · `Adm_T_UserLanguage` · `Adm_T_UserSkill` · `Adm_T_UserCertificate` | Eğitim geçmişi (`EducationLevel` kullanıcınınkiyle aynı değerler, `SchoolName`, `Department`, `StartDate`, `EndDate`, `IsGraduated`), yabancı dil (`SkillId` → `Languages` tipindeki `Hr_T_Skill`, `SkillLevelId` dilin tipine ait `Hr_T_SkillLevel`, cascade), yetenek (`SkillId` → `Languages` dışındaki tipler, `Level` 1-5 statik lookup, check constraint) ve yeterlilik belgesi (`CertificateId` → wizard tablosu `Hr_T_Certificate`, EF ilişkisi yok; `CertificateNo`, `IssuedBy`, `IssueDate`, `ExpiryDate`). Lookup'lar `LookupQueryValues.Tenant{LanguageSkill,SkillLevelBySkill,NonLanguageSkill,Certificate}Values`. `EducationSkillsTab.tsx`; yetki Work Experience ile aynıdır. |
@ -1333,7 +1333,7 @@ ekranları ABP tablolarıdır, Licence/Dev Request host tablolarını okur.
| **Forum** | menü kodu `App.Forum` · yetki `App.ForumManagement.Publish` · `/admin/forum` | `Sas_T_Forum*` | Forumun kullanıcı tarafı. | | **Forum** | menü kodu `App.Forum` · yetki `App.ForumManagement.Publish` · `/admin/forum` | `Sas_T_Forum*` | Forumun kullanıcı tarafı. |
Menüde görünmeyen ama route'u olan ekranlar: `/admin/dashboard`, `/admin/ai`, Menüde görünmeyen ama route'u olan ekranlar: `/admin/dashboard`, `/admin/ai`,
`/admin/activityLog`, `/admin/changeLog`, `/admin/profile/{general,password,notification-settings}`, `/admin/activityLog`, `/admin/changeLog`, `/admin/profile/{general,employee-info,leave,timesheet,performance,password,notification-settings}`,
`/admin/users/detail/:userId` (yetki `AbpIdentity.Users.Update`), `/admin/videoroom/list` ve `/admin/users/detail/:userId` (yetki `AbpIdentity.Users.Update`), `/admin/videoroom/list` ve
`/admin/videoroom/room/:id`, `/admin/reports/*`, `/admin/listform/{wizard,wizardManager}`, `/admin/videoroom/room/:id`, `/admin/reports/*`, `/admin/listform/{wizard,wizardManager}`,
`/admin/listform/edit/:listFormCode`, `/admin/developerkit/components/design/:id` ve dinamik ekran `/admin/listform/edit/:listFormCode`, `/admin/developerkit/components/design/:id` ve dinamik ekran
@ -1404,9 +1404,9 @@ listesinde `Hr` varsa görür (modül listesi boş kiracıda süzme yoktur — `
| **Recruitment › Application** | `App.Hr.JobApplication` (alt formlar `App.Hr.Interview`, `App.Hr.JobOffer`) | `Hr_T_JobApplication` | tenant | Aday × ilan başvurusu (çift tekil). `Stage`: `Applied` → `Screening` → `Interview` → `Offer` → `Hired` / `Rejected`; **Todo (kanban) görünümü** aşama sütunlarıyla. Mülakat eklenince `Interview`, teklif eklenince `Offer`, teklif `Accepted` olunca `Hired`'a ilerler (after-command SQL, geri gitmez; insert sonrası komutlar yalnızca eklenen satıra `@Id` ile çalışır). `CandidateName`/`PostingTitle` kopyalanır (kanban başlığı). Ret sebebi yalnızca `Rejected` aşamasında düzenlenir. Alt form olduğu için widget yok. Wizard. | | **Recruitment › Application** | `App.Hr.JobApplication` (alt formlar `App.Hr.Interview`, `App.Hr.JobOffer`) | `Hr_T_JobApplication` | tenant | Aday × ilan başvurusu (çift tekil). `Stage`: `Applied` → `Screening` → `Interview` → `Offer` → `Hired` / `Rejected`; **Todo (kanban) görünümü** aşama sütunlarıyla. Mülakat eklenince `Interview`, teklif eklenince `Offer`, teklif `Accepted` olunca `Hired`'a ilerler (after-command SQL, geri gitmez; insert sonrası komutlar yalnızca eklenen satıra `@Id` ile çalışır). `CandidateName`/`PostingTitle` kopyalanır (kanban başlığı). Ret sebebi yalnızca `Rejected` aşamasında düzenlenir. Alt form olduğu için widget yok. Wizard. |
| **Recruitment › Interview** | `App.Hr.Interview` (alt form `App.Hr.InterviewScore`) | `Hr_T_Interview` · `Hr_T_InterviewScore` | tenant | Mülakat planlama: tür, başlangıç/bitiş (**Scheduler** görünümü), mülakatçı, durum, öneri. Kayıt eklenince mülakatçıya `IseAlimMulakatAtandi` bildirimi SQL ile yazılır (tipin `Custom` kuralları kanalı belirler, metin `App.Hr.Interview.Notification.*`). Puan alt formu kriter başına 1–5; `OverallScore` = Σ(puan × ağırlık) / Σ ağırlık, başvurunun `AverageScore`'u mülakatların ortalaması. Wizard. | | **Recruitment › Interview** | `App.Hr.Interview` (alt form `App.Hr.InterviewScore`) | `Hr_T_Interview` · `Hr_T_InterviewScore` | tenant | Mülakat planlama: tür, başlangıç/bitiş (**Scheduler** görünümü), mülakatçı, durum, öneri. Kayıt eklenince mülakatçıya `IseAlimMulakatAtandi` bildirimi SQL ile yazılır (tipin `Custom` kuralları kanalı belirler, metin `App.Hr.Interview.Notification.*`). Puan alt formu kriter başına 1–5; `OverallScore` = Σ(puan × ağırlık) / Σ ağırlık, başvurunun `AverageScore`'u mülakatların ortalaması. Wizard. |
| **Recruitment › Job Offer** | `App.Hr.JobOffer` | `Hr_T_JobOffer` | tenant | Teklif: `OfferNo` (`TKL-…`), başvuru seçilince pozisyon bilgileri ilandan `copy` ile dolar, brüt ücret + para birimi, yan haklar (`Hr_T_BenefitType` kodları), `Status` (`Draft`/`Sent`/`Accepted`/`Declined`/`Expired`, yanıt tarihi otomatik). İç onay **Grid Workflow** (Kadro Talebi ile aynı akış). Onaylı satırda **Teklif Mektubu** butonu (`DynamicReport`, `Raporlar` kategorisi). Wizard. | | **Recruitment › Job Offer** | `App.Hr.JobOffer` | `Hr_T_JobOffer` | tenant | Teklif: `OfferNo` (`TKL-…`), başvuru seçilince pozisyon bilgileri ilandan `copy` ile dolar, brüt ücret + para birimi, yan haklar (`Hr_T_BenefitType` kodları), `Status` (`Draft`/`Sent`/`Accepted`/`Declined`/`Expired`, yanıt tarihi otomatik). İç onay **Grid Workflow** (Kadro Talebi ile aynı akış). Onaylı satırda **Teklif Mektubu** butonu (`DynamicReport`, `Raporlar` kategorisi). Wizard. |
| **Performance Management** | `App.Hr.Performance` dalı (yalnız İK): `PerformancePeriod`, `PerformanceReview`, `PerformanceCalibration`, `DevelopmentPlan`; tanım `App.Hr.PerformanceRating` (İK › Tanımlar). Çalışana ait `MyPerformanceReview`, `TeamPerformanceReview`, `My360Feedback` menüsüzdür; Intranet › Dashboard'daki custom widget'lardan (`MyPerformanceWidget`, `TeamPerformanceWidget`, `My360FeedbackWidget`) açılır | `Hr_T_PerformanceRating` · `Hr_T_PerformancePeriod` · `Hr_T_PerformancePeriodSkill` · `Hr_T_PerformanceReview` · `Hr_T_PerformanceGoal` · `Hr_T_PerformanceCompetency` · `Hr_T_PerformanceFeedback` · `Hr_T_PerformanceFeedbackScore` · `Hr_T_DevelopmentPlan` | tenant | Dönem → değerlendirme (hedef KPI/OKR + yetkinlik `Hr_T_Skill`/`Hr_T_SkillLevel` + isteğe bağlı 360) → kalibrasyon → gelişim planı. **Aynı tabloya persona ekranları**: İK (tümü), çalışan (`EmployeeId = '@USERID'`), yönetici (`ManagerId = '@USERID'`), 360 değerlendiricisi (`ReviewerId = '@USERID'`); kilitli alanlar İK ekranının yetkisine bağlıdır (sunucu düşürür), satır sahipliği ve aşama kuralı `Hr_S_PerformanceGuard` (before-command). Hesap ve durum geçişi `Hr_S_PerformanceReviewRecalc` / `Hr_S_PerformanceReviewAfterSave`; tamamlanan değerlendirme `Adm_T_UserSkill`'e yazar. Widget'lar veriyi ekranın kendi sorgusundan (`list-form-select/select`) okur. Çalışma şekli ve kararlar: `configs/seeds/docs/modules/HrPerformance.md`. Wizard + Custom Component (pano kutusu). | | **Performance Management** | `App.Hr.Performance` dalı (yalnız İK): `PerformancePeriod`, `PerformanceReview`, `PerformanceCalibration`, `DevelopmentPlan`; tanım `App.Hr.PerformanceRating` (İK › Tanımlar). Çalışana ait `MyPerformanceReview`, `TeamPerformanceReview`, `My360Feedback` menüsüzdür; Intranet › Dashboard'daki custom widget'lardan (`MyPerformanceWidget`, `TeamPerformanceWidget`, `My360FeedbackWidget`) açılır; çalışanınkiler profilin **Performansım** sekmesinde de görünür | `Hr_T_PerformanceRating` · `Hr_T_PerformancePeriod` · `Hr_T_PerformancePeriodSkill` · `Hr_T_PerformanceReview` · `Hr_T_PerformanceGoal` · `Hr_T_PerformanceCompetency` · `Hr_T_PerformanceFeedback` · `Hr_T_PerformanceFeedbackScore` · `Hr_T_DevelopmentPlan` | tenant | Dönem → değerlendirme (hedef KPI/OKR + yetkinlik `Hr_T_Skill`/`Hr_T_SkillLevel` + isteğe bağlı 360) → kalibrasyon → gelişim planı. **Aynı tabloya persona ekranları**: İK (tümü), çalışan (`EmployeeId = '@USERID'`), yönetici (`ManagerId = '@USERID'`), 360 değerlendiricisi (`ReviewerId = '@USERID'`); kilitli alanlar İK ekranının yetkisine bağlıdır (sunucu düşürür), satır sahipliği ve aşama kuralı `Hr_S_PerformanceGuard` (before-command). Hesap ve durum geçişi `Hr_S_PerformanceReviewRecalc` / `Hr_S_PerformanceReviewAfterSave`; tamamlanan değerlendirme `Adm_T_UserSkill`'e yazar. Widget'lar veriyi ekranın kendi sorgusundan (`list-form-select/select`) okur. Çalışma şekli ve kararlar: `configs/seeds/docs/modules/HrPerformance.md`. Wizard + Custom Component (pano kutusu). |
| **Leave Management** | `App.Hr.Leave` dalı (yalnız İK): `LeaveRequest` (Grid Workflow: Müdür Onayı `@ManagerUserNames` → İK Onayı; üst pozisyonu olmayan çalışanda müdür adımı `RequiresManagerApproval` karşılaştırmasıyla atlanır), `LeaveBalance` (alt formlar `LeaveBalanceAdjustment`, `LeaveBalanceYear`). Çalışana ait `MyLeaveRequest`, `MyLeaveBalance` ve müdüre ait `TeamLeaveRequest` menüsüzdür; Intranet › Dashboard'daki `MyLeaveWidget` / `TeamLeaveWidget`'tan açılır; kullanıcı detayında **İzinler** sekmesi | `Hr_T_LeaveRequest` · `Hr_T_LeaveBalanceAdjustment` · görünümler `Hr_V_LeaveBalanceYear` · `Hr_V_LeaveSummary` | tenant | Gün sayısı `Hr_F_LeaveWorkDays` (Ayarlar › İnsan Kaynakları › İzin çalışma günleri + `Hr_T_WorkCalendar`); bakiye İş Kanunu md.53 kademeleri (ayarlardan) + devir + düzeltme; müdür = departmanın en üst pozisyonunun sahibi. Doğrulama, müdür çözümü, rapor ↔ `Adm_T_UserSickLeave` eşlemesi ve bildirim `Hr_S_LeaveRequestAfterSave`; satır koruması `Hr_S_LeaveRequestGuard`; Sağlık Kartı tarafı `Hr_S_LeaveSickLeaveSync`. SQL'den ayar okuma `Hr_F_HrSetting` (kiracı → genel → varsayılan). Çalışma şekli: `configs/seeds/docs/modules/HrLeave.md`. Wizard + Custom Component + küçük UI/motor eki. | | **Leave Management** | `App.Hr.Leave` dalı (yalnız İK): `LeaveRequest` (Grid Workflow: Müdür Onayı `@ManagerUserNames` → İK Onayı; üst pozisyonu olmayan çalışanda müdür adımı `RequiresManagerApproval` karşılaştırmasıyla atlanır), `LeaveBalance` (alt formlar `LeaveBalanceAdjustment`, `LeaveBalanceYear`). Çalışana ait `MyLeaveRequest`, `MyLeaveBalance` ve müdüre ait `TeamLeaveRequest` menüsüzdür; Intranet › Dashboard'daki `MyLeaveWidget` / `TeamLeaveWidget`'tan açılır; çalışanınkiler profilin **İzinlerim** sekmesinde de görünür; kullanıcı detayında **İzinler** sekmesi | `Hr_T_LeaveRequest` · `Hr_T_LeaveBalanceAdjustment` · görünümler `Hr_V_LeaveBalanceYear` · `Hr_V_LeaveSummary` | tenant | Gün sayısı `Hr_F_LeaveWorkDays` (Ayarlar › İnsan Kaynakları › İzin çalışma günleri + `Hr_T_WorkCalendar`); bakiye İş Kanunu md.53 kademeleri (ayarlardan) + devir + düzeltme; müdür = departmanın en üst pozisyonunun sahibi. Doğrulama, müdür çözümü, rapor ↔ `Adm_T_UserSickLeave` eşlemesi ve bildirim `Hr_S_LeaveRequestAfterSave`; satır koruması `Hr_S_LeaveRequestGuard`; Sağlık Kartı tarafı `Hr_S_LeaveSickLeaveSync`. SQL'den ayar okuma `Hr_F_HrSetting` (kiracı → genel → varsayılan). Çalışma şekli: `configs/seeds/docs/modules/HrLeave.md`. Wizard + Custom Component + küçük UI/motor eki. |
| **Timesheet & Attendance** | `App.Hr.Attendance` dalı (yalnız İK): `AttendanceLog` (ham okutma; Excel import), `Timesheet` (çalışan × gün; düzenlenen gün `IsManual`), `TimesheetMonthly` (salt okunur aylık özet, alt form `Timesheet` `Id → PeriodKey`), `TimesheetPeriod` (ay yeniden hesap / kapanış). Çalışana ait `MyTimesheet` (+ alt form `MyTimesheetDay`) menüsüzdür; Intranet › Dashboard'daki `MyTimesheetWidget`'tan açılır; kullanıcı detayında **Puantaj** sekmesi | `Hr_T_AttendanceLog` · `Hr_T_Timesheet` · `Hr_T_TimesheetPeriod` · görünümler `Hr_V_AttendanceShift` · `Hr_V_TimesheetMonthly` | tenant | Hesap `Hr_S_TimesheetCalculate` (gün tipi: rapor > ücretsiz izin > `Hr_T_WorkCalendar` tatili > hafta tatili (İzin çalışma günü ayarları) > ücretli izin > okutma > eğitim katılımı (`Training`) > varsayılan çalışma / devamsız; izinli günde `LeaveTypeId`, eğitim gününde `TrainingId`; mola `MAX(vardiya, okutulan)`; geç/erken vardiya toleransı aşılırsa tamamı; fazla mesai haftalık 45 saati aşan kısım + tatil çalışması). Kişi eşlemesi `AbpUsers.PdksCardNo` / `RegistrationNo`; kartı olan çalışan ilk PDKS okutmasından itibaren okutmayla, diğerleri elle (kayıtsız gün `IsAssumed`) takip edilir. Tetikler: hareket kaydı `Hr_S_AttendanceProcess`, PDKS Custom Endpoint `Hr_S_AttendanceImport` (`/hr/attendance/`), dönem `Hr_S_TimesheetPeriodAfterSave`, gece işi `Hr_S_TimesheetNightly`, izin/rapor prosedürleri, `Hr_S_TrainingSync`, `Hr_S_WorkCalendarAfterSave`. Modüller arası çakışma kuralları (raporlu gün devamsız değildir, rapora denk gelen izin günü bakiyeye döner, kapalı aya izin/rapor yazılmaz) `HrAttendance.md` §7.1'dedir. Ayarlar `App.HrSettings.Attendance.*`. Çalışma şekli: `configs/seeds/docs/modules/HrAttendance.md`. Wizard + Custom Component + Custom Endpoint + zamanlanmış iş + küçük UI eki. | | **Timesheet & Attendance** | `App.Hr.Attendance` dalı (yalnız İK): `AttendanceLog` (ham okutma; Excel import), `Timesheet` (çalışan × gün; düzenlenen gün `IsManual`), `TimesheetMonthly` (salt okunur aylık özet, alt form `Timesheet` `Id → PeriodKey`), `TimesheetPeriod` (ay yeniden hesap / kapanış). Çalışana ait `MyTimesheet` (+ alt form `MyTimesheetDay`) menüsüzdür; Intranet › Dashboard'daki `MyTimesheetWidget`'tan açılır; profilin **Puantajım** sekmesinde de görünür; kullanıcı detayında **Puantaj** sekmesi | `Hr_T_AttendanceLog` · `Hr_T_Timesheet` · `Hr_T_TimesheetPeriod` · görünümler `Hr_V_AttendanceShift` · `Hr_V_TimesheetMonthly` | tenant | Hesap `Hr_S_TimesheetCalculate` (gün tipi: rapor > ücretsiz izin > `Hr_T_WorkCalendar` tatili > hafta tatili (İzin çalışma günü ayarları) > ücretli izin > okutma > eğitim katılımı (`Training`) > varsayılan çalışma / devamsız; izinli günde `LeaveTypeId`, eğitim gününde `TrainingId`; mola `MAX(vardiya, okutulan)`; geç/erken vardiya toleransı aşılırsa tamamı; fazla mesai haftalık 45 saati aşan kısım + tatil çalışması). Kişi eşlemesi `AbpUsers.PdksCardNo` / `RegistrationNo`; kartı olan çalışan ilk PDKS okutmasından itibaren okutmayla, diğerleri elle (kayıtsız gün `IsAssumed`) takip edilir. Tetikler: hareket kaydı `Hr_S_AttendanceProcess`, PDKS Custom Endpoint `Hr_S_AttendanceImport` (`/hr/attendance/`), dönem `Hr_S_TimesheetPeriodAfterSave`, gece işi `Hr_S_TimesheetNightly`, izin/rapor prosedürleri, `Hr_S_TrainingSync`, `Hr_S_WorkCalendarAfterSave`. Modüller arası çakışma kuralları (raporlu gün devamsız değildir, rapora denk gelen izin günü bakiyeye döner, kapalı aya izin/rapor yazılmaz) `HrAttendance.md` §7.1'dedir. Ayarlar `App.HrSettings.Attendance.*`. Çalışma şekli: `configs/seeds/docs/modules/HrAttendance.md`. Wizard + Custom Component + Custom Endpoint + zamanlanmış iş + küçük UI eki. |
| **Org Chart** | `App.Hr.OrgChart` · `/admin/organization` | `Hr_T_Department` · `Hr_T_JobPosition` | tenant | Departman/ünvan verisinin organizasyon şeması görünümü (özel React sayfası `hr/OrgChart`, veri `api/app/org-chart`). Menü ve route aynı yetkiyi ister. | | **Org Chart** | `App.Hr.OrgChart` · `/admin/organization` | `Hr_T_Department` · `Hr_T_JobPosition` | tenant | Departman/ünvan verisinin organizasyon şeması görünümü (özel React sayfası `hr/OrgChart`, veri `api/app/org-chart`). Menü ve route aynı yetkiyi ister. |
| **Intranet › Announcement** | `App.Hr.Intranet.Announcement` (alt formlar Like/Comment) | `Hr_T_Announcement` · `Hr_T_AnnouncementLike` · `Hr_T_AnnouncementComment` | tenant | Duyuru + beğeni + yorum: **ana kayıt / alt etkileşim** kalıbının hazır örneği. | | **Intranet › Announcement** | `App.Hr.Intranet.Announcement` (alt formlar Like/Comment) | `Hr_T_Announcement` · `Hr_T_AnnouncementLike` · `Hr_T_AnnouncementComment` | tenant | Duyuru + beğeni + yorum: **ana kayıt / alt etkileşim** kalıbının hazır örneği. |
| **Intranet › Survey** | `App.Hr.Intranet.Survey` (alt form Question → Option; Response → Answer) | `Hr_T_Survey` · `Hr_T_SurveyQuestion` · `Hr_T_SurveyQuestionOption` · `Hr_T_SurveyResponse` · `Hr_T_SurveyAnswer` | tenant | Anket motoru; soru–seçenek–yanıt zinciri **iki seviyeli alt form** ihtiyacının referansıdır. | | **Intranet › Survey** | `App.Hr.Intranet.Survey` (alt form Question → Option; Response → Answer) | `Hr_T_Survey` · `Hr_T_SurveyQuestion` · `Hr_T_SurveyQuestionOption` · `Hr_T_SurveyResponse` · `Hr_T_SurveyAnswer` | tenant | Anket motoru; soru–seçenek–yanıt zinciri **iki seviyeli alt form** ihtiyacının referansıdır. |
@ -1512,6 +1512,7 @@ alanları da doldurur (`lowcode` §0.9).
| "sadece şu rol görsün" | Administration › Identity › Roles / Permissions | Yetki kodu + ekranın `PermissionJson`'ı | | "sadece şu rol görsün" | Administration › Identity › Roles / Permissions | Yetki kodu + ekranın `PermissionJson`'ı |
| "herkes yalnızca kendi kayıtlarını görsün" / "şu rol şu bölgeyi görsün" | ListForm editörü › Kişiselleştirme | `ServerWhere` kaydı (herkes/rol/kullanıcı; §2.5) ya da `DefaultFilter` | | "herkes yalnızca kendi kayıtlarını görsün" / "şu rol şu bölgeyi görsün" | ListForm editörü › Kişiselleştirme | `ServerWhere` kaydı (herkes/rol/kullanıcı; §2.5) ya da `DefaultFilter` |
| "intranet ana sayfasına kutu eklensin" | Saas › Developer Kit › Intranet Widgets | `Custom` kaynaklı widget + custom component | | "intranet ana sayfasına kutu eklensin" | Saas › Developer Kit › Intranet Widgets | `Custom` kaynaklı widget + custom component |
| "çalışan kendi bilgisini / izinini / puantajını görsün" | Profil (`/admin/profile/*`) + Intranet panosu | Menüsüz `App.Hr.My*` persona ekranı + widget (`lowcode` §0.12.1); profil sekmesi sabit listedir, yenisi kod değişikliğidir |
| "şube bazlı olsun" | Saas › Branches | `IsBranch = true` | | "şube bazlı olsun" | Saas › Branches | `IsBranch = true` |
| "birim ağacına göre süzülsün" | Administration › Identity › Organization Units | `IsOrganizationUnit = true` | | "birim ağacına göre süzülsün" | Administration › Identity › Organization Units | `IsOrganizationUnit = true` |
| "mesai dışında girilemesin" / "sadece ofis IP'si" | Administration › Restrictions | Work Hour · IP Restriction kaydı | | "mesai dışında girilemesin" / "sadece ofis IP'si" | Administration › Restrictions | Work Hour · IP Restriction kaydı |

View file

@ -657,7 +657,8 @@ değildir) ve içindeki satırları taşır; satırlar demo veridir (§0.10.1).
**Migrate** döngünün parçası değildir ve MCP'den tetiklenmez. Parti bitince yetkili host **Migrate** döngünün parçası değildir ve MCP'den tetiklenmez. Parti bitince yetkili host
kullanıcısı Wizard Manager'daki DB Migrate'i (`/api/setup/migrate`, `Setup.Migrate`) çalıştırır; kullanıcısı Wizard Manager'daki DB Migrate'i (`/api/setup/migrate`, `Setup.Migrate`) çalıştırır;
migrate ekranları, menü/yetki/dil satırlarını ve demo verisini kurar, Redis'i temizler ve bütün migrate ekranları, menü/yetki/dil satırlarını ve demo verisini kurar, uygulamanın önbellek anahtarlarını temizler
(`DistributedCacheCleaner`; Redis'teki diğer uygulamalara dokunmaz) ve bütün
kiracıları dolaşır. Rapor bu adımı tek satırla belirtir. kiracıları dolaşır. Rapor bu adımı tek satırla belirtir.
### 0.12 Talep analizi — `Backlog` → `Analysis` ### 0.12 Talep analizi — `Backlog` → `Analysis`
@ -708,6 +709,9 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma.
| Ara (yalnızca talep istiyorsa, her biri) | … | … / … | … | Var / Yeni | | Ara (yalnızca talep istiyorsa, her biri) | … | … / … | … | Var / Yeni |
| Ekran | `ListFormCode` | … / … | … | Yeni | | Ekran | `ListFormCode` | … / … | … | Yeni |
Çalışanın/yöneticinin kendi listesi (persona ekranı) menüye konmaz; satıra "menüsüz — Intranet
widget'ı" yazılır (§0.12.1).
Altında `ModuleId`, izin grubu ve route yazılır. Var olan seviyeler `list_wizards` ve menü Altında `ModuleId`, izin grubu ve route yazılır. Var olan seviyeler `list_wizards` ve menü
seed'inden okunur, kodları oradaki gibi yazılır; sıra (`Order`) deploy'da hesaplanır, analize seed'inden okunur, kodları oradaki gibi yazılır; sıra (`Order`) deploy'da hesaplanır, analize
yazılmaz. Seviye sayısı talepten gelir, sabit değildir: `Kök › Ekran` iki, `Kök › Ara › Ekran` yazılmaz. Seviye sayısı talepten gelir, sabit değildir: `Kök › Ekran` iki, `Kök › Ara › Ekran`
@ -807,6 +811,36 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma.
ihtiyaç, §0.8, adıyla). Talebi daraltan geçici çözüm önerilmez; "Hayır" taşıyan talep yetenek ihtiyaç, §0.8, adıyla). Talebi daraltan geçici çözüm önerilmez; "Hayır" taşıyan talep yetenek
eklenene kadar `Ready to Start`'a alınmaz. eklenene kadar `Ready to Start`'a alınmaz.
### 0.12.1 Çalışan (persona) ekranları — Wizard ve MCP için
İK menüsü İK departmanının ekranlarını taşır. Çalışanın ya da yöneticinin kendisiyle ilgili listeleri
("Taleplerim", "Değerlendirmelerim", "Ekibimin …") İK menüsüne konmaz; keşif turunda bu kalıp
varsayılan olarak önerilir:
- **Ekran:** menüsüz wizard (`CreateMenu: false`), kodu `App.Hr.My{Ekran}` / `App.Hr.Team{Ekran}`,
İK ekranıyla aynı tabloya açılır. Satırları `DefaultFilter` + alan varsayılanıyla oturumdaki
kullanıcıya süzülür (`"EmployeeId" = '@USERID'`); yetki okuma yetkisidir, İK yetkisi verilmez.
Kalıp: `configs/seeds/docs/modules/HrPerformance.md` §3.2–§3.3.
- **Erişim:** Intranet panosunda `Sas_T_IntranetWidget` kaydı (`SourceType: 1`, custom component;
`PermissionName` = persona ekranının okuma yetkisi) özet gösterir ve listeye bağlanır.
- **Profil:** `/admin/profile` sekmeleri sabittir — Çalışan Bilgileri, İzinlerim
(`App.Hr.MyLeaveRequest`, `App.Hr.MyLeaveBalance`), Puantajım (`App.Hr.MyTimesheet`), Performansım
(`App.Hr.MyPerformanceReview`, `.MyPerformanceGoal`, `.MyDevelopmentPlan`, `.My360Feedback`).
Sekmeler çekirdek, **salt okunur** bileşenlerdir (`ui/src/views/admin/profile/components`): persona
ekranının satırlarını `list-form-select` ile okur, grid/ListForm çizmez; düzenle/sil yoktur. Tek
aksiyon "İzin Talebi Oluştur" (`/admin/form/App.Hr.MyLeaveRequest`, `.Create` yetkisiyle). Süreç
işlemleri (öz değerlendirme, 360 puanlama, açık talebi düzeltme) panodaki widget'ların açtığı persona
ekranında kalır; çalışanın karar verilmiş kaydı değiştirememesi sunucu tarafında guard
prosedürüyle korunur (`Hr_S_LeaveRequestGuard`, `Hr_S_PerformanceGuard`). Sekme, ekranlarından
hiçbirinin yetkisi yoksa görünmez. Yeni bir persona ekranı profile
kendiliğinden girmez: sekmeye eklemek `ui/src/views/admin/profile` kodu + rota satırıdır, yani
çerçeve dışıdır (§0.8, onay). Wizard/MCP ekranı ve widget'ı üretir; profil sekmesini üretim notunda
öneri olarak yazar.
- **Kullanıcı alanı:** çalışanın kendi kullanıcı alanları profilde `api/account/my-profile` ek
alanlarından okunur (`PlatformProfileAppService.ProfileExtraProperties`). Kullanıcıya eklenen yeni
bir alanın profilde görünmesi isteniyorsa liste ve `MyEmployeeInfo.tsx` birlikte değişir; sağlık,
ücret ve işten çıkış alanları listeye eklenmez.
### 0.13 Modül dokümanı — `configs/seeds/docs/modules/{Modül}.md` ### 0.13 Modül dokümanı — `configs/seeds/docs/modules/{Modül}.md`
Üretilen her modül kendi çalışma şeklini bir md dosyasında taşır; "bu ekran nasıl çalışıyor / şu Üretilen her modül kendi çalışma şeklini bir md dosyasında taşır; "bu ekran nasıl çalışıyor / şu
@ -2177,7 +2211,7 @@ olmalıdır.
| Kolon | Ne yapar | | Kolon | Ne yapar |
| --- | --- | | --- | --- |
| `InsertCommand` / `UpdateCommand` / `DeleteCommand` | Özel SQL komutları; boşsa platform üretir | | `InsertCommand` / `UpdateCommand` / `DeleteCommand` | Özel SQL komutları; boşsa platform üretir. Üretilen silme her zaman anahtar alanla sınırlanır, `TenantId` gibi diğer parametreler yalnızca ek koşuldur (`QueryManager`) |
| `*BeforeCommand` / `*AfterCommand` | Komut öncesi/sonrası çalışan SQL kancaları — **stok düşme, log yazma, durum güncelleme gibi işleri kod yazmadan burada yaparsın** | | `*BeforeCommand` / `*AfterCommand` | Komut öncesi/sonrası çalışan SQL kancaları — **stok düşme, log yazma, durum güncelleme gibi işleri kod yazmadan burada yaparsın** |
| `Insert/Update/DeleteFieldsDefaultValueJson` | Otomatik doldurulan alanlar (`@NOW`, `@USERID`, `@NEWID`, `@ID`) | | `Insert/Update/DeleteFieldsDefaultValueJson` | Otomatik doldurulan alanlar (`@NOW`, `@USERID`, `@NEWID`, `@ID`) |
| `Insert/Update/DeleteServiceAddress` | Varsayılan `list-form-data/*`; özel endpoint ya da Dynamic Service'e yönlendirilebilir | | `Insert/Update/DeleteServiceAddress` | Varsayılan `list-form-data/*`; özel endpoint ya da Dynamic Service'e yönlendirilebilir |
@ -3277,8 +3311,8 @@ crud/{entity}.json (component'lerin/nesnenin kulland
endpoint'lerinin dayandığı tablolar) endpoint'lerinin dayandığı tablolar)
``` ```
Script dosyaları sabit kombinasyondan tahmin edilmez; kapsam kökündeki script klasörleri diskten Script dosyaları sabit kombinasyondan tahmin edilmez; seed kökündeki script klasörleri (`host/`
taranır. Script bulunamaması uyarı üretmez — her tablo seed ile yönetilmez (ABP tabloları EF alt klasörleri dahil) diskten taranır. Script bulunamaması uyarı üretmez — her tablo seed ile yönetilmez (ABP tabloları EF
migration'larıyla kurulur); "hedefte var mı" kararını import analizi verir. Çözülemeyen bir migration'larıyla kurulur); "hedefte var mı" kararını import analizi verir. Çözülemeyen bir
**kayıtlı referans** (alt form, `SeedFilePath`, `DataSources[].SeedFile`, component satırı) **kayıtlı referans** (alt form, `SeedFilePath`, `DataSources[].SeedFile`, component satırı)
indirmeyi engellemez ama sessiz de kalmaz: `ExportAnalysis` ucu arşivin tam içeriğini ve eksikleri indirmeyi engellemez ama sessiz de kalmaz: `ExportAnalysis` ucu arşivin tam içeriğini ve eksikleri
@ -4363,8 +4397,9 @@ Dört ekran, tamamı **Administration › Bildirimler** altındadır: **Notifica
kanaldan alacak), **Notification** (üretilmiş bildirimler / geçmiş **ve** elle gönderim formu — kanaldan alacak), **Notification** (üretilmiş bildirimler / geçmiş **ve** elle gönderim formu —
kod tekildir: `App.Notifications.Notification`). Dört tablo da kiracı kapsamındadır kod tekildir: `App.Notifications.Notification`). Dört tablo da kiracı kapsamındadır
(`Adm_T_NotificationTemplate`, `Adm_T_NotificationType`, `Adm_T_NotificationRule`, (`Adm_T_NotificationTemplate`, `Adm_T_NotificationType`, `Adm_T_NotificationRule`,
`Adm_T_Notification`); yeni kiracıya host seed dosyalarındaki tür ve kural kümesi yeni kimliklerle `Adm_T_Notification`); kendi veritabanı olan kiracı tür ve kuralları seed dosyasından alır; host veritabanını paylaşan
kopyalanır (`NotificationTenantDefaultsSeedContributor`, `README.md` §13.5.1). kiracıya aynı dosyalardaki küme yeni kimliklerle kopyalanır (`NotificationTenantDefaultsSeedContributor`,
`README.md` §13.5.1).
| `NotificationRule` alanı | Anlam | | `NotificationRule` alanı | Anlam |
| --- | --- | | --- | --- |
@ -4448,7 +4483,7 @@ platformun hazır aksiyonları buradan çağrılır. Uydurma metot yazma; kullan
| Çağrı | Ne yapar | Sunucu yetkisi | | Çağrı | Ne yapar | Sunucu yetkisi |
| --- | --- | --- | | --- | --- | --- |
| `UiEvalService.ApiGenerateBackgroundWorkers()` | Zamanlanmış iş tanımlarını yeniden üretir | — | | `UiEvalService.ApiGenerateBackgroundWorkers()` | Zamanlanmış iş tanımlarını yeniden üretir | — |
| `UiEvalService.ApiClearRedisCache()` | Dağıtık önbelleği temizler | — | | `UiEvalService.ApiClearRedisCache()` | Uygulamanın dağıtık önbellek anahtarlarını ve dil sözlüğünün süreç içi kopyasını temizler (`DistributedCacheCleaner`) | `App.Languages.LanguageText` |
| `UiEvalService.ApiKickUser(userId, gridRef)` | Kullanıcının oturumunu düşürür, grid'i yeniler | — | | `UiEvalService.ApiKickUser(userId, gridRef)` | Kullanıcının oturumunu düşürür, grid'i yeniler | — |
| `UiEvalService.ApiDeleteDynamicService(id, gridRef)` | Dynamic Service kaydını onay alarak siler (yüklü assembly de düşer), grid'i yeniler | `App.DeveloperKit.DynamicServices.Delete` | | `UiEvalService.ApiDeleteDynamicService(id, gridRef)` | Dynamic Service kaydını onay alarak siler (yüklü assembly de düşer), grid'i yeniler | `App.DeveloperKit.DynamicServices.Delete` |
| `UiEvalService.ApiDbMigrate()` | Önce onay sorar (`App.DbMigrate.ConfirmMessage`), sonra migration + seed akışını başlatır, log panelini açar. Kendi onay diyaloğu olan `DbMigrateButton` (SQL Query Manager, Wizard Manager) ikinci parametreyle (`confirmed: true`) bu adımı atlar | `App.Setup.Migrate` | | `UiEvalService.ApiDbMigrate()` | Önce onay sorar (`App.DbMigrate.ConfirmMessage`), sonra migration + seed akışını başlatır, log panelini açar. Kendi onay diyaloğu olan `DbMigrateButton` (SQL Query Manager, Wizard Manager) ikinci parametreyle (`confirmed: true`) bu adımı atlar | `App.Setup.Migrate` |
@ -4476,6 +4511,7 @@ kullanılacağı** `lowcode-reference.instructions.md` §12'dedir.
| "Dosya yükleme/yönetme" | File Manager | `/admin/files` | | "Dosya yükleme/yönetme" | File Manager | `/admin/files` |
| "Global aramada çıksın" | Global Search kayıtları (`System`, `Group`, `Term`, `Weight`, `Url`) | `/admin/list/App.Definitions.GlobalSearch` | | "Global aramada çıksın" | Global Search kayıtları (`System`, `Group`, `Term`, `Weight`, `Url`) | `/admin/list/App.Definitions.GlobalSearch` |
| "Duyuru, anket, sosyal akış, etkinlik" | İK › Intranet ekranları | `/admin/list/App.Hr.Intranet.*` | | "Duyuru, anket, sosyal akış, etkinlik" | İK › Intranet ekranları | `/admin/list/App.Hr.Intranet.*` |
| "Çalışan kendi izin/puantaj/performans/kişisel bilgisini görsün" | Profil sekmeleri (Çalışan Bilgileri · İzinlerim · Puantajım · Performansım) + Intranet panosu widget'ı; persona ekranı menüsüz `App.Hr.My*` wizard'ıdır (§0.12.1) | `/admin/profile/*` · `/admin/intranet` |
| "Para birimi, ülke/il/ilçe, birim, sektör…" | Definitions ekranları | `/admin/list/App.Definitions.*` | | "Para birimi, ülke/il/ilçe, birim, sektör…" | Definitions ekranları | `/admin/list/App.Definitions.*` |
| "Departman, pozisyon/ünvan, yetkinlik, organizasyon şeması" | İK › Definitions · Org Chart (şube bazlı departman/pozisyon) | `/admin/list/App.Hr.*` · `/admin/organization` | | "Departman, pozisyon/ünvan, yetkinlik, organizasyon şeması" | İK › Definitions · Org Chart (şube bazlı departman/pozisyon) | `/admin/list/App.Hr.*` · `/admin/organization` |
| "Eğitim, araç/şoför/araç masrafı, vardiya, olay günlüğü, disiplin dosyası" | İK › Trainings · Vehicles · Shift · Event Log · Disciplinary Case | `/admin/list/App.Hr.{Training,Vehicle,Driver,VehicleExpense,VehicleMovement,Shift,EventLog,DisciplinaryCase}` | | "Eğitim, araç/şoför/araç masrafı, vardiya, olay günlüğü, disiplin dosyası" | İK › Trainings · Vehicles · Shift · Event Log · Disciplinary Case | `/admin/list/App.Hr.{Training,Vehicle,Driver,VehicleExpense,VehicleMovement,Shift,EventLog,DisciplinaryCase}` |

View file

@ -249,10 +249,14 @@ kullanıcısı). Uç DbMigrator'ü migrate ile aynı kilit ve SSE akışıyla ç
`PlatformDbMigrationService` yalnızca o tenant'ın şema + seed + `execute` adımlarını yürütür. Loglar `PlatformDbMigrationService` yalnızca o tenant'ın şema + seed + `execute` adımlarını yürütür. Loglar
DB Migrate'teki `DbMigrateLogPanel` ile gösterilir. DB Migrate'teki `DbMigrateLogPanel` ile gösterilir.
`configs/seeds` tek klasördür; tenant'a özel bir seed kapsamı yoktur. Tek bir `PlatformDbContext` `configs/seeds` tek klasördür; tenant'a özel bir seed kapsamı yoktur. Kendi veritabanı olan
olduğu için kendi veritabanı olan tenant ekran tanımlarını, menüleri, yetkileri ve wizard tenant'ta ekran tanımları, menüler ve yetki tanımları gibi kiracısız varlıklar host'ta kalır,
tablolarını kendi veritabanından okur. Bu yüzden host ve kendi veritabanı olan her tenant aynı çünkü ABP'nin EF repository'si `IMultiTenant` olmayan varlığı her zaman host bağlantısıyla okur.
dosyalarla kurulur. Host veritabanını paylaşan (bağlantı cümlesi olmayan) tenant'ta dosyalar Kiracının veritabanına iki şey yazılır: `IMultiTenant` varlıklar (kullanıcı, rol, yetki ataması)
ve kiracı tablolarının satırları. Bu satırlar `Code = tenant adı` olan `DataSource` üzerinden
yazılır (`DataSourceManager`). Tabloların scriptleri ile veri dosyalarının kiracı satırları o
veritabanında da gerektiği için host ve kendi veritabanı olan her tenant aynı dosyalarla kurulur.
Kiracısız satırlar host'ta zaten olduğundan bu adımda atlanır. Host veritabanını paylaşan (bağlantı cümlesi olmayan) tenant'ta dosyalar
yeniden uygulanmaz, çünkü host seed'i onları aynı veritabanına zaten yazmıştır. Karar yeniden uygulanmaz, çünkü host seed'i onları aynı veritabanına zaten yazmıştır. Karar
`SeedFileScope`'tadır: migrate kararı `DataSeedContext`'e yazar, dosya okuyan her seeder `SeedFileScope`'tadır: migrate kararı `DataSeedContext`'e yazar, dosya okuyan her seeder
(`SqlDataSeeder`, `CrudDataSeeder`, `WizardDataSeeder`, `ListFormSeedDataApplier`, `FinalSeeder`) (`SqlDataSeeder`, `CrudDataSeeder`, `WizardDataSeeder`, `ListFormSeedDataApplier`, `FinalSeeder`)
@ -630,8 +634,8 @@ paylaşılan dosyalar arşive bir kez, birleşik satırlarla girer ve import ar
| `crud/{entity}.json` | Component'lerin/nesnenin kullandığı CRUD endpoint tanımları | | `crud/{entity}.json` | Component'lerin/nesnenin kullandığı CRUD endpoint tanımları |
| `{sql\|postgres}/[host/]{object\|execute}/{nesne}.sql` | `SelectCommand`'a karşılık gelen SQL nesnesi, lookup sorgularının (`LookupDto.LookupQuery`) `FROM`/`JOIN` ile işaret ettiği nesneler ve Custom yolunda CRUD endpoint'lerinin dayandığı tablolar | | `{sql\|postgres}/[host/]{object\|execute}/{nesne}.sql` | `SelectCommand`'a karşılık gelen SQL nesnesi, lookup sorgularının (`LookupDto.LookupQuery`) `FROM`/`JOIN` ile işaret ettiği nesneler ve Custom yolunda CRUD endpoint'lerinin dayandığı tablolar |
Script dosyaları sabit bir sağlayıcı/klasör kombinasyonundan **tahmin edilmez**: kapsam kökündeki Script dosyaları sabit bir sağlayıcı/klasör kombinasyonundan **tahmin edilmez**: seed kökündeki
script klasörleri (`sql/object`, `postgres/execute` …) diskten taranır ve nesne adıyla eşleşen her script klasörleri (`sql/object`, `sql/host/execute`, `postgres/execute` …) diskten taranır ve nesne adıyla eşleşen her
dosya arşive girer. Yeni bir sağlayıcı ya da script klasörü eklendiğinde toplayıcı değişmez. dosya arşive girer. Yeni bir sağlayıcı ya da script klasörü eklendiğinde toplayıcı değişmez.
Script bulunamaması **uyarı üretmez**: her tablo seed ile yönetilmez — ABP'nin kendi tabloları Script bulunamaması **uyarı üretmez**: her tablo seed ile yönetilmez — ABP'nin kendi tabloları
@ -1039,7 +1043,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
| Modül | Kapsam | | Modül | Kapsam |
| --- | --- | | --- | --- |
| **Sozsoft.Languages** | Dil tanımları, dil anahtarları, çeviri metinleri; veritabanı tabanlı lokalizasyon (`DatabaseResourceLocalizer`). Çeviri okuma (public sayfalar dahil) ABP'nin `application-localization` ucundandır ve yetki istemez; dil/anahtar/metin yönetim uçları ile önbellek temizleme `App.Languages.Language*` / `App.Languages.LanguageText*` yetkilerine bağlıdır (`LanguagesPermissions`). Önbellek temizleme yalnızca uygulamanın anahtarlarını siler (`DistributedCacheCleaner`). | | **Sozsoft.Languages** | Dil tanımları, dil anahtarları, çeviri metinleri; veritabanı tabanlı lokalizasyon (`DatabaseResourceLocalizer`). Çeviri okuma (public sayfalar dahil) ABP'nin `application-localization` ucundandır ve yetki istemez; dil/anahtar/metin yönetim uçları ile önbellek temizleme `App.Languages.Language*` / `App.Languages.LanguageText*` yetkilerine bağlıdır (`LanguagesPermissions`). Önbellek temizleme yalnızca uygulamanın anahtarlarını siler (`DistributedCacheCleaner`). `DatabaseResourceLocalizer` metinleri süreç içinde 30 saniye tutar; ham SQL ile, başka bir API örneğinde ya da DbMigrator'da değişen bir metin en geç 30 saniyede görünür. |
| **Sozsoft.Settings** | Ayar tanımları, sağlayıcılar, şifreleme bayrakları ve ayar ekranı metadata'sı. | | **Sozsoft.Settings** | Ayar tanımları, sağlayıcılar, şifreleme bayrakları ve ayar ekranı metadata'sı. |
| **Sozsoft.Notifications** | Bildirim tipleri, kuralları ve kullanıcı bildirimleri; kanal bazlı yönlendirme. | | **Sozsoft.Notifications** | Bildirim tipleri, kuralları ve kullanıcı bildirimleri; kanal bazlı yönlendirme. |
| **Sozsoft.MailQueue** | Şablon (Scriban) tabanlı gövde üretimi, ek dosya yaşam döngüsü, kuyruk çalıştırma ve günlükleme. | | **Sozsoft.MailQueue** | Şablon (Scriban) tabanlı gövde üretimi, ek dosya yaşam döngüsü, kuyruk çalıştırma ve günlükleme. |
@ -1072,7 +1076,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
| Video oda | `/admin/videoroom/*` | Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı. | | Video oda | `/admin/videoroom/*` | Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı. |
| Raporlar | `/admin/reports/:report/view|design/...` | DevExpress rapor görüntüleyici ve tasarımcı. `Raporlar` kategorisindeki şablon: `/admin/reports/DynamicReport/view/{ŞablonId}[/{ListFormCode}?recordId=...]` (§15). | | Raporlar | `/admin/reports/:report/view|design/...` | DevExpress rapor görüntüleyici ve tasarımcı. `Raporlar` kategorisindeki şablon: `/admin/reports/DynamicReport/view/{ŞablonId}[/{ListFormCode}?recordId=...]` (§15). |
| AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. | | AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. |
| Profil | `/admin/profile/*` | Genel bilgiler, şifre, bildirim ayarları. | | Profil | `/admin/profile/*` | Genel bilgiler, çalışan bilgileri, izinlerim, puantajım, performansım, şifre, bildirim ayarları. Genel ve Çalışan Bilgileri `api/account/my-profile` ucundan okunur. ABP'nin eşlemesi yalnızca modül uzantısında tanımlı ek alanları taşıdığı için `PlatformProfileAppService` kullanıcının kendi kaydındaki alanları (`ProfileExtraProperties`; sağlık, ücret ve işten çıkış alanları hariç) ve departman/pozisyon/şube/rol adlarını cevaba ekler. Çalışan Bilgileri salt okunurdur. İzin, puantaj ve performans sekmeleri çekirdek, salt okunur bileşenlerdir: `App.Hr.My*` persona ekranlarının satırlarını `list-form-select` ile okuyup kart + tablo olarak gösterir, düzenle/sil çizmez (tek aksiyon izin talebi oluşturma). Sekme, ekranlarından birinin yetkisi yoksa görünmez. |
### 10.4. Public site ### 10.4. Public site
@ -1309,8 +1313,13 @@ başlığındaki ⓘ simgesi ise ekranın kendi ayrıntılı açıklamasını g
- `PlatformConsts.IsMultiTenant = true`. - `PlatformConsts.IsMultiTenant = true`.
- Tenant çözümleme host adı (subdomain) ve ABP tenant resolver'ları ile yapılır (`utils/subdomain.ts`). - Tenant çözümleme host adı (subdomain) ve ABP tenant resolver'ları ile yapılır (`utils/subdomain.ts`).
- **Tenant başına ayrı veritabanı:** `TenantConnectionString` kayıtları ile her tenant kendi - **Tenant başına ayrı veritabanı:** `TenantConnectionString` kayıtları ile her tenant kendi
veritabanına yönlendirilebilir; migration'lar `DatabaseMigrationEventHandlerBase` üzerinden veritabanına yönlendirilebilir. Bağlantı kaydedildiğinde kiracı adıyla bir `DataSource` açılır
tenant oluşturulduğunda otomatik uygulanır. (`TenantConnectionStringEventHandler`). Şema ve seed DbMigrator ile kurulur: tam migrate
bütün kiracıları dolaşır, Tenants ekranının Seed sekmesi yalnızca seçilen kiracıyı kurar
(`POST /api/setup/tenants/{id}/seed`, `TenantSeedOptions`). EF ile yalnızca `IMultiTenant`
varlıklar kiracı veritabanına gider. ListForm, menü ve yetki tanımı gibi kiracısız varlıkları
ABP her zaman host'ta okur. Kiracı tablolarının satırları `DataSourceManager` üzerinden kiracının
veri kaynağına gider.
- **Şube (Branch):** Tenant altındaki fiziksel/örgütsel kırılım; kullanıcı–şube eşlemesi - **Şube (Branch):** Tenant altındaki fiziksel/örgütsel kırılım; kullanıcı–şube eşlemesi
`BranchUsers` ile tutulur, lisans limiti girişte kontrol edilir. `BranchUsers` ile tutulur, lisans limiti girişte kontrol edilir.
- **Organizasyon birimi:** ABP `OrganizationUnit` ağacı; ListForm'lar OU bazlı filtrelenebilir. - **Organizasyon birimi:** ABP `OrganizationUnit` ağacı; ListForm'lar OU bazlı filtrelenebilir.
@ -2159,7 +2168,7 @@ configs/seeds/
| `/api/abp/application-configuration` 503 dönüyor | Veritabanına erişilemiyor. Bağlantı dizesini ve veri konteynerini kontrol edin; `/api/setup/application-status` ile durumu görün. | | `/api/abp/application-configuration` 503 dönüyor | Veritabanına erişilemiyor. Bağlantı dizesini ve veri konteynerini kontrol edin; `/api/setup/application-status` ile durumu görün. |
| UI `/setup` ekranına düşüyor | Şema oluşmamış veya bekleyen migration var. Setup ekranından migration'ı tetikleyin ya da DbMigrator'ı çalıştırın. | | UI `/setup` ekranına düşüyor | Şema oluşmamış veya bekleyen migration var. Setup ekranından migration'ı tetikleyin ya da DbMigrator'ı çalıştırın. |
| Giriş "UserCannotSignIn..." hatası veriyor | [11.3](#113-giriş-engelleri) tablosuna bakın: IP, çalışma saati, doğrulama, şube/eş zamanlı kullanıcı limiti. | | Giriş "UserCannotSignIn..." hatası veriyor | [11.3](#113-giriş-engelleri) tablosuna bakın: IP, çalışma saati, doğrulama, şube/eş zamanlı kullanıcı limiti. |
| Yeni sürüm tarayıcıda görünmüyor | Service worker `prompt` modundadır; güncelleme bildirimini onaylayın veya sekmeyi tamamen kapatıp açın. | | Yeni sürüm tarayıcıda görünmüyor | Yeni worker indirmeyi bitirince kendiliğinden devreye girer ve sayfa yenilenir (`swRegistration.ts`). Kurulum takıldıysa sekmeyi tamamen kapatıp açın ya da DevTools › Application › Service Workers'tan eski worker'ı kaldırın. |
| DevExtreme teması yüklenmiyor | `public/css` altındaki tema dosyaları runtime cache'tedir (StaleWhileRevalidate); hard refresh yeterlidir. | | DevExtreme teması yüklenmiyor | `public/css` altındaki tema dosyaları runtime cache'tedir (StaleWhileRevalidate); hard refresh yeterlidir. |
| Hangfire başlangıçta hata veriyor | Bağlantı dizesi boşsa Hangfire kaydı atlanır ama ABP modülleri yüklenir. Bağlantı dizesini doğrulayın. | | Hangfire başlangıçta hata veriyor | Bağlantı dizesi boşsa Hangfire kaydı atlanır ama ABP modülleri yüklenir. Bağlantı dizesini doğrulayın. |
| Dinamik servis yayınlanmıyor | Önce `TestCompile` çalıştırın; derleme hatası mesajı UI'da döner. `Publish` için ilgili yetki gerekir. | | Dinamik servis yayınlanmıyor | Önce `TestCompile` çalıştırın; derleme hatası mesajı UI'da döner. `Publish` için ilgili yetki gerekir. |

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@ -6,12 +7,16 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Sozsoft.Platform.BlobStoring; using Sozsoft.Platform.BlobStoring;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Extensions; using Sozsoft.Platform.Extensions;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Account; using Volo.Abp.Account;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.Users; using Volo.Abp.Users;
using static Sozsoft.Platform.PlatformConsts.AbpIdentity;
using IdentityUser = Volo.Abp.Identity.IdentityUser; using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace Sozsoft.Platform.Identity; namespace Sozsoft.Platform.Identity;
@ -22,27 +27,56 @@ namespace Sozsoft.Platform.Identity;
[Route("/api/account/my-profile")] [Route("/api/account/my-profile")]
public class PlatformProfileAppService : ProfileAppService, IProfileAppService public class PlatformProfileAppService : ProfileAppService, IProfileAppService
{ {
/// <summary>
/// Profilin Genel ve Çalışan Bilgileri sekmelerinin gösterdiği ek alanlar. ABP'nin eşlemesi yalnızca
/// modül uzantısında tanımlı alanları taşır; bunlar kullanıcıya kendi kaydı olarak elle eklenir.
/// Sağlık, ücret ve işten çıkış alanları bilinçli olarak listede yoktur.
/// </summary>
private static readonly string[] ProfileExtraProperties =
[
User.HomeAddress, User.Nationality, User.Gender, User.EducationLevel, User.BloodType,
User.SecondaryEmail, User.SecondaryPhoneNumber, User.MilitaryStatus, User.MilitaryPostponeEndDate,
User.IdentityNumber, User.BirthPlace, User.BirthDate, User.FatherName, User.MotherName,
User.MaritalStatus, User.MarriageDate, User.RegistrationNo, User.SskNo, User.PdksCardNo,
User.EmploymentType, User.ContractType, User.HireDate, User.ContractEndDate,
User.ProbationEndDate, User.SeniorityStartDate, User.DrivingLicenseClass, User.DrivingLicenseNo,
User.DrivingLicenseExpiryDate,
];
public BlobManager _blobCdnManager { get; } public BlobManager _blobCdnManager { get; }
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly IRepository<Department, Guid> _departmentRepository;
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
private readonly IRepository<Branch, Guid> _branchRepository;
private readonly IRepository<BranchUsers, Guid> _branchUsersRepository;
public PlatformProfileAppService( public PlatformProfileAppService(
IdentityUserManager userManager, IdentityUserManager userManager,
IOptions<IdentityOptions> identityOptions, IOptions<IdentityOptions> identityOptions,
BlobManager blobCdnManager, BlobManager blobCdnManager,
IConfiguration configuration IConfiguration configuration,
IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository,
IRepository<Branch, Guid> branchRepository,
IRepository<BranchUsers, Guid> branchUsersRepository
) : base(userManager, identityOptions) ) : base(userManager, identityOptions)
{ {
_blobCdnManager = blobCdnManager; _blobCdnManager = blobCdnManager;
_configuration = configuration; _configuration = configuration;
_departmentRepository = departmentRepository;
_jobPositionRepository = jobPositionRepository;
_branchRepository = branchRepository;
_branchUsersRepository = branchUsersRepository;
} }
[RemoteService(false)] [RemoteService(false)]
public override Task<ProfileDto> UpdateAsync(UpdateProfileDto input) => throw new NotImplementedException(); public override Task<ProfileDto> UpdateAsync(UpdateProfileDto input) => throw new NotImplementedException();
[HttpGet] [HttpGet]
public override Task<ProfileDto> GetAsync() public override async Task<ProfileDto> GetAsync()
{ {
return base.GetAsync(); var user = await UserManager.GetByIdAsync(CurrentUser.GetId());
return await MapToProfileAsync(user);
} }
[HttpPost] [HttpPost]
@ -85,7 +119,40 @@ public class PlatformProfileAppService : ProfileAppService, IProfileAppService
await CurrentUnitOfWork.SaveChangesAsync(); await CurrentUnitOfWork.SaveChangesAsync();
} }
return ObjectMapper.Map<IdentityUser, ProfileDto>(user); return await MapToProfileAsync(user);
}
/// <summary>
/// Departman, pozisyon ve şube ad olarak eklenir: lookup'ları kullanıcı ekranının
/// (<c>AbpIdentity.Users</c>) yetkisiyle okunur, profil ise yetki istemez.
/// </summary>
private async Task<ProfileDto> MapToProfileAsync(IdentityUser user)
{
var profile = ObjectMapper.Map<IdentityUser, ProfileDto>(user);
foreach (var name in ProfileExtraProperties)
{
profile.ExtraProperties[name] = user.GetProperty(name);
}
var departmentId = user.GetDepartmentId();
var jobPositionId = user.GetJobPositionId();
var branchUsers = await _branchUsersRepository.GetQueryableAsync();
var branchIds = await AsyncExecuter.ToListAsync(
branchUsers.Where(a => a.UserId == user.Id).Select(a => a.BranchId));
profile.ExtraProperties["DepartmentName"] = departmentId == Guid.Empty
? null
: (await _departmentRepository.FindAsync(departmentId))?.Name;
profile.ExtraProperties["JobPositionName"] = jobPositionId == Guid.Empty
? null
: (await _jobPositionRepository.FindAsync(jobPositionId))?.Name;
profile.ExtraProperties["Branches"] = (await _branchRepository.GetListAsync(a => branchIds.Contains(a.Id)))
.Select(a => a.Name)
.ToArray();
profile.ExtraProperties["Roles"] = (await UserManager.GetRolesAsync(user)).ToArray();
return profile;
} }
} }

View file

@ -335,6 +335,13 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
karşılık gelen yetkiyle eklenir. `create_table` bu yüzden `.Write`'a ek olarak `App.SqlQueryManager` karşılık gelen yetkiyle eklenir. `create_table` bu yüzden `.Write`'a ek olarak `App.SqlQueryManager`
ister; tablo scriptini tasarımcının `generateCreateTableSql` çıktısıyla aynı biçimde üretir ister; tablo scriptini tasarımcının `generateCreateTableSql` çıktısıyla aynı biçimde üretir
(`McpTableCreator`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir. (`McpTableCreator`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir.
- **Çalışan (persona) ekranları ve profil** → çalışanın/yöneticinin kendi listeleri İK menüsüne
konmaz: menüsüz `App.Hr.My*`/`Team*` wizard + Intranet panosu widget'ı. Profil sayfasının İzinlerim /
Puantajım / Performansım sekmeleri persona ekranlarını salt okunur gösteren çekirdek bileşenlerdir
(düzenle/sil yok, tek aksiyon izin talebi oluşturma); yeni persona
ekranını profile eklemek kod + rota değişikliğidir (onay). Çalışan Bilgileri sekmesi ve Genel sekmesi
kullanıcının kendi alanlarını `my-profile` ek alanlarından okur (`PlatformProfileAppService.ProfileExtraProperties`);
sağlık, ücret ve işten çıkış alanları bu listeye girmez. Ayrıntı: `lowcode.instructions.md` §0.12.1.
- **Bildirim kapsamı** → bildirim tipi, kuralı ve kuyruğu kiracıya aittir (`Adm_T_NotificationTemplate`, - **Bildirim kapsamı** → bildirim tipi, kuralı ve kuyruğu kiracıya aittir (`Adm_T_NotificationTemplate`,
`Adm_T_NotificationType`, `Adm_T_NotificationRule`, `Adm_T_Notification`; dördü de `IMultiTenant`) ve ekranların tamamı `Adm_T_NotificationType`, `Adm_T_NotificationRule`, `Adm_T_Notification`; dördü de `IMultiTenant`) ve ekranların tamamı
Administration › Bildirimler altındadır. Ayrı bir manuel gönderim tablosu yoktur: **Bildirimler** Administration › Bildirimler altındadır. Ayrı bir manuel gönderim tablosu yoktur: **Bildirimler**

View file

@ -9790,6 +9790,62 @@
"Key": "Abp.Identity.Profile.General", "Key": "Abp.Identity.Profile.General",
"Value": "Genel" "Value": "Genel"
}, },
{
"Id": "193920df-a568-5cbb-8ba2-968b8ce5d56a",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.EmployeeInfo",
"Value": "Employee Info"
},
{
"Id": "9327d82b-00d3-58e4-b36e-5e5b9340c20c",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.EmployeeInfo",
"Value": "Çalışan Bilgileri"
},
{
"Id": "9e34f6c5-f535-5681-aa5f-c2c47bb7cf7d",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.Leave",
"Value": "My Leave"
},
{
"Id": "e69b4dc5-533e-521a-a11c-6c338b606bd2",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.Leave",
"Value": "İzinlerim"
},
{
"Id": "e7913140-c7f2-5525-ab1c-78b2c33ecd64",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.Timesheet",
"Value": "My Timesheet"
},
{
"Id": "d2b0ffe5-04e3-57c7-bee7-e623f091d125",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.Timesheet",
"Value": "Puantajım"
},
{
"Id": "a7cc018e-28b1-58c3-b06e-e76b769da6e3",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.Performance",
"Value": "My Performance"
},
{
"Id": "31d15a57-6cc5-5d60-ad2f-55dbbec62b33",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Abp.Identity.Profile.Performance",
"Value": "Performansım"
},
{ {
"Id": "ad1d44df-623f-55fa-be90-8ad9aca6cd5c", "Id": "ad1d44df-623f-55fa-be90-8ad9aca6cd5c",
"CultureName": "en", "CultureName": "en",

View file

@ -460,6 +460,82 @@
"SeoPriority": null, "SeoPriority": null,
"SeoStructuredData": null "SeoStructuredData": null
}, },
{
"Id": "8b7bddc0-21db-5c3f-9964-a3cca0c294ba",
"Key": "admin.profile.employeeInfo",
"Path": "/admin/profile/employee-info",
"ComponentType": "normal",
"ComponentPath": "@/views/admin/profile/Profile",
"RouteType": "protected",
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "43b30761-b5ed-5f27-8684-d516f7323a7b",
"Key": "admin.profile.leave",
"Path": "/admin/profile/leave",
"ComponentType": "normal",
"ComponentPath": "@/views/admin/profile/Profile",
"RouteType": "protected",
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "7840151c-13da-5cf3-af54-f7d34e37530c",
"Key": "admin.profile.timesheet",
"Path": "/admin/profile/timesheet",
"ComponentType": "normal",
"ComponentPath": "@/views/admin/profile/Profile",
"RouteType": "protected",
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{
"Id": "6e9972bc-c8d6-50bf-acab-4c68f2832907",
"Key": "admin.profile.performance",
"Path": "/admin/profile/performance",
"ComponentType": "normal",
"ComponentPath": "@/views/admin/profile/Profile",
"RouteType": "protected",
"Authority": "",
"SeoTitle": null,
"SeoDescription": null,
"SeoKeywords": null,
"SeoOgImage": null,
"SeoCanonicalUrl": null,
"SeoNoIndex": true,
"SeoIncludeInSitemap": false,
"SeoChangeFrequency": null,
"SeoPriority": null,
"SeoStructuredData": null
},
{ {
"Id": "c64d7bd0-e884-55a6-915a-7a43e0c5e684", "Id": "c64d7bd0-e884-55a6-915a-7a43e0c5e684",
"Key": "admin.activityLog", "Key": "admin.activityLog",

View file

@ -18,7 +18,7 @@ tek ek vardır (kullanıcı onayıyla): kullanıcı detayındaki **Puantaj** sek
| Persona | Ne yapar | Ekran | | Persona | Ne yapar | Ekran |
| --- | --- | --- | | --- | --- | --- |
| Çalışan | Seçtiği ayın net saatini, geç kalma/erken çıkış dakikasını, fazla mesaisini, devamsız/izinli/raporlu günlerini ve yıllık toplamı (270 saatlik fazla mesai sınırıyla) görür | Intranet panosu: **Puantajım** widget'ı → Puantajım (aylar) → ayın günleri | | Çalışan | Seçtiği ayın net saatini, geç kalma/erken çıkış dakikasını, fazla mesaisini, devamsız/izinli/raporlu günlerini ve yıllık toplamı (270 saatlik fazla mesai sınırıyla) görür | Intranet panosu: **Puantajım** widget'ı → Puantajım (aylar) → ayın günleri · Profil › Puantajım (aylık puantaj salt okunur) |
| İK | Hareketleri izler/düzeltir, elle takip edilen çalışanın gelmediği günü işaretler, aylık puantajı izler, ayı yeniden hesaplar ve kapatır, çalışanın PDKS Kart No'sunu kullanıcı kartına (İş sekmesi) girer, toleransı ve fazla mesai kurallarını ayarlar | İK › Puantaj ve Devam Takibi · Ayarlar › İnsan Kaynakları › Puantaj · Kullanıcı detayı › Puantaj | | İK | Hareketleri izler/düzeltir, elle takip edilen çalışanın gelmediği günü işaretler, aylık puantajı izler, ayı yeniden hesaplar ve kapatır, çalışanın PDKS Kart No'sunu kullanıcı kartına (İş sekmesi) girer, toleransı ve fazla mesai kurallarını ayarlar | İK › Puantaj ve Devam Takibi · Ayarlar › İnsan Kaynakları › Puantaj · Kullanıcı detayı › Puantaj |
| PDKS (servis kullanıcısı) | Kart okutmalarını toplu gönderir | `POST /api/app/custom-endpoint/hr/attendance/` | | PDKS (servis kullanıcısı) | Kart okutmalarını toplu gönderir | `POST /api/app/custom-endpoint/hr/attendance/` |

View file

@ -16,7 +16,7 @@ detayındaki **İzinler** sekmesi.
| Persona | Ne yapar | Ekran | | Persona | Ne yapar | Ekran |
| --- | --- | --- | | --- | --- | --- |
| Çalışan | Bakiyesini izler, izin talebi açar, taleplerinin onay durumunu takip eder, henüz karar verilmemiş talebini düzeltir/siler | Intranet panosu: **İzinlerim** widget'ı → İzin Talebi Oluştur · Taleplerim · Bakiyem | | Çalışan | Bakiyesini izler, izin talebi açar, taleplerinin onay durumunu takip eder, henüz karar verilmemiş talebini düzeltir/siler | Intranet panosu: **İzinlerim** widget'ı → İzin Talebi Oluştur · Taleplerim · Bakiyem · Profil › İzinlerim (bakiye + talepler salt okunur, İzin Talebi Oluştur) |
| Departman müdürü | Departmanının taleplerini onaylar/reddeder | Intranet panosu: **Ekibimin İzinleri** widget'ı → Ekibimin İzin Talepleri | | Departman müdürü | Departmanının taleplerini onaylar/reddeder | Intranet panosu: **Ekibimin İzinleri** widget'ı → Ekibimin İzin Talepleri |
| İK | Tüm talepleri izler, ikinci onayı verir, çalışan adına talep girer, bakiyeleri izler, açılış/devir düzeltmesi girer, çalışma günlerini ve kademeleri ayarlar | İK › İzin Yönetimi › İzin Talepleri · İzin Bakiyeleri · Ayarlar › İnsan Kaynakları › İzin · Kullanıcı detayı › İzinler | | İK | Tüm talepleri izler, ikinci onayı verir, çalışan adına talep girer, bakiyeleri izler, açılış/devir düzeltmesi girer, çalışma günlerini ve kademeleri ayarlar | İK › İzin Yönetimi › İzin Talepleri · İzin Bakiyeleri · Ayarlar › İnsan Kaynakları › İzin · Kullanıcı detayı › İzinler |
| Sağlık birimi | Raporu Sağlık Kartı'na girer; kayıt izin listesine onaylı düşer | İK › Sağlık Kartları › İstirahat Raporları | | Sağlık birimi | Raporu Sağlık Kartı'na girer; kayıt izin listesine onaylı düşer | İK › Sağlık Kartları › İstirahat Raporları |

View file

@ -18,7 +18,7 @@ Component + SQL nesneleri); yeni entity, migration ya da React kodu yoktur.
| Persona | Ne yapar | Ekran | | Persona | Ne yapar | Ekran |
| --- | --- | --- | | --- | --- | --- |
| İK | Dönem ve yetkinlik seti tanımlar, tüm değerlendirmeleri izler, 360 değerlendiricisi atar, kalibre eder, gelişim planlarını izler | Dönemler · Tüm Değerlendirmeler · Kalibrasyon · Gelişim Planları · Performans Dereceleri | | İK | Dönem ve yetkinlik seti tanımlar, tüm değerlendirmeleri izler, 360 değerlendiricisi atar, kalibre eder, gelişim planlarını izler | Dönemler · Tüm Değerlendirmeler · Kalibrasyon · Gelişim Planları · Performans Dereceleri |
| Çalışan | Hedeflerini girer, gerçekleşeni ve öz değerlendirmesini yazar, yöneticiye gönderir, gelişim aksiyonlarının ilerlemesini günceller | Intranet panosu: **Performansım** widget'ı → Değerlendirmelerim · Gelişim Planım | | Çalışan | Hedeflerini girer, gerçekleşeni ve öz değerlendirmesini yazar, yöneticiye gönderir, gelişim aksiyonlarının ilerlemesini günceller | Intranet panosu: **Performansım** widget'ı → Değerlendirmelerim · Gelişim Planım · Profil › Performansım (Değerlendirmelerim · Hedeflerim · Gelişim Planım · 360 Taleplerim salt okunur) |
| Yönetici | Hedefleri onaylar, yetkinlik seviyesini ve yorumunu verir, kalibrasyona gönderir, gelişim aksiyonu planlar | Intranet panosu: **Ekibimin Performansı** widget'ı → Ekibimin Değerlendirmeleri | | Yönetici | Hedefleri onaylar, yetkinlik seviyesini ve yorumunu verir, kalibrasyona gönderir, gelişim aksiyonu planlar | Intranet panosu: **Ekibimin Performansı** widget'ı → Ekibimin Değerlendirmeleri |
| 360 değerlendiricisi | Kendisine atanan çalışanın yetkinliklerini puanlar ve gönderir | Intranet panosu: **360 Taleplerim** widget'ı → 360 Taleplerim | | 360 değerlendiricisi | Kendisine atanan çalışanın yetkinliklerini puanlar ve gönderir | Intranet panosu: **360 Taleplerim** widget'ı → 360 Taleplerim |

View file

@ -45,6 +45,10 @@
general: '/admin/profile/general', general: '/admin/profile/general',
password: '/admin/profile/password', password: '/admin/profile/password',
notificationSettings: '/admin/profile/notification-settings', notificationSettings: '/admin/profile/notification-settings',
employeeInfo: '/admin/profile/employee-info',
leave: '/admin/profile/leave',
timesheet: '/admin/profile/timesheet',
performance: '/admin/profile/performance',
}, },
activityLog: '/admin/activityLog', activityLog: '/admin/activityLog',
changeLog: '/admin/changeLog', changeLog: '/admin/changeLog',

View file

@ -2,12 +2,25 @@ import AdaptableCard from '@/components/shared/AdaptableCard'
import Container from '@/components/shared/Container' import Container from '@/components/shared/Container'
import Tabs from '@/components/ui/Tabs' import Tabs from '@/components/ui/Tabs'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission'
import { Suspense, lazy, type ReactNode } from 'react' import { Suspense, lazy, type ReactNode } from 'react'
import PageTitle from '@/components/shared/PageTitle' import PageTitle from '@/components/shared/PageTitle'
import { FaCheckCircle, FaCompressAlt, FaUser } from 'react-icons/fa' import {
FaCalendarAlt,
FaChartLine,
FaCheckCircle,
FaClock,
FaCompressAlt,
FaIdCard,
FaUser,
} from 'react-icons/fa'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
const General = lazy(() => import('./components/General')) const General = lazy(() => import('./components/General'))
const MyEmployeeInfo = lazy(() => import('./components/MyEmployeeInfo'))
const MyLeave = lazy(() => import('./components/MyLeave'))
const MyTimesheet = lazy(() => import('./components/MyTimesheet'))
const MyPerformance = lazy(() => import('./components/MyPerformance'))
const Password = lazy(() => import('./components/Password')) const Password = lazy(() => import('./components/Password'))
const NotificationSettings = lazy(() => import('./components/NotificationSettings')) const NotificationSettings = lazy(() => import('./components/NotificationSettings'))
@ -16,12 +29,15 @@ const PROFILE_BASE_PATH = '/admin/profile'
const Profile = () => { const Profile = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const settingsMenu: Record< const { checkPermission } = usePermission()
const allMenus: Record<
string, string,
{ {
label: string label: string
path: string path: string
icon?: ReactNode icon?: ReactNode
/** Sekme, bu "My" ekranlarından en az birinin yetkisi varsa görünür. */
permissions?: string[]
} }
> = { > = {
general: { general: {
@ -29,6 +45,34 @@ const Profile = () => {
path: 'general', path: 'general',
icon: <FaUser />, icon: <FaUser />,
}, },
employeeInfo: {
label: translate('::Abp.Identity.Profile.EmployeeInfo'),
path: 'employee-info',
icon: <FaIdCard />,
},
leave: {
label: translate('::Abp.Identity.Profile.Leave'),
path: 'leave',
icon: <FaCalendarAlt />,
permissions: ['App.Hr.MyLeaveRequest', 'App.Hr.MyLeaveBalance'],
},
timesheet: {
label: translate('::Abp.Identity.Profile.Timesheet'),
path: 'timesheet',
icon: <FaClock />,
permissions: ['App.Hr.MyTimesheet'],
},
performance: {
label: translate('::Abp.Identity.Profile.Performance'),
path: 'performance',
icon: <FaChartLine />,
permissions: [
'App.Hr.MyPerformanceReview',
'App.Hr.MyPerformanceGoal',
'App.Hr.MyDevelopmentPlan',
'App.Hr.My360Feedback',
],
},
password: { password: {
label: translate('::Abp.Identity.Password'), label: translate('::Abp.Identity.Password'),
path: 'password', path: 'password',
@ -40,6 +84,11 @@ const Profile = () => {
icon: <FaCompressAlt />, icon: <FaCompressAlt />,
}, },
} }
const settingsMenu = Object.fromEntries(
Object.entries(allMenus).filter(
([, menu]) => !menu.permissions || menu.permissions.some((p) => checkPermission(p)),
),
)
const location = useLocation() const location = useLocation()
const path = location.pathname.substring(location.pathname.lastIndexOf('/') + 1) const path = location.pathname.substring(location.pathname.lastIndexOf('/') + 1)
@ -61,9 +110,7 @@ const Profile = () => {
<TabList> <TabList>
{Object.keys(settingsMenu).map((key) => ( {Object.keys(settingsMenu).map((key) => (
<TabNav key={key} value={key}> <TabNav key={key} value={key}>
{settingsMenu[key].icon && ( {settingsMenu[key].icon && <span className="mr-2">{settingsMenu[key].icon}</span>}
<span className="mr-2">{settingsMenu[key].icon}</span>
)}
{settingsMenu[key].label} {settingsMenu[key].label}
</TabNav> </TabNav>
))} ))}
@ -72,6 +119,10 @@ const Profile = () => {
<div className="px-4 py-6"> <div className="px-4 py-6">
<Suspense fallback={<></>}> <Suspense fallback={<></>}>
{currentTab === 'general' && <General />} {currentTab === 'general' && <General />}
{currentTab === 'employeeInfo' && <MyEmployeeInfo />}
{currentTab === 'leave' && <MyLeave />}
{currentTab === 'timesheet' && <MyTimesheet />}
{currentTab === 'performance' && <MyPerformance />}
{currentTab === 'password' && <Password />} {currentTab === 'password' && <Password />}
{currentTab === 'notificationSettings' && <NotificationSettings />} {currentTab === 'notificationSettings' && <NotificationSettings />}
</Suspense> </Suspense>

View file

@ -0,0 +1,210 @@
import { Input } from '@/components/ui'
import { FormContainer, FormItem } from '@/components/ui/Form'
import { ProfileDto } from '@/proxy/account/models'
import { getProfile } from '@/services/account.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { getActiveCulture, LOCALE_DATE_FORMAT, toTwoLetterCulture } from '@/utils/localeFormat'
import { maritalStatuses } from '@/views/admin/user-management/userLookups'
import dayjs from 'dayjs'
import { useEffect, useState } from 'react'
import {
FaBriefcase,
FaCalendarAlt,
FaEnvelope,
FaHashtag,
FaIdCard,
FaMapMarkerAlt,
FaPhone,
FaUserTie,
} from 'react-icons/fa'
import type { IconType } from 'react-icons'
type Row = [label: string, value?: string, icon?: IconType]
const formatDate = (value?: string) =>
value
? dayjs(value).locale(toTwoLetterCulture(getActiveCulture())).format(LOCALE_DATE_FORMAT)
: undefined
/** Genel sekmesiyle aynı düzen: başlık + devre dışı alanlar; çalışan değeri yalnızca görür. */
const InfoColumn = ({ title, rows }: { title: string; rows: Row[] }) => (
<div>
<h6 className="mb-4">{title}</h6>
{rows.map(([label, value, Icon]) => (
<FormItem key={label} label={label}>
<Input
disabled
type="text"
prefix={Icon && <Icon className="text-xl" />}
value={value ?? ''}
/>
</FormItem>
))}
</div>
)
/**
* Kullanıcının İK'nın tuttuğu kendi bilgileri (salt okunur). Değerler `my-profile` cevabının ek
* alanlarındandır (`PlatformProfileAppService.ProfileExtraProperties`); Genel sekmesinde gösterilen
* alanlar burada tekrarlanmaz.
*/
const MyEmployeeInfo = () => {
const { translate } = useLocalization()
const [profile, setProfile] = useState<ProfileDto>()
useEffect(() => {
let active = true
getProfile()
.then((response) => active && setProfile(response.data))
.catch(() => active && setProfile(undefined))
return () => {
active = false
}
}, [])
if (!profile) return null
// Sunucu ek alan adlarını kayıttaki gibi (PascalCase) yazar; serileştirici ayarına karşı iki yazım da okunur.
const extra = (key: string) =>
profile.extraProperties?.[key] ??
profile.extraProperties?.[`${key.charAt(0).toLowerCase()}${key.slice(1)}`]
const text = (key: string) => {
const value = extra(key)
return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined
}
const list = (key: string) => {
const value = extra(key)
return Array.isArray(value) ? value.join(', ') : undefined
}
const info = {
departmentName: text('DepartmentName'),
jobPositionName: text('JobPositionName'),
branches: list('Branches'),
roles: list('Roles'),
workHour: text('WorkHour'),
registrationNo: text('RegistrationNo'),
sskNo: text('SskNo'),
pdksCardNo: text('PdksCardNo'),
employmentType: text('EmploymentType'),
contractType: text('ContractType'),
hireDate: text('HireDate'),
seniorityStartDate: text('SeniorityStartDate'),
probationEndDate: text('ProbationEndDate'),
contractEndDate: text('ContractEndDate'),
secondaryEmail: text('SecondaryEmail'),
secondaryPhoneNumber: text('SecondaryPhoneNumber'),
identityNumber: text('IdentityNumber'),
birthPlace: text('BirthPlace'),
birthDate: text('BirthDate'),
fatherName: text('FatherName'),
motherName: text('MotherName'),
maritalStatus: text('MaritalStatus'),
marriageDate: text('MarriageDate'),
militaryStatus: text('MilitaryStatus'),
militaryPostponeEndDate: text('MilitaryPostponeEndDate'),
drivingLicenseClass: text('DrivingLicenseClass'),
drivingLicenseNo: text('DrivingLicenseNo'),
drivingLicenseExpiryDate: text('DrivingLicenseExpiryDate'),
}
const t = (key: string) => translate(`::${key}`)
const user = (key: string) => t(`Abp.Identity.User.UserInformation.${key}`)
const staticLookup = (value?: string, prefix = '') =>
value ? t(`App.StaticLookup.${prefix}${value}`) : undefined
const maritalKey = maritalStatuses.find(([value]) => value === info.maritalStatus)?.[1]
return (
<FormContainer size="md">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 w-full">
<InfoColumn
title={t('Abp.Identity.User.Section.Organization')}
rows={[
[t('App.Listform.ListformField.Department'), info.departmentName, FaBriefcase],
[user('JobPositionId'), info.jobPositionName, FaUserTie],
[t('App.Branches'), info.branches],
[t('AbpIdentity.Roles'), info.roles],
[t('App.Listform.ListformField.WorkHour'), info.workHour],
[user('RegistrationNo'), info.registrationNo, FaHashtag],
[user('SskNo'), info.sskNo, FaIdCard],
[user('PdksCardNo'), info.pdksCardNo, FaIdCard],
]}
/>
<InfoColumn
title={t('Abp.Identity.User.Section.Contract')}
rows={[
[user('EmploymentType'), staticLookup(info.employmentType)],
[user('ContractType'), staticLookup(info.contractType)],
[user('HireDate'), formatDate(info.hireDate), FaCalendarAlt],
[user('SeniorityStartDate'), formatDate(info.seniorityStartDate), FaCalendarAlt],
[user('ProbationEndDate'), formatDate(info.probationEndDate), FaCalendarAlt],
...(info.contractType === 'Definite'
? [[user('ContractEndDate'), formatDate(info.contractEndDate), FaCalendarAlt] as Row]
: []),
]}
/>
<InfoColumn
title={t('Abp.Identity.User.UserInformation.ContactInformation')}
rows={[
[user('SecondaryEmail'), info.secondaryEmail, FaEnvelope],
[user('SecondaryPhoneNumber'), info.secondaryPhoneNumber, FaPhone],
]}
/>
<InfoColumn
title={t('Abp.Identity.User.Section.IdentityCard')}
rows={[
[user('IdentityNumber'), info.identityNumber, FaIdCard],
[user('BirthPlace'), info.birthPlace, FaMapMarkerAlt],
[t('App.Listform.ListformField.BirthDate'), formatDate(info.birthDate), FaCalendarAlt],
]}
/>
<InfoColumn
title={t('Abp.Identity.User.Section.BirthFamily')}
rows={[
[user('FatherName'), info.fatherName],
[user('MotherName'), info.motherName],
[
t('App.Listform.ListformField.MaritalStatus'),
maritalKey ? t(`App.MaritalStatus.${maritalKey}`) : info.maritalStatus,
],
[user('MarriageDate'), formatDate(info.marriageDate), FaCalendarAlt],
]}
/>
<InfoColumn
title={t('Abp.Identity.User.Section.Military')}
rows={[
[user('MilitaryStatus'), staticLookup(info.militaryStatus, 'Military')],
...(info.militaryStatus === 'Postponed'
? [
[
user('MilitaryPostponeEndDate'),
formatDate(info.militaryPostponeEndDate),
FaCalendarAlt,
] as Row,
]
: []),
]}
/>
<InfoColumn
title={t('Abp.Identity.User.Section.DrivingLicense')}
rows={[
[user('DrivingLicenseClass'), info.drivingLicenseClass?.split('|').join(', ')],
[user('DrivingLicenseNo'), info.drivingLicenseNo, FaIdCard],
[
user('DrivingLicenseExpiryDate'),
formatDate(info.drivingLicenseExpiryDate),
FaCalendarAlt,
],
]}
/>
</div>
</FormContainer>
)
}
export default MyEmployeeInfo

View file

@ -0,0 +1,104 @@
import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission'
import { FaCalendarCheck, FaUmbrellaBeach } from 'react-icons/fa'
import { ProfileCard, ProfileLinkButton, ReadOnlyTable, StatusBadge } from './ProfileSections'
import { useMyRows } from './profileData'
const REQUESTS = 'App.Hr.MyLeaveRequest'
const BALANCE = 'App.Hr.MyLeaveBalance'
const formatDays = (value: number) => (Number.isInteger(value) ? String(value) : value.toFixed(1))
/**
* İzinlerim: bakiye ve talepler salt okunur gösterilir. Çalışan yalnızca yeni talep açar; talebin
* düzeltilmesi, silinmesi ve kararı onay sürecinde ve İK'dadır (Hr_S_LeaveRequestGuard).
*/
const MyLeave = () => {
const { translate } = useLocalization()
const { checkPermission } = usePermission()
const requests = useMyRows(REQUESTS)
const balance = useMyRows(BALANCE)
const sum = (field: string) =>
balance.rows.reduce((total, row) => total + Number(row[field] ?? 0), 0)
const remaining = sum('RemainingDays')
const stats: [string, number, string][] = [
['EntitledDays', sum('EntitledDays'), 'text-gray-900 dark:text-gray-100'],
['UsedDays', sum('UsedDays'), 'text-gray-900 dark:text-gray-100'],
['PendingDays', sum('PendingDays'), 'text-orange-500'],
['RemainingDays', remaining, remaining < 0 ? 'text-red-600' : 'text-teal-600'],
]
return (
<div className="flex flex-col gap-6">
{balance.allowed && (
<ProfileCard
title={translate(`::${BALANCE}`)}
icon={<FaUmbrellaBeach className="h-5 w-5 text-teal-500" />}
>
<div className="mb-4 grid grid-cols-2 gap-3 md:grid-cols-4">
{stats.map(([field, value, color]) => (
<div
key={field}
className="rounded-lg border border-gray-200 bg-gray-50 p-3 text-center dark:border-gray-700 dark:bg-gray-900/40"
>
<div className={`text-xl font-semibold ${color}`}>{formatDays(value)}</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{translate(`::App.Listform.ListformField.${field}`)}
</div>
</div>
))}
</div>
<ReadOnlyTable
loading={balance.loading}
rows={balance.rows}
columns={[
{ field: 'YearNo' },
{ field: 'AccrualDate', date: true },
{ field: 'PeriodEndDate', date: true },
{ field: 'EntitledDays' },
{ field: 'UsedDays' },
{ field: 'PendingDays' },
{ field: 'RemainingDays' },
]}
/>
</ProfileCard>
)}
{requests.allowed && (
<ProfileCard
title={translate(`::${REQUESTS}`)}
icon={<FaCalendarCheck className="h-5 w-5 text-teal-500" />}
action={
checkPermission(`${REQUESTS}.Create`) && (
<ProfileLinkButton to={`/admin/form/${REQUESTS}`}>
{translate('::App.DashboardWidget.Leave.CreateRequest')}
</ProfileLinkButton>
)
}
>
<ReadOnlyTable
loading={requests.loading}
rows={[...requests.rows].sort((a, b) =>
String(b.StartDate).localeCompare(String(a.StartDate)),
)}
columns={[
{ field: 'RequestNo' },
{ field: 'StartDate', date: true },
{ field: 'EndDate', date: true },
{ field: 'Days' },
{
field: 'ApprovalStatus',
render: (row) => <StatusBadge value={row.ApprovalStatus} />,
},
{ field: 'ApprovalDate', date: true },
{ field: 'ApprovalDescription' },
]}
/>
</ProfileCard>
)}
</div>
)
}
export default MyLeave

View file

@ -0,0 +1,90 @@
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ReactNode } from 'react'
import { FaBullseye, FaChartLine, FaComments, FaSeedling } from 'react-icons/fa'
import { ProfileCard, ProfileColumn, ReadOnlyTable, StatusBadge } from './ProfileSections'
import { useMyRows } from './profileData'
const Section = ({
listFormCode,
icon,
columns,
}: {
listFormCode: string
icon: ReactNode
columns: ProfileColumn[]
}) => {
const { translate } = useLocalization()
const { allowed, rows, loading } = useMyRows(listFormCode)
if (!allowed) return null
return (
<ProfileCard title={translate(`::${listFormCode}`)} icon={icon}>
<ReadOnlyTable loading={loading} rows={rows} columns={columns} />
</ProfileCard>
)
}
const status = (field: string, caption?: string): ProfileColumn => ({
field,
caption,
render: (row) => <StatusBadge value={row[field]} />,
})
/**
* Performansım: değerlendirme, hedef, gelişim planı ve 360 talepleri salt okunur. Öz değerlendirme
* ve 360 puanlaması süreç ekranlarında (Intranet panosundaki widget'lardan) yapılır.
*/
const MyPerformance = () => (
<div className="flex flex-col gap-6">
<Section
listFormCode="App.Hr.MyPerformanceReview"
icon={<FaChartLine className="h-5 w-5 text-indigo-500" />}
columns={[
status('Status', 'ReviewStatus'),
{ field: 'GoalScore' },
{ field: 'CompetencyScore' },
{ field: 'Feedback360Score' },
{ field: 'OverallScore' },
{ field: 'CalibratedScore' },
{ field: 'SelfSubmittedDate', date: true },
]}
/>
<Section
listFormCode="App.Hr.MyPerformanceGoal"
icon={<FaBullseye className="h-5 w-5 text-rose-500" />}
columns={[
{ field: 'Title', caption: 'GoalTitle' },
{ field: 'GoalType' },
{ field: 'TargetValue' },
{ field: 'ActualValue' },
{ field: 'Weight' },
{ field: 'AchievementRate' },
]}
/>
<Section
listFormCode="App.Hr.MyDevelopmentPlan"
icon={<FaSeedling className="h-5 w-5 text-green-500" />}
columns={[
{ field: 'Title', caption: 'ActionTitle' },
{ field: 'ActionType' },
{ field: 'StartDate', date: true },
{ field: 'DueDate', date: true },
status('Status'),
{ field: 'Progress' },
]}
/>
<Section
listFormCode="App.Hr.My360Feedback"
icon={<FaComments className="h-5 w-5 text-amber-500" />}
columns={[
{ field: 'Relation' },
status('Status'),
{ field: 'Score', caption: 'FeedbackScoreValue' },
{ field: 'SubmittedDate', date: true },
]}
/>
</div>
)
export default MyPerformance

View file

@ -0,0 +1,46 @@
import { useLocalization } from '@/utils/hooks/useLocalization'
import { FaClock } from 'react-icons/fa'
import { ProfileCard, ReadOnlyTable, StatusBadge } from './ProfileSections'
import { useMyRows } from './profileData'
const TIMESHEET = 'App.Hr.MyTimesheet'
/** Puantajım: aylık puantaj salt okunur; düzeltme İK'nın Puantaj ekranındadır. */
const MyTimesheet = () => {
const { translate } = useLocalization()
const timesheet = useMyRows(TIMESHEET)
if (!timesheet.allowed) return null
const rows = [...timesheet.rows].sort(
(a, b) =>
Number(b.PeriodYear) - Number(a.PeriodYear) || Number(b.PeriodMonth) - Number(a.PeriodMonth),
)
return (
<ProfileCard
title={translate(`::${TIMESHEET}`)}
icon={<FaClock className="h-5 w-5 text-indigo-500" />}
>
<ReadOnlyTable
loading={timesheet.loading}
rows={rows}
columns={[
{ field: 'PeriodYear' },
{ field: 'PeriodMonth' },
{ field: 'PeriodStatus', render: (row) => <StatusBadge value={row.PeriodStatus} /> },
{ field: 'WorkDays' },
{ field: 'NetHours' },
{ field: 'LateMinutes' },
{ field: 'EarlyLeaveMinutes' },
{ field: 'OvertimeHours' },
{ field: 'LeaveDays' },
{ field: 'SickDays' },
{ field: 'HolidayDays' },
]}
/>
</ProfileCard>
)
}
export default MyTimesheet

View file

@ -0,0 +1,114 @@
import Table from '@/components/ui/Table'
import TBody from '@/components/ui/Table/TBody'
import Td from '@/components/ui/Table/Td'
import Th from '@/components/ui/Table/Th'
import THead from '@/components/ui/Table/THead'
import Tr from '@/components/ui/Table/Tr'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { formatLocaleValue } from '@/utils/localeFormat'
import { formatDay, ProfileRow } from './profileData'
import { ReactNode } from 'react'
import { Link } from 'react-router-dom'
/** Intranet panosundaki widget'larla aynı çerçeve: başlık, isteğe bağlı aksiyon, içerik. */
export const ProfileCard = ({
title,
icon,
action,
children,
}: {
title: string
icon?: ReactNode
action?: ReactNode
children: ReactNode
}) => (
<div className="overflow-hidden rounded-xl border border-gray-200/50 bg-white shadow-lg dark:border-gray-700/50 dark:bg-gray-800">
<div className="flex items-center justify-between gap-2 border-b border-gray-200 p-4 dark:border-gray-700">
<div className="flex items-center gap-2">
{icon}
<h2 className="text-base font-semibold text-gray-900 dark:text-white">{title}</h2>
</div>
{action}
</div>
<div className="p-3">{children}</div>
</div>
)
export const ProfileLinkButton = ({ to, children }: { to: string; children: ReactNode }) => (
<Link
to={to}
className="rounded-md bg-teal-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-teal-700"
>
{children}
</Link>
)
export type ProfileColumn = {
field: string
/** `App.Listform.ListformField.*` altındaki başlık anahtarı; boşsa alan adı kullanılır. */
caption?: string
/** Tarih kolonu saat taşımaz; gün olarak gösterilir. */
date?: boolean
render?: (row: ProfileRow) => ReactNode
}
const formatCell = (row: ProfileRow, column: ProfileColumn) => {
if (column.render) return column.render(row)
const value = column.date ? formatDay(row[column.field]) : formatLocaleValue(row[column.field])
return value || '—'
}
/** Salt okunur satır tablosu; düzenleme, silme ve satır komutu yoktur. */
export const ReadOnlyTable = ({
rows,
columns,
loading,
}: {
rows: ProfileRow[]
columns: ProfileColumn[]
loading?: boolean
}) => {
const { translate } = useLocalization()
if (loading) return <div className="p-4 text-center text-sm text-gray-500">...</div>
if (rows.length === 0) {
return (
<div className="p-4 text-center text-sm text-gray-500 dark:text-gray-400">
{translate('::App.Platform.NoDataFound')}
</div>
)
}
return (
<Table compact>
<THead>
<Tr>
{columns.map((column) => (
<Th key={column.field}>
{translate(`::App.Listform.ListformField.${column.caption ?? column.field}`)}
</Th>
))}
</Tr>
</THead>
<TBody>
{rows.map((row, index) => (
<Tr key={String(row.Id ?? index)}>
{columns.map((column) => (
<Td key={column.field}>{formatCell(row, column)}</Td>
))}
</Tr>
))}
</TBody>
</Table>
)
}
export const StatusBadge = ({ value }: { value: unknown }) =>
value ? (
<span className="rounded bg-gray-100 px-1.5 py-0.5 text-xs text-gray-700 dark:bg-gray-700 dark:text-gray-200">
{String(value)}
</span>
) : (
<>—</>
)

View file

@ -0,0 +1,42 @@
import apiService from '@/services/api.service'
import { usePermission } from '@/utils/hooks/usePermission'
import { getActiveCulture, LOCALE_DATE_FORMAT, toTwoLetterCulture } from '@/utils/localeFormat'
import dayjs from 'dayjs'
import { useEffect, useState } from 'react'
export type ProfileRow = Record<string, unknown>
/**
* Persona ekranının ("My" ListForm) satırlarını okur. Satır süzmesi (oturumdaki kullanıcı) ve
* yetki ListForm'da kalır; profil yalnızca gösterir. Yetki yoksa istek atılmaz.
*/
export const useMyRows = (listFormCode: string) => {
const { checkPermission } = usePermission()
const allowed = checkPermission(listFormCode)
const [rows, setRows] = useState<ProfileRow[]>([])
const [loading, setLoading] = useState(allowed)
useEffect(() => {
if (!allowed) return
let active = true
apiService
.fetchData<{ data?: ProfileRow[] }>({
method: 'GET',
url: '/api/app/list-form-select/select',
params: { listFormCode, take: 500 },
})
.then((response) => active && setRows(response.data?.data ?? []))
.catch(() => active && setRows([]))
.finally(() => active && setLoading(false))
return () => {
active = false
}
}, [allowed, listFormCode])
return { allowed, rows, loading }
}
export const formatDay = (value: unknown) =>
value
? dayjs(String(value)).locale(toTwoLetterCulture(getActiveCulture())).format(LOCALE_DATE_FORMAT)
: ''

View file

@ -110,6 +110,13 @@ import {
SelectField, SelectField,
TextField, TextField,
} from './userFormFields' } from './userFormFields'
import {
contractTypes,
drivingLicenseClasses,
employmentTypes,
maritalStatuses,
militaryStatuses,
} from './userLookups'
import { useUserFieldLookup } from './useUserFieldLookup' import { useUserFieldLookup } from './useUserFieldLookup'
export interface ClaimTypeDto { export interface ClaimTypeDto {
@ -117,16 +124,6 @@ export interface ClaimTypeDto {
claimValue: string claimValue: string
} }
const militaryStatuses = ['Done', 'Exempt', 'Postponed', 'NotDone']
const employmentTypes = ['FullTime', 'PartTime']
const contractTypes = ['Indefinite', 'Definite']
const drivingLicenseClasses = ['M', 'A1', 'A2', 'A', 'B1', 'B', 'BE', 'C1', 'C1E', 'C', 'CE', 'D1', 'D1E', 'D', 'DE', 'F', 'G']
const maritalStatuses = [
['Bekar', 'Single'],
['Evli', 'Married'],
['Boşanmış', 'Divorced'],
['Dul', 'Widowed'],
] as const
// Uyruk ülke adıyla saklanır; ad dile göre değişebildiği için Türkiye telefon koduyla tanınır. // Uyruk ülke adıyla saklanır; ad dile göre değişebildiği için Türkiye telefon koduyla tanınır.
const TURKEY_PHONE_CODE = 90 const TURKEY_PHONE_CODE = 90

View file

@ -0,0 +1,14 @@
/**
* Kullanıcı kaydındaki sabit seçenekli alanların değerleri. Kullanıcı detayı (düzenleme) ve profilin
* Çalışan Bilgileri sekmesi (salt okunur) aynı değer → dil anahtarı eşlemesini kullanır.
*/
export const militaryStatuses = ['Done', 'Exempt', 'Postponed', 'NotDone']
export const employmentTypes = ['FullTime', 'PartTime']
export const contractTypes = ['Indefinite', 'Definite']
export const drivingLicenseClasses = ['M', 'A1', 'A2', 'A', 'B1', 'B', 'BE', 'C1', 'C1E', 'C', 'CE', 'D1', 'D1E', 'D', 'DE', 'F', 'G']
export const maritalStatuses = [
['Bekar', 'Single'],
['Evli', 'Married'],
['Boşanmış', 'Divorced'],
['Dul', 'Widowed'],
] as const