From 2d59f7116ad12330213af37b90d8c22f0d706e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:11:06 +0300 Subject: [PATCH] =?UTF-8?q?My=20Profile=20d=C3=BCzenlemesi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lowcode-reference.instructions.md | 15 +- .github/instructions/lowcode.instructions.md | 50 ++++- README.md | 31 ++- .../Identity/PlatformProfileAppService.cs | 75 ++++++- claude.md | 7 + .../data/App.Languages.LanguageText.json | 56 +++++ configs/seeds/data/App.Menus.Routes.json | 76 +++++++ configs/seeds/docs/modules/HrAttendance.md | 2 +- configs/seeds/docs/modules/HrLeave.md | 2 +- configs/seeds/docs/modules/HrPerformance.md | 2 +- ui/src/routes/route.constant.ts | 4 + ui/src/views/admin/profile/Profile.tsx | 61 ++++- .../profile/components/MyEmployeeInfo.tsx | 210 ++++++++++++++++++ .../admin/profile/components/MyLeave.tsx | 104 +++++++++ .../profile/components/MyPerformance.tsx | 90 ++++++++ .../admin/profile/components/MyTimesheet.tsx | 46 ++++ .../profile/components/ProfileSections.tsx | 114 ++++++++++ .../admin/profile/components/profileData.ts | 42 ++++ .../views/admin/user-management/Details.tsx | 17 +- .../admin/user-management/userLookups.ts | 14 ++ 20 files changed, 971 insertions(+), 47 deletions(-) create mode 100644 ui/src/views/admin/profile/components/MyEmployeeInfo.tsx create mode 100644 ui/src/views/admin/profile/components/MyLeave.tsx create mode 100644 ui/src/views/admin/profile/components/MyPerformance.tsx create mode 100644 ui/src/views/admin/profile/components/MyTimesheet.tsx create mode 100644 ui/src/views/admin/profile/components/ProfileSections.tsx create mode 100644 ui/src/views/admin/profile/components/profileData.ts create mode 100644 ui/src/views/admin/user-management/userLookups.ts diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md index 8f8c9180..da35fccf 100644 --- a/.github/instructions/lowcode-reference.instructions.md +++ b/.github/instructions/lowcode-reference.instructions.md @@ -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 › 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ğı. | -| **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. | | **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. | | **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). | @@ -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 › 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 › 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 › 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. | @@ -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ı. | 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/videoroom/room/:id`, `/admin/reports/*`, `/admin/listform/{wizard,wizardManager}`, `/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 › 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. | -| **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). | -| **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. | -| **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. | +| **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; ç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; 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. | | **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. | @@ -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`'ı | | "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 | +| "ç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` | | "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ı | diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index cb0aa8a6..5e9793ab 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -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 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. ### 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 | | 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ü 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` @@ -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 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` Ü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 | | --- | --- | -| `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** | | `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 | @@ -3277,8 +3311,8 @@ crud/{entity}.json (component'lerin/nesnenin kulland endpoint'lerinin dayandığı tablolar) ``` -Script dosyaları sabit kombinasyondan tahmin edilmez; kapsam kökündeki script klasörleri diskten -taranır. Script bulunamaması uyarı üretmez — her tablo seed ile yönetilmez (ABP tabloları EF +Script dosyaları sabit kombinasyondan tahmin edilmez; seed kökündeki script klasörleri (`host/` +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 **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 @@ -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 — kod tekildir: `App.Notifications.Notification`). Dört tablo da kiracı kapsamındadır (`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 -kopyalanır (`NotificationTenantDefaultsSeedContributor`, `README.md` §13.5.1). +`Adm_T_Notification`); kendi veritabanı olan kiracı tür ve kuralları seed dosyasından alır; host veritabanını paylaşan +kiracıya aynı dosyalardaki küme yeni kimliklerle kopyalanır (`NotificationTenantDefaultsSeedContributor`, +`README.md` §13.5.1). | `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 | | --- | --- | --- | | `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.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` | @@ -4476,6 +4511,7 @@ kullanılacağı** `lowcode-reference.instructions.md` §12'dedir. | "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` | | "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.*` | | "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}` | diff --git a/README.md b/README.md index da98b268..4ff55ba9 100644 --- a/README.md +++ b/README.md @@ -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 DB Migrate'teki `DbMigrateLogPanel` ile gösterilir. -`configs/seeds` tek klasördür; tenant'a özel bir seed kapsamı yoktur. Tek bir `PlatformDbContext` -olduğu için kendi veritabanı olan tenant ekran tanımlarını, menüleri, yetkileri ve wizard -tablolarını kendi veritabanından okur. Bu yüzden host ve kendi veritabanı olan her tenant aynı -dosyalarla kurulur. Host veritabanını paylaşan (bağlantı cümlesi olmayan) tenant'ta dosyalar +`configs/seeds` tek klasördür; tenant'a özel bir seed kapsamı yoktur. Kendi veritabanı olan +tenant'ta ekran tanımları, menüler ve yetki tanımları gibi kiracısız varlıklar host'ta kalır, +çünkü ABP'nin EF repository'si `IMultiTenant` olmayan varlığı her zaman host bağlantısıyla okur. +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 `SeedFileScope`'tadır: migrate kararı `DataSeedContext`'e yazar, dosya okuyan her seeder (`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ı | | `{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 klasörleri (`sql/object`, `postgres/execute` …) diskten taranır ve nesne adıyla eşleşen her +Script dosyaları sabit bir sağlayıcı/klasör kombinasyonundan **tahmin edilmez**: seed kökündeki +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. 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 | | --- | --- | -| **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.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. | @@ -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ı. | | 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ü. | -| 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 @@ -1309,8 +1313,13 @@ başlığındaki ⓘ simgesi ise ekranın kendi ayrıntılı açıklamasını g - `PlatformConsts.IsMultiTenant = true`. - 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 - veritabanına yönlendirilebilir; migration'lar `DatabaseMigrationEventHandlerBase` üzerinden - tenant oluşturulduğunda otomatik uygulanır. + veritabanına yönlendirilebilir. Bağlantı kaydedildiğinde kiracı adıyla bir `DataSource` açılı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 `BranchUsers` ile tutulur, lisans limiti girişte kontrol edilir. - **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. | | 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. | -| 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. | | 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. | diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs index 2b5b4ce9..2e3a0220 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -6,12 +7,16 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Sozsoft.Platform.BlobStoring; +using Sozsoft.Platform.Entities; using Sozsoft.Platform.Extensions; using Volo.Abp; using Volo.Abp.Account; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; using Volo.Abp.Users; +using static Sozsoft.Platform.PlatformConsts.AbpIdentity; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace Sozsoft.Platform.Identity; @@ -22,27 +27,56 @@ namespace Sozsoft.Platform.Identity; [Route("/api/account/my-profile")] public class PlatformProfileAppService : ProfileAppService, IProfileAppService { + /// + /// 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. + /// + 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; } private readonly IConfiguration _configuration; + private readonly IRepository _departmentRepository; + private readonly IRepository _jobPositionRepository; + private readonly IRepository _branchRepository; + private readonly IRepository _branchUsersRepository; public PlatformProfileAppService( IdentityUserManager userManager, IOptions identityOptions, BlobManager blobCdnManager, - IConfiguration configuration + IConfiguration configuration, + IRepository departmentRepository, + IRepository jobPositionRepository, + IRepository branchRepository, + IRepository branchUsersRepository ) : base(userManager, identityOptions) { _blobCdnManager = blobCdnManager; _configuration = configuration; + _departmentRepository = departmentRepository; + _jobPositionRepository = jobPositionRepository; + _branchRepository = branchRepository; + _branchUsersRepository = branchUsersRepository; } [RemoteService(false)] public override Task UpdateAsync(UpdateProfileDto input) => throw new NotImplementedException(); [HttpGet] - public override Task GetAsync() + public override async Task GetAsync() { - return base.GetAsync(); + var user = await UserManager.GetByIdAsync(CurrentUser.GetId()); + return await MapToProfileAsync(user); } [HttpPost] @@ -85,7 +119,40 @@ public class PlatformProfileAppService : ProfileAppService, IProfileAppService await CurrentUnitOfWork.SaveChangesAsync(); } - return ObjectMapper.Map(user); + return await MapToProfileAsync(user); + } + + /// + /// Departman, pozisyon ve şube ad olarak eklenir: lookup'ları kullanıcı ekranının + /// (AbpIdentity.Users) yetkisiyle okunur, profil ise yetki istemez. + /// + private async Task MapToProfileAsync(IdentityUser user) + { + var profile = ObjectMapper.Map(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; } } diff --git a/claude.md b/claude.md index 3ce39a5f..3cba6971 100644 --- a/claude.md +++ b/claude.md @@ -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` 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. +- **Ç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`, `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** diff --git a/configs/seeds/data/App.Languages.LanguageText.json b/configs/seeds/data/App.Languages.LanguageText.json index f61a1cf9..abf6d75a 100644 --- a/configs/seeds/data/App.Languages.LanguageText.json +++ b/configs/seeds/data/App.Languages.LanguageText.json @@ -9790,6 +9790,62 @@ "Key": "Abp.Identity.Profile.General", "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", "CultureName": "en", diff --git a/configs/seeds/data/App.Menus.Routes.json b/configs/seeds/data/App.Menus.Routes.json index a88205b3..5544303a 100644 --- a/configs/seeds/data/App.Menus.Routes.json +++ b/configs/seeds/data/App.Menus.Routes.json @@ -460,6 +460,82 @@ "SeoPriority": 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", "Key": "admin.activityLog", diff --git a/configs/seeds/docs/modules/HrAttendance.md b/configs/seeds/docs/modules/HrAttendance.md index 370d198b..488153e4 100644 --- a/configs/seeds/docs/modules/HrAttendance.md +++ b/configs/seeds/docs/modules/HrAttendance.md @@ -18,7 +18,7 @@ tek ek vardır (kullanıcı onayıyla): kullanıcı detayındaki **Puantaj** sek | 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 | | PDKS (servis kullanıcısı) | Kart okutmalarını toplu gönderir | `POST /api/app/custom-endpoint/hr/attendance/` | diff --git a/configs/seeds/docs/modules/HrLeave.md b/configs/seeds/docs/modules/HrLeave.md index 000352ba..cd91d2c3 100644 --- a/configs/seeds/docs/modules/HrLeave.md +++ b/configs/seeds/docs/modules/HrLeave.md @@ -16,7 +16,7 @@ detayındaki **İzinler** sekmesi. | 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 | | İ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ı | diff --git a/configs/seeds/docs/modules/HrPerformance.md b/configs/seeds/docs/modules/HrPerformance.md index 407874b0..278c6dea 100644 --- a/configs/seeds/docs/modules/HrPerformance.md +++ b/configs/seeds/docs/modules/HrPerformance.md @@ -18,7 +18,7 @@ Component + SQL nesneleri); yeni entity, migration ya da React kodu yoktur. | 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 | -| Ç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 | | 360 değerlendiricisi | Kendisine atanan çalışanın yetkinliklerini puanlar ve gönderir | Intranet panosu: **360 Taleplerim** widget'ı → 360 Taleplerim | diff --git a/ui/src/routes/route.constant.ts b/ui/src/routes/route.constant.ts index 49376709..3562dd5e 100644 --- a/ui/src/routes/route.constant.ts +++ b/ui/src/routes/route.constant.ts @@ -45,6 +45,10 @@ general: '/admin/profile/general', password: '/admin/profile/password', 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', changeLog: '/admin/changeLog', diff --git a/ui/src/views/admin/profile/Profile.tsx b/ui/src/views/admin/profile/Profile.tsx index 2e87f588..f7f91aad 100644 --- a/ui/src/views/admin/profile/Profile.tsx +++ b/ui/src/views/admin/profile/Profile.tsx @@ -2,12 +2,25 @@ import AdaptableCard from '@/components/shared/AdaptableCard' import Container from '@/components/shared/Container' import Tabs from '@/components/ui/Tabs' import { useLocalization } from '@/utils/hooks/useLocalization' +import { usePermission } from '@/utils/hooks/usePermission' import { Suspense, lazy, type ReactNode } from 'react' 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' 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 NotificationSettings = lazy(() => import('./components/NotificationSettings')) @@ -16,12 +29,15 @@ const PROFILE_BASE_PATH = '/admin/profile' const Profile = () => { const { translate } = useLocalization() - const settingsMenu: Record< + const { checkPermission } = usePermission() + const allMenus: Record< string, { label: string path: string icon?: ReactNode + /** Sekme, bu "My" ekranlarından en az birinin yetkisi varsa görünür. */ + permissions?: string[] } > = { general: { @@ -29,6 +45,34 @@ const Profile = () => { path: 'general', icon: , }, + employeeInfo: { + label: translate('::Abp.Identity.Profile.EmployeeInfo'), + path: 'employee-info', + icon: , + }, + leave: { + label: translate('::Abp.Identity.Profile.Leave'), + path: 'leave', + icon: , + permissions: ['App.Hr.MyLeaveRequest', 'App.Hr.MyLeaveBalance'], + }, + timesheet: { + label: translate('::Abp.Identity.Profile.Timesheet'), + path: 'timesheet', + icon: , + permissions: ['App.Hr.MyTimesheet'], + }, + performance: { + label: translate('::Abp.Identity.Profile.Performance'), + path: 'performance', + icon: , + permissions: [ + 'App.Hr.MyPerformanceReview', + 'App.Hr.MyPerformanceGoal', + 'App.Hr.MyDevelopmentPlan', + 'App.Hr.My360Feedback', + ], + }, password: { label: translate('::Abp.Identity.Password'), path: 'password', @@ -40,6 +84,11 @@ const Profile = () => { icon: , }, } + const settingsMenu = Object.fromEntries( + Object.entries(allMenus).filter( + ([, menu]) => !menu.permissions || menu.permissions.some((p) => checkPermission(p)), + ), + ) const location = useLocation() const path = location.pathname.substring(location.pathname.lastIndexOf('/') + 1) @@ -61,9 +110,7 @@ const Profile = () => { {Object.keys(settingsMenu).map((key) => ( - {settingsMenu[key].icon && ( - {settingsMenu[key].icon} - )} + {settingsMenu[key].icon && {settingsMenu[key].icon}} {settingsMenu[key].label} ))} @@ -72,6 +119,10 @@ const Profile = () => {
}> {currentTab === 'general' && } + {currentTab === 'employeeInfo' && } + {currentTab === 'leave' && } + {currentTab === 'timesheet' && } + {currentTab === 'performance' && } {currentTab === 'password' && } {currentTab === 'notificationSettings' && } diff --git a/ui/src/views/admin/profile/components/MyEmployeeInfo.tsx b/ui/src/views/admin/profile/components/MyEmployeeInfo.tsx new file mode 100644 index 00000000..c5d844fd --- /dev/null +++ b/ui/src/views/admin/profile/components/MyEmployeeInfo.tsx @@ -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[] }) => ( +
+
{title}
+ {rows.map(([label, value, Icon]) => ( + + } + value={value ?? ''} + /> + + ))} +
+) + +/** + * 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() + + 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 ( + +
+ + + + + + + + + + + + + +
+
+ ) +} + +export default MyEmployeeInfo diff --git a/ui/src/views/admin/profile/components/MyLeave.tsx b/ui/src/views/admin/profile/components/MyLeave.tsx new file mode 100644 index 00000000..e4822943 --- /dev/null +++ b/ui/src/views/admin/profile/components/MyLeave.tsx @@ -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 ( +
+ {balance.allowed && ( + } + > +
+ {stats.map(([field, value, color]) => ( +
+
{formatDays(value)}
+
+ {translate(`::App.Listform.ListformField.${field}`)} +
+
+ ))} +
+ +
+ )} + + {requests.allowed && ( + } + action={ + checkPermission(`${REQUESTS}.Create`) && ( + + {translate('::App.DashboardWidget.Leave.CreateRequest')} + + ) + } + > + + String(b.StartDate).localeCompare(String(a.StartDate)), + )} + columns={[ + { field: 'RequestNo' }, + { field: 'StartDate', date: true }, + { field: 'EndDate', date: true }, + { field: 'Days' }, + { + field: 'ApprovalStatus', + render: (row) => , + }, + { field: 'ApprovalDate', date: true }, + { field: 'ApprovalDescription' }, + ]} + /> + + )} +
+ ) +} + +export default MyLeave diff --git a/ui/src/views/admin/profile/components/MyPerformance.tsx b/ui/src/views/admin/profile/components/MyPerformance.tsx new file mode 100644 index 00000000..1ff0fdaa --- /dev/null +++ b/ui/src/views/admin/profile/components/MyPerformance.tsx @@ -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 ( + + + + ) +} + +const status = (field: string, caption?: string): ProfileColumn => ({ + field, + caption, + render: (row) => , +}) + +/** + * 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 = () => ( +
+
} + columns={[ + status('Status', 'ReviewStatus'), + { field: 'GoalScore' }, + { field: 'CompetencyScore' }, + { field: 'Feedback360Score' }, + { field: 'OverallScore' }, + { field: 'CalibratedScore' }, + { field: 'SelfSubmittedDate', date: true }, + ]} + /> +
} + columns={[ + { field: 'Title', caption: 'GoalTitle' }, + { field: 'GoalType' }, + { field: 'TargetValue' }, + { field: 'ActualValue' }, + { field: 'Weight' }, + { field: 'AchievementRate' }, + ]} + /> +
} + columns={[ + { field: 'Title', caption: 'ActionTitle' }, + { field: 'ActionType' }, + { field: 'StartDate', date: true }, + { field: 'DueDate', date: true }, + status('Status'), + { field: 'Progress' }, + ]} + /> +
} + columns={[ + { field: 'Relation' }, + status('Status'), + { field: 'Score', caption: 'FeedbackScoreValue' }, + { field: 'SubmittedDate', date: true }, + ]} + /> +
+) + +export default MyPerformance diff --git a/ui/src/views/admin/profile/components/MyTimesheet.tsx b/ui/src/views/admin/profile/components/MyTimesheet.tsx new file mode 100644 index 00000000..b4ecdc18 --- /dev/null +++ b/ui/src/views/admin/profile/components/MyTimesheet.tsx @@ -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 ( + } + > + }, + { field: 'WorkDays' }, + { field: 'NetHours' }, + { field: 'LateMinutes' }, + { field: 'EarlyLeaveMinutes' }, + { field: 'OvertimeHours' }, + { field: 'LeaveDays' }, + { field: 'SickDays' }, + { field: 'HolidayDays' }, + ]} + /> + + ) +} + +export default MyTimesheet diff --git a/ui/src/views/admin/profile/components/ProfileSections.tsx b/ui/src/views/admin/profile/components/ProfileSections.tsx new file mode 100644 index 00000000..ca87f3a2 --- /dev/null +++ b/ui/src/views/admin/profile/components/ProfileSections.tsx @@ -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 +}) => ( +
+
+
+ {icon} +

{title}

+
+ {action} +
+
{children}
+
+) + +export const ProfileLinkButton = ({ to, children }: { to: string; children: ReactNode }) => ( + + {children} + +) + +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
...
+ + if (rows.length === 0) { + return ( +
+ {translate('::App.Platform.NoDataFound')} +
+ ) + } + + return ( + + + + {columns.map((column) => ( + + ))} + + + + {rows.map((row, index) => ( + + {columns.map((column) => ( + + ))} + + ))} + +
+ {translate(`::App.Listform.ListformField.${column.caption ?? column.field}`)} +
{formatCell(row, column)}
+ ) +} + +export const StatusBadge = ({ value }: { value: unknown }) => + value ? ( + + {String(value)} + + ) : ( + <>— + ) diff --git a/ui/src/views/admin/profile/components/profileData.ts b/ui/src/views/admin/profile/components/profileData.ts new file mode 100644 index 00000000..4d77f481 --- /dev/null +++ b/ui/src/views/admin/profile/components/profileData.ts @@ -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 + +/** + * 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([]) + 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) + : '' diff --git a/ui/src/views/admin/user-management/Details.tsx b/ui/src/views/admin/user-management/Details.tsx index d00a1e96..18018a07 100644 --- a/ui/src/views/admin/user-management/Details.tsx +++ b/ui/src/views/admin/user-management/Details.tsx @@ -110,6 +110,13 @@ import { SelectField, TextField, } from './userFormFields' +import { + contractTypes, + drivingLicenseClasses, + employmentTypes, + maritalStatuses, + militaryStatuses, +} from './userLookups' import { useUserFieldLookup } from './useUserFieldLookup' export interface ClaimTypeDto { @@ -117,16 +124,6 @@ export interface ClaimTypeDto { 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. const TURKEY_PHONE_CODE = 90 diff --git a/ui/src/views/admin/user-management/userLookups.ts b/ui/src/views/admin/user-management/userLookups.ts new file mode 100644 index 00000000..c733b9aa --- /dev/null +++ b/ui/src/views/admin/user-management/userLookups.ts @@ -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