Mcp problemleri giderildi

This commit is contained in:
Sedat ÖZTÜRK 2026-10-07 11:17:46 +03:00
parent c1454ab818
commit 53e26ce787
47 changed files with 6848 additions and 6150 deletions

View file

@ -23,7 +23,7 @@ Companion documents (this file wins on conflict):
| `dotnet.instructions.md` | Binding code standard for everything under `api/` | | `dotnet.instructions.md` | Binding code standard for everything under `api/` |
| `list.instructions.md` | Step-by-step procedure for adding a module / list through seeders | | `list.instructions.md` | Step-by-step procedure for adding a module / list through seeders |
| `../../README.md` | Technical map: what exists, where it lives, how it is wired | | `../../README.md` | Technical map: what exists, where it lives, how it is wired |
| `../../configs/seeds/docs/modules/{Module}.md` | **How a generated module works**: discovery answers, approved recipe, screens, formulas, status rules, permissions, FAQ. Read it first when asked how a module/component behaves; write or update it with every module/component change (`lowcode.instructions.md` §0.13). Index: `README.md` §10.5 | | `../../configs/seeds/docs/modules/{Menu}.md` | **How a generated module works**: discovery answers, approved recipe, screens, formulas, status rules, permissions, FAQ. Read it first when asked how a module/component behaves; write or update it with every module/component change (`lowcode.instructions.md` §0.13). Index: `README.md` §10.5 |
| `../../CLAUDE.md` | Operating rules and standing defaults for AI agents in this repo | | `../../CLAUDE.md` | Operating rules and standing defaults for AI agents in this repo |
--- ---
@ -629,7 +629,7 @@ A proposal is complete only if all are present:
4. Menu-route-component mapping included. 4. Menu-route-component mapping included.
5. Validation checklist included. 5. Validation checklist included.
6. Risk/rollback notes included. 6. Risk/rollback notes included.
7. Once produced, the module document `configs/seeds/{scope}/docs/modules/{Module}.md` is completed (analysis, 7. Once produced, the module document `configs/seeds/{scope}/docs/modules/{Menu}.md` is completed (analysis,
decisions, behaviour) and indexed in `README.md` §10.5. decisions, behaviour) and indexed in `README.md` §10.5.
--- ---

View file

@ -1311,7 +1311,7 @@ ekranları ABP tablolarıdır, Licence/Dev Request host tablolarını okur.
| Ekran | Kod / route | Tablo | Komponent üretirken ne işe yarar | | Ekran | Kod / route | Tablo | Komponent üretirken ne işe yarar |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| **Definitions › Sector** | `App.Definitions.Sector` | `Adm_T_Sector` | Sektör lookup’ı. Veri `data/App.Definitions.Sector.json`'dan seed edilir. | | **Definitions › Sector** | `App.Definitions.Sector` | `Adm_T_Sector` | Sektör lookup’ı. Veri `data/App.Definitions.Sector.json`'dan seed edilir. |
| **Definitions › Contacts** | `App.Definitions.Contact` (menüsüz sekmeler `.ContactSales`, `.ContactPurchasing`, `.ContactFinancial`, `.ContactAccounting`, `.ContactNotes`, `.ContactPerson`, `.ContactBankAccount`) | `Adm_T_Contact` · `Adm_T_ContactPerson` · `Adm_T_ContactBankAccount` | Satış ve satınalmanın ortak müşteri/tedarikçi kartı; belge ekranlarında cari lookup'ı buradan alınır (`IsActive = 1`, `TenantId = '@TENANTID'`). Wizard ekranıdır, ayrıntı `configs/seeds/docs/modules/AdministrationDefinitions.md`. Seed `data/App.Definitions.Contact.json`. | | **Definitions › Contacts** | `App.Definitions.Contact` (menüsüz sekmeler `.ContactSales`, `.ContactPurchasing`, `.ContactFinancial`, `.ContactAccounting`, `.ContactNotes`, `.ContactPerson`, `.ContactBankAccount`) | `Adm_T_Contact` · `Adm_T_ContactPerson` · `Adm_T_ContactBankAccount` | Satış ve satınalmanın ortak müşteri/tedarikçi kartı; belge ekranlarında cari lookup'ı buradan alınır (`IsActive = 1`, `TenantId = '@TENANTID'`). Wizard ekranıdır, ayrıntı `configs/seeds/docs/modules/App.Administration.md` › Tanımlamalar. Seed `data/App.Definitions.Contact.json`. |
| **Definitions › Contact Tags** | `App.Definitions.ContactTag` | `Adm_T_ContactTag` | Kontak sınıflandırma etiketleri; Kontaklar'ın `Tags` alanı (`dxTagBox`) buradan seçer. Seed `data/App.Definitions.ContactTag.json`. | | **Definitions › Contact Tags** | `App.Definitions.ContactTag` | `Adm_T_ContactTag` | Kontak sınıflandırma etiketleri; Kontaklar'ın `Tags` alanı (`dxTagBox`) buradan seçer. Seed `data/App.Definitions.ContactTag.json`. |
| **Definitions › Sequence** | `App.Definitions.Sequence` | `Adm_T_Sequence` | Belge numarası üretimi (`lowcode` §9.9); alan varsayılanında `CustomValueType: 5` ile bağlanır. Entity `IMultiTenant`'tır; ekran `IsTenant = true`, yetkiler `MultiTenancySide = Both` — her kiracı kendi numaratörlerini tutar. Seed `data/App.Definitions.Sequence.json`. | | **Definitions › Sequence** | `App.Definitions.Sequence` | `Adm_T_Sequence` | Belge numarası üretimi (`lowcode` §9.9); alan varsayılanında `CustomValueType: 5` ile bağlanır. Entity `IMultiTenant`'tır; ekran `IsTenant = true`, yetkiler `MultiTenancySide = Both` — her kiracı kendi numaratörlerini tutar. Seed `data/App.Definitions.Sequence.json`. |
| **Definitions › UoM Category** | `App.Definitions.UomCategory` (alt form `.Uom`) | `Adm_T_UomCategory` · `Adm_T_Uom` | Ölçü birimi, `Ratio`/`Rounding` — miktar alanı olan ekranlarda birim lookup'ı ve çevrim. Entity'ler `IMultiTenant`'tır; ekranlar `IsTenant = true`, yetkiler `MultiTenancySide = Both`. Seed `data/App.Definitions.{UomCategory,Uom}.json`. | | **Definitions › UoM Category** | `App.Definitions.UomCategory` (alt form `.Uom`) | `Adm_T_UomCategory` · `Adm_T_Uom` | Ölçü birimi, `Ratio`/`Rounding` — miktar alanı olan ekranlarda birim lookup'ı ve çevrim. Entity'ler `IMultiTenant`'tır; ekranlar `IsTenant = true`, yetkiler `MultiTenancySide = Both`. Seed `data/App.Definitions.{UomCategory,Uom}.json`. |
@ -1412,9 +1412,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; ç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). | | **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/App.Hr.md` › Performans Yönetimi. 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. | | **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/App.Hr.md` › İzin Yönetimi. 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. | | **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) `App.Hr.md` › Puantaj ve Devam Takibi §7.1'dedir. Ayarlar `App.HrSettings.Attendance.*`. Çalışma şekli: `configs/seeds/docs/modules/App.Hr.md` › Puantaj ve Devam Takibi. 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. |
@ -1533,9 +1533,9 @@ alanları da doldurur (`lowcode` §0.9).
| "izin türü, masraf türü, yan hak türü, vardiya, resmi tatil / çalışma takvimi" | İK › Definitions · İK › Work Calendar · İK › Shift | Var olan `Hr_T_WorkCalendar` / `Hr_T_LeaveType` / `Hr_T_ExpenseType` / `Hr_T_BenefitType` / `Hr_T_Shift` tablosuna lookup bağla; yenisini üretme | | "izin türü, masraf türü, yan hak türü, vardiya, resmi tatil / çalışma takvimi" | İK › Definitions · İK › Work Calendar · İK › Shift | Var olan `Hr_T_WorkCalendar` / `Hr_T_LeaveType` / `Hr_T_ExpenseType` / `Hr_T_BenefitType` / `Hr_T_Shift` tablosuna lookup bağla; yenisini üretme |
| "eğitim / katılımcı, araç / şoför / araç masrafı / araç çıkış-giriş, olay günlüğü, disiplin / tutanak / savunma / ihtar" | İK › Trainings · İK › Vehicles · İK › Event Log · İK › Disciplinary Case | Var olan `Hr_T_Training*` / `Hr_T_Vehicle*` / `Hr_T_Driver*` / `Hr_T_EventLog` / `Hr_T_Disciplinary*` ekranını genişlet ya da lookup bağla; paralel tablo üretme | | "eğitim / katılımcı, araç / şoför / araç masrafı / araç çıkış-giriş, olay günlüğü, disiplin / tutanak / savunma / ihtar" | İK › Trainings · İK › Vehicles · İK › Event Log · İK › Disciplinary Case | Var olan `Hr_T_Training*` / `Hr_T_Vehicle*` / `Hr_T_Driver*` / `Hr_T_EventLog` / `Hr_T_Disciplinary*` ekranını genişlet ya da lookup bağla; paralel tablo üretme |
| "işe alım, kadro talebi, ilan, aday / CV, mülakat, teklif" | İK › Recruitment | Var olan `Hr_T_StaffRequest` / `Hr_T_JobPosting` / `Hr_T_Candidate` / `Hr_T_JobApplication` / `Hr_T_Interview` / `Hr_T_JobOffer` ekranını genişlet; personel kullanıcısını BT elle açar, aday kaydına `UserId` ile bağlanır | | "işe alım, kadro talebi, ilan, aday / CV, mülakat, teklif" | İK › Recruitment | Var olan `Hr_T_StaffRequest` / `Hr_T_JobPosting` / `Hr_T_Candidate` / `Hr_T_JobApplication` / `Hr_T_Interview` / `Hr_T_JobOffer` ekranını genişlet; personel kullanıcısını BT elle açar, aday kaydına `UserId` ile bağlanır |
| "performans değerlendirme, hedef / KPI / OKR, yetkinlik değerlendirmesi, 360, kalibrasyon, gelişim planı" | İK › Performance Management | Var olan `Hr_T_Performance*` / `Hr_T_DevelopmentPlan` ekranını genişlet; yeni persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_PerformanceGuard` ile açılır (`configs/seeds/docs/modules/HrPerformance.md`) | | "performans değerlendirme, hedef / KPI / OKR, yetkinlik değerlendirmesi, 360, kalibrasyon, gelişim planı" | İK › Performance Management | Var olan `Hr_T_Performance*` / `Hr_T_DevelopmentPlan` ekranını genişlet; yeni persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_PerformanceGuard` ile açılır (`configs/seeds/docs/modules/App.Hr.md` › Performans Yönetimi) |
| "izin talebi, izin onayı, izin bakiyesi, yıllık izin hakkı, rapor/istirahat izni" | İK › Leave Management | Var olan `Hr_T_LeaveRequest` ekranını genişlet; persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_LeaveRequestGuard` ile açılır, bakiye `Hr_V_LeaveSummary`'den okunur (`configs/seeds/docs/modules/HrLeave.md`) | | "izin talebi, izin onayı, izin bakiyesi, yıllık izin hakkı, rapor/istirahat izni" | İK › Leave Management | Var olan `Hr_T_LeaveRequest` ekranını genişlet; persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_LeaveRequestGuard` ile açılır, bakiye `Hr_V_LeaveSummary`'den okunur (`configs/seeds/docs/modules/App.Hr.md` › İzin Yönetimi) |
| "puantaj, devam takibi, PDKS, giriş-çıkış, geç kalma, fazla mesai, devamsızlık" | İK › Timesheet & Attendance | Var olan `Hr_T_AttendanceLog` / `Hr_T_Timesheet` ekranlarını genişlet; hesap `Hr_S_TimesheetCalculate`'te tek yerdedir, kural değişikliği oraya yazılır (`configs/seeds/docs/modules/HrAttendance.md`) | | "puantaj, devam takibi, PDKS, giriş-çıkış, geç kalma, fazla mesai, devamsızlık" | İK › Timesheet & Attendance | Var olan `Hr_T_AttendanceLog` / `Hr_T_Timesheet` ekranlarını genişlet; hesap `Hr_S_TimesheetCalculate`'te tek yerdedir, kural değişikliği oraya yazılır (`configs/seeds/docs/modules/App.Hr.md` › Puantaj ve Devam Takibi) |
| "yapay zekâ cevaplasın" | Saas › Definitions › AiBot | Bot tanımı; `/admin/ai` bu tanımı kullanır | | "yapay zekâ cevaplasın" | Saas › Definitions › AiBot | Bot tanımı; `/admin/ai` bu tanımı kullanır |
### 12.7 Canlı referans uygulamalar ### 12.7 Canlı referans uygulamalar

View file

@ -54,7 +54,7 @@ configs/seeds/
├── {sql|postgres}/host/object|execute/… ← yalnızca host veritabanında çalışan script (ör. HangFire'a dayanan view) ├── {sql|postgres}/host/object|execute/… ← yalnızca host veritabanında çalışan script (ör. HangFire'a dayanan view)
├── crud/{EntityName}.json ← tablonun REST uçları ├── crud/{EntityName}.json ← tablonun REST uçları
├── wizard/{WizardName}.json ← ekran + menü + yetki + dil anahtarları (List yolunda sunucu yazar, §4.1) ├── wizard/{WizardName}.json ← ekran + menü + yetki + dil anahtarları (List yolunda sunucu yazar, §4.1)
├── docs/modules/{Modül}.md ← modül dokümanı; iskelet + envanteri deploy yazar (§0.13) ├── docs/modules/{Menü}.md ← modül dokümanı; iskelet + envanteri deploy yazar (§0.13)
├── data/{ListFormCode}.json ← ekrandan girilen liste verisinin aynası (opsiyonel, §9.15) ├── data/{ListFormCode}.json ← ekrandan girilen liste verisinin aynası (opsiyonel, §9.15)
│ data/{ListFormCode}/{anahtar}.json ← aynı aynanın klasör modu (satır başına dosya) │ data/{ListFormCode}/{anahtar}.json ← aynı aynanın klasör modu (satır başına dosya)
│ Custom Component'ler klasör modunda yaşar: │ Custom Component'ler klasör modunda yaşar:
@ -75,7 +75,7 @@ kurulur; host veritabanını paylaşan tenant'ta dosyalar yeniden uygulanmaz (`S
| 5 | Alt ekranlar (varsa) | ek `wizard/…` dosyaları | Ana wizard'dan **önceki** `SeededAt` damgasını alır | | 5 | Alt ekranlar (varsa) | ek `wizard/…` dosyaları | Ana wizard'dan **önceki** `SeededAt` damgasını alır |
| 6 | Liste verisi (isteniyorsa) | `data/{ListFormCode}.json` | Ekran ve tablo hazır olmalı; `ListForm.SeedFilePath` bu yolu gösterir (§9.15) | | 6 | Liste verisi (isteniyorsa) | `data/{ListFormCode}.json` | Ekran ve tablo hazır olmalı; `ListForm.SeedFilePath` bu yolu gösterir (§9.15) |
| 7 | SEO alanları (public yol ürettiysen) | Yolu tanımlayan satırın kendisi: `data/App.Menus.Routes.json` ya da `data/App.DeveloperKit.CustomComponents/{Id}.json` | Yolu üreten dosya yazıldıktan sonra; ziyaretçiye açık hiçbir yol metasız kalmaz (§0.9) | | 7 | SEO alanları (public yol ürettiysen) | Yolu tanımlayan satırın kendisi: `data/App.Menus.Routes.json` ya da `data/App.DeveloperKit.CustomComponents/{Id}.json` | Yolu üreten dosya yazıldıktan sonra; ziyaretçiye açık hiçbir yol metasız kalmaz (§0.9) |
| 8 | Modül dokümanı | `docs/modules/{Modül}.md` (iskelet + envanteri deploy açar) + `README.md` §10.5 dizin satırı | Son adımdır; anlatım bölümleri doldurulur, MCP'de `save_module_doc` (§0.13) | | 8 | Modül dokümanı | `docs/modules/{Menü}.md` (iskelet + envanteri deploy açar) + `README.md` §10.5 dizin satırı | Son adımdır; anlatım bölümleri doldurulur, MCP'de `save_module_doc` (§0.13) |
**`Wizard.SeededAt` bir bağımlılık aracıdır.** Dosya adı tarih taşımaz (`{WizardName}.json`); **`Wizard.SeededAt` bir bağımlılık aracıdır.** Dosya adı tarih taşımaz (`{WizardName}.json`);
`WizardDataSeeder` `wizard/` klasörünü **`Wizard.SeededAt` damgasına göre sıralı** işler (eşitlikte `WizardDataSeeder` `wizard/` klasörünü **`Wizard.SeededAt` damgasına göre sıralı** işler (eşitlikte
@ -515,7 +515,7 @@ ister ve kiracı kullanıcısına hiçbiri açılmaz.
| `delete_wizard` | Wizard'ı ve yalnızca onun ürettiği kayıtları siler; `dropTables: true` ekranın tablosunu da düşürür | Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; `App.SqlQueryManager` ister (§4.5) | | `delete_wizard` | Wizard'ı ve yalnızca onun ürettiği kayıtları siler; `dropTables: true` ekranın tablosunu da düşürür | Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; `App.SqlQueryManager` ister (§4.5) |
| `start_dev_request` | `App.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `App.DevRequests.Update` ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11) | | `start_dev_request` | `App.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `App.DevRequests.Update` ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11) |
| `get_dev_request` | Talebi durumuna dokunmadan okur (aynı metin + analiz + resim içerikleri) | Analizden önce ya da yarım kalan talebe dönmek için; `App.DevRequests` ister | | `get_dev_request` | Talebi durumuna dokunmadan okur (aynı metin + analiz + resim içerikleri) | Analizden önce ya da yarım kalan talebe dönmek için; `App.DevRequests` ister |
| `save_dev_request_analysis` | `Backlog`/`Analysis` durumundaki talebin analizini `Analysis` kolonuna yazar (var olanı değiştirir), talebi `Analysis` durumuna alır; diğer durumları reddeder | Ek olarak `App.DevRequests.Update` ister. Standart §0.12; `Description`'a dokunmaz | | `save_dev_request_analysis` | `Backlog`/`Analysis` durumundaki talebin analizini `Analysis` kolonuna yazar (var olanı değiştirir), talebi `Analysis` durumuna alır; diğer durumları reddeder. `menuCode` dalının modül dokümanı bölümüne de yazar, dosya/bölüm yoksa açar (§0.13) | Ek olarak `App.DevRequests.Update` ve `App.Listforms.Wizard` ister. Standart §0.12; `Description`'a dokunmaz |
| `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler | Üretim sonuç notu için; `Description` ve `Analysis` `update_row` ile yazılmaz (§0.11) | | `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler | Üretim sonuç notu için; `Description` ve `Analysis` `update_row` ile yazılmaz (§0.11) |
| `add_dev_request_subtasks` | `SubTasks` listesine tamamlanmış satırlar ekler | Atanan = bağlanan kullanıcı, bitiş = bugün (sunucu yazar); `SubTasks`'ı `update_row` ile yazma (§0.11) | | `add_dev_request_subtasks` | `SubTasks` listesine tamamlanmış satırlar ekler | Atanan = bağlanan kullanıcı, bitiş = bugün (sunucu yazar); `SubTasks`'ı `update_row` ile yazma (§0.11) |
| `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. `passed` yalnızca engelleyici bir kontrol kalırsa `false` | | `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. `passed` yalnızca engelleyici bir kontrol kalırsa `false` |
@ -673,7 +673,7 @@ bileşen, seed dosyası yazılmaz; yalnızca okuma araçları ve `save_dev_reque
| 1. Kuyruğu oku (yalnızca toplu) | `query_list` — `ListFormCode: App.DevRequests`, filtre `["Status","=","Backlog"]`, sıralama `SortOrder` | — | | 1. Kuyruğu oku (yalnızca toplu) | `query_list` — `ListFormCode: App.DevRequests`, filtre `["Status","=","Backlog"]`, sıralama `SortOrder` | — |
| 2. Talebi oku | `get_dev_request` — metin + görseller | — | | 2. Talebi oku | `get_dev_request` — metin + görseller | — |
| 3. Keşif | `list_wizards` (menü kökleri, modüller, izin grupları), `list_lists` / `describe_list` / `search_schema` (var olan ekran, tablo, lookup kaynağı), `list_components`, `get_dev_guide` (§0.6, §0.7, §0.8, §1) | — | | 3. Keşif | `list_wizards` (menü kökleri, modüller, izin grupları), `list_lists` / `describe_list` / `search_schema` (var olan ekran, tablo, lookup kaynağı), `list_components`, `get_dev_guide` (§0.6, §0.7, §0.8, §1) | — |
| 4. Yaz | `save_dev_request_analysis` — analizin tamamı, HTML (Timesheets satırını sunucu yazar, §0.11) | Analysis | | 4. Yaz | `save_dev_request_analysis` — analizin tamamı, HTML + `menuCode` (menü yerindeki dalın ya da ekranın kodu). Analiz o dalın modül dokümanı bölümüne ("2. Analiz ve kararlar", §0.13) de yazılır; kök menünün ilk talebiyse dosya ve bölüm burada açılır (Timesheets satırını sunucu yazar, §0.11) | Analysis |
**Durum kuralı sunucudadır:** araç yalnızca `Backlog` ya da `Analysis` durumundaki talebi kabul eder; **Durum kuralı sunucudadır:** araç yalnızca `Backlog` ya da `Analysis` durumundaki talebi kabul eder;
`Analysis`'teki talebe yeniden çağrılırsa analiz değiştirilir. `Ready to Start` ve sonrası `Analysis`'teki talebe yeniden çağrılırsa analiz değiştirilir. `Ready to Start` ve sonrası
@ -820,7 +820,7 @@ varsayılan olarak önerilir:
- **Ekran:** menüsüz wizard (`CreateMenu: false`), kodu `App.Hr.My{Ekran}` / `App.Hr.Team{Ekran}`, - **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 İ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. 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. Kalıp: `configs/seeds/docs/modules/App.Hr.md` › Performans Yönetimi §3.2–§3.3.
- **Erişim:** Intranet panosunda `Sas_T_IntranetWidget` kaydı (`SourceType: 1`, custom component; - **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. `PermissionName` = persona ekranının okuma yetkisi) özet gösterir ve listeye bağlanır.
- **Profil:** `/admin/profile` sekmeleri sabittir — Çalışan Bilgileri, İzinlerim - **Profil:** `/admin/profile` sekmeleri sabittir — Çalışan Bilgileri, İzinlerim
@ -841,42 +841,58 @@ varsayılan olarak önerilir:
bir alanın profilde görünmesi isteniyorsa liste ve `MyEmployeeInfo.tsx` birlikte değişir; sağlık, 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. ü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/{Menü}.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
puan nereden geliyor" sorusu kod ve seed taranmadan bu dosyadan cevaplanır (MCP'de `get_module_doc`). puan nereden geliyor" sorusu kod ve seed taranmadan bu dosyadan cevaplanır (MCP'de `get_module_doc`).
Doküman **wizard'ın parçasıdır**: Wizard ekranından ya da MCP `deploy_wizard` ile üretilen her Doküman **wizard'ın parçasıdır**: Wizard ekranından ya da MCP `deploy_wizard` ile üretilen her
modülde sunucu onu kendisi açar ve güncel tutar; anlatım bölümlerini üreten kişi/AI doldurur. modülde sunucu onu kendisi açar ve güncel tutar; anlatım bölümlerini üreten kişi/AI doldurur.
Wizard dışı (entity, özel React) modüller dokümanı elle yazar. Wizard dışı (entity, özel React) modüller bölümünü elle yazar.
- **Yer:** seed klasörü — `configs/seeds/docs/modules/{Modül}.md`. Sunucu yalnızca seed köküne - **Yer ve ad:** seed klasörü — `configs/seeds/docs/modules/{Menü}.md`, **kök menü başına tek
yazabildiği için depo kökünde değildir. Wizard export zip'i dokümanı taşır, import yazar (`docs/modules/*.md`). dosya**. Ad kök menünün kodudur (`App.Administration.md`, `App.Hr.md`,
- **Hangi wizard hangi dokümana** (`ModuleDocCatalog`; wizard sözleşmesine alan eklenmez): `App.Prc.md`). Ad sabit bir listeden değil menü ve dil seed dosyalarından okunur
1. Dokümanın ilk satırındaki beyan ekranı sahipleniyorsa o doküman: (`ModuleDocCatalog.DocNameOfRoot`); yeni bir kök menü ilk deploy'unda kendi dosyasını açar.
`<!-- module-doc: claims=App.Hr.CandidateSource, App.Hr.RejectionReason -->` (başka dalda Sunucu yalnızca seed köküne yazabildiği için depo kökünde değildir. Wizard export zip'i dokümanı
duran tanım ekranları için). taşır, import yazar (`docs/modules/*.md`).
2. Menüsüz alt form, onu `SubForms` içinde taşıyan ekranın dokümanına gider. - **Bölüm:** kök menünün altındaki her dal dosyada bir bölümdür; bölüm bir beyan satırıyla başlar,
3. Üst menü zincirinde kökün altında bir dal varsa dal (`App.Hr › App.Hr.Performance › …` → başlığı `## `, alt bölümleri `### `'tür:
`App.Hr.Performance`), yoksa ekranın kendi kodu. Ad koddan türer: `App.` atılır, noktalar ```markdown
silinir → `HrPerformance.md`. <!-- module-doc:section App.Hr.Recruitment claims=App.Hr.CandidateSource, App.Hr.RejectionReason -->
## İşe Alım (`App.Hr.Recruitment`)
### 1. Amaç ve kapsam
```
Bölümler menü sırasıyla dizilir; menüde karşılığı olmayan elle yazılmış bölüm (ör.
`App.Hr.Personnel`) yerini korur.
- **Hangi wizard hangi bölüme** (`ModuleDocCatalog`; wizard sözleşmesine alan eklenmez):
1. Bir bölümün beyanındaki `claims=` ekranı sahipleniyorsa o bölüm (başka dalda duran tanım
ekranları için).
2. Menüsüz alt form, onu `SubForms` içinde taşıyan ekranın bölümüne gider.
3. Menü ağacında kökün altındaki ilk seviye (`App.Hr › App.Hr.Performance › …` →
`App.Hr.Performance`); ekran doğrudan kökün altındaysa ekranın kendi kodu. Kök ve dal
`data/App.Menus.Menu.json`'dan bulunur.
- **Otomatik kısım:** deploy (`ListFormWizardAppService.Create`), silme, tasarımcı kaydı - **Otomatik kısım:** deploy (`ListFormWizardAppService.Create`), silme, tasarımcı kaydı
(`WizardSeedSynchronizer`) ve import kapanışı `ModuleDocManager.RefreshAllAsync` çağırır: (`WizardSeedSynchronizer`) ve import kapanışı `ModuleDocManager.RefreshAllAsync` çağırır:
doküman yoksa iskelet açılır (bölümler `_Henüz yazılmadı._ <!-- module-doc:todo -->`), dosya yoksa kök başlığı açılır, wizard'ı olan dalın bölümü yoksa iskeleti menü sırasındaki yerine
eklenir (alt bölümler `_Henüz yazılmadı._ <!-- module-doc:todo -->`),
`<!-- module-doc:auto:begin -->` … `<!-- module-doc:auto:end -->` arasındaki **"Ek — Ekran `<!-- module-doc:auto:begin -->` … `<!-- module-doc:auto:end -->` arasındaki **"Ek — Ekran
envanteri"** wizard dosyalarından yeniden yazılır (menü, kaynak, görünümler, alt formlar, envanteri"** bölümlere göre gruplanarak wizard dosyalarından yeniden yazılır (menü, kaynak,
widget'lar, komutlar/prosedürler, varsayılanlar, alan tablosu). Envanter elle düzenlenmez. görünümler, alt formlar, widget'lar, komutlar/prosedürler, varsayılanlar, alan tablosu). Envanter
Wizard'ı kalmayan ve hiç yazılmamış iskelet silinir. Wizard'a yeni bir kayıt yolu eklenirse elle düzenlenmez. Wizard'ı kalmayan ve hiç yazılmamış bölüm, bölümü kalmayan dosya silinir.
`RefreshAllAsync` çağrısı da eklenir. Wizard'a yeni bir kayıt yolu eklenirse `RefreshAllAsync` çağrısı da eklenir.
- **Elle/AI ile yazılan kısım:** aşağıdaki bölümler; doldurulan bölümden `module-doc:todo` - **Elle/AI ile yazılan kısım:** aşağıdaki alt bölümler; doldurulan alt bölümden `module-doc:todo`
işareti kaldırılır, boş kalacak bölüme "Yok." yazılır. MCP'de `save_module_doc` (dosyanın işareti kaldırılır, boş kalacak alt bölüme "Yok." yazılır. MCP'de `get_module_doc` /
tamamı; envanter gönderilmez, sunucu ekler; beyan satırı korunur). Claude Code depoda dosyayı `save_module_doc` ekran ya da dal koduyla çağrılır: yalnızca o bölüm okunur ve yazılır (beyan
doğrudan yazar. satırı gönderilmezse mevcut beyan korunur; envanter gönderilmez, sunucu ekler). Dosya adıyla
çağrılırsa dosyanın tamamıdır. Menüde henüz olmayan modülün kodu (analizdeki `App.Inventory.Stock`)
kök ve dalı koddan alır: okuma iskelet döner, yazma `App.Inventory.md` dosyasını açar. Claude Code
depoda dosyayı doğrudan yazar.
- **Zorunluluk:** MCP döngüsünde talep `update_row` ile `Testing`'e alınırken talebin wizard'larının - **Zorunluluk:** MCP döngüsünde talep `update_row` ile `Testing`'e alınırken talebin wizard'larının
dokümanında `module-doc:todo` kalmışsa ya da doküman yoksa istek reddedilir **bölümünde** `module-doc:todo` kalmışsa ya da bölüm yoksa istek reddedilir; aynı dosyadaki
(`IModuleDocAppService.GetIncompleteForDevRequestAsync`). başka dalların eksikleri talebi engellemez (`IModuleDocAppService.GetIncompleteForDevRequestAsync`).
- **Dizin:** `README.md` §10.5'e bir satır eklenir; README'de modül en fazla ilgili bölümde tek - **Dizin:** yeni kök menü `README.md` §10.5 tablosuna bir satır, yeni dal o satırın "Bölümler"
cümleyle anılıp dokümana bağlanır. hücresine bir ad ekler; README'de modül en fazla ilgili bölümde tek cümleyle anılıp dokümana bağlanır.
- **Bölümler** (`ModuleDocRenderer.Sections`): - **Alt bölümler** (`ModuleDocRenderer.Sections`, her dal bölümünde `### `):
1. **Amaç ve kapsam** — kim kullanır, hangi işi çözer. 1. **Amaç ve kapsam** — kim kullanır, hangi işi çözer.
2. **Analiz ve kararlar** — keşif turunun cevapları (soru → seçilen seçenek → gerekçe) ya da 2. **Analiz ve kararlar** — keşif turunun cevapları (soru → seçilen seçenek → gerekçe) ya da
MCP'de onaylanmış analiz, onaylanan tarif ve `(varsayılan)` kalan satırlar. MCP'de onaylanmış analiz, onaylanan tarif ve `(varsayılan)` kalan satırlar.
@ -2183,14 +2199,14 @@ Kurallar:
Karşılaştırma **sayısaldır** (`EvaluateCondition`: `>`, `>=`, `<`, `<=`, `=`, `!=`); bit/metin kolon Karşılaştırma **sayısaldır** (`EvaluateCondition`: `>`, `>=`, `<`, `<=`, `=`, `!=`); bit/metin kolon
okunamaz ve hata verir. Kayda göre adım atlamak için prosedür sayısal bir bayrak kolonu yazar ve okunamaz ve hata verir. Kayda göre adım atlamak için prosedür sayısal bir bayrak kolonu yazar ve
kolon ekranda gizli alan olur (örnek: İzin Yönetimi'nde üst pozisyonu olmayan çalışanın müdür kolon ekranda gizli alan olur (örnek: İzin Yönetimi'nde üst pozisyonu olmayan çalışanın müdür
adımı, `RequiresManagerApproval`, `HrLeave.md` §5.6). adımı, `RequiresManagerApproval`, `App.Hr.md` › İzin Yönetimi §5.6).
- `Approval` düğümü `NextOnApprove` / `NextOnReject`, `Start` ve `Inform` düğümleri - `Approval` düğümü `NextOnApprove` / `NextOnReject`, `Start` ve `Inform` düğümleri
`NextOnStart` alanını kullanır. `End` düğümünün çıkışı yoktur. `NextOnStart` alanını kullanır. `End` düğümünün çıkışı yoktur.
- **Kayda göre değişen onaycı:** `Approver` değeri `@Kolon` ise (ör. `@ManagerUserNames`) onaycı kaydın o - **Kayda göre değişen onaycı:** `Approver` değeri `@Kolon` ise (ör. `@ManagerUserNames`) onaycı kaydın o
kolonundan okunur (`ListFormWorkflowAppService.ResolveNodeApprover`); kolon `|` ile ayrılmış kullanıcı kolonundan okunur (`ListFormWorkflowAppService.ResolveNodeApprover`); kolon `|` ile ayrılmış kullanıcı
adı/e-posta taşır ve ekranın alanı olmalıdır (satır ListForm sorgusundan okunur; gizli alan yeter). Kolon adı/e-posta taşır ve ekranın alanı olmalıdır (satır ListForm sorgusundan okunur; gizli alan yeter). Kolon
boşsa akış hata verir — boş onaycı herkese onay yetkisi verirdi. Kolonu kayıt sonrası prosedür doldurur boşsa akış hata verir — boş onaycı herkese onay yetkisi verirdi. Kolonu kayıt sonrası prosedür doldurur
(örnek: İzin Yönetimi'nde departman müdürü, `HrLeave.md`). Tasarımcıda onaycı listesi kullanıcılarla (örnek: İzin Yönetimi'nde departman müdürü, `App.Hr.md` › İzin Yönetimi). Tasarımcıda onaycı listesi kullanıcılarla
birlikte `Alan: {Kolon}` seçeneklerini sunar. birlikte `Alan: {Kolon}` seçeneklerini sunar.
- **`Inform` düğümü e-posta gönderir** (MailQueue üzerinden, `Approver` alanındaki kullanıcı/rol - **`Inform` düğümü e-posta gönderir** (MailQueue üzerinden, `Approver` alanındaki kullanıcı/rol
adreslerine, önceki adımların notlarıyla birlikte). Uygulama içi bildirim ya da SMS isteniyorsa adreslerine, önceki adımların notlarıyla birlikte). Uygulama içi bildirim ya da SMS isteniyorsa
@ -2631,7 +2647,7 @@ Script `AsyncFunction` ile derlenir, aşağıdaki adlar parametre olarak enjekte
| `clear('A','B')` | Verilen alanları `null` yapar | | `clear('A','B')` | Verilen alanları `null` yapar |
| `copy('kolon','Alan')` | Lookup/GridBox'ta **seçili kayıttan** taşır; hedef verilmezse yolun son parçası hedef olur. Seçili kayıt, olay formdan gelse de (popup form, form sayfası) değişen alanın editöründen okunur; lookup sorgusu taşınacak kolonları döndürmelidir (ör. `SELECT Id AS [Key], …, DepartmentId, JobPositionId`) | | `copy('kolon','Alan')` | Lookup/GridBox'ta **seçili kayıttan** taşır; hedef verilmezse yolun son parçası hedef olur. Seçili kayıt, olay formdan gelse de (popup form, form sayfası) değişen alanın editöründen okunur; lookup sorgusu taşınacak kolonları döndürmelidir (ör. `SELECT Id AS [Key], …, DepartmentId, JobPositionId`) |
| `selected('Yol')` · `snum()` · `sstr()` | Seçili kaydın tamamı / sayı / metin hâli | | `selected('Yol')` · `snum()` · `sstr()` | Seçili kaydın tamamı / sayı / metin hâli |
| `readOnly('Alan', kosul)` | Formdaki **herhangi bir** editörü kilitler/açar (yalnızca script'in bağlı olduğu alanı değil). Grid popup'ında açılış çalışması (`isReady`) editörler her yeniden çizildiğinde tekrarlanır ve form verisi o anda güncel değildir; koşul başka bir alana bakıyorsa değer `editor.component.cellValue(satır, 'Alan')` ile okunur (örnek: `HrLeave.md` §5.1) | | `readOnly('Alan', kosul)` | Formdaki **herhangi bir** editörü kilitler/açar (yalnızca script'in bağlı olduğu alanı değil). Grid popup'ında açılış çalışması (`isReady`) editörler her yeniden çizildiğinde tekrarlanır ve form verisi o anda güncel değildir; koşul başka bir alana bakıyorsa değer `editor.component.cellValue(satır, 'Alan')` ile okunur (örnek: `App.Hr.md` › İzin Yönetimi §5.1) |
| `round(x, 2)` · `sum('A','B')` | Matematik | | `round(x, 2)` · `sum('A','B')` | Matematik |
| `days('Bas','Bit')` · `hours('Bas','Bit')` | Gün farkı (bitiş dahil) · saat farkı (negatifse +24) | | `days('Bas','Bit')` · `hours('Bas','Bit')` | Gün farkı (bitiş dahil) · saat farkı (negatifse +24) |
| `tpl('{Alan} - {value}')` | Şablon: `{Alan}`, `{value}`, `{selected.Yol}` | | `tpl('{Alan} - {value}')` | Şablon: `{Alan}`, `{value}`, `{selected.Yol}` |
@ -4820,7 +4836,7 @@ tedarikçi lookup'ı + iki kademeli onay).
veri aynası ve `InsertedRecords` içindeki kullanılmayan kayıtlar gider; tablo yalnızca veri aynası ve `InsertedRecords` içindeki kullanılmayan kayıtlar gider; tablo yalnızca
onayda seçilirse (MCP: `dropTables: true`) scripti ve crud dosyasıyla düşer (§4.5). onayda seçilirse (MCP: `dropTables: true`) scripti ve crud dosyasıyla düşer (§4.5).
Dosyayı diskten silmek kayıtları temizlemez. Dosyayı diskten silmek kayıtları temizlemez.
- [ ] Modül dokümanı (`configs/seeds/docs/modules/{Modül}.md`) yazıldı ya da güncellendi; analiz/kararlar, - [ ] Modül dokümanı (`configs/seeds/docs/modules/{Menü}.md`) yazıldı ya da güncellendi; analiz/kararlar,
ekranlar, hesaplar ve yetkiler üretilen artefaktların gerçek adlarıyla yazılı, README §10.5 ekranlar, hesaplar ve yetkiler üretilen artefaktların gerçek adlarıyla yazılı, README §10.5
dizininde satırı var (§0.13). dizininde satırı var (§0.13).

View file

@ -989,12 +989,12 @@ sebebi ve değerlendirme kriteri tanımları İK › Tanımlar (`App.Hr.Definiti
kullanıcı (personel) kaydını BT elle açar; İK aday kaydını `UserId` ile o kullanıcıya bağlar. Ayrıntı: kullanıcı (personel) kaydını BT elle açar; İK aday kaydını `UserId` ile o kullanıcıya bağlar. Ayrıntı:
`lowcode-reference.instructions.md` §12.4. **Performans Yönetimi** (`App.Hr.Performance`) wizard + `lowcode-reference.instructions.md` §12.4. **Performans Yönetimi** (`App.Hr.Performance`) wizard +
custom component ile üretilmiştir: İK menüsü şartları ve takibi taşır, çalışana ait listeler menüsüzdür ve custom component ile üretilmiştir: İK menüsü şartları ve takibi taşır, çalışana ait listeler menüsüzdür ve
Intranet panosundaki widget'lardan açılır; çalışma şekli [HrPerformance.md](configs/seeds/docs/modules/HrPerformance.md). Intranet panosundaki widget'lardan açılır; çalışma şekli [App.Hr › Performans Yönetimi](configs/seeds/docs/modules/App.Hr.md).
**İzin Yönetimi** (`App.Hr.Leave`) aynı kalıptadır (talep, Müdür → İK Grid Workflow onayı, İş Kanunu md.53 bakiyesi, **İzin Yönetimi** (`App.Hr.Leave`) aynı kalıptadır (talep, Müdür → İK Grid Workflow onayı, İş Kanunu md.53 bakiyesi,
rapor ↔ Sağlık Kartı eşlemesi, kullanıcı detayında İzinler sekmesi); çalışma şekli [HrLeave.md](configs/seeds/docs/modules/HrLeave.md). rapor ↔ Sağlık Kartı eşlemesi, kullanıcı detayında İzinler sekmesi); çalışma şekli [App.Hr › İzin Yönetimi](configs/seeds/docs/modules/App.Hr.md).
**Puantaj ve Devam Takibi** (`App.Hr.Attendance`) hareketlerden (PDKS ucu, Excel, elle) günlük ve aylık puantaj kurar; **Puantaj ve Devam Takibi** (`App.Hr.Attendance`) hareketlerden (PDKS ucu, Excel, elle) günlük ve aylık puantaj kurar;
izin, rapor, çalışma takvimi ve vardiyadan beslenir, kullanıcı detayında Puantaj sekmesi ve Intranet'te Puantajım widget'ı vardır; izin, rapor, çalışma takvimi ve vardiyadan beslenir, kullanıcı detayında Puantaj sekmesi ve Intranet'te Puantajım widget'ı vardır;
çalışma şekli [HrAttendance.md](configs/seeds/docs/modules/HrAttendance.md). çalışma şekli [App.Hr › Puantaj ve Devam Takibi](configs/seeds/docs/modules/App.Hr.md).
**Yetki kodları** `PlatformConsts.AppCodes` içinde merkezî olarak tanımlıdır ve **Yetki kodları** `PlatformConsts.AppCodes` içinde merkezî olarak tanımlıdır ve
`configs/seeds/data/Abp.Identity.Permissions.json` ile seed edilir (gruplar: `configs/seeds/data/Abp.Identity.Permissions.json` ile seed edilir (gruplar:
@ -1248,34 +1248,22 @@ rota ve menüleri.
### 10.5. Modül dokümanları (`configs/seeds/docs/modules/`) ### 10.5. Modül dokümanları (`configs/seeds/docs/modules/`)
Konfigürasyonla üretilen her modülün çalışma şekli, keşif analizi ve kararları kendi dosyasındadır; Konfigürasyonla üretilen modüllerin çalışma şekli, keşif analizi ve kararları **kök menü başına tek
"bu ekran nasıl çalışıyor?" sorusunun cevabı oradadır (MCP'de `get_module_doc`). Dokümanlar seed dosyadadır**; menünün altındaki her dal dosyada bir bölümdür (`module-doc:section`). "Bu ekran nasıl
klasöründedir: wizard deploy'u (ekran ya da MCP) dosyayı yoksa açar ve sonundaki "Ek — Ekran çalışıyor?" sorusunun cevabı oradadır (MCP'de `get_module_doc`; ekran ya da dal kodu verilirse
envanteri"ni her deploy, silme, tasarımcı kaydı ve import'ta wizard dosyalarından yeniden yazar; yalnızca o bölüm döner). Dosya adı kök menünün kodudur (`App.Hr` → `App.Hr.md`,
anlatım bölümlerini üreten doldurur (MCP'de `save_module_doc`). Wizard export'u dokümanı taşır. `App.Prc` → `App.Prc.md`); sabit bir liste yoktur, menü ağacından okunur. Wizard deploy'u
Yeni modül bu tabloya bir satır ekler (`lowcode.instructions.md` §0.13). Liste ekranlarının (ekran ya da MCP) yeni kök menünün dosyasını, yeni dalın bölümünü menü sırasındaki yerine açar ve
başlığındaki ⓘ simgesi ise ekranın kendi ayrıntılı açıklamasını gösterir (`DescTr`/`DescEn`, sondaki "Ek — Ekran envanteri"ni her deploy, silme, tasarımcı kaydı ve import'ta wizard dosyalarından
`lowcode.instructions.md` §4.4.2). yeniden yazar; anlatım bölümlerini üreten doldurur (MCP'de `save_module_doc`). Wizard export'u
dokümanı taşır (`lowcode.instructions.md` §0.13). Liste ekranlarının başlığındaki ⓘ simgesi ise
ekranın kendi ayrıntılı açıklamasını gösterir (`DescTr`/`DescEn`, `lowcode.instructions.md` §4.4.2).
| Modül | Menü | Doküman | | Doküman | Kök menü | Bölümler |
| --- | --- | --- | | --- | --- | --- |
| Performans Yönetimi | İK › Performans Yönetimi (`App.Hr.Performance`) | [HrPerformance.md](configs/seeds/docs/modules/HrPerformance.md) | | [App.Administration.md](configs/seeds/docs/modules/App.Administration.md) | Yönetim (`App.Administration`) | Tanımlamalar (Kontaklar: satış ve satınalmanın ortak müşteri/tedarikçi kartı ve sekmeleri, kontak etiketleri) |
| İşe Alım | İK › İşe Alım (`App.Hr.Recruitment`) | [HrRecruitment.md](configs/seeds/docs/modules/HrRecruitment.md) | | [App.Hr.md](configs/seeds/docs/modules/App.Hr.md) | İnsan Kaynakları (`App.Hr`) | Tanımlamalar (dizin + genel tanımlar), Organizasyon ve Yetkinlik, Personel Kaydı (Kimlik › Kullanıcılar › detay), İşe Alım, Çalışma Takvimi ve Vardiyalar, İzin Yönetimi, Puantaj ve Devam Takibi, Sağlık Kartları, Eğitimler, Performans Yönetimi, Disiplin Dosyaları, Araçlar, Ziyaretçiler, Olay Günlüğü, Intranet |
| İzin Yönetimi | İK › İzin Yönetimi (`App.Hr.Leave`) | [HrLeave.md](configs/seeds/docs/modules/HrLeave.md) | | [App.Prc.md](configs/seeds/docs/modules/App.Prc.md) | Satınalma (`App.Prc`) | Tanımlamalar (ödeme/teslimat koşulları, ödeme/teslim yöntemleri) |
| Puantaj ve Devam Takibi | İK › Puantaj ve Devam Takibi (`App.Hr.Attendance`) | [HrAttendance.md](configs/seeds/docs/modules/HrAttendance.md) |
| Eğitimler | İK › Eğitimler (`App.Hr.Trainings`) | [HrTrainings.md](configs/seeds/docs/modules/HrTrainings.md) |
| Araçlar | İK › Araçlar (`App.Hr.Vehicles`) | [HrVehicles.md](configs/seeds/docs/modules/HrVehicles.md) |
| Disiplin Dosyaları | İK › Disiplin Dosyaları (`App.Hr.DisciplinaryCase`) | [HrDisciplinaryCase.md](configs/seeds/docs/modules/HrDisciplinaryCase.md) |
| Tanımlamalar (dizin + genel tanımlar) | İK › Tanımlamalar (`App.Hr.Definitions`) | [HrDefinitions.md](configs/seeds/docs/modules/HrDefinitions.md) |
| Organizasyon ve Yetkinlik | İK › Departmanlar, Pozisyonlar, Yetenek Tipleri, Organizasyon Şeması | [HrOrganization.md](configs/seeds/docs/modules/HrOrganization.md) |
| Çalışma Takvimi ve Vardiyalar | İK › Çalışma Takvimi, Vardiyalar | [HrWorkCalendar.md](configs/seeds/docs/modules/HrWorkCalendar.md) |
| Sağlık Kartları | İK › Sağlık Kartları (`App.Hr.HealthCard`) | [HrHealthCard.md](configs/seeds/docs/modules/HrHealthCard.md) |
| Ziyaretçiler | İK › Ziyaretçiler (`App.Hr.Visitor`) | [HrVisitor.md](configs/seeds/docs/modules/HrVisitor.md) |
| Olay Günlüğü | İK › Olay Günlüğü (`App.Hr.EventLog`) | [HrEventLog.md](configs/seeds/docs/modules/HrEventLog.md) |
| Personel Kaydı (özlük, sözleşme, eğitim, aile, mali bilgiler) | Kimlik › Kullanıcılar › detay | [HrPersonnel.md](configs/seeds/docs/modules/HrPersonnel.md) |
| Intranet (pano, duyuru, anket, sosyal duvar, etkinlik, doküman, mesajlaşma) | İK › Intranet (`App.Hr.Intranet`) | [HrIntranet.md](configs/seeds/docs/modules/HrIntranet.md) |
| Satınalma Tanımlamaları (ödeme/teslimat koşulları, ödeme/teslim yöntemleri) | Satınalma › Tanımlamalar (`App.Prc.Definitions`) | [PrcDefinitions.md](configs/seeds/docs/modules/PrcDefinitions.md) |
| Kontaklar (satış ve satınalmanın ortak müşteri/tedarikçi kartı ve sekmeleri, kontak etiketleri) | Yönetim › Tanımlamalar (`App.Administration.Definitions`) | [AdministrationDefinitions.md](configs/seeds/docs/modules/AdministrationDefinitions.md) |
--- ---
@ -1572,13 +1560,13 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. Dosyaya kullanıcının etkin talebi `DevRequest` olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur ve `SeedSync*` bayrakları açılır — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. | | `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. Dosyaya kullanıcının etkin talebi `DevRequest` olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur ve `SeedSync*` bayrakları açılır — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. |
| `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). | | `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). |
| `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `App.DevRequests` döngüsünün adımı değildir — döngü `Testing`'de biter, kullanıcı elle test edip `Staging`'e alır (`lowcode.instructions.md` §0.11). | | `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `App.DevRequests` döngüsünün adımı değildir — döngü `Testing`'de biter, kullanıcı elle test edip `Staging`'e alır (`lowcode.instructions.md` §0.11). |
| `start_dev_request` | Geliştirme talebini Id ya da tam başlıkla üstlenir: yalnızca `Ready to Start` durumundakini `In Progress` durumuna alır, diğerlerini reddeder; talebin metnini döndürür. Açıklamaya yapıştırılmış base64 görseller (`data:image/png|jpeg|gif|webp`) metinden ayıklanır (`McpDevRequestImages`), yerlerinde `[image-N]` kalır ve her biri ayrı MCP resim içeriği (`ImageContentBlock`) olarak döner — model görseli resim olarak görür, görsel hiçbir yere yüklenmez. `.Write` + `App.DevRequests.Update` ister. | | `start_dev_request` | Geliştirme talebini Id ya da tam başlıkla üstlenir: yalnızca `Ready to Start` durumundakini `In Progress` durumuna alır, diğerlerini reddeder; talebin metnini döndürür. Açıklamaya yapıştırılmış base64 görseller (`data:image/png|jpeg|gif|webp`) metinden ayıklanır (`McpDevRequestImages`), yerlerinde `[image-N]` kalır ve her biri ayrı MCP resim içeriği (`ImageContentBlock`) olarak döner — model görseli resim olarak görür, görsel hiçbir yere yüklenmez. Sonuç istemcinin 1 MB sınırına sığdırılır: büyük görseller küçültülüp JPEG'e çevrilir (`IMcpImageShrinker`), sığmayan gönderilmez ve metinde bildirilir. `.Write` + `App.DevRequests.Update` ister. |
| `get_dev_request` | Talebi durumuna dokunmadan, `start_dev_request` ile aynı biçimde (metin + analiz + resim içerikleri) okur; analizden önce ya da yarım kalan talebe dönmek için. `App.DevRequests` ister. | | `get_dev_request` | Talebi durumuna dokunmadan, `start_dev_request` ile aynı biçimde (metin + analiz + resim içerikleri) okur; analizden önce ya da yarım kalan talebe dönmek için. `App.DevRequests` ister. |
| `save_dev_request_analysis` | `Backlog` ya da `Analysis` durumundaki talebin analizini (HTML) `Analysis` kolonuna yazar — var olanı değiştirir — ve talebi `Analysis` durumuna alır; diğer durumları reddeder. `Description`'a dokunmaz. Analizin standardı `lowcode.instructions.md` §0.12. `.Write` + `App.DevRequests.Update` ister. | | `save_dev_request_analysis` | `Backlog` ya da `Analysis` durumundaki talebin analizini (HTML) `Analysis` kolonuna yazar — var olanı değiştirir — ve talebi `Analysis` durumuna alır; diğer durumları reddeder. `Description`'a dokunmaz. Zorunlu `menuCode` (dalın ya da ekranın kodu) ile analizi modül dokümanındaki dal bölümüne ("2. Analiz ve kararlar") de yazar; kök menünün ilk talebiyse dosya (`App.Inventory.md`) ve bölüm burada açılır, elle yazılmış alt bölüme dokunulmaz. Analizin standardı `lowcode.instructions.md` §0.12. `.Write` + `App.DevRequests.Update` + `App.Listforms.Wizard` ister. |
| `add_dev_request_subtasks` | Talebin `SubTasks` listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (`userName`), bitiş tarihi bugün (`yyyy-MM-dd`) olarak sunucuda yazılır; var olan satırlar korunur. `.Write` + `App.DevRequests.Update` ister. | | `add_dev_request_subtasks` | Talebin `SubTasks` listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (`userName`), bitiş tarihi bugün (`yyyy-MM-dd`) olarak sunucuda yazılır; var olan satırlar korunur. `.Write` + `App.DevRequests.Update` ister. |
| `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler (üretim sonuç notu); `Description` talebin yazıldığı hâliyle kalır. İkisi de `update_row` ile yazılmaz — açıklama base64 görsel taşır. `.Write` + `App.DevRequests.Update` ister. | | `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler (üretim sonuç notu); `Description` talebin yazıldığı hâliyle kalır. İkisi de `update_row` ile yazılmaz — açıklama base64 görsel taşır. `.Write` + `App.DevRequests.Update` ister. |
| `list_components` / `get_component` / `save_component` / `delete_component` | Custom component'ler `ICustomComponentAppService` üzerinden; seed senkronu ve `DataSources` çözümü aynen çalışır. `save_component` ada göre ekler/günceller, tasarımcı belgesi (`Props.visualDesigner`, `sourceMode ≠ code`) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi. | | `list_components` / `get_component` / `save_component` / `delete_component` | Custom component'ler `ICustomComponentAppService` üzerinden; seed senkronu ve `DataSources` çözümü aynen çalışır. `save_component` ada göre ekler/günceller, tasarımcı belgesi (`Props.visualDesigner`, `sourceMode ≠ code`) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi. |
| `get_module_doc` / `save_module_doc` | Modül dokümanı (`configs/seeds/docs/modules/{Modül}.md`, `IModuleDocAppService`). Ad yerine ekran kodu ya da wizard dosya adı verilebilir. `save_module_doc` elle yazılan bölümleri kaydeder, "Ek — Ekran envanteri"ni sunucu üretir. Talebi `update_row` ile `Testing`'e almak, talebin wizard'larının dokümanı tamamlanmadan (dosya var, `module-doc:todo` kalmamış) reddedilir. Okuma `App.Listforms.Wizard`, yazma ayrıca `Wizards.Update` ister. | | `get_module_doc` / `save_module_doc` | Modül dokümanı (`configs/seeds/docs/modules/{Menü}.md`, `IModuleDocAppService`). Kök menü kodu (`App.Hr`) dosyanın tamamını, ekran/dal kodu ya da wizard dosya adı yalnızca o dalın bölümünü okur/yazar. `save_module_doc` elle yazılan bölümleri kaydeder, "Ek — Ekran envanteri"ni sunucu üretir. Talebi `update_row` ile `Testing`'e almak, talebin wizard'larının bölümü tamamlanmadan (bölüm var, `module-doc:todo` kalmamış) reddedilir. Okuma `App.Listforms.Wizard`, yazma ayrıca `Wizards.Update` ister. |
| `list_report_templates` / `get_report_template` / `save_report_category` / `save_report_template` / `delete_report_template` | Rapor kategorileri ve `DynamicReport` şablonları (`McpReportAppService`). Okuma depodan, ekranın okuma yetkisiyle yapılır. Yazma `App.Reports.Categories` / `App.Reports.ReportTemplates` ekranlarının C/U/D sözleşmesinden (`IListFormDataAppService`) geçer; seed senkronu aynen çalışır. Kayıt ada göre ekler/günceller. `listFormCode` verilirse HTML'deki `{{AlanAdi}}` yer tutucuları o ekranın alanlarına göre doğrulanır ve cevapta satır butonu (`rowButton`) döner (§15). | | `list_report_templates` / `get_report_template` / `save_report_category` / `save_report_template` / `delete_report_template` | Rapor kategorileri ve `DynamicReport` şablonları (`McpReportAppService`). Okuma depodan, ekranın okuma yetkisiyle yapılır. Yazma `App.Reports.Categories` / `App.Reports.ReportTemplates` ekranlarının C/U/D sözleşmesinden (`IListFormDataAppService`) geçer; seed senkronu aynen çalışır. Kayıt ada göre ekler/günceller. `listFormCode` verilirse HTML'deki `{{AlanAdi}}` yer tutucuları o ekranın alanlarına göre doğrulanır ve cevapta satır butonu (`rowButton`) döner (§15). |
**Timesheets.** Talebin `Timesheets` listesini sunucu doldurur (`IMcpDevRequestTimesheetRecorder`): **Timesheets.** Talebin `Timesheets` listesini sunucu doldurur (`IMcpDevRequestTimesheetRecorder`):
@ -1668,7 +1656,7 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay
| `delete_report_template` | — | `.Write` | `App.Reports.ReportTemplates.Delete` | | `delete_report_template` | — | `.Write` | `App.Reports.ReportTemplates.Delete` |
| `start_dev_request` | — | `.Write` | `App.DevRequests.Update` | | `start_dev_request` | — | `.Write` | `App.DevRequests.Update` |
| `get_dev_request` | — | — | `App.DevRequests` | | `get_dev_request` | — | — | `App.DevRequests` |
| `save_dev_request_analysis` | — | `.Write` | `App.DevRequests.Update` | | `save_dev_request_analysis` | — | `.Write` | `App.DevRequests.Update`, `App.Listforms.Wizard` (modül dokümanı) |
| `append_dev_request_note` | — | `.Write` | `App.DevRequests.Update` | | `append_dev_request_note` | — | `.Write` | `App.DevRequests.Update` |
| `add_dev_request_subtasks` | — | `.Write` | `App.DevRequests.Update` | | `add_dev_request_subtasks` | — | `.Write` | `App.DevRequests.Update` |
@ -1719,12 +1707,14 @@ kimlik doğrulamayı istemci MCP OAuth akışıyla kendisi yürütür:
sunucusunu (`AuthServer:Authority`) öğrenir, `/.well-known/oauth-authorization-server` sunucusunu (`AuthServer:Authority`) öğrenir, `/.well-known/oauth-authorization-server`
(OpenIddict keşif belgesi, `registration_endpoint` eklenmiş) belgesini okur. (OpenIddict keşif belgesi, `registration_endpoint` eklenmiş) belgesini okur.
3. `/connect/register` (RFC 7591, `McpOAuthClientManager`): 3. `/connect/register` (RFC 7591, `McpOAuthClientManager`):
- İstenen geri dönüş adresleri **bilinen istemcide** (`Platform_Mcp`; Claude, ChatGPT — - İstenen geri dönüş adresleri bir **bilinen istemcide** kayıtlıysa onun kimliği döner:
`DbMigrator` ayarı `OpenIddict:Applications:Platform_Mcp:RedirectUris`) kayıtlıysa onun `Platform_Mcp` (Claude, ChatGPT; onay ekranı çıkmaz) ya da `Platform_Mcp_Local` (yerel
kimliği döner; onay ekranı çıkmaz. `mcp-remote` proxy'si, `127.0.0.1` callback'i; açık onaylı). Adresler `DbMigrator` ayarı
`OpenIddict:Applications:{ClientId}:RedirectUris`'tedir; ayarı olmayan istemci seed edilmez.
- Değilse (Cursor, VS Code, Claude Code, başka bir AI istemcisi) **dinamik istemci** üretilir: - Değilse (Cursor, VS Code, Claude Code, başka bir AI istemcisi) **dinamik istemci** üretilir:
`mcp_` önekli, PKCE zorunlu, **açık onaylı** OpenIddict uygulaması. Aynı adres kümesi aynı `mcp_` önekli, PKCE zorunlu, **açık onaylı** OpenIddict uygulaması. Aynı adres kümesi aynı
uygulamaya bağlanır; tavan `McpConsts.MaxDynamicClients`. Kabul edilen adresler: HTTPS, uygulamaya bağlanır — kayıt kilit altında ve kendi UoW'sinde commit edilir, masaüstü
istemcisinin aynı anda başlattığı proxy süreçleri ikinci kayıt açamaz; tavan `McpConsts.MaxDynamicClients`. Kabul edilen adresler: HTTPS,
localhost HTTP (RFC 8252), uygulama şemaları (`cursor://…`); `javascript:` vb. reddedilir. localhost HTTP (RFC 8252), uygulama şemaları (`cursor://…`); `javascript:` vb. reddedilir.
Onay ekranındaki ad istemcinin beyanı + geri dönüş adresinin sahibidir (ad doğrulanamaz). Onay ekranındaki ad istemcinin beyanı + geri dönüş adresinin sahibidir (ad doğrulanamaz).
4. Kullanıcı platformun giriş ekranına (`/Account/Login`, kiracı seçimi dahil) yönlenir; 4. Kullanıcı platformun giriş ekranına (`/Account/Login`, kiracı seçimi dahil) yönlenir;
@ -2148,7 +2138,7 @@ ve `SeedPathResolver` üzerinden çözülür:
configs/seeds/ configs/seeds/
├── wizard/ # {Ad}.json → WizardDataSeeder ├── wizard/ # {Ad}.json → WizardDataSeeder
├── crud/ # {EntityName}.json → CrudEndpointSeedApplier ├── crud/ # {EntityName}.json → CrudEndpointSeedApplier
├── docs/modules/ # {Modül}.md → ModuleDocManager (seed edilmez) ├── docs/modules/ # {Menü}.md → ModuleDocManager (seed edilmez)
├── data/ # {ListFormCode}.json ya da {ListFormCode}/{anahtar}.json → ListFormSeedDataApplier ├── data/ # {ListFormCode}.json ya da {ListFormCode}/{anahtar}.json → ListFormSeedDataApplier
│ # App.Menus.{Routes,Module,Menu}.json — route/modül/menü ağacı │ # App.Menus.{Routes,Module,Menu}.json — route/modül/menü ağacı
│ # Abp.Identity.Permission{Groups,s}.json — yetki tanımları │ # Abp.Identity.Permission{Groups,s}.json — yetki tanımları

View file

@ -39,9 +39,10 @@ public interface IMcpDevelopAppService : IApplicationService
/// <summary> /// <summary>
/// Talebin analizini yazar (var olani degistirir) ve talebi Analysis durumuna alir; yalnizca /// Talebin analizini yazar (var olani degistirir) ve talebi Analysis durumuna alir; yalnizca
/// Backlog ya da Analysis durumundaki talep analiz edilir. /// Backlog ya da Analysis durumundaki talep analiz edilir. Analiz <paramref name="menuCode"/>
/// dalinin modul dokumani bolumune de yazilir; dosya ya da bolum yoksa acilir.
/// </summary> /// </summary>
Task<McpDevRequestDto> SaveDevRequestAnalysisAsync(string request, string html); Task<McpDevRequestDto> SaveDevRequestAnalysisAsync(string request, string? menuCode, string html);
/// <summary> /// <summary>
/// Analizin sonuna HTML ekler. Aciklama talebin yazildigi haliyle kalir; uretim notlari /// Analizin sonuna HTML ekler. Aciklama talebin yazildigi haliyle kalir; uretim notlari

View file

@ -60,8 +60,8 @@ public class McpDataAppService(
} }
/// <summary> /// <summary>
/// Gelistirme talebi Testing'e alinirken talebin urettigi wizard'larin modul dokumanlari tamam /// Gelistirme talebi Testing'e alinirken talebin urettigi wizard'larin modul dokumani bolumleri
/// olmalidir (lowcode §0.13): dosya var ve doldurulmamis (<c>module-doc:todo</c>) bolum yok. /// tamam olmalidir (lowcode §0.13): bolum var ve doldurulmamis (<c>module-doc:todo</c>) alt bolum yok.
/// Talebi Testing'e alan tek yol <c>update_row</c> oldugu icin kural burada uygulanir. Wizard /// Talebi Testing'e alan tek yol <c>update_row</c> oldugu icin kural burada uygulanir. Wizard
/// okuma yetkisi olmayan kullanici wizard uretemeyecegi icin kontrol ona uygulanmaz. /// okuma yetkisi olmayan kullanici wizard uretemeyecegi icin kontrol ona uygulanmaz.
/// </summary> /// </summary>

View file

@ -491,9 +491,10 @@ public class McpDevelopAppService(
/// <summary> /// <summary>
/// Durum kurali modelin dikkatine birakilmaz: yalnizca Backlog ya da Analysis durumundaki talep /// Durum kurali modelin dikkatine birakilmaz: yalnizca Backlog ya da Analysis durumundaki talep
/// analiz edilir. Analysis'teki talebin analizi begenilmediyse yeniden yazilir; Ready to Start'a /// analiz edilir. Analysis'teki talebin analizi begenilmediyse yeniden yazilir; Ready to Start'a
/// kullanici alir. /// kullanici alir. Analiz modulun dokumanina da tasinir: talep ilk kez bir menude yapiliyorsa
/// dokuman ve dalin bolumu burada acilir, uretim beklenmez.
/// </summary> /// </summary>
public async Task<McpDevRequestDto> SaveDevRequestAnalysisAsync(string request, string html) public async Task<McpDevRequestDto> SaveDevRequestAnalysisAsync(string request, string? menuCode, string html)
{ {
await EnsureToolAllowedAsync(McpToolNames.SaveDevRequestAnalysis); await EnsureToolAllowedAsync(McpToolNames.SaveDevRequestAnalysis);
await AuthorizationService.CheckAsync(DevRequestUpdatePermission); await AuthorizationService.CheckAsync(DevRequestUpdatePermission);
@ -503,6 +504,13 @@ public class McpDevelopAppService(
throw new UserFriendlyException(L[McpErrorCodes.DevRequestAnalysisRequired]); throw new UserFriendlyException(L[McpErrorCodes.DevRequestAnalysisRequired]);
} }
// Arac listesini onbellekten okuyan istemci parametreyi hic gondermeyebilir; marshaller'in
// ArgumentException'i modele ulasmaz, bu mesaj ise ulasir ve model cagriyi kodla tekrarlar.
if (string.IsNullOrWhiteSpace(menuCode))
{
throw new UserFriendlyException(L[McpErrorCodes.DevRequestMenuCodeRequired]);
}
var devRequest = await FindDevRequestAsync(request); var devRequest = await FindDevRequestAsync(request);
if (devRequest.Status is not (McpConsts.DevRequestStatuses.Backlog or McpConsts.DevRequestStatuses.Analysis)) if (devRequest.Status is not (McpConsts.DevRequestStatuses.Backlog or McpConsts.DevRequestStatuses.Analysis))
@ -510,6 +518,15 @@ public class McpDevelopAppService(
throw new UserFriendlyException(L[McpErrorCodes.DevRequestNotAnalyzable, devRequest.Title ?? devRequest.Id.ToString(), devRequest.Status ?? string.Empty]); throw new UserFriendlyException(L[McpErrorCodes.DevRequestNotAnalyzable, devRequest.Title ?? devRequest.Id.ToString(), devRequest.Status ?? string.Empty]);
} }
// Dokuman once yazilir: menu kodu gecersizse analiz de kaydedilmez, model kodu duzeltip tekrar dener.
await moduleDocAppService.SaveAnalysisAsync(new ModuleDocAnalysisInput
{
MenuCode = menuCode,
DevRequestId = devRequest.Id,
DevRequestTitle = devRequest.Title,
Html = html,
});
devRequest.Analysis = html.Trim(); devRequest.Analysis = html.Trim();
devRequest.Status = McpConsts.DevRequestStatuses.Analysis; devRequest.Status = McpConsts.DevRequestStatuses.Analysis;
await devRequestRepository.UpdateAsync(devRequest, autoSave: true); await devRequestRepository.UpdateAsync(devRequest, autoSave: true);

View file

@ -44,6 +44,13 @@ public static class McpConsts
/// </summary> /// </summary>
public const string OAuthClientId = "Platform_Mcp"; public const string OAuthClientId = "Platform_Mcp";
/// <summary>
/// Yerel makinedeki MCP proxy'sinin (mcp-remote) istemci kimligi. Geri donus adresleri
/// DbMigrator'da <c>OpenIddict:Applications:Platform_Mcp_Local</c> ayarindadir. Loopback adresine
/// makinedeki her surec dinleyebildigi icin acik onay ister.
/// </summary>
public const string LocalOAuthClientId = "Platform_Mcp_Local";
/// <summary> /// <summary>
/// Dinamik kayitla (RFC 7591) uretilen istemcilerin kimlik oneki. Bilinen saglayicilarin /// Dinamik kayitla (RFC 7591) uretilen istemcilerin kimlik oneki. Bilinen saglayicilarin
/// (Claude, ChatGPT) disindaki MCP istemcileri bu onekle kendi OpenIddict uygulamasini alir. /// (Claude, ChatGPT) disindaki MCP istemcileri bu onekle kendi OpenIddict uygulamasini alir.
@ -67,6 +74,7 @@ public static class McpConsts
public static bool IsMcpClient(string? clientId) public static bool IsMcpClient(string? clientId)
=> clientId is not null => clientId is not null
&& (string.Equals(clientId, OAuthClientId, System.StringComparison.Ordinal) && (string.Equals(clientId, OAuthClientId, System.StringComparison.Ordinal)
|| string.Equals(clientId, LocalOAuthClientId, System.StringComparison.Ordinal)
|| clientId.StartsWith(DynamicClientIdPrefix, System.StringComparison.Ordinal)); || clientId.StartsWith(DynamicClientIdPrefix, System.StringComparison.Ordinal));
/// <summary>MCP ucunun sunucu adresine gore yolu.</summary> /// <summary>MCP ucunun sunucu adresine gore yolu.</summary>

View file

@ -57,6 +57,7 @@ public static class McpErrorCodes
public const string DevRequestNotReady = Prefix + "DevRequestNotReady"; public const string DevRequestNotReady = Prefix + "DevRequestNotReady";
public const string DevRequestNotAnalyzable = Prefix + "DevRequestNotAnalyzable"; public const string DevRequestNotAnalyzable = Prefix + "DevRequestNotAnalyzable";
public const string DevRequestAnalysisRequired = Prefix + "DevRequestAnalysisRequired"; public const string DevRequestAnalysisRequired = Prefix + "DevRequestAnalysisRequired";
public const string DevRequestMenuCodeRequired = Prefix + "DevRequestMenuCodeRequired";
public const string DevRequestNoteRequired = Prefix + "DevRequestNoteRequired"; public const string DevRequestNoteRequired = Prefix + "DevRequestNoteRequired";
public const string DevRequestSubTasksRequired = Prefix + "DevRequestSubTasksRequired"; public const string DevRequestSubTasksRequired = Prefix + "DevRequestSubTasksRequired";
public const string DevRequestSubTasksInvalid = Prefix + "DevRequestSubTasksInvalid"; public const string DevRequestSubTasksInvalid = Prefix + "DevRequestSubTasksInvalid";

View file

@ -149,6 +149,7 @@ public static class McpToolNames
[DeleteWizard] = [PlatformConsts.AppCodes.Listforms.Wizard, PlatformConsts.AppCodes.Listforms.Wizards.Delete, PlatformConsts.AppCodes.SqlQueryManagers.Default], [DeleteWizard] = [PlatformConsts.AppCodes.Listforms.Wizard, PlatformConsts.AppCodes.Listforms.Wizards.Delete, PlatformConsts.AppCodes.SqlQueryManagers.Default],
[GetModuleDoc] = [PlatformConsts.AppCodes.Listforms.Wizard], [GetModuleDoc] = [PlatformConsts.AppCodes.Listforms.Wizard],
[SaveModuleDoc] = [PlatformConsts.AppCodes.Listforms.Wizard, PlatformConsts.AppCodes.Listforms.Wizards.Update], [SaveModuleDoc] = [PlatformConsts.AppCodes.Listforms.Wizard, PlatformConsts.AppCodes.Listforms.Wizards.Update],
[SaveDevRequestAnalysis] = [PlatformConsts.AppCodes.Listforms.Wizard],
}; };
/// <summary> /// <summary>

View file

@ -7,16 +7,15 @@ using Volo.Abp.DependencyInjection;
namespace Sozsoft.Mcp.Data; namespace Sozsoft.Mcp.Data;
/// <summary> /// <summary>
/// Bilinen MCP istemcisini (<see cref="McpConsts.OAuthClientId"/>) seed eder. Geri donus /// Bilinen MCP istemcilerini (<see cref="McpConsts.OAuthClientId"/>,
/// adresleri <c>OpenIddict:Applications:Platform_Mcp:RedirectUris</c> ayarindadir; yeni bir /// <see cref="McpConsts.LocalOAuthClientId"/>) seed eder. Geri donus adresleri
/// saglayici eklemek bu listeye callback eklemektir. /// <c>OpenIddict:Applications:{ClientId}:RedirectUris</c> ayarindadir; yeni bir saglayici eklemek
/// bu listeye callback eklemektir. Ayari olmayan istemci olusturulmaz.
/// </summary> /// </summary>
public class McpOAuthClientDataSeedContributor( public class McpOAuthClientDataSeedContributor(
IConfiguration configuration, IConfiguration configuration,
IMcpOAuthClientManager clientManager) : IDataSeedContributor, ITransientDependency IMcpOAuthClientManager clientManager) : IDataSeedContributor, ITransientDependency
{ {
private const string RedirectUrisKey = "OpenIddict:Applications:Platform_Mcp:RedirectUris";
public async Task SeedAsync(DataSeedContext context) public async Task SeedAsync(DataSeedContext context)
{ {
// OpenIddict uygulamalari host kapsamindadir; kiraci seed turlarinda tekrarlanmaz. // OpenIddict uygulamalari host kapsamindadir; kiraci seed turlarinda tekrarlanmaz.
@ -25,12 +24,15 @@ public class McpOAuthClientDataSeedContributor(
return; return;
} }
var redirectUris = configuration.GetSection(RedirectUrisKey).Get<string[]>(); foreach (var clientId in new[] { McpConsts.OAuthClientId, McpConsts.LocalOAuthClientId })
{
var redirectUris = configuration.GetSection($"OpenIddict:Applications:{clientId}:RedirectUris").Get<string[]>();
if (redirectUris is null || redirectUris.Length == 0) if (redirectUris is null || redirectUris.Length == 0)
{ {
return; continue;
} }
await clientManager.EnsureKnownClientAsync(redirectUris); await clientManager.EnsureKnownClientAsync(clientId, redirectUris);
}
} }
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@ -6,18 +6,22 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenIddict.Abstractions; using OpenIddict.Abstractions;
using Sozsoft.Platform; using Sozsoft.Platform;
using Volo.Abp;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.OpenIddict.Applications; using Volo.Abp.OpenIddict.Applications;
using Volo.Abp.Uow;
namespace Sozsoft.Mcp.Services; namespace Sozsoft.Mcp.Services;
/// <summary> /// <summary>
/// Uzak MCP istemcilerinin OpenIddict uygulamalari. Iki tur istemci vardir: /// Uzak MCP istemcilerinin OpenIddict uygulamalari. Iki tur istemci vardir:
/// <list type="bullet"> /// <list type="bullet">
/// <item><b>Bilinen istemci</b> (<see cref="McpConsts.OAuthClientId"/>): geri donus adresleri /// <item><b>Bilinen istemci</b>: geri donus adresleri DbMigrator ayarinda sabit olan istemciler —
/// DbMigrator ayarinda sabit olan saglayicilar (Claude, ChatGPT). Onay ekrani gostermez.</item> /// <see cref="McpConsts.OAuthClientId"/> (Claude, ChatGPT; onay ekrani gostermez) ve
/// <see cref="McpConsts.LocalOAuthClientId"/> (yerel mcp-remote proxy'si; acik onayli).</item>
/// <item><b>Dinamik istemci</b> (<see cref="McpConsts.DynamicClientIdPrefix"/>): baska bir MCP /// <item><b>Dinamik istemci</b> (<see cref="McpConsts.DynamicClientIdPrefix"/>): baska bir MCP
/// istemcisi (Cursor, VS Code, Claude Code, ...) kayit ucundan kendi uygulamasini alir. Kayit /// istemcisi (Cursor, VS Code, Claude Code, ...) kayit ucundan kendi uygulamasini alir. Kayit
/// herkese acik oldugu icin bu istemciler <b>acik onay</b> ister: kullanici giris yaptiktan sonra /// herkese acik oldugu icin bu istemciler <b>acik onay</b> ister: kullanici giris yaptiktan sonra
@ -28,10 +32,10 @@ namespace Sozsoft.Mcp.Services;
public interface IMcpOAuthClientManager public interface IMcpOAuthClientManager
{ {
/// <summary>Bilinen istemciyi olusturur ya da geri donus adreslerini gunceller.</summary> /// <summary>Bilinen istemciyi olusturur ya da geri donus adreslerini gunceller.</summary>
Task EnsureKnownClientAsync(IReadOnlyCollection<string> redirectUris); Task EnsureKnownClientAsync(string clientId, IReadOnlyCollection<string> redirectUris);
/// <summary> /// <summary>
/// Istemci kaydi. Adresler bilinen istemcide kayitliysa onu, degilse ayni adres kumesine /// Istemci kaydi. Adresler bir bilinen istemcide kayitliysa onu, degilse ayni adres kumesine
/// sahip dinamik istemciyi dondurur; yoksa yenisini uretir. /// sahip dinamik istemciyi dondurur; yoksa yenisini uretir.
/// </summary> /// </summary>
Task<McpClientRegistration> RegisterAsync(string? clientName, IReadOnlyCollection<string> redirectUris); Task<McpClientRegistration> RegisterAsync(string? clientName, IReadOnlyCollection<string> redirectUris);
@ -40,25 +44,33 @@ public interface IMcpOAuthClientManager
public class McpOAuthClientManager( public class McpOAuthClientManager(
IAbpApplicationManager applicationManager, IAbpApplicationManager applicationManager,
IRepository<OpenIddictApplication, Guid> applicationRepository, IRepository<OpenIddictApplication, Guid> applicationRepository,
ICurrentTenant currentTenant) ICurrentTenant currentTenant,
IAbpDistributedLock distributedLock,
IUnitOfWorkManager unitOfWorkManager)
: DomainService, IMcpOAuthClientManager : DomainService, IMcpOAuthClientManager
{ {
private const int MaxClientNameLength = 100; private const int MaxClientNameLength = 100;
private const string RegistrationLockName = "Mcp:OAuthClientRegistration";
private static readonly TimeSpan RegistrationLockTimeout = TimeSpan.FromSeconds(15);
private static readonly Dictionary<string, (string DisplayName, string ConsentType)> KnownClients = new(StringComparer.Ordinal)
{
[McpConsts.OAuthClientId] = ("MCP Connectors", OpenIddictConstants.ConsentTypes.Implicit),
[McpConsts.LocalOAuthClientId] = ("MCP Local Proxy", OpenIddictConstants.ConsentTypes.Explicit)
};
/// <summary>Tarayici disi calistirilabilir icerik tasiyan, geri donus adresi olamayacak semalar.</summary> /// <summary>Tarayici disi calistirilabilir icerik tasiyan, geri donus adresi olamayacak semalar.</summary>
private static readonly string[] ForbiddenSchemes = ["javascript", "data", "file", "vbscript", "about", "blob"]; private static readonly string[] ForbiddenSchemes = ["javascript", "data", "file", "vbscript", "about", "blob"];
public async Task EnsureKnownClientAsync(IReadOnlyCollection<string> redirectUris) public async Task EnsureKnownClientAsync(string clientId, IReadOnlyCollection<string> redirectUris)
{ {
var (displayName, consentType) = KnownClients[clientId];
// OpenIddict uygulamalari host kapsamindadir. // OpenIddict uygulamalari host kapsamindadir.
using (currentTenant.Change(null)) using (currentTenant.Change(null))
{ {
var existing = await applicationManager.FindByClientIdAsync(McpConsts.OAuthClientId); var existing = await applicationManager.FindByClientIdAsync(clientId);
var descriptor = BuildDescriptor( var descriptor = BuildDescriptor(clientId, displayName, redirectUris, consentType);
McpConsts.OAuthClientId,
"MCP Connectors",
redirectUris,
OpenIddictConstants.ConsentTypes.Implicit);
if (existing is null) if (existing is null)
{ {
@ -88,16 +100,38 @@ public class McpOAuthClientManager(
using (currentTenant.Change(null)) using (currentTenant.Change(null))
{ {
var known = await applicationManager.FindByClientIdAsync(McpConsts.OAuthClientId); foreach (var knownClientId in KnownClients.Keys)
if (known is not null)
{ {
var known = await applicationManager.FindByClientIdAsync(knownClientId);
if (known is null)
{
continue;
}
var knownUris = await applicationManager.GetRedirectUrisAsync(known); var knownUris = await applicationManager.GetRedirectUrisAsync(known);
if (redirectUris.All(uri => knownUris.Any(registered => SameUri(registered, uri)))) if (redirectUris.All(uri => knownUris.Any(registered => SameUri(registered, uri))))
{ {
return McpClientRegistration.Accepted(McpConsts.OAuthClientId); return McpClientRegistration.Accepted(knownClientId);
} }
} }
// Masaustu istemcisi ayni anda birden cok proxy sureci baslatir ve hepsi ayni adreslerle
// kayit olur. Kontrol ile ekleme kilitsiz ve istegin UoW'sinde kalirsa hicbiri digerinin
// kaydini goremez, ayni adres kumesi icin birden cok uygulama acilir. Kilit altinda
// calisip kendi UoW'sinde commit eden kayit, sonraki istegin onu bulmasini saglar.
await using var handle = await distributedLock.TryAcquireAsync(RegistrationLockName, RegistrationLockTimeout)
?? throw new AbpException($"Could not acquire '{RegistrationLockName}' lock.");
using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);
var registration = await RegisterDynamicClientAsync(clientName, redirectUris);
await uow.CompleteAsync();
return registration;
}
}
private async Task<McpClientRegistration> RegisterDynamicClientAsync(string? clientName, IReadOnlyCollection<string> redirectUris)
{
var dynamicClients = await GetDynamicClientsAsync(); var dynamicClients = await GetDynamicClientsAsync();
// Ayni istemci her baglantida yeniden kayit olabilir; ayni adres kumesi tek uygulamaya // Ayni istemci her baglantida yeniden kayit olabilir; ayni adres kumesi tek uygulamaya
@ -122,7 +156,6 @@ public class McpOAuthClientManager(
return McpClientRegistration.Accepted(clientId); return McpClientRegistration.Accepted(clientId);
} }
}
private async Task<List<(string? ClientId, IReadOnlyList<string> RedirectUris)>> GetDynamicClientsAsync() private async Task<List<(string? ClientId, IReadOnlyList<string> RedirectUris)>> GetDynamicClientsAsync()
{ {

View file

@ -0,0 +1,18 @@
namespace Sozsoft.Mcp.Tools;
/// <summary>
/// Arac sonucundaki gorselleri istemcinin boyut sinirina sigdirir. MCP istemcileri tek bir arac
/// sonucunu sinirlar (Claude Desktop 1 MB); talep aciklamasina yapistirilan ekran goruntuleri
/// tek basina bu siniri asabilir. Gorsel isleme kutuphanesi barindiran uygulamadadir; modul
/// grafik paketine baglanmaz.
/// </summary>
public interface IMcpImageShrinker
{
/// <summary>
/// Base64 uzunlugu <paramref name="maxLength"/>'i asmayan gorsel dondurur: sigiyorsa oldugu gibi,
/// sigmiyorsa kucultulmus JPEG. Okunabilir boyutta sigdirilamazsa ya da cozulemezse null.
/// </summary>
McpShrunkImage? Shrink(string mimeType, string base64, int maxLength);
}
public sealed record McpShrunkImage(string MimeType, string Data);

View file

@ -43,7 +43,7 @@ public static class McpDevelopPrompts
"Talebi start_dev_request ile ustlen. Arac reddederse (talep Ready to Start degil, bulunamadi ya da " + "Talebi start_dev_request ile ustlen. Arac reddederse (talep Ready to Start degil, bulunamadi ya da " +
"birden fazla eslesme var) hicbir sey uretme, baska bir talebe gecme: nedenini bildir ve dur. " + "birden fazla eslesme var) hicbir sey uretme, baska bir talebe gecme: nedenini bildir ve dur. " +
"Talep (Description) ve onaylanmis analizi (Analysis) birlikte tarifin kendisidir: soru sorma, DB Migrate'i bekleme. " + "Talep (Description) ve onaylanmis analizi (Analysis) birlikte tarifin kendisidir: soru sorma, DB Migrate'i bekleme. " +
"Testing'e almadan once uretilen modulun dokumanini get_module_doc ile oku, todo bolumlerini save_module_doc ile doldur (lowcode 0.13); sonra talebi Testing'e alip bitir. " + "Testing'e almadan once uretilen ekranin modul dokumani bolumunu get_module_doc ile ekran koduyla oku, todo alt bolumlerini save_module_doc ile ayni kodla doldur (lowcode 0.13); sonra talebi Testing'e alip bitir. " +
"Sonunda rapor ver: uretilen artefaktlar, talebin son durumu ve DB Migrate gerektigi."; "Sonunda rapor ver: uretilen artefaktlar, talebin son durumu ve DB Migrate gerektigi.";
[McpServerPrompt(Name = McpPromptNames.ProcessReadyRequests)] [McpServerPrompt(Name = McpPromptNames.ProcessReadyRequests)]
@ -53,6 +53,6 @@ public static class McpDevelopPrompts
"Once get_dev_guide ile lowcode 0.11 bolumunu oku ve oradaki adimlari uygula. " + "Once get_dev_guide ile lowcode 0.11 bolumunu oku ve oradaki adimlari uygula. " +
"Her talebi start_dev_request ile ustlen; reddedilen talebi atla ve rapora yaz. " + "Her talebi start_dev_request ile ustlen; reddedilen talebi atla ve rapora yaz. " +
"Talep (Description) ve onaylanmis analizi (Analysis) birlikte tarifin kendisidir: soru sorma, DB Migrate'i bekleme. " + "Talep (Description) ve onaylanmis analizi (Analysis) birlikte tarifin kendisidir: soru sorma, DB Migrate'i bekleme. " +
"Her talepte Testing'e almadan once modul dokumanini save_module_doc ile doldur (lowcode 0.13), sonra talebi Testing'e alip digerine gec. " + "Her talepte Testing'e almadan once ekranin modul dokumani bolumunu save_module_doc ile doldur (lowcode 0.13), sonra talebi Testing'e alip digerine gec. " +
"Sonunda parti raporu ver: talep basina uretilen artefaktlar ve son durum; DB Migrate gerektigini tek satirda belirt."; "Sonunda parti raporu ver: talep basina uretilen artefaktlar ve son durum; DB Migrate gerektigini tek satirda belirt.";
} }

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
@ -17,10 +18,16 @@ namespace Sozsoft.Mcp.Tools;
/// yolla ayni sekilde olusur. /// yolla ayni sekilde olusur.
/// </summary> /// </summary>
[McpServerToolType] [McpServerToolType]
public sealed class McpDevelopTools(IMcpDevelopAppService developAppService) public sealed class McpDevelopTools(IMcpDevelopAppService developAppService, IMcpImageShrinker imageShrinker)
{ {
private static readonly JsonSerializerOptions TextOptions = new(JsonSerializerDefaults.Web); private static readonly JsonSerializerOptions TextOptions = new(JsonSerializerDefaults.Web);
/// <summary>
/// Talep sonucunun (metin + base64 gorseller) ust siniri, karakter. Claude Desktop arac sonucunu
/// 1 MB ile sinirlar; JSON-RPC zarfi ve kacislar icin pay birakilir.
/// </summary>
private const int MaxResultLength = 900_000;
[McpServerTool(Name = McpToolNames.GetDevGuide, ReadOnly = true)] [McpServerTool(Name = McpToolNames.GetDevGuide, ReadOnly = true)]
[Description("Ekran ve bilesen uretim sozlesmesini okur: wizard alanlari, enum degerleri, editor/lookup/script sozlesmesi, custom component kurallari. section bos ise baslik listesi doner; once listeyi al, sonra ihtiyac duydugun bolumu numarasiyla iste (ornegin 4.2). Rehberde dosya yazmak anlatilan yerlerde dosya yerine bu sunucunun araclarini kullan.")] [Description("Ekran ve bilesen uretim sozlesmesini okur: wizard alanlari, enum degerleri, editor/lookup/script sozlesmesi, custom component kurallari. section bos ise baslik listesi doner; once listeyi al, sonra ihtiyac duydugun bolumu numarasiyla iste (ornegin 4.2). Rehberde dosya yazmak anlatilan yerlerde dosya yerine bu sunucunun araclarini kullan.")]
public async Task<McpDevGuideDto> GetDevGuideAsync( public async Task<McpDevGuideDto> GetDevGuideAsync(
@ -110,8 +117,10 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService)
[Description("Talebin Id'si (GUID) ya da tam basligi.")] [Description("Talebin Id'si (GUID) ya da tam basligi.")]
string request, string request,
[Description("Analizin tamami, HTML.")] [Description("Analizin tamami, HTML.")]
string html) string html,
=> ToContent(await developAppService.SaveDevRequestAnalysisAsync(request, html)); [Description("Zorunlu. Talebin menu yeri: kok menunun altindaki dalin ya da ekranin kodu (App.Inventory.Definitions, App.Hr.LeaveRequest); analizdeki Menu yeri ile ayni. Menude henuz olmayan yeni kod da verilir. Analiz bu dalin modul dokumani bolumune ('2. Analiz ve kararlar') yazilir; kok menunun dosyasi (App.Inventory.md) ya da dalin bolumu yoksa acilir.")]
string? menuCode = null)
=> ToContent(await developAppService.SaveDevRequestAnalysisAsync(request, menuCode, html));
[McpServerTool(Name = McpToolNames.AppendDevRequestNote, Destructive = false, Idempotent = false)] [McpServerTool(Name = McpToolNames.AppendDevRequestNote, Destructive = false, Idempotent = false)]
[Description("Talebin Analysis alaninin sonuna HTML ekler; var olan analize ve Description'a dokunmaz. Description talebin yazildigi haliyle kalir; uretim sonuc notunu (<h4>MCP</h4> + uretilenler, varsayimlar ve DB Migrate bekleyen wizard'lar) bu aracla ekle. Description'i ya da Analysis'i update_row ile yazma.")] [Description("Talebin Analysis alaninin sonuna HTML ekler; var olan analize ve Description'a dokunmaz. Description talebin yazildigi haliyle kalir; uretim sonuc notunu (<h4>MCP</h4> + uretilenler, varsayimlar ve DB Migrate bekleyen wizard'lar) bu aracla ekle. Description'i ya da Analysis'i update_row ile yazma.")]
@ -139,33 +148,58 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService)
[McpServerTool(Name = McpToolNames.GetModuleDoc, ReadOnly = true)] [McpServerTool(Name = McpToolNames.GetModuleDoc, ReadOnly = true)]
[Description("Modul dokumanini (docs/modules/{Modul}.md, seed klasoru) okur: nasil calistigi, analiz/kararlar, ekranlar, hesaplar, yetkiler ve otomatik 'Ek — Ekran envanteri'. isComplete false ise missingSections doldurulmamis bolumlerdir. 'Bu modul/ekran nasil calisiyor?' sorusunda once bunu oku. Ad yerine ekranin kodu (App.Hr.PerformanceReview) ya da wizard dosya adi da verilebilir; deploy_wizard dokumani yoksa iskeletini kendisi acar.")] [Description("Modul dokumanini (docs/modules/{Menu}.md, seed klasoru; kok menu basina tek dosya, adi kok menu kodu: App.Hr, App.Prc, App.Administration) okur: nasil calistigi, analiz/kararlar, ekranlar, hesaplar, yetkiler ve otomatik 'Ek — Ekran envanteri'. Menunun altindaki her dal dosyada bir bolumdur; ekranin ya da dalin kodu (App.Hr.PerformanceReview, App.Hr.Performance) ya da wizard dosya adi verilirse yalnizca o bolum ve envanteri doner, dosya adi verilirse dosyanin tamami. isComplete false ise missingSections doldurulmamis alt bolumlerdir. 'Bu modul/ekran nasil calisiyor?' sorusunda once bunu ekran koduyla oku; deploy_wizard dosyayi ve dalin bolumunu yoksa kendisi acar. Menude henuz olmayan yeni modulun kodu (App.Inventory, App.Inventory.Stock) hata vermez: exists false ve doldurulacak iskelet doner; save_module_doc ayni kodla yazinca dosya acilir.")]
public async Task<ModuleDocDto> GetModuleDocAsync( public async Task<ModuleDocDto> GetModuleDocAsync(
[Description("Dokuman adi (HrPerformance), ekran kodu ya da wizard dosya adi.")] [Description("Ekran ya da dal kodu, wizard dosya adi (o bolum) veya kok menu kodu (App.Hr; dosyanin tamami).")]
string name) string name)
=> await developAppService.GetModuleDocAsync(name); => await developAppService.GetModuleDocAsync(name);
[McpServerTool(Name = McpToolNames.SaveModuleDoc, Destructive = true, Idempotent = true)] [McpServerTool(Name = McpToolNames.SaveModuleDoc, Destructive = true, Idempotent = true)]
[Description("Modul dokumaninin elle yazilan bolumlerini kaydeder (Markdown, dosyanin tamami). Uretimin parcasidir: deploy_wizard'dan sonra once get_module_doc ile iskeleti oku, module-doc:todo isaretli her bolumu (Amac ve kapsam, Analiz ve kararlar — onaylanmis analiz ve kararlar, Menu ve ekranlar, Veri modeli, Is kurallari ve hesaplar, Yetki ve gorunurluk, Entegrasyonlar, Akis ornegi, Sik sorulanlar, Dosyalar ve geri alma) doldurup isaretleri kaldirarak gonder; bos kalacak bolume 'Yok.' yaz. 'Ek — Ekran envanteri' bolumunu gonderme, sunucu uretir. Ilk satirdaki '<!-- module-doc: claims=... -->' beyanini koru. Talep, dokumanlari tamamlanmadan update_row ile Testing'e alinamaz. Bolum standardi get_dev_guide lowcode 0.13.")] [Description("Modul dokumaninin elle yazilan bolumunu kaydeder (Markdown). Uretimin parcasidir: deploy_wizard'dan sonra get_module_doc ile ekranin bolumunu oku, module-doc:todo isaretli her alt bolumu (Amac ve kapsam, Analiz ve kararlar — onaylanmis analiz ve kararlar, Menu ve ekranlar, Veri modeli, Is kurallari ve hesaplar, Yetki ve gorunurluk, Entegrasyonlar, Akis ornegi, Sik sorulanlar, Dosyalar ve geri alma) doldurup isaretleri kaldirarak ayni ekran ya da dal koduyla gonder: yalnizca o bolum yazilir, diger dallara dokunulmaz. Bos kalacak alt bolume 'Yok.' yaz. 'Ek — Ekran envanteri' bolumunu gonderme, sunucu uretir. Bolumun '<!-- module-doc:section ... claims=... -->' beyan satirini gondermezsen mevcut beyan korunur. Kok menu koduyla (App.Hr) cagrilirsa dosyanin tamami yazilir. Talep, bolumu tamamlanmadan update_row ile Testing'e alinamaz. Bolum standardi get_dev_guide lowcode 0.13.")]
public async Task<ModuleDocDto> SaveModuleDocAsync( public async Task<ModuleDocDto> SaveModuleDocAsync(
[Description("Dokuman adi (HrPerformance), ekran kodu ya da wizard dosya adi.")] [Description("Ekran ya da dal kodu, wizard dosya adi (o bolum) veya kok menu kodu (App.Hr; dosyanin tamami).")]
string name, string name,
[Description("Dokumanin tamami, Markdown.")] [Description("Bolumun (ya da dosya adi verildiyse dosyanin) tamami, Markdown.")]
string content) string content)
=> await developAppService.SaveModuleDocAsync(name, content); => await developAppService.SaveModuleDocAsync(name, content);
/// <summary> /// <summary>
/// Talep metni JSON olarak, gorseller ayri resim icerigi olarak doner; model resmi ancak bu /// Talep metni JSON olarak, gorseller ayri resim icerigi olarak doner; model resmi ancak bu
/// bicimde gorur, base64 metni olarak goremez. /// bicimde gorur, base64 metni olarak goremez. Sonucun tamami istemcinin sinirina sigar: kalan
/// pay gorsellere esit bolunur, payini asan gorsel kucultulur, kucultulse de sigmayan gonderilmez
/// ve hangisinin eksik oldugu metinde bildirilir.
/// </summary> /// </summary>
private static List<ContentBlock> ToContent(McpDevRequestDto devRequest) private List<ContentBlock> ToContent(McpDevRequestDto devRequest)
{ {
List<ContentBlock> content = [new TextContentBlock { Text = JsonSerializer.Serialize(devRequest, TextOptions) }]; var text = JsonSerializer.Serialize(devRequest, TextOptions);
List<ContentBlock> content = [new TextContentBlock { Text = text }];
foreach (var image in devRequest.Images) var remaining = MaxResultLength - text.Length;
var omitted = new List<string>();
for (var i = 0; i < devRequest.Images.Count; i++)
{ {
var image = devRequest.Images[i];
var share = Math.Max(0, remaining) / (devRequest.Images.Count - i);
var shrunk = imageShrinker.Shrink(image.MimeType, image.Data, share);
if (shrunk is null)
{
omitted.Add(image.Placeholder);
continue;
}
remaining -= shrunk.Data.Length;
// Veri zaten base64; cozup yeniden kodlamak buyuk gorsellerde bosuna bellek harcar. // Veri zaten base64; cozup yeniden kodlamak buyuk gorsellerde bosuna bellek harcar.
content.Add(new ImageContentBlock { Data = Encoding.UTF8.GetBytes(image.Data), MimeType = image.MimeType }); content.Add(new ImageContentBlock { Data = Encoding.UTF8.GetBytes(shrunk.Data), MimeType = shrunk.MimeType });
}
if (omitted.Count > 0)
{
content.Add(new TextContentBlock
{
Text = $"Boyut siniri nedeniyle gonderilmeyen gorseller: {string.Join(", ", omitted)}. Gerekirse kullanicidan gorselin ne gosterdigini sor."
});
} }
return content; return content;

View file

@ -6,9 +6,9 @@ using Volo.Abp.Application.Services;
namespace Sozsoft.Platform.ListForms; namespace Sozsoft.Platform.ListForms;
/// <summary> /// <summary>
/// Modul dokumanlari (<c>docs/modules/{Modul}.md</c>, kapsam seed klasoru). Dokumanin elle ya da /// Modul dokumanlari (<c>docs/modules/{Menu}.md</c>, kok menu basina bir dosya; kokun altindaki her
/// AI ile yazilan bolumleri kaydedilir; "Ek — Ekran envanteri" bolumu wizard dosyalarindan uretilir /// dal dosyada bir bolum). Dokumanin elle ya da AI ile yazilan bolumleri kaydedilir; "Ek — Ekran
/// ve her deploy, silme, tasarimci kaydi ve import'ta yenilenir. /// envanteri" bolumu wizard dosyalarindan uretilir ve her deploy, silme, tasarimci kaydi ve import'ta yenilenir.
/// </summary> /// </summary>
public interface IModuleDocAppService : IApplicationService public interface IModuleDocAppService : IApplicationService
{ {
@ -16,13 +16,15 @@ public interface IModuleDocAppService : IApplicationService
Task<List<ModuleDocDto>> GetListAsync(); Task<List<ModuleDocDto>> GetListAsync();
/// <summary> /// <summary>
/// Dokumani adiyla (<c>HrPerformance</c>) ya da ait oldugu ekranin kodu/wizard dosya adiyla /// Dokumanin tamamini kok menu koduyla (<c>App.Hr</c>), bir dalin bolumunu dal kodu ya da ekranin
/// (<c>App.Hr.PerformanceReview</c>, <c>PerformanceReview.json</c>) dondurur. /// kodu/wizard dosya adiyla (<c>App.Hr.Performance</c>, <c>App.Hr.PerformanceReview</c>,
/// <c>PerformanceReview.json</c>) dondurur.
/// </summary> /// </summary>
Task<ModuleDocDto> GetByNameAsync(string name); Task<ModuleDocDto> GetByNameAsync(string name);
/// <summary> /// <summary>
/// Dokumanin elle yazilan bolumlerini kaydeder; otomatik envanter bolumu girdiden atilir ve /// Dokumanin elle yazilan bolumlerini kaydeder: ad dokumana isaret ediyorsa dosyanin tamami, dala
/// ya da ekrana isaret ediyorsa yalnizca o bolum yazilir. Otomatik envanter girdiden atilir ve
/// yeniden uretilir. /// yeniden uretilir.
/// </summary> /// </summary>
Task<ModuleDocDto> SaveAsync(ModuleDocSaveInput input); Task<ModuleDocDto> SaveAsync(ModuleDocSaveInput input);
@ -32,39 +34,62 @@ public interface IModuleDocAppService : IApplicationService
/// bolum var). Bos liste talebin dokumantasyonunun tamam oldugunu gosterir. /// bolum var). Bos liste talebin dokumantasyonunun tamam oldugunu gosterir.
/// </summary> /// </summary>
Task<List<ModuleDocDto>> GetIncompleteForDevRequestAsync(Guid devRequestId); Task<List<ModuleDocDto>> GetIncompleteForDevRequestAsync(Guid devRequestId);
/// <summary>
/// Talep analizini dalin bolumune ("2. Analiz ve kararlar") yazar; bolum ya da dosya yoksa acar.
/// Elle yazilmis alt bolume dokunulmaz.
/// </summary>
Task<ModuleDocDto> SaveAnalysisAsync(ModuleDocAnalysisInput input);
} }
public class ModuleDocDto public class ModuleDocDto
{ {
/// <summary>Dosya adi, uzantisiz (<c>HrPerformance</c>).</summary> /// <summary>Dosya adi, uzantisiz; kok menunun kodu (<c>App.Hr</c>).</summary>
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
/// <summary>Seed klasorune gore goreli yol (<c>docs/modules/HrPerformance.md</c>).</summary> /// <summary>Dalin bolum anahtari (<c>App.Hr.Performance</c>); dokumanin tamami istendiyse null.</summary>
public string? Section { get; set; }
/// <summary>Seed klasorune gore goreli yol (<c>docs/modules/App.Hr.md</c>).</summary>
public string Path { get; set; } = string.Empty; public string Path { get; set; } = string.Empty;
/// <summary>Dosya (bolum istendiyse dosyada bolum) var.</summary>
public bool Exists { get; set; } public bool Exists { get; set; }
/// <summary> /// <summary>
/// Dosyada doldurulmamis bolum yoksa true. Otomatik acilan iskeletin bolumleri /// Dosyada (bolum istendiyse bolumde) doldurulmamis alt bolum yoksa true. Otomatik acilan
/// <c>module-doc:todo</c> isaretini tasir; isaret kalmayinca dokuman tamam sayilir. /// iskeletin alt bolumleri <c>module-doc:todo</c> isaretini tasir; isaret kalmayinca tamam sayilir.
/// </summary> /// </summary>
public bool IsComplete { get; set; } public bool IsComplete { get; set; }
/// <summary>Doldurulmamis bolum basliklari.</summary> /// <summary>Doldurulmamis alt bolumler (<c>Bolum › Alt bolum</c>).</summary>
public List<string> MissingSections { get; set; } = []; public List<string> MissingSections { get; set; } = [];
/// <summary>Dokumana bagli ekran kodlari (wizard'larin ListFormCode / menu kodu).</summary> /// <summary>Dokumana (ya da bolume) bagli ekran kodlari (wizard'larin ListFormCode / menu kodu).</summary>
public List<string> Wizards { get; set; } = []; public List<string> Wizards { get; set; } = [];
/// <summary>Dosyanin tamami; listede doldurulmaz.</summary> /// <summary>Dosyanin tamami ya da bolum ve envanteri; listede doldurulmaz.</summary>
public string? Content { get; set; } public string? Content { get; set; }
} }
public class ModuleDocSaveInput public class ModuleDocSaveInput
{ {
/// <summary>Dokuman adi ya da ait oldugu ekranin kodu/wizard dosya adi.</summary> /// <summary>Dokuman adi (dosyanin tamami) ya da dal/ekran kodu, wizard dosya adi (o dalin bolumu).</summary>
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
/// <summary>Markdown. Otomatik envanter bolumu varsa yok sayilir.</summary> /// <summary>Markdown. Otomatik envanter bolumu varsa yok sayilir.</summary>
public string Content { get; set; } = string.Empty; public string Content { get; set; } = string.Empty;
} }
public class ModuleDocAnalysisInput
{
/// <summary>Talebin menu yeri: dalin ya da ekranin kodu (<c>App.Inventory.Definitions</c>).</summary>
public string MenuCode { get; set; } = string.Empty;
public Guid DevRequestId { get; set; }
public string? DevRequestTitle { get; set; }
/// <summary>Analiz, HTML.</summary>
public string Html { get; set; } = string.Empty;
}

View file

@ -2752,7 +2752,7 @@ public class ListFormWizardAppService(
// host/object|execute); digerlerinde alt klasor yoktur. // host/object|execute); digerlerinde alt klasor yoktur.
if (isDocsFolder) if (isDocsFolder)
{ {
// Yalnizca modul dokumanlari: docs/modules/{Modul}.md. // Yalnizca modul dokumanlari: docs/modules/{Menu}.md.
if (segments.Length != 3 || !segments[1].Equals(SeedPathResolver.ModuleDocsFolder, StringComparison.OrdinalIgnoreCase)) if (segments.Length != 3 || !segments[1].Equals(SeedPathResolver.ModuleDocsFolder, StringComparison.OrdinalIgnoreCase))
return null; return null;
} }

View file

@ -27,4 +27,14 @@ public class ModuleDocAppService(ModuleDocManager manager) : PlatformAppService,
public Task<List<ModuleDocDto>> GetIncompleteForDevRequestAsync(Guid devRequestId) => public Task<List<ModuleDocDto>> GetIncompleteForDevRequestAsync(Guid devRequestId) =>
manager.GetIncompleteForDevRequestAsync(devRequestId); manager.GetIncompleteForDevRequestAsync(devRequestId);
/// <summary>
/// Talep analizinin parcasidir; talebi analiz edebilen (Wizard ekranini okuyabilen) kullanici
/// dokumanin iskeletini ve analiz alt bolumunu yazabilir, diger bolumler icin <see cref="SaveAsync"/> gerekir.
/// </summary>
public Task<ModuleDocDto> SaveAnalysisAsync(ModuleDocAnalysisInput input)
{
Check.NotNull(input, nameof(input));
return manager.SaveAnalysisAsync(input.MenuCode, input.DevRequestId, input.DevRequestTitle, input.Html);
}
} }

View file

@ -11,14 +11,19 @@ namespace Sozsoft.Platform.ListForms;
/// <summary> /// <summary>
/// Bir seed klasorundeki wizard dosyalarini ve modul dokumanlarini okur; her wizard'in hangi /// Bir seed klasorundeki wizard dosyalarini ve modul dokumanlarini okur; her wizard'in hangi
/// dokumana ait oldugunu cozer. Kural, wizard sozlesmesine alan eklemeden menu agacindan cikar: /// dokumana ve dokumanin hangi bolumune ait oldugunu cozer.
/// <para>
/// Dokuman kok menu basina tektir ve adi menu kodudur (<c>App.Hr</c> → <c>App.Hr.md</c>); kokun altindaki her dal
/// dokumanda bir bolumdur (<c>&lt;!-- module-doc:section App.Hr.Leave --&gt;</c> + <c>## </c> basligi).
/// Kural, wizard sozlesmesine alan eklemeden menu agacindan cikar:
/// </para>
/// <list type="number"> /// <list type="number">
/// <item>Bir dokuman ekranin kodunu basligindaki beyanla sahiplenmisse (<c>module-doc: claims=…</c>) o dokuman.</item> /// <item>Bir bolum ekranin kodunu beyanla sahiplenmisse (<c>module-doc:section … claims=…</c>) o bolum.</item>
/// <item>Menusuz alt form, onu <c>SubForms</c> icinde tasiyan ekranin dokumanina gider.</item> /// <item>Menusuz alt form, onu <c>SubForms</c> icinde tasiyan ekranin bolumune gider.</item>
/// <item>Ust menu zincirinde kokun altinda bir dal varsa (<c>App.Hr › App.Hr.Performance › …</c>) /// <item>Menu agacinda kokun altindaki ilk seviye (<c>App.Hr › App.Hr.Performance › …</c>) bolumdur;
/// dalin kodu, yoksa (ekran dogrudan kokun altinda) ekranin kendi kodu.</item> /// ekran dogrudan kokun altindaysa ekranin kendi kodu.</item>
/// </list> /// </list>
/// Dokuman adi koddan turer: bastaki <c>App.</c> atilir, noktalar silinir (<c>App.Hr.Performance</c> → <c>HrPerformance</c>). /// Ad sabit bir listeden degil menu agacindan gelir; yeni bir kok menu ilk deploy'unda kendi dosyasini acar.
/// </summary> /// </summary>
public sealed class ModuleDocCatalog public sealed class ModuleDocCatalog
{ {
@ -28,10 +33,20 @@ public sealed class ModuleDocCatalog
/// <summary>Doldurulmamis bolumun isareti; isaret kalmayan dokuman tamam sayilir.</summary> /// <summary>Doldurulmamis bolumun isareti; isaret kalmayan dokuman tamam sayilir.</summary>
public const string TodoMarker = "<!-- module-doc:todo -->"; public const string TodoMarker = "<!-- module-doc:todo -->";
private static readonly Regex ClaimsPattern = new(@"<!--\s*module-doc:\s*claims=(?<codes>[^>]*?)\s*-->", RegexOptions.Compiled); private static readonly Regex SectionPattern = new(
private static readonly Regex NamePattern = new("^[A-Za-z0-9_-]+$", RegexOptions.Compiled); @"^<!--\s*module-doc:section\s+(?<key>[^\s>]+?)(?:\s+claims=(?<codes>[^>]*?))?\s*-->[ \t]*$",
RegexOptions.Compiled | RegexOptions.Multiline);
private readonly Dictionary<string, string> claims = new(StringComparer.OrdinalIgnoreCase); private static readonly Regex NamePattern = new(@"^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$", RegexOptions.Compiled);
/// <summary>Ekran kodu → (dokuman, bolum) beyanlari.</summary>
private readonly Dictionary<string, (string Doc, string Section)> claims = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Menu kodu → ust menu kodu ve sirasi (<c>data/App.Menus.Menu.json</c>).</summary>
private readonly Dictionary<string, (string? Parent, int Order)> menus = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Menu kodu → Turkce adi (<c>data/App.Languages.LanguageText.json</c>); bolum basliklari icin.</summary>
private readonly Dictionary<string, string> menuTitles = new(StringComparer.OrdinalIgnoreCase);
private ModuleDocCatalog(string scopeRoot) private ModuleDocCatalog(string scopeRoot)
{ {
@ -67,19 +82,71 @@ public sealed class ModuleDocCatalog
} }
} }
await catalog.LoadMenusAsync();
foreach (var name in catalog.ExistingDocNames()) foreach (var name in catalog.ExistingDocNames())
{ {
var match = ClaimsPattern.Match(await File.ReadAllTextAsync(catalog.DocPath(name))); foreach (var section in ParseSections(await File.ReadAllTextAsync(catalog.DocPath(name))))
if (!match.Success) {
continue; catalog.claims.TryAdd(section.Key, (name, section.Key));
foreach (var code in section.Claims)
foreach (var code in match.Groups["codes"].Value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) catalog.claims.TryAdd(code, (name, section.Key));
catalog.claims.TryAdd(code, name); }
} }
return catalog; return catalog;
} }
/// <summary>
/// Menu agaci ve menu adlari seed dosyalarindan okunur; wizard deploy'u menu ve dil satirlarini
/// ayni dosyalara yazdigi icin yeni bir dal da migrate beklemeden gorunur.
/// </summary>
private async Task LoadMenusAsync()
{
foreach (var row in await ReadRowsAsync("App.Menus.Menu.json"))
{
var code = Text(row, "Code");
if (code == null)
continue;
menus[code] = (Text(row, "ParentCode"), row.TryGetProperty("Order", out var order) && order.TryGetInt32(out var value) ? value : int.MaxValue);
}
foreach (var row in await ReadRowsAsync("App.Languages.LanguageText.json"))
{
var key = Text(row, "Key");
if (key == null || !menus.ContainsKey(key))
continue;
if (string.Equals(Text(row, "CultureName"), "tr", StringComparison.OrdinalIgnoreCase) && Text(row, "Value") is { } title)
menuTitles[key] = title;
}
}
private async Task<List<JsonElement>> ReadRowsAsync(string fileName)
{
var path = Path.Combine(ScopeRoot, SeedPathResolver.DataFolder, fileName);
if (!File.Exists(path))
return [];
try
{
using var document = JsonDocument.Parse(await File.ReadAllTextAsync(path));
return document.RootElement.TryGetProperty("Rows", out var rows) && rows.ValueKind == JsonValueKind.Array
? [.. rows.EnumerateArray().Select(r => r.Clone())]
: [];
}
catch (JsonException)
{
return [];
}
}
private static string? Text(JsonElement row, string property) =>
row.TryGetProperty(property, out var value) && value.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(value.GetString())
? value.GetString()!.Trim()
: null;
public IEnumerable<string> ExistingDocNames() => public IEnumerable<string> ExistingDocNames() =>
Directory.Exists(DocsPath) Directory.Exists(DocsPath)
? Directory.GetFiles(DocsPath, "*.md").Select(Path.GetFileNameWithoutExtension).OfType<string>() ? Directory.GetFiles(DocsPath, "*.md").Select(Path.GetFileNameWithoutExtension).OfType<string>()
@ -91,26 +158,33 @@ public sealed class ModuleDocCatalog
public static bool IsValidName(string? name) => !string.IsNullOrWhiteSpace(name) && NamePattern.IsMatch(name); public static bool IsValidName(string? name) => !string.IsNullOrWhiteSpace(name) && NamePattern.IsMatch(name);
/// <summary><c>App.Hr.Performance</c> → <c>HrPerformance</c>.</summary> /// <summary>
public static string NameFromCode(string code) /// Kok menunun dokuman adi: menu kodunun kendisi (<c>App.Hr</c> → <c>App.Hr.md</c>). Dosya adina
{ /// girmeyecek karakterler atilir.
var trimmed = code.Trim(); /// </summary>
if (trimmed.StartsWith("App.", StringComparison.OrdinalIgnoreCase)) public static string DocNameOfRoot(string rootCode) =>
trimmed = trimmed[4..]; string.Concat(rootCode.Trim().Where(c => char.IsLetterOrDigit(c) || c is '.' or '_' or '-'));
return string.Concat(trimmed.Where(char.IsLetterOrDigit)); /// <summary>Menu kodunun Turkce adi; dil satiri yoksa null.</summary>
} public string? MenuTitle(string code) => menuTitles.GetValueOrDefault(code);
/// <summary>Bolumun kok menu altindaki sirasi; menude olmayan bolum (ornegin elle yazilmis) sona gider.</summary>
public int SectionOrder(string sectionKey) => menus.TryGetValue(sectionKey, out var menu) ? menu.Order : int.MaxValue;
/// <summary>Wizard'in ait oldugu dokumanin adi.</summary> /// <summary>Wizard'in ait oldugu dokumanin adi.</summary>
public string Resolve(ListFormWizardDto wizard) => Resolve(wizard, new HashSet<string>(StringComparer.OrdinalIgnoreCase)); public string Resolve(ListFormWizardDto wizard) => ResolveTarget(wizard).Doc;
private string Resolve(ListFormWizardDto wizard, HashSet<string> visited) /// <summary>Wizard'in ait oldugu dokuman, bolum ve kok menu.</summary>
public (string Doc, string Section, string Root) ResolveTarget(ListFormWizardDto wizard) =>
ResolveTarget(wizard, new HashSet<string>(StringComparer.OrdinalIgnoreCase));
private (string Doc, string Section, string Root) ResolveTarget(ListFormWizardDto wizard, HashSet<string> visited)
{ {
var listFormCode = wizard.ResolveListFormCode(); var listFormCode = wizard.ResolveListFormCode();
var menuCode = wizard.ResolveMenuCode(); var menuCode = wizard.ResolveMenuCode();
if (claims.TryGetValue(listFormCode, out var claimed) || claims.TryGetValue(menuCode, out claimed)) if (claims.TryGetValue(listFormCode, out var claimed) || claims.TryGetValue(menuCode, out claimed))
return claimed; return (claimed.Doc, claimed.Section, RootOfDoc(claimed.Doc, wizard));
visited.Add(listFormCode); visited.Add(listFormCode);
@ -121,30 +195,80 @@ public sealed class ModuleDocCatalog
.FirstOrDefault(w => !visited.Contains(w.ResolveListFormCode()) && .FirstOrDefault(w => !visited.Contains(w.ResolveListFormCode()) &&
(w.SubForms ?? []).Any(s => string.Equals(s.Code, listFormCode, StringComparison.OrdinalIgnoreCase))); (w.SubForms ?? []).Any(s => string.Equals(s.Code, listFormCode, StringComparison.OrdinalIgnoreCase)));
if (parent != null) if (parent != null)
return Resolve(parent, visited); return ResolveTarget(parent, visited);
} }
return NameFromCode(BranchCode(wizard)); var path = MenuPath(wizard);
} var root = path[0];
var section = path.Count >= 2 ? path[1] : menuCode;
/// <summary>Wizard'in dokumanini tasiyan menu dali: kokun altindaki ilk seviye ya da ekranin kendisi.</summary> return (DocNameOfRoot(root), section, root);
public static string BranchCode(ListFormWizardDto wizard)
{
var chain = wizard.ResolveMenuParentChain();
return chain.Count >= 2 ? chain[1].Code : wizard.ResolveMenuCode();
} }
/// <summary>Dokuman adlarina gore gruplanmis wizard'lar.</summary>
public Dictionary<string, List<(string FileName, WizardSeedFileDto Seed)>> GroupByDoc() =>
Wizards
.GroupBy(w => Resolve(w.Seed.Wizard), StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.ToList(), StringComparer.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Ad, var olan dokuman adi ya da bir wizard'a (dosya adi, ekran ya da menu kodu) isaret /// Ekranin kokten kendisine menu yolu. Menu tablosu zinciri tamamlar: wizard'da yalnizca dogrudan
/// edebilir; ikincisinde wizard'in dokumani doner. /// ust menu yazili olsa da kok bulunur. Tabloda olmayan seviyeler wizard'daki zincirden gelir.
/// </summary> /// </summary>
public string? FindName(string? input) private List<string> MenuPath(ListFormWizardDto wizard)
{
var chain = wizard.ResolveMenuParentChain().Select(l => l.Code).ToList();
var path = new List<string>();
var current = chain.Count > 0 ? chain[^1] : null;
var guard = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
while (current != null && menus.TryGetValue(current, out var menu) && guard.Add(current))
{
path.Insert(0, current);
current = string.IsNullOrWhiteSpace(menu.Parent) ? null : menu.Parent;
}
if (path.Count == 0 || chain.Count > path.Count)
path = chain;
path.Add(wizard.ResolveMenuCode());
return path;
}
private string RootOfDoc(string doc, ListFormWizardDto wizard)
{
var root = menus.Where(m => string.IsNullOrWhiteSpace(m.Value.Parent)).Select(m => m.Key)
.FirstOrDefault(code => string.Equals(DocNameOfRoot(code), doc, StringComparison.OrdinalIgnoreCase));
return root ?? MenuPath(wizard)[0];
}
/// <summary>Dokuman adina, sonra bolum anahtarina gore gruplanmis wizard'lar.</summary>
public Dictionary<string, DocGroup> GroupByDoc()
{
var result = new Dictionary<string, DocGroup>(StringComparer.OrdinalIgnoreCase);
foreach (var wizard in Wizards)
{
var (doc, section, root) = ResolveTarget(wizard.Seed.Wizard);
if (!result.TryGetValue(doc, out var group))
result[doc] = group = new DocGroup(root);
if (!group.Sections.TryGetValue(section, out var list))
group.Sections[section] = list = [];
list.Add(wizard);
}
return result;
}
public sealed class DocGroup(string rootCode)
{
public string RootCode { get; } = rootCode;
public Dictionary<string, List<(string FileName, WizardSeedFileDto Seed)>> Sections { get; } = new(StringComparer.OrdinalIgnoreCase);
public IEnumerable<(string FileName, WizardSeedFileDto Seed)> All => Sections.Values.SelectMany(v => v);
}
/// <summary>
/// Girdinin isaret ettigi dokuman ve (varsa) bolum. Girdi dokuman adi, bolum anahtari ya da bir
/// wizard (dosya adi, ekran ya da menu kodu) olabilir; son ikisinde bolum de doner.
/// </summary>
public (string Doc, string? Section)? Find(string? input)
{ {
if (string.IsNullOrWhiteSpace(input)) if (string.IsNullOrWhiteSpace(input))
return null; return null;
@ -155,7 +279,7 @@ public sealed class ModuleDocCatalog
var existing = ExistingDocNames().FirstOrDefault(n => string.Equals(n, value, StringComparison.OrdinalIgnoreCase)); var existing = ExistingDocNames().FirstOrDefault(n => string.Equals(n, value, StringComparison.OrdinalIgnoreCase));
if (existing != null) if (existing != null)
return existing; return (existing, null);
var wizard = Wizards.FirstOrDefault(w => var wizard = Wizards.FirstOrDefault(w =>
string.Equals(w.FileName, value, StringComparison.OrdinalIgnoreCase) || string.Equals(w.FileName, value, StringComparison.OrdinalIgnoreCase) ||
@ -164,9 +288,44 @@ public sealed class ModuleDocCatalog
string.Equals(w.Seed.Wizard.ResolveListFormCode(), value, StringComparison.OrdinalIgnoreCase) || string.Equals(w.Seed.Wizard.ResolveListFormCode(), value, StringComparison.OrdinalIgnoreCase) ||
string.Equals(w.Seed.Wizard.ResolveMenuCode(), value, StringComparison.OrdinalIgnoreCase)); string.Equals(w.Seed.Wizard.ResolveMenuCode(), value, StringComparison.OrdinalIgnoreCase));
if (wizard.Seed != null) if (wizard.Seed != null)
return Resolve(wizard.Seed.Wizard); {
var target = ResolveTarget(wizard.Seed.Wizard);
return (target.Doc, target.Section);
}
return IsValidName(value) ? value : null; // Bolum anahtari (wizard'i olmayan elle yazilmis bolum de dahil) ya da bir dal kodu.
if (claims.TryGetValue(value, out var claimed))
return (claimed.Doc, claimed.Section);
var branch = Wizards
.Select(w => ResolveTarget(w.Seed.Wizard))
.FirstOrDefault(t => string.Equals(t.Section, value, StringComparison.OrdinalIgnoreCase));
if (branch.Doc != null)
return (branch.Doc, branch.Section);
// Wizard'i olmayan menu kodu: kok ise dosyanin tamami, degilse kokun altindaki dalin bolumu
// (entity tabanli dalin bolumu elle boyle acilir).
var path = new List<string>();
for (var code = value; code != null && menus.TryGetValue(code, out var menu) && !path.Contains(code); code = menu.Parent)
path.Insert(0, code);
// Menude henuz olmayan kod (analizi yapilan yeni modul): kok ve dal menu kodu biciminden
// cikarilir (App.Inventory.Stock → App.Inventory › App.Inventory.Stock); deploy menuyu yazinca
// ayni dosya ve bolum bulunur.
if (path.Count == 0)
{
var segments = value.Split('.');
if (!IsValidName(value) || segments.Length < 2)
return null;
path = [string.Join('.', segments[..2])];
if (segments.Length > 2)
path.Add(string.Join('.', segments[..3]));
}
return path.Count == 1
? (DocNameOfRoot(path[0]), null)
: (DocNameOfRoot(path[0]), path[1]);
} }
/// <summary>Otomatik envanter bolumu cikarilmis icerik.</summary> /// <summary>Otomatik envanter bolumu cikarilmis icerik.</summary>
@ -182,27 +341,65 @@ public sealed class ModuleDocCatalog
return content[..begin].TrimEnd() + tail; return content[..begin].TrimEnd() + tail;
} }
/// <summary>Doldurulmamis bolumlerin basliklari (<see cref="TodoMarker"/> tasiyan bolumler).</summary> /// <summary>Dokumanin bolumleri (envanter haric), dosyadaki sirayla.</summary>
public static List<string> MissingSections(string content) public static List<DocSection> ParseSections(string content)
{
var manual = StripAuto(content.Replace("\r\n", "\n"));
var matches = SectionPattern.Matches(manual);
var sections = new List<DocSection>();
for (var i = 0; i < matches.Count; i++)
{
var start = matches[i].Index;
var end = i + 1 < matches.Count ? matches[i + 1].Index : manual.Length;
var text = manual[start..end].TrimEnd() + "\n";
var codes = matches[i].Groups["codes"].Success
? matches[i].Groups["codes"].Value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
: [];
var heading = text.Split('\n').FirstOrDefault(l => l.StartsWith("## ", StringComparison.Ordinal));
sections.Add(new DocSection(matches[i].Groups["key"].Value, [.. codes], heading?[3..].Trim() ?? matches[i].Groups["key"].Value, start, end, text));
}
return sections;
}
/// <summary>Dokumandaki bir bolum: anahtar, sahiplendigi ekran kodlari, basligi ve metni.</summary>
public sealed record DocSection(string Key, IReadOnlyList<string> Claims, string Title, int Start, int End, string Text)
{
/// <summary>Doldurulmamis alt bolumler (<see cref="TodoMarker"/> tasiyan <c>### </c> basliklari).</summary>
public List<string> Missing()
{ {
var missing = new List<string>(); var missing = new List<string>();
string? heading = null; string? heading = null;
foreach (var line in StripAuto(content).Split('\n')) foreach (var line in Text.Split('\n'))
{ {
if (line.StartsWith("## ", StringComparison.Ordinal)) if (line.StartsWith("### ", StringComparison.Ordinal))
heading = line[3..].Trim(); heading = line[4..].Trim();
else if (line.Contains(TodoMarker, StringComparison.Ordinal) && heading != null && !missing.Contains(heading)) else if (line.Contains(TodoMarker, StringComparison.Ordinal))
missing.Add(heading); {
var item = $"{Title} › {heading ?? Title}";
if (!missing.Contains(item))
missing.Add(item);
}
} }
return missing; return missing;
} }
/// <summary>Hicbir bolumu doldurulmamis (otomatik acildigi gibi duran) dokuman.</summary> /// <summary>Hicbir alt bolumu doldurulmamis (otomatik acildigi gibi duran) bolum.</summary>
public static bool IsUntouchedSkeleton(string content) public bool IsUntouchedSkeleton
{ {
var sections = StripAuto(content).Split('\n').Count(l => l.StartsWith("## ", StringComparison.Ordinal)); get
return sections > 0 && MissingSections(content).Count == sections; {
var subSections = Text.Split('\n').Count(l => l.StartsWith("### ", StringComparison.Ordinal));
return subSections > 0 && Missing().Count == subSections;
} }
}
}
/// <summary>Doldurulmamis bolumlerin basliklari (<c>Bolum › Alt bolum</c>).</summary>
public static List<string> MissingSections(string content) =>
[.. ParseSections(content).SelectMany(s => s.Missing())];
} }

View file

@ -13,9 +13,10 @@ using Volo.Abp.DependencyInjection;
namespace Sozsoft.Platform.ListForms; namespace Sozsoft.Platform.ListForms;
/// <summary> /// <summary>
/// Modul dokumanlarinin dosya islemleri. Wizard deploy'u, silme, tasarimci senkronizasyonu ve /// Modul dokumanlarinin dosya islemleri. Dokuman kok menu basina tektir (<c>{Menu}.md</c>), kokun
/// import kapanisi <see cref="RefreshAllAsync"/> cagirir: wizard'i olan her dokumanin envanteri /// altindaki her dal dokumanda bir bolumdur. Wizard deploy'u, silme, tasarimci senkronizasyonu ve
/// yeniden yazilir, dokumani olmayan modul icin iskelet acilir. Elle yazilan bolumlere dokunulmaz. /// import kapanisi <see cref="RefreshAllAsync"/> cagirir: yeni kok menu icin dokuman, yeni dal icin
/// bolum iskeleti acilir, envanter yeniden yazilir. Elle yazilan bolumlere dokunulmaz.
/// </summary> /// </summary>
public class ModuleDocManager( public class ModuleDocManager(
IConfiguration configuration, IConfiguration configuration,
@ -23,6 +24,9 @@ public class ModuleDocManager(
{ {
private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false); private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false);
/// <summary>Analizin yazdigi alt bolumun isareti; isaret tasiyan govde sonraki analizde yenilenir.</summary>
private const string AnalysisMarker = "<!-- module-doc:analysis";
private string ScopeRoot => Path.GetFullPath(SeedPathResolver.GetRootPath(configuration)); private string ScopeRoot => Path.GetFullPath(SeedPathResolver.GetRootPath(configuration));
public Task<ModuleDocCatalog> LoadCatalogAsync() => ModuleDocCatalog.LoadAsync(ScopeRoot); public Task<ModuleDocCatalog> LoadCatalogAsync() => ModuleDocCatalog.LoadAsync(ScopeRoot);
@ -40,7 +44,7 @@ public class ModuleDocManager(
var names = groups.Keys.Union(catalog.ExistingDocNames(), StringComparer.OrdinalIgnoreCase).ToList(); var names = groups.Keys.Union(catalog.ExistingDocNames(), StringComparer.OrdinalIgnoreCase).ToList();
foreach (var name in names) foreach (var name in names)
await RefreshAsync(catalog, name, groups.GetValueOrDefault(name) ?? []); await RefreshAsync(catalog, name, groups.GetValueOrDefault(name));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -48,37 +52,42 @@ public class ModuleDocManager(
} }
} }
private async Task RefreshAsync(ModuleDocCatalog catalog, string name, List<(string FileName, WizardSeedFileDto Seed)> wizards) private async Task RefreshAsync(ModuleDocCatalog catalog, string name, ModuleDocCatalog.DocGroup? group)
{ {
if (!ModuleDocCatalog.IsValidName(name)) if (!ModuleDocCatalog.IsValidName(name))
return; return;
var path = catalog.DocPath(name); var path = catalog.DocPath(name);
var existing = File.Exists(path) ? await File.ReadAllTextAsync(path) : null; var existing = File.Exists(path) ? Normalize(await File.ReadAllTextAsync(path)) : null;
var hasWizards = group != null && group.Sections.Count > 0;
// Wizard'i olmayan elle yazilmis dokuman (entity tabanli modul) envanter tasimaz; ona dokunulmaz. // Wizard'i olmayan elle yazilmis dokuman envanter tasimaz; ona dokunulmaz.
if (existing == null && wizards.Count == 0) if (existing == null && !hasWizards)
return; return;
if (existing != null && wizards.Count == 0 && !existing.Contains(ModuleDocCatalog.AutoBegin, StringComparison.Ordinal)) if (existing != null && !hasWizards && !existing.Contains(ModuleDocCatalog.AutoBegin, StringComparison.Ordinal))
return; return;
var manual = existing == null var manual = existing == null
? Skeleton(wizards) ? ModuleDocRenderer.DocSkeleton(RootTitle(catalog, group!), group!.RootCode)
: ModuleDocCatalog.StripAuto(Normalize(existing)); : ModuleDocCatalog.StripAuto(existing);
if (wizards.Count == 0 && ModuleDocCatalog.IsUntouchedSkeleton(manual)) manual = SyncSections(catalog, manual, group);
var sections = ModuleDocCatalog.ParseSections(manual);
if (!hasWizards && sections.Count == 0)
{ {
// Wizard'i silinmis ve hic yazilmamis iskelet: geride bos dokuman birakilmaz. // Wizard'lari silinmis ve hic yazilmamis bolumler kaldirildi: geride bos dokuman birakilmaz.
File.Delete(path); File.Delete(path);
logger.LogInformation("Module doc removed (no wizards, never written): {Path}", path); logger.LogInformation("Module doc removed (no wizards, never written): {Path}", path);
return; return;
} }
var content = wizards.Count == 0 var content = !hasWizards
? manual.TrimEnd() + "\n" ? manual.TrimEnd() + "\n"
: manual.TrimEnd() + "\n\n" + ModuleDocRenderer.Inventory(wizards); : manual.TrimEnd() + "\n\n" + ModuleDocRenderer.Inventory(sections.Select(s =>
(s.Title, (IReadOnlyList<(string FileName, WizardSeedFileDto Seed)>)(group!.Sections.GetValueOrDefault(s.Key) ?? []))));
if (existing != null && Normalize(existing) == content) if (existing == content)
return; return;
Directory.CreateDirectory(catalog.DocsPath); Directory.CreateDirectory(catalog.DocsPath);
@ -86,12 +95,119 @@ public class ModuleDocManager(
logger.LogInformation("Module doc written: {Path}", path); logger.LogInformation("Module doc written: {Path}", path);
} }
private static string Skeleton(List<(string FileName, WizardSeedFileDto Seed)> wizards) /// <summary>
/// Wizard'i olan her dal icin bolum bulunur, yoksa iskeleti menu sirasindaki yerine eklenir;
/// wizard'i kalmamis ve hic yazilmamis bolum kaldirilir.
/// </summary>
private static string SyncSections(ModuleDocCatalog catalog, string manual, ModuleDocCatalog.DocGroup? group)
{ {
var branch = ModuleDocCatalog.BranchCode(wizards[0].Seed.Wizard); var sections = ModuleDocCatalog.ParseSections(manual);
return ModuleDocRenderer.Skeleton(ModuleDocRenderer.Title(wizards.Select(w => w.Seed), branch), branch); var wizardSections = group?.Sections ?? [];
foreach (var stale in sections.Where(s => !wizardSections.ContainsKey(s.Key) && s.IsUntouchedSkeleton).OrderByDescending(s => s.Start))
manual = manual[..stale.Start] + manual[stale.End..];
var missing = wizardSections.Keys
.Where(key => !ModuleDocCatalog.ParseSections(manual).Any(s => string.Equals(s.Key, key, StringComparison.OrdinalIgnoreCase)))
.OrderBy(catalog.SectionOrder)
.ThenBy(key => key, StringComparer.OrdinalIgnoreCase);
foreach (var key in missing)
{
manual = InsertSection(catalog, manual, key, ModuleDocRenderer.SectionSkeleton(
ModuleDocRenderer.Title(catalog, wizardSections[key].Select(w => w.Seed), key), key));
} }
return manual;
}
/// <summary>
/// Bolumu menu sirasindaki yerine ekler. Menude karsiligi olmayan (elle yazilmis) bolumler yerini
/// korur; yeni bolum menu sirasi kendisinden buyuk ilk dalin onune girer.
/// </summary>
private static string InsertSection(ModuleDocCatalog catalog, string manual, string key, string text)
{
var order = catalog.SectionOrder(key);
var next = ModuleDocCatalog.ParseSections(manual).FirstOrDefault(s =>
catalog.SectionOrder(s.Key) is var sectionOrder && sectionOrder != int.MaxValue && sectionOrder > order);
return next == null
? manual.TrimEnd() + "\n\n" + text
: manual[..next.Start] + text + "\n" + manual[next.Start..];
}
/// <summary>
/// Talep analizini modulun dokumanina tasir: dalin bolumu yoksa (dosyasi dahil) acilir, analiz
/// "2. Analiz ve kararlar" alt bolumune yazilir. Alt bolum elle yazilmissa korunur; yalnizca
/// doldurulmamis ya da daha once bir analizle yazilmis alt bolum yenilenir.
/// </summary>
public async Task<ModuleDocDto> SaveAnalysisAsync(string code, Guid devRequestId, string? title, string html)
{
var catalog = await LoadCatalogAsync();
var (name, sectionKey) = catalog.Find(code) ?? throw new UserFriendlyException($"Geçersiz menü kodu: {code}");
if (sectionKey == null)
throw new UserFriendlyException($"Analizin yazılacağı dal belirsiz: {code}. Kök menünün altındaki dalın ya da ekranın kodunu verin.");
var path = catalog.DocPath(name);
var manual = File.Exists(path)
? ModuleDocCatalog.StripAuto(Normalize(await File.ReadAllTextAsync(path)))
: ModuleDocRenderer.DocSkeleton(catalog.MenuTitle(name) ?? name, name);
var group = catalog.GroupByDoc().GetValueOrDefault(name);
var section = ModuleDocCatalog.ParseSections(manual).FirstOrDefault(s => string.Equals(s.Key, sectionKey, StringComparison.OrdinalIgnoreCase));
if (section == null)
{
var seeds = group?.Sections.GetValueOrDefault(sectionKey)?.Select(w => w.Seed) ?? [];
manual = InsertSection(catalog, manual, sectionKey, ModuleDocRenderer.SectionSkeleton(ModuleDocRenderer.Title(catalog, seeds, sectionKey), sectionKey));
section = ModuleDocCatalog.ParseSections(manual).First(s => string.Equals(s.Key, sectionKey, StringComparison.OrdinalIgnoreCase));
}
var body = $"{AnalysisMarker} {devRequestId} -->\n_Talep: {title}_\n\n{html.Trim()}\n";
var updated = ReplaceSubSection(section.Text, ModuleDocRenderer.Sections[1], body);
if (updated != null)
manual = manual[..section.Start] + updated + (section.End < manual.Length ? "\n" + manual[section.End..].TrimStart('\n') : string.Empty);
Directory.CreateDirectory(catalog.DocsPath);
await File.WriteAllTextAsync(path, manual.TrimEnd() + "\n", Utf8NoBom);
catalog = await LoadCatalogAsync();
var groups = catalog.GroupByDoc();
await RefreshAsync(catalog, name, groups.GetValueOrDefault(name));
return await ToDtoAsync(catalog, name, sectionKey, groups, includeContent: false);
}
/// <summary>
/// Bolumdeki <c>### {heading}</c> alt bolumunun govdesini degistirir. Govde doldurulmamissa ya da
/// bir analizle yazilmissa yenilenir, elle yazilmissa null doner (dokunulmaz).
/// </summary>
private static string? ReplaceSubSection(string sectionText, string heading, string body)
{
var lines = sectionText.TrimEnd('\n').Split('\n').ToList();
var start = lines.FindIndex(l => l.StartsWith("### ", StringComparison.Ordinal) && l[4..].Trim() == heading);
if (start < 0)
return sectionText.TrimEnd() + $"\n\n### {heading}\n\n{body}";
var end = lines.FindIndex(start + 1, l => l.StartsWith("### ", StringComparison.Ordinal));
if (end < 0)
end = lines.Count;
var current = string.Join('\n', lines.Skip(start + 1).Take(end - start - 1));
if (!current.Contains(ModuleDocCatalog.TodoMarker, StringComparison.Ordinal) && !current.Contains(AnalysisMarker, StringComparison.Ordinal))
return null;
lines.RemoveRange(start + 1, end - start - 1);
lines.Insert(start + 1, string.Empty);
lines.Insert(start + 2, body.TrimEnd('\n'));
if (start + 3 < lines.Count)
lines.Insert(start + 3, string.Empty);
return string.Join('\n', lines) + "\n";
}
private static string RootTitle(ModuleDocCatalog catalog, ModuleDocCatalog.DocGroup group) =>
ModuleDocRenderer.Title(catalog, group.All.Select(w => w.Seed), group.RootCode);
public async Task<List<ModuleDocDto>> GetListAsync() public async Task<List<ModuleDocDto>> GetListAsync()
{ {
var catalog = await LoadCatalogAsync(); var catalog = await LoadCatalogAsync();
@ -99,28 +215,50 @@ public class ModuleDocManager(
var result = new List<ModuleDocDto>(); var result = new List<ModuleDocDto>();
foreach (var name in groups.Keys.Union(catalog.ExistingDocNames(), StringComparer.OrdinalIgnoreCase).OrderBy(n => n, StringComparer.OrdinalIgnoreCase)) foreach (var name in groups.Keys.Union(catalog.ExistingDocNames(), StringComparer.OrdinalIgnoreCase).OrderBy(n => n, StringComparer.OrdinalIgnoreCase))
result.Add(await ToDtoAsync(catalog, name, groups, includeContent: false)); result.Add(await ToDtoAsync(catalog, name, null, groups, includeContent: false));
return result; return result;
} }
/// <summary>
/// Dokuman adi verilirse dosyanin tamami, ekran/dal kodu verilirse yalnizca o dalin bolumu
/// (ve bolumun envanteri) doner.
/// </summary>
public async Task<ModuleDocDto> GetAsync(string input) public async Task<ModuleDocDto> GetAsync(string input)
{ {
var catalog = await LoadCatalogAsync(); var catalog = await LoadCatalogAsync();
var name = catalog.FindName(input) ?? throw new UserFriendlyException($"Modül dokümanı bulunamadı: {input}"); var (name, section) = catalog.Find(input) ?? throw new UserFriendlyException($"Modül dokümanı bulunamadı: {input}");
return await ToDtoAsync(catalog, name, catalog.GroupByDoc(), includeContent: true); return await ToDtoAsync(catalog, name, section, catalog.GroupByDoc(), includeContent: true);
} }
/// <summary>
/// Dokuman adi verilirse dosyanin tamami, ekran/dal kodu verilirse yalnizca o dalin bolumu
/// yazilir; envanter girdiden atilir ve yeniden uretilir.
/// </summary>
public async Task<ModuleDocDto> SaveAsync(string input, string content) public async Task<ModuleDocDto> SaveAsync(string input, string content)
{ {
var catalog = await LoadCatalogAsync(); var catalog = await LoadCatalogAsync();
var name = catalog.FindName(input) ?? throw new UserFriendlyException($"Geçersiz modül dokümanı adı: {input}"); var (name, section) = catalog.Find(input) ?? throw new UserFriendlyException($"Geçersiz modül dokümanı adı: {input}");
if (string.IsNullOrWhiteSpace(content)) if (string.IsNullOrWhiteSpace(content))
throw new UserFriendlyException("Doküman içeriği boş olamaz."); throw new UserFriendlyException("Doküman içeriği boş olamaz.");
var path = catalog.DocPath(name); var path = catalog.DocPath(name);
var manual = ModuleDocCatalog.StripAuto(Normalize(content)).TrimEnd() + "\n";
// Bolum yazilacaksa dosya ve bolum iskeleti once acilir; bolum ona gore yerine konur.
if (section != null && !File.Exists(path))
await RefreshAsync(catalog, name, catalog.GroupByDoc().GetValueOrDefault(name));
// Henuz deploy edilmemis modulun (analiz) dosyasi da burada acilir: kok basligi + bolum.
var text = ModuleDocCatalog.StripAuto(Normalize(content)).TrimEnd() + "\n";
var manual = section == null
? text
: ReplaceSection(
File.Exists(path)
? ModuleDocCatalog.StripAuto(Normalize(await File.ReadAllTextAsync(path)))
: ModuleDocRenderer.DocSkeleton(catalog.MenuTitle(name) ?? name, name),
section,
text);
Directory.CreateDirectory(catalog.DocsPath); Directory.CreateDirectory(catalog.DocsPath);
await File.WriteAllTextAsync(path, manual, Utf8NoBom); await File.WriteAllTextAsync(path, manual, Utf8NoBom);
@ -128,9 +266,31 @@ public class ModuleDocManager(
// Envanter dosyaya yeniden eklenir; sahiplik beyani degismis olabilecegi icin katalog tazelenir. // Envanter dosyaya yeniden eklenir; sahiplik beyani degismis olabilecegi icin katalog tazelenir.
catalog = await LoadCatalogAsync(); catalog = await LoadCatalogAsync();
var groups = catalog.GroupByDoc(); var groups = catalog.GroupByDoc();
await RefreshAsync(catalog, name, groups.GetValueOrDefault(name) ?? []); await RefreshAsync(catalog, name, groups.GetValueOrDefault(name));
return await ToDtoAsync(catalog, name, groups, includeContent: true); return await ToDtoAsync(catalog, name, section, groups, includeContent: true);
}
/// <summary>
/// Bolumu yeni metniyle degistirir. Metin beyan satiriyla baslamiyorsa bolumun mevcut beyani
/// (sahiplendigi kodlar dahil) korunur.
/// </summary>
private static string ReplaceSection(string manual, string sectionKey, string text)
{
var current = ModuleDocCatalog.ParseSections(manual)
.FirstOrDefault(s => string.Equals(s.Key, sectionKey, StringComparison.OrdinalIgnoreCase));
if (!text.TrimStart().StartsWith("<!-- module-doc:section", StringComparison.Ordinal))
{
var marker = current?.Text.Split('\n')[0] ?? $"<!-- module-doc:section {sectionKey} -->";
text = marker + "\n" + text.TrimStart();
}
if (current == null)
return manual.TrimEnd() + "\n\n" + text;
var tail = manual[current.End..];
return manual[..current.Start] + text + (tail.Length > 0 ? "\n" + tail.TrimStart('\n') : string.Empty);
} }
public async Task<List<ModuleDocDto>> GetIncompleteForDevRequestAsync(Guid devRequestId) public async Task<List<ModuleDocDto>> GetIncompleteForDevRequestAsync(Guid devRequestId)
@ -138,18 +298,19 @@ public class ModuleDocManager(
var catalog = await LoadCatalogAsync(); var catalog = await LoadCatalogAsync();
var groups = catalog.GroupByDoc(); var groups = catalog.GroupByDoc();
var names = catalog.Wizards var targets = catalog.Wizards
.Where(w => w.Seed.DevRequest?.Id == devRequestId) .Where(w => w.Seed.DevRequest?.Id == devRequestId)
.Select(w => catalog.Resolve(w.Seed.Wizard)) .Select(w => catalog.ResolveTarget(w.Seed.Wizard))
.Distinct(StringComparer.OrdinalIgnoreCase) .Select(t => (t.Doc, t.Section))
.Distinct()
.ToList(); .ToList();
var result = new List<ModuleDocDto>(); var result = new List<ModuleDocDto>();
foreach (var name in names) foreach (var (doc, section) in targets)
{ {
var doc = await ToDtoAsync(catalog, name, groups, includeContent: false); var dto = await ToDtoAsync(catalog, doc, section, groups, includeContent: false);
if (!doc.Exists || !doc.IsComplete) if (!dto.Exists || !dto.IsComplete)
result.Add(doc); result.Add(dto);
} }
return result; return result;
@ -158,23 +319,49 @@ public class ModuleDocManager(
private static async Task<ModuleDocDto> ToDtoAsync( private static async Task<ModuleDocDto> ToDtoAsync(
ModuleDocCatalog catalog, ModuleDocCatalog catalog,
string name, string name,
Dictionary<string, List<(string FileName, WizardSeedFileDto Seed)>> groups, string? sectionKey,
Dictionary<string, ModuleDocCatalog.DocGroup> groups,
bool includeContent) bool includeContent)
{ {
var path = catalog.DocPath(name); var path = catalog.DocPath(name);
var exists = File.Exists(path); var content = File.Exists(path) ? Normalize(await File.ReadAllTextAsync(path)) : null;
var content = exists ? Normalize(await File.ReadAllTextAsync(path)) : null; var group = groups.GetValueOrDefault(name);
var missing = content == null ? [.. ModuleDocRenderer.Sections] : ModuleDocCatalog.MissingSections(content);
var sections = content == null ? [] : ModuleDocCatalog.ParseSections(content);
var section = sectionKey == null ? null : sections.FirstOrDefault(s => string.Equals(s.Key, sectionKey, StringComparison.OrdinalIgnoreCase));
var exists = content != null && (sectionKey == null || section != null);
List<string> missing = !exists
? [.. ModuleDocRenderer.Sections]
: section != null ? section.Missing() : ModuleDocCatalog.MissingSections(content!);
var wizards = sectionKey == null
? group?.All ?? []
: group?.Sections.GetValueOrDefault(sectionKey) ?? [];
// Henuz yazilmamis dosya ya da bolum hata degildir (analizi yapilan yeni modul); doldurulacak
// iskelet doner, save_module_doc ayni adla yazinca dosya acilir.
string? text = null;
if (includeContent)
{
if (section != null)
text = section.Text.TrimEnd() + "\n\n" + ModuleDocRenderer.Inventory([(section.Title, (IReadOnlyList<(string FileName, WizardSeedFileDto Seed)>)[.. wizards])]);
else if (sectionKey != null)
text = ModuleDocRenderer.SectionSkeleton(ModuleDocRenderer.Title(catalog, wizards.Select(w => w.Seed), sectionKey), sectionKey);
else
text = content ?? ModuleDocRenderer.DocSkeleton(catalog.MenuTitle(name) ?? name, name);
}
return new ModuleDocDto return new ModuleDocDto
{ {
Name = name, Name = name,
Section = section?.Key ?? sectionKey,
Path = ModuleDocCatalog.RelativePath(name), Path = ModuleDocCatalog.RelativePath(name),
Exists = exists, Exists = exists,
IsComplete = exists && missing.Count == 0, IsComplete = exists && missing.Count == 0,
MissingSections = missing, MissingSections = missing,
Wizards = [.. (groups.GetValueOrDefault(name) ?? []).Select(w => w.Seed.Wizard.ResolveListFormCode())], Wizards = [.. wizards.Select(w => w.Seed.Wizard.ResolveListFormCode())],
Content = includeContent ? content : null, Content = text,
}; };
} }

View file

@ -34,43 +34,67 @@ public static class ModuleDocRenderer
private static readonly Regex ProcedurePattern = new(@"\bEXEC(?:UTE)?\s+(?:\[?dbo\]?\.)?\[?(?<name>\w+)\]?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex ProcedurePattern = new(@"\bEXEC(?:UTE)?\s+(?:\[?dbo\]?\.)?\[?(?<name>\w+)\]?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex FromPattern = new(@"\bFROM\s+""?\[?(?<name>\w+)\]?""?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex FromPattern = new(@"\bFROM\s+""?\[?(?<name>\w+)\]?""?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public static string Skeleton(string title, string branchCode) /// <summary>Kok menu dokumaninin iskeleti; dallar <see cref="SectionSkeleton"/> ile eklenir.</summary>
public static string DocSkeleton(string title, string rootCode)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("# ").Append(title).Append(" (`").Append(branchCode).Append("`)\n\n"); sb.Append("# ").Append(title).Append(" (`").Append(rootCode).Append("`)\n\n");
sb.Append("> Bu dosya ilk deploy'da otomatik açıldı. `module-doc:todo` işaretli bölümleri modülü üreten kişi ya da AI doldurur ") sb.Append("> Menünün altındaki her dal bu dosyada bir bölümdür (`module-doc:section`); dosya ve bölümler ilk deploy'da ")
.Append("otomatik açılır. `module-doc:todo` işaretli alt bölümleri modülü üreten kişi ya da AI doldurur ")
.Append("(`lowcode.instructions.md` §0.13); MCP ile üretimde talep bu bölümler doldurulmadan Testing'e alınamaz. ") .Append("(`lowcode.instructions.md` §0.13); MCP ile üretimde talep bu bölümler doldurulmadan Testing'e alınamaz. ")
.Append("\"Ek — Ekran envanteri\" bölümü wizard dosyalarından üretilir, elle düzenlenmez.\n"); .Append("\"Ek — Ekran envanteri\" bölümü wizard dosyalarından üretilir, elle düzenlenmez.\n");
foreach (var section in Sections)
sb.Append("\n## ").Append(section).Append("\n\n_Henüz yazılmadı._ ").Append(ModuleDocCatalog.TodoMarker).Append('\n');
return sb.ToString(); return sb.ToString();
} }
/// <summary>Iskeletin basligi: dalin iki dilli adi wizard cevaplarinda varsa Turkcesi, yoksa kod.</summary> /// <summary>Bir dalin bolum iskeleti: beyan satiri, <c>## </c> basligi ve doldurulacak alt bolumler.</summary>
public static string Title(IEnumerable<WizardSeedFileDto> seeds, string branchCode) public static string SectionSkeleton(string title, string sectionKey)
{ {
var sb = new StringBuilder();
sb.Append("<!-- module-doc:section ").Append(sectionKey).Append(" -->\n");
sb.Append("## ").Append(title).Append(" (`").Append(sectionKey).Append("`)\n");
foreach (var section in Sections)
sb.Append("\n### ").Append(section).Append("\n\n_Henüz yazılmadı._ ").Append(ModuleDocCatalog.TodoMarker).Append('\n');
return sb.ToString();
}
/// <summary>Menu kodunun basligi: menu dil satirindaki Turkce ad, yoksa wizard cevaplarindaki, yoksa kod.</summary>
public static string Title(ModuleDocCatalog catalog, IEnumerable<WizardSeedFileDto> seeds, string code)
{
var fromMenu = catalog.MenuTitle(code);
if (!string.IsNullOrWhiteSpace(fromMenu))
return fromMenu;
foreach (var wizard in seeds.Select(s => s.Wizard)) foreach (var wizard in seeds.Select(s => s.Wizard))
{ {
if (string.Equals(wizard.ResolveMenuCode(), branchCode, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(wizard.MenuTr)) if (string.Equals(wizard.ResolveMenuCode(), code, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(wizard.MenuTr))
return wizard.MenuTr; return wizard.MenuTr;
var level = wizard.ResolveMenuParentChain().FirstOrDefault(l => string.Equals(l.Code, branchCode, StringComparison.OrdinalIgnoreCase)); var level = wizard.ResolveMenuParentChain().FirstOrDefault(l => string.Equals(l.Code, code, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(level?.Tr)) if (!string.IsNullOrWhiteSpace(level?.Tr))
return level.Tr; return level.Tr;
} }
return branchCode; return code;
} }
public static string Inventory(IReadOnlyList<(string FileName, WizardSeedFileDto Seed)> wizards) /// <summary>Dokumanin envanteri: bolumler dokumandaki sirayla, her bolumun ekranlari menu sirasiyla.</summary>
public static string Inventory(IEnumerable<(string Title, IReadOnlyList<(string FileName, WizardSeedFileDto Seed)> Wizards)> sections)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append(ModuleDocCatalog.AutoBegin).Append('\n'); sb.Append(ModuleDocCatalog.AutoBegin).Append('\n');
sb.Append("## Ek — Ekran envanteri (otomatik)\n\n"); sb.Append("## Ek — Ekran envanteri (otomatik)\n\n");
sb.Append("> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.\n"); sb.Append("> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.\n");
foreach (var (title, wizards) in sections)
{
if (wizards.Count == 0)
continue;
sb.Append("\n### ").Append(Cell(title)).Append('\n');
var ordered = wizards var ordered = wizards
.OrderBy(w => w.Seed.Wizard.CreateMenu ? 0 : 1) .OrderBy(w => w.Seed.Wizard.CreateMenu ? 0 : 1)
.ThenBy(w => w.Seed.Wizard.MenuOrder) .ThenBy(w => w.Seed.Wizard.MenuOrder)
@ -78,6 +102,7 @@ public static class ModuleDocRenderer
foreach (var (fileName, seed) in ordered) foreach (var (fileName, seed) in ordered)
AppendWizard(sb, fileName, seed); AppendWizard(sb, fileName, seed);
}
sb.Append('\n').Append(ModuleDocCatalog.AutoEnd).Append('\n'); sb.Append('\n').Append(ModuleDocCatalog.AutoEnd).Append('\n');
return sb.ToString(); return sb.ToString();
@ -90,7 +115,7 @@ public static class ModuleDocRenderer
var code = wizard.ResolveListFormCode(); var code = wizard.ResolveListFormCode();
var title = FirstText(wizard.MenuTr, wizard.TitleTr, wizard.MenuEn, code); var title = FirstText(wizard.MenuTr, wizard.TitleTr, wizard.MenuEn, code);
sb.Append("\n### ").Append(Cell(title)).Append(" — `").Append(code).Append("`\n\n"); sb.Append("\n#### ").Append(Cell(title)).Append(" — `").Append(code).Append("`\n\n");
sb.Append("| Özellik | Değer |\n| --- | --- |\n"); sb.Append("| Özellik | Değer |\n| --- | --- |\n");
Row(sb, "Wizard", $"`{SeedPathResolver.WizardFolder}/{fileName}`"); Row(sb, "Wizard", $"`{SeedPathResolver.WizardFolder}/{fileName}`");

View file

@ -36,6 +36,11 @@
"https://claude.com/api/mcp/auth_callback", "https://claude.com/api/mcp/auth_callback",
"https://chatgpt.com/connector_platform_oauth_redirect" "https://chatgpt.com/connector_platform_oauth_redirect"
] ]
},
"Platform_Mcp_Local": {
"RedirectUris": [
"http://127.0.0.1:19876/oauth/callback"
]
} }
} }
} }

View file

@ -0,0 +1,63 @@
using System;
using SkiaSharp;
using Sozsoft.Mcp.Tools;
using Volo.Abp.DependencyInjection;
namespace Sozsoft.Platform.Identity;
/// <summary>
/// MCP arac sonucundaki gorselleri SkiaSharp ile kucultur (rapor motorunun grafik katmani zaten
/// SkiaSharp'tir). Gorsel once modelin isleyebildigi en uzun kenara indirilir, sigmazsa adim adim
/// kucultulur; okunamayacak kadar kuculecekse gonderilmez.
/// </summary>
public class SkiaMcpImageShrinker : IMcpImageShrinker, ISingletonDependency
{
/// <summary>Modelin gorseli isledigi en uzun kenar; daha buyugu yalnizca boyut tasir.</summary>
private const int MaxEdge = 1568;
/// <summary>Bunun altinda ekran goruntusundeki metin okunmaz.</summary>
private const int MinEdge = 480;
private const int JpegQuality = 80;
private const float ScaleStep = 0.75f;
public McpShrunkImage? Shrink(string mimeType, string base64, int maxLength)
{
if (base64.Length <= maxLength)
return new McpShrunkImage(mimeType, base64);
using var bitmap = SKBitmap.Decode(Convert.FromBase64String(base64));
if (bitmap is null)
return null;
using var source = SKImage.FromBitmap(bitmap);
var longest = Math.Max(bitmap.Width, bitmap.Height);
var scale = Math.Min(1f, MaxEdge / (float)longest);
while (true)
{
var encoded = EncodeJpeg(source, scale);
if (encoded.Length <= maxLength)
return new McpShrunkImage("image/jpeg", encoded);
scale *= ScaleStep;
if (longest * scale < MinEdge)
return null;
}
}
private static string EncodeJpeg(SKImage source, float scale)
{
var width = Math.Max(1, (int)Math.Round(source.Width * scale));
var height = Math.Max(1, (int)Math.Round(source.Height * scale));
// JPEG saydamlik tasimaz; saydam alanlar siyaha donmesin diye beyaz zemine cizilir.
using var surface = SKSurface.Create(new SKImageInfo(width, height, SKColorType.Rgba8888, SKAlphaType.Premul));
surface.Canvas.Clear(SKColors.White);
surface.Canvas.DrawImage(source, new SKRect(0, 0, width, height), new SKSamplingOptions(SKCubicResampler.Mitchell));
using var snapshot = surface.Snapshot();
using var data = snapshot.Encode(SKEncodedImageFormat.Jpeg, JpegQuality);
return Convert.ToBase64String(data.AsSpan());
}
}

View file

@ -111,7 +111,7 @@ These sections encode expectations the user will **not** repeat in every prompt.
| `api/` kod standardı (sürüm, nullable, ölü kod, modern C#, ABP katmanları) | `.github/instructions/dotnet.instructions.md` | | `api/` kod standardı (sürüm, nullable, ölü kod, modern C#, ABP katmanları) | `.github/instructions/dotnet.instructions.md` |
| Modül/liste ekleme prosedürü, seeder dosya sorumlulukları | `.github/instructions/list.instructions.md` | | Modül/liste ekleme prosedürü, seeder dosya sorumlulukları | `.github/instructions/list.instructions.md` |
| Ne nerede yaşıyor, hangi ekran ne yapıyor | `README.md` | | Ne nerede yaşıyor, hangi ekran ne yapıyor | `README.md` |
| Bir modülün nasıl çalıştığı, analizi ve kararları | `configs/seeds/docs/modules/{Modül}.md` (dizin: `README.md` §10.5) | | Bir modülün nasıl çalıştığı, analizi ve kararları | `configs/seeds/docs/modules/{Menü}.md` (dizin: `README.md` §10.5) |
| Yetki kodları | `PlatformConsts.AppCodes` (backend) + `constants/permission.constant.ts` (UI) | | Yetki kodları | `PlatformConsts.AppCodes` (backend) + `constants/permission.constant.ts` (UI) |
| Seed yolları | `SeedPathResolver` | | Seed yolları | `SeedPathResolver` |
| SQL Server → PostgreSQL çeviri kuralları | `.github/instructions/lowcode.instructions.md` §8.3.1 | | SQL Server → PostgreSQL çeviri kuralları | `.github/instructions/lowcode.instructions.md` §8.3.1 |
@ -185,12 +185,15 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
Alt form ekranının menü/yetki/düzenleme kuralları (menüsüz alt formun yetkisi ana ekranın altında, Alt form ekranının menü/yetki/düzenleme kuralları (menüsüz alt formun yetkisi ana ekranın altında,
`Form` sekmesi tek kayıt + `AllowAdding`/`AllowUpdating` açık): `lowcode.instructions.md` §4.6. `Form` sekmesi tek kayıt + `AllowAdding`/`AllowUpdating` açık): `lowcode.instructions.md` §4.6.
14. **Modül dokümanı** → MCP ya da wizard ile oluşturulan her modül çalışma şeklini 14. **Modül dokümanı** → MCP ya da wizard ile oluşturulan her modül çalışma şeklini
`configs/seeds/docs/modules/{Modül}.md` dosyasında taşır: keşif cevapları ve onaylanan `configs/seeds/docs/modules/{Menü}.md` dosyasında taşır: dosya kök menü başına tektir ve adı kök
tarif (analiz), ekranlar, veri modeli, hesaplar/durum geçişleri, yetki dağılımı, entegrasyonlar, menünün kodudur (`App.Hr.md`, `App.Prc.md`, `App.Administration.md`),
sık sorulanlar. Dosyayı ve "Ek — Ekran envanteri"ni deploy kendisi açar/yeniler; anlatım menünün altındaki her dal dosyada bir `module-doc:section` bölümüdür. Bölüm keşif cevapları ve
bölümlerini (`module-doc:todo`) üreten doldurur — MCP'de `save_module_doc`, Claude Code'da dosyaya onaylanan tarifi (analiz), ekranları, veri modelini, hesapları/durum geçişlerini, yetki dağılımını,
doğrudan. MCP talebi doküman tamamlanmadan `Testing`'e alınamaz. Wizard dışı modül dokümanı elle entegrasyonları, sık sorulanları taşır. Dosyayı, dalın bölümünü ve "Ek — Ekran envanteri"ni deploy
yazılır. README'ye yalnızca §10.5 dizin satırı ve ilgili bölümde tek cümlelik bağlantı yazılır. kendisi açar/yeniler; anlatım alt bölümlerini (`module-doc:todo`) üreten doldurur — MCP'de
`save_module_doc` (ekran/dal koduyla yalnızca o bölüm), Claude Code'da dosyaya doğrudan. MCP talebi
bölümü tamamlanmadan `Testing`'e alınamaz. Wizard dışı modülün bölümü elle yazılır. README'ye
yalnızca §10.5 dizin satırı/bölüm adı ve ilgili bölümde tek cümlelik bağlantı yazılır.
"X nasıl çalışıyor?" sorusunda önce bu dosya okunur. Ayrıntı: `lowcode.instructions.md` §0.13. "X nasıl çalışıyor?" sorusunda önce bu dosya okunur. Ayrıntı: `lowcode.instructions.md` §0.13.
15. **Ekran açıklaması** → wizard/MCP ile üretilen her liste ekranının (alt formlar dahil) 15. **Ekran açıklaması** → wizard/MCP ile üretilen her liste ekranının (alt formlar dahil)
`DescTr`/`DescEn` metni ayrıntılıdır; başlıktaki ⓘ simgesinde gösterilir: ne işe yaradığı, `DescTr`/`DescEn` metni ayrıntılıdır; başlıktaki ⓘ simgesinde gösterilir: ne işe yaradığı,
@ -216,8 +219,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
- **Modül dokümanı** → `ModuleDocManager.RefreshAllAsync` wizard'ı yazan her yoldan çağrılır (deploy, - **Modül dokümanı** → `ModuleDocManager.RefreshAllAsync` wizard'ı yazan her yoldan çağrılır (deploy,
silme, `WizardSeedSynchronizer`, import kapanışı); wizard dosyasını yazan yeni bir yol eklenirse silme, `WizardSeedSynchronizer`, import kapanışı); wizard dosyasını yazan yeni bir yol eklenirse
çağrı da eklenir, yoksa envanter bayatlar. Wizard → doküman eşlemesi tek yerdedir çağrı da eklenir, yoksa envanter bayatlar. Wizard → doküman/bölüm eşlemesi tek yerdedir
(`ModuleDocCatalog`: beyan → alt formun ana ekranı → menü dalı); envanter biçimi (`ModuleDocCatalog`: dosya = kök menü, bölüm = beyan → alt formun ana ekranı → menü dalı; kök ve
dal `data/App.Menus.Menu.json`'dan okunur, sabit liste yoktur); envanter biçimi
`ModuleDocRenderer`'dadır. Export kapanışı dokümanı taşır (`WizardExportCollector`), import `ModuleDocRenderer`'dadır. Export kapanışı dokümanı taşır (`WizardExportCollector`), import
`docs/modules/*.md` yolunu kabul eder. `docs/modules/*.md` yolunu kabul eder.
@ -317,7 +321,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
- **Talepteki görsel** → `App.DevRequests` açıklamasına yapıştırılan görsel base64 `data:` URI olarak - **Talepteki görsel** → `App.DevRequests` açıklamasına yapıştırılan görsel base64 `data:` URI olarak
kalır, hiçbir yere yüklenmez. `start_dev_request` / `get_dev_request` onu metinden ayıklar kalır, hiçbir yere yüklenmez. `start_dev_request` / `get_dev_request` onu metinden ayıklar
(`McpDevRequestImages`) ve MCP resim içeriği (`ImageContentBlock`) olarak döndürür; metinde (`McpDevRequestImages`) ve MCP resim içeriği (`ImageContentBlock`) olarak döndürür; metinde
`[image-N]` kalır. Açıklama bu yüzden `update_row` ile yeniden yazılmaz. `SubTasks` de `[image-N]` kalır. Sonucun tamamı istemcinin araç sonucu sınırına (Claude Desktop 1 MB) sığar:
kalan pay görsellere bölünür, payını aşan görsel `IMcpImageShrinker` (Host'ta SkiaSharp) ile küçültülüp
JPEG'e çevrilir, yine sığmayan gönderilmez ve metinde bildirilir; kaynak açıklama değişmez.
Açıklama bu yüzden `update_row` ile yeniden yazılmaz. `SubTasks` de
`add_dev_request_subtasks` ile yazılır: atanan kişi bağlanan kullanıcı, bitiş tarihi bugün — `add_dev_request_subtasks` ile yazılır: atanan kişi bağlanan kullanıcı, bitiş tarihi bugün —
sunucu doldurur, modele bırakılmaz. `Timesheets`'i tamamen sunucu yazar sunucu doldurur, modele bırakılmaz. `Timesheets`'i tamamen sunucu yazar
(`IMcpDevRequestTimesheetRecorder`): `get_dev_request`/`start_dev_request` talebi etkin talep yapar, (`IMcpDevRequestTimesheetRecorder`): `get_dev_request`/`start_dev_request` talebi etkin talep yapar,
@ -331,7 +338,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
- **Talep analizi** → `Description` talebin yazıldığı hâliyle kalır; MCP ona yazmaz. Analiz ve - **Talep analizi** → `Description` talebin yazıldığı hâliyle kalır; MCP ona yazmaz. Analiz ve
üretim notları `Analysis` kolonundadır (Todo panosunda `AnalysisExpr`, Açıklama yanında sekme). üretim notları `Analysis` kolonundadır (Todo panosunda `AnalysisExpr`, Açıklama yanında sekme).
Durum sırası `Backlog → Analysis → Ready to Start → …`: "Analiz et" `save_dev_request_analysis` Durum sırası `Backlog → Analysis → Ready to Start → …`: "Analiz et" `save_dev_request_analysis`
ile yalnızca `Backlog`/`Analysis` talebini analiz edip `Analysis`'e alır (hiçbir şey üretmez), ile yalnızca `Backlog`/`Analysis` talebini analiz edip `Analysis`'e alır (ekran/tablo üretmez),
analizi `menuCode` dalının modül dokümanı bölümüne ("2. Analiz ve kararlar") de yazar — menüde ilk
talepse kök menünün dosyası (`App.Inventory.md`) ve dal bölümü burada açılır, elle yazılmış alt bölüme dokunulmaz;
`Ready to Start`'a kullanıcı taşır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir. `Ready to Start`'a kullanıcı taşır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir.
Analiz standardı tek yerdedir: `lowcode.instructions.md` §0.12; prompt'lar oraya yönlendirir. Analiz standardı tek yerdedir: `lowcode.instructions.md` §0.12; prompt'lar oraya yönlendirir.
- **MCP şema araçları** → tablo/sütun bilgisi canlı katalogdan değil `Sas_H_DbTableColumn` - **MCP şema araçları** → tablo/sütun bilgisi canlı katalogdan değil `Sas_H_DbTableColumn`
@ -341,6 +350,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
değil ekranların alanlarını döker: bir tablonun ekranda alan olarak tanımlanmamış kolonunu MCP göremez. değil ekranların alanlarını döker: bir tablonun ekranda alan olarak tanımlanmamış kolonunu MCP göremez.
Modelin okuması gereken kolon (ör. `AbpUsers`'ın kişisel bilgileri) ilgili ekranda gizli alan olarak durur. Modelin okuması gereken kolon (ör. `AbpUsers`'ın kişisel bilgileri) ilgili ekranda gizli alan olarak durur.
- **MCP OAuth** → bilinen sağlayıcılar (Claude, ChatGPT) seed edilmiş `Platform_Mcp` istemcisini, - **MCP OAuth** → bilinen sağlayıcılar (Claude, ChatGPT) seed edilmiş `Platform_Mcp` istemcisini,
yerel `mcp-remote` proxy'si açık onaylı `Platform_Mcp_Local` istemcisini,
diğer MCP istemcileri `/connect/register` ile üretilen `mcp_` önekli açık onaylı istemcileri diğer MCP istemcileri `/connect/register` ile üretilen `mcp_` önekli açık onaylı istemcileri
kullanır (`McpOAuthClientManager`; seed `McpOAuthClientDataSeedContributor`). Bilinen sağlayıcı kullanır (`McpOAuthClientManager`; seed `McpOAuthClientDataSeedContributor`). Bilinen sağlayıcı
eklemek = DbMigrator'daki `Platform_Mcp:RedirectUris` listesine callback eklemek. MCP eklemek = DbMigrator'daki `Platform_Mcp:RedirectUris` listesine callback eklemek. MCP
@ -542,7 +552,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
tipler düz metin aramasında referanssız görünür. tipler düz metin aramasında referanssız görünür.
- Yorum satırına alınmış kod bloğu bırakılmaz. - Yorum satırına alınmış kod bloğu bırakılmaz.
- Modül/ekran/bileşen ekleyen ya da davranışını değiştiren iş modül dokümanını - Modül/ekran/bileşen ekleyen ya da davranışını değiştiren iş modül dokümanını
(`configs/seeds/docs/modules/{Modül}.md`) yazar veya günceller; liste ekranının açıklaması (`configs/seeds/docs/modules/{Menü}.md`) yazar veya günceller; liste ekranının açıklaması
ayrıntılıdır (standing default 14–15). ayrıntılıdır (standing default 14–15).
- `api/` değişikliğinden sonra: `dotnet build -p:EnforceCodeStyleInBuild=true` — 0 hata, yeni - `api/` değişikliğinden sonra: `dotnet build -p:EnforceCodeStyleInBuild=true` — 0 hata, yeni
`IDE00xx` yok. `IDE00xx` yok.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -17,7 +17,7 @@
"Assignees": null, "Assignees": null,
"Priority": null, "Priority": null,
"Completed": false, "Completed": false,
"SortOrder": 0, "SortOrder": 2,
"Timesheets": null, "Timesheets": null,
"SubTasks": null, "SubTasks": null,
"Analysis": null "Analysis": null

View file

@ -69430,6 +69430,20 @@
"Key": "App.Mcp.Error.DevRequestAnalysisRequired", "Key": "App.Mcp.Error.DevRequestAnalysisRequired",
"Value": "Analiz boş olamaz." "Value": "Analiz boş olamaz."
}, },
{
"Id": "c8039b7b-e33c-5a20-950b-772e6620e568",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Mcp.Error.DevRequestMenuCodeRequired",
"Value": "menuCode is required: pass the menu location of the request, i.e. the code of the branch or screen under the root menu (App.Inventory.Definitions). The analysis is written to that branch's section of the module document."
},
{
"Id": "1c11fb6e-9011-5de0-b7c7-b3d9227c00c7",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Mcp.Error.DevRequestMenuCodeRequired",
"Value": "menuCode zorunludur: talebin menü yerini, yani kök menünün altındaki dalın ya da ekranın kodunu (App.Inventory.Definitions) gönderin. Analiz bu dalın modül dokümanı bölümüne yazılır."
},
{ {
"Id": "aa72e998-f4a1-5873-95ef-1fcfe0b601f9", "Id": "aa72e998-f4a1-5873-95ef-1fcfe0b601f9",
"CultureName": "en", "CultureName": "en",
@ -81727,14 +81741,14 @@
"CultureName": "en", "CultureName": "en",
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "App.Hr.PerformanceReview.Desc", "Key": "App.Hr.PerformanceReview.Desc",
"Value": "All Reviews — every employee review of every period; HR follows and manages the process end to end here.\nWhere: Human Resources › Performance Management › All Reviews (HR only). The detail has Goals, Competencies, 360 Feedback and Development Plan tabs; the Todo view groups reviews by stage.\nStages: Goal Setting → Self Review → Manager Review → Calibration → Completed.\nFields:\n• Period, employee (department/position copied), manager → HR selects the reviewing manager\n• Scores → goal, competency, 360 average (info), overall, calibrated score and rating; calculated automatically\n• Self, manager and calibration comments; submission and completion dates\nAutomatic: scores are recalculated on every save. HR may make any stage change; leaving Goal Setting requires at least one goal with weights totalling 100. Transitions notify the employee/manager; on Completed manager levels are written to the employee's skill profile.\nRelated: My Reviews (employee), Team Reviews (manager), Calibration, Development Plans. Details: docs/modules/HrPerformance.md." "Value": "All Reviews — every employee review of every period; HR follows and manages the process end to end here.\nWhere: Human Resources › Performance Management › All Reviews (HR only). The detail has Goals, Competencies, 360 Feedback and Development Plan tabs; the Todo view groups reviews by stage.\nStages: Goal Setting → Self Review → Manager Review → Calibration → Completed.\nFields:\n• Period, employee (department/position copied), manager → HR selects the reviewing manager\n• Scores → goal, competency, 360 average (info), overall, calibrated score and rating; calculated automatically\n• Self, manager and calibration comments; submission and completion dates\nAutomatic: scores are recalculated on every save. HR may make any stage change; leaving Goal Setting requires at least one goal with weights totalling 100. Transitions notify the employee/manager; on Completed manager levels are written to the employee's skill profile.\nRelated: My Reviews (employee), Team Reviews (manager), Calibration, Development Plans. Details: docs/modules/App.Hr.md › Performans Yönetimi."
}, },
{ {
"Id": "af68340a-00bc-55f0-a436-e23b78326c9d", "Id": "af68340a-00bc-55f0-a436-e23b78326c9d",
"CultureName": "tr", "CultureName": "tr",
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "App.Hr.PerformanceReview.Desc", "Key": "App.Hr.PerformanceReview.Desc",
"Value": "Tüm Değerlendirmeler — bütün dönemlerin bütün çalışan değerlendirmeleri; İK süreci uçtan uca buradan izler ve yönetir.\nNerede: İnsan Kaynakları › Performans Yönetimi › Tüm Değerlendirmeler (yalnız İK). Detayda Hedefler, Yetkinlikler, 360 Derece ve Gelişim Planı sekmeleri; Todo görünümünde değerlendirmeler aşama sütunlarındadır.\nAşamalar: Hedef Belirleme → Öz Değerlendirme → Yönetici Değerlendirmesi → Kalibrasyon → Tamamlandı.\nAlanlar:\n• Dönem, çalışan (departman/pozisyon çalışandan dolar), yönetici → değerlendiren yöneticiyi İK seçer\n• Puanlar → hedef puanı, yetkinlik puanı, 360 ortalaması (bilgi), genel puan, kalibre puan ve derece; hesaplar otomatiktir\n• Öz değerlendirme, yönetici ve kalibrasyon yorumları; gönderim ve tamamlanma tarihleri\nOtomatik: her kayıtta puanlar yeniden hesaplanır. İK her aşama geçişini yapabilir; Hedef Belirleme'den çıkmak için ağırlık toplamı 100 olan en az bir hedef gerekir. Geçişlerde çalışana/yöneticiye bildirim gider; Tamamlandı'da yönetici seviyeleri çalışanın yetkinlik profiline yazılır.\nİlişkili: Değerlendirmelerim (çalışan), Ekibimin Değerlendirmeleri (yönetici), Kalibrasyon, Gelişim Planları. Ayrıntı: docs/modules/HrPerformance.md." "Value": "Tüm Değerlendirmeler — bütün dönemlerin bütün çalışan değerlendirmeleri; İK süreci uçtan uca buradan izler ve yönetir.\nNerede: İnsan Kaynakları › Performans Yönetimi › Tüm Değerlendirmeler (yalnız İK). Detayda Hedefler, Yetkinlikler, 360 Derece ve Gelişim Planı sekmeleri; Todo görünümünde değerlendirmeler aşama sütunlarındadır.\nAşamalar: Hedef Belirleme → Öz Değerlendirme → Yönetici Değerlendirmesi → Kalibrasyon → Tamamlandı.\nAlanlar:\n• Dönem, çalışan (departman/pozisyon çalışandan dolar), yönetici → değerlendiren yöneticiyi İK seçer\n• Puanlar → hedef puanı, yetkinlik puanı, 360 ortalaması (bilgi), genel puan, kalibre puan ve derece; hesaplar otomatiktir\n• Öz değerlendirme, yönetici ve kalibrasyon yorumları; gönderim ve tamamlanma tarihleri\nOtomatik: her kayıtta puanlar yeniden hesaplanır. İK her aşama geçişini yapabilir; Hedef Belirleme'den çıkmak için ağırlık toplamı 100 olan en az bir hedef gerekir. Geçişlerde çalışana/yöneticiye bildirim gider; Tamamlandı'da yönetici seviyeleri çalışanın yetkinlik profiline yazılır.\nİlişkili: Değerlendirmelerim (çalışan), Ekibimin Değerlendirmeleri (yönetici), Kalibrasyon, Gelişim Planları. Ayrıntı: docs/modules/App.Hr.md › Performans Yönetimi."
}, },
{ {
"Id": "710a78b8-9f30-5049-81ff-44892e5eeb03", "Id": "710a78b8-9f30-5049-81ff-44892e5eeb03",

View file

@ -1,12 +1,17 @@
# Yönetim › Tanımlamalar (`App.Administration.Definitions`) # Yönetim (`App.Administration`)
> "Ek — Ekran envanteri" bölümü wizard dosyalarından üretilir, elle düzenlenmez. Sektörler, Diziler ve Ölçü Kategorileri wizard ekranı değildir (`ListFormSeeder_Administration`); burada yalnızca menü sırası için anılır. > Menünün altındaki her dal bu dosyada bir bölümdür (`module-doc:section`); dosya ve bölümler ilk deploy'da otomatik açılır. `module-doc:todo` işaretli alt bölümleri modülü üreten kişi ya da AI doldurur (`lowcode.instructions.md` §0.13); MCP ile üretimde talep bu bölümler doldurulmadan Testing'e alınamaz. "Ek — Ekran envanteri" bölümü wizard dosyalarından üretilir, elle düzenlenmez.
## 1. Amaç ve kapsam <!-- module-doc:section App.Administration.Definitions -->
## Tanımlamalar (`App.Administration.Definitions`)
> Sektörler, Diziler ve Ölçü Kategorileri wizard ekranı değildir (`ListFormSeeder_Administration`); burada yalnızca menü sırası için anılır.
### 1. Amaç ve kapsam
Satış ve satınalmanın ortak kullandığı **Kontaklar** kartı (müşteri ve tedarikçi; şirket ya da bireysel) ve onu sınıflandıran **Kontak Etiketleri**. İleride yapılacak Satış ve Satınalma belgeleri (teklif, sipariş, fatura) kontağı bu ekrandan seçer. Kart başlangıçta Satınalma › Tanımlamalar altında `App.Prc.Supplier` olarak üretildi; satış tarafı da kullanacağı için Yönetim modülüne taşındı. Satış ve satınalmanın ortak kullandığı **Kontaklar** kartı (müşteri ve tedarikçi; şirket ya da bireysel) ve onu sınıflandıran **Kontak Etiketleri**. İleride yapılacak Satış ve Satınalma belgeleri (teklif, sipariş, fatura) kontağı bu ekrandan seçer. Kart başlangıçta Satınalma › Tanımlamalar altında `App.Prc.Supplier` olarak üretildi; satış tarafı da kullanacağı için Yönetim modülüne taşındı.
## 2. Analiz ve kararlar ### 2. Analiz ve kararlar
- Modül `Adm`, tablo öneki `Adm_T_`, kod öneki `App.Definitions.` (Sektörler ile aynı aile). Eski adlardan karşılık: `Prc_T_Supplier` → `Adm_T_Contact`, `Prc_T_SupplierContact` → `Adm_T_ContactPerson`, `Prc_T_SupplierBankAccount` → `Adm_T_ContactBankAccount`, `Prc_T_SupplierTag` → `Adm_T_ContactTag`; alt tablolardaki bağ kolonu `SupplierId` → `ContactId`. - Modül `Adm`, tablo öneki `Adm_T_`, kod öneki `App.Definitions.` (Sektörler ile aynı aile). Eski adlardan karşılık: `Prc_T_Supplier` → `Adm_T_Contact`, `Prc_T_SupplierContact` → `Adm_T_ContactPerson`, `Prc_T_SupplierBankAccount` → `Adm_T_ContactBankAccount`, `Prc_T_SupplierTag` → `Adm_T_ContactTag`; alt tablolardaki bağ kolonu `SupplierId` → `ContactId`.
- Ana liste "Kontaklar" olduğu için kartın kişi sekmesi **İlgili Kişiler** (`App.Definitions.ContactPerson`) adını aldı; iki ekranın adı çakışmaz. - Ana liste "Kontaklar" olduğu için kartın kişi sekmesi **İlgili Kişiler** (`App.Definitions.ContactPerson`) adını aldı; iki ekranın adı çakışmaz.
@ -22,7 +27,7 @@ Satış ve satınalmanın ortak kullandığı **Kontaklar** kartı (müşteri ve
- Sektör (`SectorId`) Sektörler ekranının (`App.Definitions.Sector`, `Adm_T_Sector`) kiracıya ait aktif kayıtlarından seçilir. `JobPosition` kolonu tabloda kalır, ekranda yoktur. - Sektör (`SectorId`) Sektörler ekranının (`App.Definitions.Sector`, `Adm_T_Sector`) kiracıya ait aktif kayıtlarından seçilir. `JobPosition` kolonu tabloda kalır, ekranda yoktur.
- Kontak telefonu (`Phone`) `phoneCountry` biçimindedir: maske seçilen ülkenin kodu ve numara kalıbıyla değişir, `pattern` `^[0-9]{4,15}$`. Ülke alanı olmayan İlgili Kişiler telefonu `phoneGlobal` maskesi ve `^[0-9]{10}$` kuralı taşır; web sitesi `pattern`, e-posta `email` doğrulaması taşır. - Kontak telefonu (`Phone`) `phoneCountry` biçimindedir: maske seçilen ülkenin kodu ve numara kalıbıyla değişir, `pattern` `^[0-9]{4,15}$`. Ülke alanı olmayan İlgili Kişiler telefonu `phoneGlobal` maskesi ve `^[0-9]{10}$` kuralı taşır; web sitesi `pattern`, e-posta `email` doğrulaması taşır.
## 3. Menü ve ekranlar ### 3. Menü ve ekranlar
Yönetim (`App.Administration`) › Tanımlamalar (`App.Administration.Definitions`): Yönetim (`App.Administration`) › Tanımlamalar (`App.Administration.Definitions`):
@ -34,7 +39,7 @@ Yönetim (`App.Administration`) › Tanımlamalar (`App.Administration.Definitio
| 4 | Diziler | `App.Definitions.Sequence` | kod seed'i | | 4 | Diziler | `App.Definitions.Sequence` | kod seed'i |
| 5 | Ölçü Kategorileri | `App.Definitions.UomCategory` | kod seed'i | | 5 | Ölçü Kategorileri | `App.Definitions.UomCategory` | kod seed'i |
## 4. Veri modeli ### 4. Veri modeli
Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır. Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
@ -43,7 +48,7 @@ Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
- `Adm_T_ContactPerson` (ContactId → `Adm_T_Contact.Id`, Photo, Name, JobTitle, Email, Phone, Mobile, Notes) - `Adm_T_ContactPerson` (ContactId → `Adm_T_Contact.Id`, Photo, Name, JobTitle, Email, Phone, Mobile, Notes)
- `Adm_T_ContactBankAccount` (ContactId → `Adm_T_Contact.Id`, BankName, Iban, AccountHolder, Currency, IsDefault) - `Adm_T_ContactBankAccount` (ContactId → `Adm_T_Contact.Id`, BankName, Iban, AccountHolder, Currency, IsDefault)
## 5. İş kuralları ve hesaplar ### 5. İş kuralları ve hesaplar
- Tanım lookup'ları yalnızca `IsActive = 1` ve silinmemiş kayıtları listeler. - Tanım lookup'ları yalnızca `IsActive = 1` ve silinmemiş kayıtları listeler.
- Ülke › İl › İlçe › Mahalle cascade: üst alan değişince alttakiler temizlenir; mahalle seçilince posta kodu `copy` tarifiyle dolar. - Ülke › İl › İlçe › Mahalle cascade: üst alan değişince alttakiler temizlenir; mahalle seçilince posta kodu `copy` tarifiyle dolar.
@ -51,15 +56,15 @@ Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
- IBAN doğrulaması: `^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$` (boşluksuz, büyük harf). - IBAN doğrulaması: `^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$` (boşluksuz, büyük harf).
- Yeni kayıtta Tür = Şirket, Grup Teklif Talebi = Sipariş Üzerine, Fatura onayı = 3 doğrulamadan sonra sor varsayılanları tablodan gelir. - Yeni kayıtta Tür = Şirket, Grup Teklif Talebi = Sipariş Üzerine, Fatura onayı = 3 doğrulamadan sonra sor varsayılanları tablodan gelir.
## 6. Yetki ve görünürlük ### 6. Yetki ve görünürlük
Yetki ağacı: `App.Administration` grubu › `App.Administration.Definitions` › `App.Definitions.Contact` (sıra 2) ve `App.Definitions.ContactTag` (sıra 3), her biri + Create/Update/Delete/Export/Import/Note. Alt formların yetkisi `App.Definitions.Contact` altındadır: Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe, Notlar (7–11), İlgili Kişiler (12), Banka Hesapları (13). `Modules = Sas|Adm`, `MultiTenancySide = Both`. Yetkiler hiçbir role otomatik atanmaz. Widget'lar: Kontaklar ve Kontak Etiketleri'nde Toplam · Aktif · Pasif; alt formlarda widget yok. Yetki ağacı: `App.Administration` grubu › `App.Administration.Definitions` › `App.Definitions.Contact` (sıra 2) ve `App.Definitions.ContactTag` (sıra 3), her biri + Create/Update/Delete/Export/Import/Note. Alt formların yetkisi `App.Definitions.Contact` altındadır: Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe, Notlar (7–11), İlgili Kişiler (12), Banka Hesapları (13). `Modules = Sas|Adm`, `MultiTenancySide = Both`. Yetkiler hiçbir role otomatik atanmaz. Widget'lar: Kontaklar ve Kontak Etiketleri'nde Toplam · Aktif · Pasif; alt formlarda widget yok.
## 7. Entegrasyonlar ### 7. Entegrasyonlar
Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalma tanımları yalnızca okunur. Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalma tanımları yalnızca okunur.
## 8. Akış örneği ### 8. Akış örneği
1. Yönetim › Tanımlamalar › Kontak Etiketleri'nde "Vendor" aktif durur. 1. Yönetim › Tanımlamalar › Kontak Etiketleri'nde "Vendor" aktif durur.
2. Kontaklar'da "Acme Corporation" eklenir, Etiketler kutusundan Vendor seçilir. 2. Kontaklar'da "Acme Corporation" eklenir, Etiketler kutusundan Vendor seçilir.
@ -67,14 +72,14 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
4. İlgili Kişiler sekmesinde Addison Olson eklenir; `ContactId` kartın Id'sinden yazılır. 4. İlgili Kişiler sekmesinde Addison Olson eklenir; `ContactId` kartın Id'sinden yazılır.
5. Banka Hesapları sekmesinde IBAN girilir ve Varsayılan işaretlenir. 5. Banka Hesapları sekmesinde IBAN girilir ve Varsayılan işaretlenir.
## 9. Sık sorulanlar ### 9. Sık sorulanlar
- **Tedarikçiler ekranı nereye gitti?** Bu ekrandır; müşteri ve tedarikçi aynı Kontaklar kartında tutulur. - **Tedarikçiler ekranı nereye gitti?** Bu ekrandır; müşteri ve tedarikçi aynı Kontaklar kartında tutulur.
- **Kişiler neden listede ayrı satır değil?** Kişi kontak değildir; kartın İlgili Kişiler sekmesindedir. - **Kişiler neden listede ayrı satır değil?** Kişi kontak değildir; kartın İlgili Kişiler sekmesindedir.
- **Yeni kontakta Satış/Satınalma sekmeleri neden yok?** Sekmeler kaydın kendisini düzenler; kayıt ana formdan eklendikten sonra görünür. - **Yeni kontakta Satış/Satınalma sekmeleri neden yok?** Sekmeler kaydın kendisini düzenler; kayıt ana formdan eklendikten sonra görünür.
- **Sekme "Kayıt Bulunamadı" gösteriyor.** Kullanıcının o sekmenin okuma yetkisi yoktur (`App.Definitions.ContactSales` vb.); yetkiler rol ekranından verilir. - **Sekme "Kayıt Bulunamadı" gösteriyor.** Kullanıcının o sekmenin okuma yetkisi yoktur (`App.Definitions.ContactSales` vb.); yetkiler rol ekranından verilir.
## 10. Dosyalar ve geri alma ### 10. Dosyalar ve geri alma
- Tablolar: `sql/object/Adm_T_Contact.sql`, `Adm_T_ContactPerson.sql`, `Adm_T_ContactBankAccount.sql`, `Adm_T_ContactTag.sql`. - Tablolar: `sql/object/Adm_T_Contact.sql`, `Adm_T_ContactPerson.sql`, `Adm_T_ContactBankAccount.sql`, `Adm_T_ContactTag.sql`.
- Wizard'lar: `ContactTag.json`, `ContactPerson.json`, `ContactBankAccount.json`, `ContactSales.json`, `ContactPurchasing.json`, `ContactFinancial.json`, `ContactAccounting.json`, `ContactNotes.json`, `Contact.json`. - Wizard'lar: `ContactTag.json`, `ContactPerson.json`, `ContactBankAccount.json`, `ContactSales.json`, `ContactPurchasing.json`, `ContactFinancial.json`, `ContactAccounting.json`, `ContactNotes.json`, `Contact.json`.
@ -86,7 +91,9 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur. > Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Kontaklar — `App.Definitions.Contact` ### Tanımlamalar (`App.Administration.Definitions`)
#### Kontaklar — `App.Definitions.Contact`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -102,6 +109,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| Kapsam | Kiracı | | Kapsam | Kiracı |
| Alt formlar | `App.Definitions.ContactSales` (Id → Id), `App.Definitions.ContactPurchasing` (Id → Id), `App.Definitions.ContactFinancial` (Id → Id), `App.Definitions.ContactAccounting` (Id → Id), `App.Definitions.ContactNotes` (Id → Id), `App.Definitions.ContactPerson` (Id → ContactId), `App.Definitions.ContactBankAccount` (Id → ContactId) | | Alt formlar | `App.Definitions.ContactSales` (Id → Id), `App.Definitions.ContactPurchasing` (Id → Id), `App.Definitions.ContactFinancial` (Id → Id), `App.Definitions.ContactAccounting` (Id → Id), `App.Definitions.ContactNotes` (Id → Id), `App.Definitions.ContactPerson` (Id → ContactId), `App.Definitions.ContactBankAccount` (Id → ContactId) |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` | | Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | Country=Türkiye, IsActive=true |
| Veri aynası | `data/App.Definitions.Contact.json` | | Veri aynası | `data/App.Definitions.Contact.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak | | Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
@ -125,7 +133,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `Tags` | Etiketler | dxTagBox | | ✓ | sorgu: Adm_T_ContactTag | | `Tags` | Etiketler | dxTagBox | | ✓ | sorgu: Adm_T_ContactTag |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | | | `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Kontak Etiketleri — `App.Definitions.ContactTag` #### Kontak Etiketleri — `App.Definitions.ContactTag`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -148,7 +156,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `Color` | Renk | dxColorBox | | ✓ | | | `Color` | Renk | dxColorBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | | | `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### İlgili Kişiler — `App.Definitions.ContactPerson` #### İlgili Kişiler — `App.Definitions.ContactPerson`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -174,7 +182,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `Mobile` | Cep Telefonu | dxTextBox | | ✓ | | | `Mobile` | Cep Telefonu | dxTextBox | | ✓ | |
| `Notes` | Notlar | dxTextArea | | ✓ | | | `Notes` | Notlar | dxTextArea | | ✓ | |
### Banka Hesapları — `App.Definitions.ContactBankAccount` #### Banka Hesapları — `App.Definitions.ContactBankAccount`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -198,7 +206,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `Currency` | Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency | | `Currency` | Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `IsDefault` | Varsayılan | dxCheckBox | | ✓ | | | `IsDefault` | Varsayılan | dxCheckBox | | ✓ | |
### Kontak Muhasebe — `App.Definitions.ContactAccounting` #### Kontak Muhasebe — `App.Definitions.ContactAccounting`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -220,7 +228,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `InvoiceAutoApprove` | Faturaları Otomatik Onayla | dxSelectBox | | ✓ | liste: Never, AfterThree, Always | | `InvoiceAutoApprove` | Faturaları Otomatik Onayla | dxSelectBox | | ✓ | liste: Never, AfterThree, Always |
| `PaymentQrType` | Ödeme QR Kodu | dxSelectBox | | ✓ | liste: None, Sepa, Emv | | `PaymentQrType` | Ödeme QR Kodu | dxSelectBox | | ✓ | liste: None, Sepa, Emv |
### Kontak Mali Bilgi ve Diğer — `App.Definitions.ContactFinancial` #### Kontak Mali Bilgi ve Diğer — `App.Definitions.ContactFinancial`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -241,7 +249,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `Reference` | Referans | dxTextBox | | ✓ | | | `Reference` | Referans | dxTextBox | | ✓ | |
| `Barcode` | Barkod | dxTextBox | | ✓ | | | `Barcode` | Barkod | dxTextBox | | ✓ | |
### Kontak Notları — `App.Definitions.ContactNotes` #### Kontak Notları — `App.Definitions.ContactNotes`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -260,7 +268,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| `Notes` | Notlar | dxHtmlEditor | | ✓ | | | `Notes` | Notlar | dxHtmlEditor | | ✓ | |
### Kontak Satınalma — `App.Definitions.ContactPurchasing` #### Kontak Satınalma — `App.Definitions.ContactPurchasing`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -287,7 +295,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `PurchaseIncotermId` | Teslimat Koşulu (Satınalma) | dxSelectBox | | ✓ | sorgu: Prc_T_Incoterm | | `PurchaseIncotermId` | Teslimat Koşulu (Satınalma) | dxSelectBox | | ✓ | sorgu: Prc_T_Incoterm |
| `PurchaseIncotermLocation` | Incoterm Konumu (Satınalma) | dxTextBox | | ✓ | | | `PurchaseIncotermLocation` | Incoterm Konumu (Satınalma) | dxTextBox | | ✓ | |
### Kontak Satış — `App.Definitions.ContactSales` #### Kontak Satış — `App.Definitions.ContactSales`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,22 @@
# Tanımlamalar (`App.Prc.Definitions`) # Satınalma (`App.Prc`)
> Bu dosya ilk deploy'da otomatik açıldı. "Ek — Ekran envanteri" bölümü wizard dosyalarından üretilir, elle düzenlenmez. > Menünün altındaki her dal bu dosyada bir bölümdür (`module-doc:section`); dosya ve bölümler ilk deploy'da otomatik açılır. `module-doc:todo` işaretli alt bölümleri modülü üreten kişi ya da AI doldurur (`lowcode.instructions.md` §0.13); MCP ile üretimde talep bu bölümler doldurulmadan Testing'e alınamaz. "Ek — Ekran envanteri" bölümü wizard dosyalarından üretilir, elle düzenlenmez.
## 1. Amaç ve kapsam <!-- module-doc:section App.Prc.Definitions -->
## Tanımlamalar (`App.Prc.Definitions`)
### 1. Amaç ve kapsam
Satınalma belgelerinde kullanılacak ödeme koşulu, ödeme yöntemi, teslimat koşulu (Incoterm) ve teslim yöntemi tanımları. Kullanıcılar satınalma ve muhasebe personelidir. Satınalma talebi, teklif, sipariş ve fatura belgeleri bu modülün kapsamında değildir; bu ekranlar onların lookup kaynağıdır. Satınalma belgelerinde kullanılacak ödeme koşulu, ödeme yöntemi, teslimat koşulu (Incoterm) ve teslim yöntemi tanımları. Kullanıcılar satınalma ve muhasebe personelidir. Satınalma talebi, teklif, sipariş ve fatura belgeleri bu modülün kapsamında değildir; bu ekranlar onların lookup kaynağıdır.
Tedarikçi kartı (eski `App.Prc.Supplier`) satış ve satınalmanın ortak kartı olarak **Kontaklar** adıyla Yönetim › Tanımlamalar'a taşındı; ayrıntı [AdministrationDefinitions.md](AdministrationDefinitions.md). Kontak kartının Satış ve Satınalma sekmeleri bu modülün tanımlarını lookup olarak kullanır. Tedarikçi kartı (eski `App.Prc.Supplier`) satış ve satınalmanın ortak kartı olarak **Kontaklar** adıyla Yönetim › Tanımlamalar'a taşındı; ayrıntı [Administration › Tanımlamalar](<App.Administration.md>). Kontak kartının Satış ve Satınalma sekmeleri bu modülün tanımlarını lookup olarak kullanır.
## 2. Analiz ve kararlar ### 2. Analiz ve kararlar
- Modül kodu `Prc`, tablo öneki `Prc_T_`; ekranlar Satınalma › Tanımlamalar altında. - Modül kodu `Prc`, tablo öneki `Prc_T_`; ekranlar Satınalma › Tanımlamalar altında.
- Kart, etiket, ilgili kişi ve banka hesabı ekranları Satış tarafı da kullanacağı için `Adm` modülüne (`Adm_T_Contact*`, `App.Definitions.Contact*`) taşındı; bu modülde yalnızca satınalma koşulu tanımları kaldı. - Kart, etiket, ilgili kişi ve banka hesabı ekranları Satış tarafı da kullanacağı için `Adm` modülüne (`Adm_T_Contact*`, `App.Definitions.Contact*`) taşındı; bu modülde yalnızca satınalma koşulu tanımları kaldı.
## 3. Menü ve ekranlar ### 3. Menü ve ekranlar
Satınalma (`App.Prc`) › Tanımlamalar (`App.Prc.Definitions`): Satınalma (`App.Prc`) › Tanımlamalar (`App.Prc.Definitions`):
@ -24,7 +27,7 @@ Satınalma (`App.Prc`) › Tanımlamalar (`App.Prc.Definitions`):
| Teslimat Koşulları | `App.Prc.Incoterm` | tanım, demo veri | | Teslimat Koşulları | `App.Prc.Incoterm` | tanım, demo veri |
| Teslim Yöntemleri | `App.Prc.DeliveryMethod` | tanım, demo veri | | Teslim Yöntemleri | `App.Prc.DeliveryMethod` | tanım, demo veri |
## 4. Veri modeli ### 4. Veri modeli
Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır. Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
@ -33,30 +36,30 @@ Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
- `Prc_T_Incoterm` (Code, Name, IsActive) - `Prc_T_Incoterm` (Code, Name, IsActive)
- `Prc_T_DeliveryMethod` (Name, IsActive) - `Prc_T_DeliveryMethod` (Name, IsActive)
## 5. İş kuralları ve hesaplar ### 5. İş kuralları ve hesaplar
- Tanım lookup'ları yalnızca `IsActive = 1` ve silinmemiş kayıtları listeler; kullanılan tanım silinmek yerine pasife alınır. - Tanım lookup'ları yalnızca `IsActive = 1` ve silinmemiş kayıtları listeler; kullanılan tanım silinmek yerine pasife alınır.
- Ödeme koşulu `DueDays` vade gününü tutar; ay sonu kuralları açıklamada yazılıdır, otomatik vade hesabı yoktur. - Ödeme koşulu `DueDays` vade gününü tutar; ay sonu kuralları açıklamada yazılıdır, otomatik vade hesabı yoktur.
## 6. Yetki ve görünürlük ### 6. Yetki ve görünürlük
Yetki ağacı: `App.Prc` › `App.Prc.Definitions` › her ekran (+ Create/Update/Delete/Export/Import/Note). Tüm ekranlar kiracı kapsamlıdır, şube filtresi yoktur. Widget'lar: her ekranda Toplam · Aktif · Pasif. Yetki ağacı: `App.Prc` › `App.Prc.Definitions` › her ekran (+ Create/Update/Delete/Export/Import/Note). Tüm ekranlar kiracı kapsamlıdır, şube filtresi yoktur. Widget'lar: her ekranda Toplam · Aktif · Pasif.
## 7. Entegrasyonlar ### 7. Entegrasyonlar
Yok. Tanımlar Yönetim › Tanımlamalar › Kontaklar'ın Satış ve Satınalma sekmelerinde (`App.Definitions.ContactSales`, `App.Definitions.ContactPurchasing`) seçilir. Yok. Tanımlar Yönetim › Tanımlamalar › Kontaklar'ın Satış ve Satınalma sekmelerinde (`App.Definitions.ContactSales`, `App.Definitions.ContactPurchasing`) seçilir.
## 8. Akış örneği ### 8. Akış örneği
1. Satınalma › Tanımlamalar › Ödeme Koşulları'nda "30 Gün" aktif durur. 1. Satınalma › Tanımlamalar › Ödeme Koşulları'nda "30 Gün" aktif durur.
2. Yönetim › Tanımlamalar › Kontaklar'da "Acme Corporation" açılır; Satınalma sekmesinde Düzenle ile Ödeme Koşulu = 30 Gün seçilip kaydedilir. 2. Yönetim › Tanımlamalar › Kontaklar'da "Acme Corporation" açılır; Satınalma sekmesinde Düzenle ile Ödeme Koşulu = 30 Gün seçilip kaydedilir.
## 9. Sık sorulanlar ### 9. Sık sorulanlar
- **Tedarikçiler ekranı nerede?** Yönetim › Tanımlamalar › Kontaklar; müşteri ve tedarikçi aynı karttadır. - **Tedarikçiler ekranı nerede?** Yönetim › Tanımlamalar › Kontaklar; müşteri ve tedarikçi aynı karttadır.
- **Pasife alınan tanım kartta görünüyor mu?** Var olan kayıtta değer durur, yeni seçimde listelenmez. - **Pasife alınan tanım kartta görünüyor mu?** Var olan kayıtta değer durur, yeni seçimde listelenmez.
## 10. Dosyalar ve geri alma ### 10. Dosyalar ve geri alma
- Tablolar: `sql/object/Prc_T_PaymentTerm.sql`, `Prc_T_PaymentMethod.sql`, `Prc_T_Incoterm.sql`, `Prc_T_DeliveryMethod.sql`. - Tablolar: `sql/object/Prc_T_PaymentTerm.sql`, `Prc_T_PaymentMethod.sql`, `Prc_T_Incoterm.sql`, `Prc_T_DeliveryMethod.sql`.
- Wizard'lar: `PaymentTerm.json`, `PaymentMethod.json`, `Incoterm.json`, `DeliveryMethod.json`. - Wizard'lar: `PaymentTerm.json`, `PaymentMethod.json`, `Incoterm.json`, `DeliveryMethod.json`.
@ -68,7 +71,9 @@ Yok. Tanımlar Yönetim › Tanımlamalar › Kontaklar'ın Satış ve Satınalm
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur. > Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Ödeme Koşulları — `App.Prc.PaymentTerm` ### Tanımlamalar (`App.Prc.Definitions`)
#### Ödeme Koşulları — `App.Prc.PaymentTerm`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -92,7 +97,7 @@ Yok. Tanımlar Yönetim › Tanımlamalar › Kontaklar'ın Satış ve Satınalm
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | | | `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | | | `Description` | Açıklama | dxTextArea | | ✓ | |
### Ödeme Yöntemleri — `App.Prc.PaymentMethod` #### Ödeme Yöntemleri — `App.Prc.PaymentMethod`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -115,7 +120,7 @@ Yok. Tanımlar Yönetim › Tanımlamalar › Kontaklar'ın Satış ve Satınalm
| `Journal` | Yevmiye | dxTextBox | | ✓ | | | `Journal` | Yevmiye | dxTextBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | | | `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Teslimat Koşulları — `App.Prc.Incoterm` #### Teslimat Koşulları — `App.Prc.Incoterm`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |
@ -138,7 +143,7 @@ Yok. Tanımlar Yönetim › Tanımlamalar › Kontaklar'ın Satış ve Satınalm
| `Name` | Ad | dxTextBox | ✓ | ✓ | | | `Name` | Ad | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | | | `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Teslim Yöntemleri — `App.Prc.DeliveryMethod` #### Teslim Yöntemleri — `App.Prc.DeliveryMethod`
| Özellik | Değer | | Özellik | Değer |
| --- | --- | | --- | --- |

View file

@ -1,552 +0,0 @@
# İK › Puantaj ve Devam Takibi (`App.Hr.Attendance`)
Günlük giriş/çıkış hareketlerinden (PDKS, Excel aktarımı, elle giriş) çalışan × gün puantajı;
personel bazlı aylık puantaj, dönem kapanışı ve PDKS'nin hareket gönderdiği uç. Kişi eşlemesi kullanıcı
kartındaki PDKS Kart No / Sicil No alanlarıyladır.
İzin Yönetimi (onaylı izin → İzinli), Sağlık Kartı (istirahat → Raporlu), Çalışma Takvimi (resmî
tatil / arife) ve Vardiyalar (saat, mola, tolerans) puantajın girdisidir. Çalışan kendi puantajını
**Intranet › Dashboard**'daki **Puantajım** widget'ından izler; İK menüsü İK içindir. Wizard ekranları,
SQL nesneleri, custom component widget'ı, Custom Endpoint ve zamanlanmış işle kuruludur. Çerçeve dışında
tek ek vardır (kullanıcı onayıyla): kullanıcı detayındaki **Puantaj** sekmesi.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| 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 · 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/` |
Kapsam: kiracı (`IsTenant`). Şube kırılımı yoktur.
---
## 2. Analiz ve kararlar
### 2.1 Talep
> Human Resources → Puantaj ve Devam Takibi modülü: günlük hareketler, personel bazlı aylık puantaj.
> Intranet › Dashboard'da aylık puantajıma ulaşabileceğim, ilgili ayın durumunu ve yıllık toplamı gösteren
> widget (sol kolon, anket tarafı). Kolonlar: ilk giriş, son çıkış, net çalışma süresi, mola düşümü, geç
> kalma (dk), erken çıkış (dk), fazla mesai (saat), gün tipi (çalışma, hafta tatili, resmî tatil, izinli,
> raporlu, devamsız). Tolerans vardiya bazında (ör. 10 dk), ayarlardan dinamik. Haftalık 45 saati aşan
> çalışma fazla mesaidir, ücreti %50 zamlıdır (4857 md. 41), yıllık üst sınır 270 saat; ayarlardan
> yönetilir. Veri elle yazılabilir ya da PDKS'den akabilir. Kullanıcı detayında İzinler gibi bir Puantaj
> sekmesi. Diğer modüllerle core koda dokunmadan entegre olmalı.
### 2.2 Depodan okunanlar (sorulmadı)
- Vardiya `Hr_T_Shift` (`StartTime`/`EndTime` `SS:dd` metin, `BreakMinutes`, `WorkingHours`, gece vardiyası); çalışanın vardiyası `AbpUsers.ShiftId`.
- Tatiller `Hr_T_WorkCalendar` (`Holiday` / `HalfDay`, `FREQ=YEARLY`); hafta tatili İzin Yönetimi'nin `App.HrSettings.Leave.Work{Gün}` ayarları.
- Onaylı izin `Hr_T_LeaveRequest.ApprovalStatus = 'Onaylandı'`; rapor `Adm_T_UserSickLeave`.
- İstihdam `AbpUsers.HireDate` / `TerminationDate`; departman/pozisyon `DepartmentId` / `JobPositionId`.
- Kullanıcı kartında (İş sekmesi) PDKS Kart No (`AbpUsers.PdksCardNo`) ve Sicil No (`RegistrationNo`) alanları var; ayrı kart tablosu açılmadı.
- Zamanlanmış işin saklı yordamı parametresiz çağrılır (`PlatformBackgroundWorker`); Custom Endpoint gövdesindeki dizi ham JSON metni olarak bağlanır.
### 2.3 Keşif turu cevapları
| # | Soru | Seçilen |
| --- | --- | --- |
| 1 | Veri modeli | Ham hareket (`Hr_T_AttendanceLog`) + hesaplanan gün (`Hr_T_Timesheet`); İK günü elle düzeltebilir, düzeltilen gün yeniden hesapta ezilmez |
| 2 | Tolerans yeri | Vardiyada (`LateToleranceMinutes`, `EarlyLeaveToleranceMinutes`), boşsa Ayarlar › Puantaj (10 dk) |
| 3 | Geç kalma dakikası | Tolerans aşılırsa dakikanın tamamı yazılır, aşılmazsa 0 |
| 4 | Fazla mesai | Haftalık (Pzt–Paz) birikim 45 saati aştığı günden itibaren aşan kısım o günlere; hafta tatili ve resmî tatildeki çalışmanın tamamı; yıllık 270 saati aşan satır işaretlenir (engellemez) |
| 5 | PDKS | Excel import + API ucu (Custom Endpoint); kişi eşlemesi kullanıcı kartındaki PDKS Kart No, Sicil No ya da kullanıcı adıyla |
| 6 | Mola | `MAX(vardiya molası, gün içi okutulan çıkış→giriş aralıkları)` |
| 7 | Hesap zamanı | Hareket kaydında (o hafta) + gece işi (okutmasız günler) + dönem kaydı (ay) |
| 8 | Kapanış | İK ayı kilitler, onay akışı yok; müdür ekranı/widget'ı yok |
| 9 | Kullanıcı detayı sekmesi | Core istisnası onaylandı (`Details.tsx` + `TimesheetTab.tsx`, İzinler sekmesi emsali) |
| 10 | Elle takip (üretim sırasında) | Elle takipte yalnız gelinmeyen gün yazılır; yazılmayan iş günü "geldi" kabul edilir |
### 2.4 Üretimde verilen kararlar
| Konu | Karar | Neden |
| --- | --- | --- |
| Takip türü | Çalışan bazında otomatik: kullanıcı kartında PDKS Kart No'su olan çalışan **ilk PDKS/aktarım okutmasının gününden itibaren** okutmayla takip edilir (okutmasız iş günü Devamsız); diğerleri elle takip edilir (kayıtsız iş günü vardiya saatleriyle `IsAssumed`). Elle girilen hareket takip türünü değiştirmez | Ayrı bir ayar gerektirmez; kartın sonradan verilmesi geçmiş günleri devamsıza çevirmez |
| Gün sınırı | Hareketin iş günü vardiya bitişi ile bir sonraki başlangıç arasındaki boşluğun ortasından ayrılır (`Hr_V_AttendanceShift.DayOffset`) | Gece vardiyasının 23:50 okutması vardiyanın başladığı ertesi güne yazılır |
| Okutma yönü | `In` / `Out` / `Auto`; `Auto` sıraya göre giriş/çıkış sayılır | PDKS'lerin çoğu yön göndermez |
| Elle düzeltme | Günlük Puantaj'da kaydedilen gün `IsManual = 1` olur; otomatiğe dönmek için satır silinir (yeniden hesap aynı günü kurar) | Ekrandan "elle mi" bilgisini ayrıca istemeden yeniden hesabın düzeltmeyi ezmesini önler |
| Kapalı dönem | Hesap kapalı ayın günlerini yazmaz, yalnız haftalık birikime alır; ekrandan gelen hareket/gün değişikliği `THROW` ile geri alınır; PDKS/gece işi okutmayı `ClosedPeriod` hatasıyla işaretler | PDKS gönderimi tek bir eski okutma yüzünden durmasın |
| Kurulum sırası | Vardiya çözümü view'dadır (`Hr_V_AttendanceShift`) | SQL nesneleri dosya adına göre kurulur; `Hr_F_*` fonksiyonları `Hr_T_*` tablolarından önce oluşur |
| Gece işi kapsamı | `Scope = Host ve Kiracılar`; prosedür veritabanındaki bütün kiracıları işler ve aynı gün işlenen kiracıyı dönemin `NightlyAt` değeriyle atlar | Saklı yordam kiracı parametresi almaz |
| İzin entegrasyonu | `Hr_S_LeaveRequestAfterSave` ve `Hr_S_LeaveSickLeaveSync` sonunda talebin/raporun günleri yeniden hesaplanır | Onaylı izin ve rapor anında puantaja düşer; çekirdek koda dokunulmaz |
| Aylık özet anahtarı | `PeriodKey = {EmployeeId}-{YYYYMM}` hem view'ın `Id`'si hem günlük satırın kolonudur | Alt form ilişkisi tek alanla kurulur |
| Widget sınıfları | Bileşen sınıfları `className` literalleriyle yazılır, koşullu kısım dizge birleştirmeyle eklenir | Safelist üreteci şablon dizgisindeki ifadeyi sınıf sanır |
### 2.5 Varsayılan kalanlar
- Varsayılan vardiya 08:00–18:00, mola 60 dk (vardiyası olmayan çalışan için, ayarlarda değişir).
- İzinli/raporlu gün haftalık 45 saat birikimine 0 katkı verir; o gün okutma varsa net süre birikime girer.
- Yarım gün izinli günde geç/erken yazılmaz; arifede (`HalfDay`) vardiya süresinin yarısında biter.
- Yalnız giriş okutması olan gün `IsIncomplete` (Eksik Okutma) işaretlenir, net süre 0'dır.
---
## 3. Menü ve ekranlar
### 3.1 İK menüsü
`İnsan Kaynakları › Puantaj ve Devam Takibi` (`App.Hr.Attendance`, sıra 15, `FcAlarmClock`) — yalnız İK.
| Sıra | Menü (TR / EN) | `ListFormCode` | Kaynak | Alt formlar | Widget'lar |
| --- | --- | --- | --- | --- | --- |
| 1 | Günlük Hareketler / Attendance Events | `App.Hr.AttendanceLog` | `Hr_T_AttendanceLog` | — | Bugün · PDKS (bu ay) · Manuel (bu ay) · Hatalı |
| 2 | Günlük Puantaj / Daily Timesheet | `App.Hr.Timesheet` | `Hr_T_Timesheet` | — | Devamsız gün · Geç kalınan gün · Fazla mesai saati · Eksik okutma (bu ay) |
| 3 | Aylık Puantaj / Monthly Timesheet | `App.Hr.TimesheetMonthly` | `Hr_V_TimesheetMonthly` (salt okunur) | `App.Hr.Timesheet` (`Id → PeriodKey`) | Çalışan · Fazla mesai · Devamsız gün (bu ay) · Yıllık sınırı aşan |
| 4 | Puantaj Dönemleri / Timesheet Periods | `App.Hr.TimesheetPeriod` | `Hr_T_TimesheetPeriod` | — | Açık · Kapalı · Toplam |
Görünümler: hepsi Grid, Card, Pivot, Chart. Günlük Puantaj'da ekleme yoktur (günleri hesap kurar).
Koşullu biçim: gün tipi (Devamsız kırmızı, Raporlu mor, İzinli mavi, tatiller gri), geç/erken/fazla
mesai > 0, hareket hatası, aylık satırda yıllık sınırın aşılması (satır).
### 3.2 Çalışana ait ekranlar (menüsüz)
| Ekran | `ListFormCode` | Satır süzmesi | Yapılabilen |
| --- | --- | --- | --- |
| Puantajım | `App.Hr.MyTimesheet` | `EmployeeId = @USERID` (`Hr_V_TimesheetMonthly`) | Ayları görür; detayda ayın günleri |
| Puantaj Günlerim | `App.Hr.MyTimesheetDay` | `EmployeeId = @USERID` (`Hr_T_Timesheet`) | Puantajım'ın sekmesi (`Id → PeriodKey`), salt okunur |
### 3.3 Intranet › Dashboard widget'ı
Sol kolon (`Column: 0`), İzinlerim'in (35/36) altında; kayıt `data/App.DeveloperKit.IntranetWidgets.json`.
| `Code` · sıra | Bileşen | `PermissionName` | Gösterir | Buton |
| --- | --- | --- | --- | --- |
| `my-timesheet` · 37 | `MyTimesheetWidget` | `App.Hr.MyTimesheet` | Ay seçici (varsayılan içinde bulunulan ay); net saat, çalışılan gün, fazla mesai, geç kalma dk, erken çıkış dk, devamsız gün; izinli/raporlu/tatil günü; yıl toplamı (net saat, geç kalma, devamsız) ve fazla mesai / yıllık sınır çubuğu | Puantajım |
Bileşen code modunda, rotasızdır; veriyi Puantajım ekranının sorgusundan okur (`GET /api/app/list-form-select/select`).
### 3.4 Kullanıcı detayı › Puantaj
`ui/src/views/admin/user-management/TimesheetTab.tsx`: içinde bulunulan yılın sekiz kartı (çalışma günü,
net saat, fazla mesai — yıllık sınırı aşınca kırmızı —, geç kalma, erken çıkış, devamsız, izinli, raporlu),
altında kullanıcıya süzülmüş **Aylık Puantaj**, **Günlük Puantaj** ve **Günlük Hareketler** listeleri. Her
parça kendi ekran yetkisiyle görünür; yeni hareket satırının `EmployeeId`'si süzgeçten dolar.
### 3.5 Ayarlar
Ayarlar › İnsan Kaynakları › Puantaj (`App.HrSettings` › `App.HrSettings.Attendance`, sağlayıcılar `T|G|D`):
| Kod | Varsayılan | Kullanım |
| --- | --- | --- |
| `App.HrSettings.Attendance.LateToleranceMinutes` | 10 | Vardiyada tolerans yoksa |
| `App.HrSettings.Attendance.EarlyLeaveToleranceMinutes` | 10 | Vardiyada tolerans yoksa |
| `App.HrSettings.Attendance.WeeklyLimitHours` | 45 | Haftalık fazla mesai sınırı |
| `App.HrSettings.Attendance.OvertimeRate` | 1.5 | Zamlı fazla mesai saati çarpanı (Aylık Puantaj) |
| `App.HrSettings.Attendance.AnnualOvertimeLimitHours` | 270 | Yıllık sınır (işaret, widget çubuğu) |
| `App.HrSettings.Attendance.DefaultShiftStart` / `DefaultShiftEnd` | 08:00 / 18:00 | Vardiyası olmayan çalışan |
| `App.HrSettings.Attendance.DefaultBreakMinutes` | 60 | Vardiyası (ya da vardiya molası) olmayan çalışan |
Hafta tatili İzin Yönetimi'nin çalışma günü ayarlarından (`App.HrSettings.Leave.Work{Gün}`) okunur.
---
## 4. Veri modeli
| Tablo / nesne | Kolonlar | Not |
| --- | --- | --- |
| `AbpUsers` (mevcut) | `PdksCardNo`, `RegistrationNo`, `ShiftId`, `HireDate`, `TerminationDate` | Kişi eşlemesi, vardiya, istihdam aralığı |
| `Hr_T_AttendanceLog` | `EmployeeId` (boş olabilir), `CardNo`, `EventTime` (yerel saat), `Direction` (In/Out/Auto), `Source` (Manual/Pdks/Import), `DeviceCode`, `Note`, `WorkDate` (hesaplanır), `IsProcessed`, `ProcessError` (UnknownCard/ClosedPeriod) | `IsProcessed = 0` satırları işlenmeyi bekler |
| `Hr_T_Timesheet` | `EmployeeId`, `WorkDate`, `PeriodYear`, `PeriodMonth`, `PeriodKey`, `ShiftId`, `DayType` (Work/WeeklyOff/Holiday/Leave/Sick/Training/Absent), `LeaveTypeId` (izinli/raporlu günün türü), `TrainingId` (eğitim günü), `FirstIn`, `LastOut`, `BreakMinutes`, `NetMinutes`, `NetHours`, `LateMinutes`, `EarlyLeaveMinutes`, `OvertimeHours`, `IsManual`, `IsIncomplete`, `IsAssumed`, `Note`, `CalculatedAt` | Çalışan × gün tek etkin satır |
| `Hr_T_TimesheetPeriod` | `PeriodYear`, `PeriodMonth`, `Status` (Open/Closed), `CalculatedAt`, `ClosedAt`, `ClosedById`, `NightlyAt`, `Note` | Kiracıda yıl+ay tekil (prosedür denetler) |
| `Hr_T_Shift` (+) | `LateToleranceMinutes`, `EarlyLeaveToleranceMinutes` | Vardiyalar ekranında düzenlenir |
| `Hr_V_AttendanceShift` | Çalışanın dakika cinsinden vardiyası, mola, toleranslar, `DayOffset` | Varsayılanlar ayardan |
| `Hr_V_TimesheetMonthly` | Çalışan × ay toplamları, `UnpaidLeaveDays` (ücretsiz türdeki izinli gün — bordroda eksik gün), `TrainingDays`, `OvertimePaidHours`, `YearOvertimeHours`, `AnnualOvertimeLimit`, `IsOverAnnualLimit`, `PeriodStatus` | `Id = PeriodKey` |
Entity yoktur (wizard tabloları); `sql/object/`.
---
## 5. İş kuralları ve hesaplar
### 5.1 Prosedürler
| Nesne | Ne yapar | Çağıran |
| --- | --- | --- |
| `Hr_S_TimesheetCalculate @TenantId, @EmployeeId, @From, @To, @ActorId` | Aralığı tam haftalara genişletip (bugünle sınırlı) çalışan × gün satırlarını kurar/günceller; kapalı ayı yazmaz | Aşağıdakilerin hepsi, izin ve rapor prosedürleri |
| `Hr_S_AttendanceProcess @FailOnClosed` | İşlenmemiş hareketlerde kart → çalışan, iş günü, hata; etkilenen günleri (eski + yeni) yeniden hesaplar | Günlük Hareketler kayıt/silme, PDKS ucu, gece işi |
| `Hr_S_AttendanceImport @TenantId, @UserId, @Items` | PDKS gönderimini (`CardNo`, `RegistrationNo` ya da `UserName`; `EventTime`, `Direction`, `DeviceCode`) yazar, tekrarı atlar; `Received/Inserted/Skipped/Errors` döner | Custom Endpoint |
| `Hr_S_TimesheetPeriodAfterSave @PeriodId, @ActorId` | Yıl/ay doğrulama ve tekillik; açık dönemde ayı yeniden hesaplar; kapatmada son hesap + kilit | Puantaj Dönemleri kayıt |
| `Hr_S_TimesheetNightly` | Kart numarası sonradan girilen "Kart tanımsız" okutmaları yeniden kuyruğa alır ve işler; her kiracı için ayın dönemini açar, dünün ayını ve kapanmamışsa önceki ayı hesaplar | Zamanlanmış iş `Timesheet Nightly` (02:00) |
| `Hr_F_WorkCalendarDayType @TenantId, @Day` | Güne düşen `Holiday` / `HalfDay` | Hesap |
| `Hr_F_TimesheetPeriodClosed @TenantId, @From, @To` | Aralığın değdiği aylardan biri kapalı dönem mi | İzin, rapor ve izin guard prosedürleri |
| `Hr_S_TrainingSync @TrainingId, @UserId, @ActorId` | Katılımcının (boşsa bütün katılımcıların) eğitim günlerini yeniden hesaplar, bağlı gelişim aksiyonunu tamamlar | Eğitimler ve Katılımcılar kayıt/silme |
| `Hr_S_WorkCalendarAfterSave @CalendarId, @ActorId` | Takvim satırına denk gelen izinlerin gün sayısını ve puantajı yeniden hesaplar | Çalışma Takvimi kayıt/silme |
### 5.2 Günün hesabı
1. **Gün tipi** (öncelik sırasıyla):
1. rapor (Sağlık Kartı istirahati; takvim günü, hafta sonu dahil) → `Sick`;
2. onaylı **ücretsiz** izin (`LeaveType.IsPaid = 0`: ücretsiz izin, doğum izni; takvim günü) → `Leave`;
3. takvim tatili → `Holiday`; ayarda çalışma günü değil → `WeeklyOff`;
4. onaylı **ücretli** izin → `Leave` (ücretli izne denk gelen tatil izin günü değildir, 4857 md. 56 — izin gün sayısıyla aynı kural); yarım gün izinde okutma varsa `Work`;
5. okutma var → `Work`;
6. eğitime "Katıldı" işaretli katılımcı → `Training` (vardiya saatleriyle, `IsAssumed`);
7. elle takip edilen çalışan → `Work` (`IsAssumed`, vardiya saatleri); geçmiş gün → `Absent`; kartlı çalışanın okutmasız bugünü yazılmaz.
İzinli/raporlu günde `LeaveTypeId`, eğitim gününde `TrainingId` yazılır. İstihdam dışındaki okutmasız gün yazılmaz.
2. **İlk giriş / son çıkış**: ilk `In` (yoksa ilk okutma) / son `Out` (ilk girişten sonraysa).
3. **Mola** = `MAX(vardiya molası, ilk giriş–son çıkış arasındaki çıkış→giriş aralıkları)`, brüt süreyi aşamaz; elle düzeltilen günde girilen mola.
4. **Net** = son çıkış − ilk giriş − mola.
5. **Geç kalma** = ilk giriş − vardiya başlangıcı, tolerans aşılırsa tamamı; **erken çıkış** = vardiya bitişi (arifede yarı süre) − son çıkış, tolerans aşılırsa tamamı. Yalnız `Work` gününde; eğitim günü haftalık birikime çalışma gibi girer.
6. **Fazla mesai** = `WeeklyOff`/`Holiday` gününde net sürenin tamamı; diğer günlerde haftanın kümülatif netinin sınırı aştığı kısım (`max(0, kümülatif − sınır) − max(0, önceki kümülatif − sınır)`).
7. Kaynağı kalkan gün: tek çalışanlık hesap, artık hiçbir izin/rapor/katılımla örtüşmeyen `Leave`/`Sick`/`Training` günlerini (talebin tarihi değişti, rapor ya da katılım silindi) aralığa katar; eski günler aynı işlemde düzelir.
8. Aylık view: `OvertimePaidHours = OvertimeHours × OvertimeRate`; `YearOvertimeHours` ocaktan o aya birikim; `IsOverAnnualLimit` sınır aşımı.
### 5.3 Ekran komutları
| Ekran | Komut |
| --- | --- |
| Günlük Hareketler | Ekle/düzenle/sil sonrası satır işlenmemiş işaretlenir → `Hr_S_AttendanceProcess 1` (kapalı dönemde kayıt geri alınır) |
| Günlük Puantaj | Düzenle/sil öncesi kapalı dönem kontrolü; düzenleme sonrası `IsManual = 1, IsAssumed = 0` ve günün haftası yeniden hesaplanır; silme sonrası gün otomatik yeniden kurulur |
| Puantaj Dönemleri | Kayıt sonrası `Hr_S_TimesheetPeriodAfterSave`; kapalı dönem silinemez; ayda onay bekleyen izin varsa kapatılamaz |
Hata kodları: 51200 (kapalı döneme hareket), 51201 (kapalı dönemin günü), 51210 (PDKS gövdesi), 51220–51223
(dönem; 51223 onay bekleyen izin), 51118 (kapalı döneme izin), 51120 (kapalı döneme rapor).
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Kök `App.Hr.Attendance` (sıra 9); dört ekran altında, her biri
`.Create/.Update/.Delete/.Export/.Import/.Note` ile. `App.Hr.MyTimesheet` (sıra 10) kökte,
`App.Hr.MyTimesheetDay` onun altında. Ayar yetkisi `App.HrSettings.Attendance` (`App.Administration`
grubu, `App.HrSettings` altında). Hiçbir yetki role otomatik atanmaz.
| Rol | Yetkiler |
| --- | --- |
| Tüm çalışanlar | `App.Hr.MyTimesheet`, `App.Hr.MyTimesheetDay` (widget ve Puantajım) |
| İK | `App.Hr.Attendance` dalının tamamı, `App.HrSettings.Attendance` |
| PDKS servis kullanıcısı | `PDKS` rolü + `App.DeveloperKit.CustomEndpoints.Post` |
---
## 7. Entegrasyonlar
| Modül | Yön | Nasıl |
| --- | --- | --- |
| Kimlik › Kullanıcılar | → puantaj | PDKS Kart No / Sicil No kişi eşlemesi, vardiya, işe giriş/çıkış; kart numarası değişikliği gece işinde yansır |
| İzin Yönetimi | → puantaj | `Hr_S_LeaveRequestAfterSave` (kayıt, karar, silme) sonunda talebin günleri — tarihi değiştiyse eski günleri de — yeniden hesaplanır; onaylı izin `Leave` |
| Sağlık Kartı › İstirahatler | ↔ izin, → puantaj | `Hr_S_LeaveSickLeaveSync` (kayıt, silme) raporu onaylı Rapor talebine eşler, rapora denk gelen izin günlerini bakiyeye döndürür (`Hr_S_LeaveDaysRefresh`), günleri yeniden hesaplar; rapor `Sick` |
| Eğitimler | → puantaj | Katıldı işaretli katılımcının eğitim günleri `Training`; eğitim/katılımcı kaydı `Hr_S_TrainingSync` ile hemen yansır |
| Çalışma Takvimi | → izin, → puantaj | Tatil `Holiday`, arife `HalfDay` (yarım vardiya); takvim kaydı denk gelen izinlerin gün sayısını ve puantajı hemen yeniler (`Hr_S_WorkCalendarAfterSave`) |
| Vardiyalar / kullanıcı vardiyası | → puantaj | Saat, mola, tolerans; değişiklik gece işinde ya da dönem kaydında yansır |
| İzin ayarları | → puantaj | Hafta tatili günleri |
| PDKS | → hareket | Custom Endpoint `POST /api/app/custom-endpoint/hr/attendance/` (`data/App.DeveloperKit.CustomEndpoints.json`, `PermissionsJson`: rol `PDKS`) |
| Excel | → hareket | Günlük Hareketler import'u aynı kayıt yolundan (`InsertAfterCommand`) geçer; `Source` boş gelir ve `Import` yazılır |
| Intranet | ← puantaj | `my-timesheet` widget'ı |
| Kullanıcı detayı | ← puantaj | Puantaj sekmesi |
### 7.1 Çakışma kuralları (modüller birbirine zıt sonuç vermez)
| Durum | Kural | Nerede |
| --- | --- | --- |
| Raporlu gün | Devamsız yazılmaz; `Sick` her şeyin önündedir (okutma olsa da) | `Hr_S_TimesheetCalculate` |
| Yıllık izin sırasında rapor | Rapora denk gelen izin günü izinden sayılmaz, bakiyeye döner; puantajda `Sick` | `Hr_F_LeaveNetDays`, `Hr_S_LeaveDaysRefresh` |
| Rapor talebi onay beklerken | Sağlık kartına ve puantaja düşmez; onayla istirahat kaydı oluşur, redde kalkar | `Hr_S_LeaveRequestAfterSave` |
| Ücretli izne denk gelen tatil | Puantajda `Holiday`/`WeeklyOff`; izin gün sayısına girmez (4857 md. 56) | hesap + `Hr_F_LeaveWorkDays` |
| Ücretsiz izin / doğum izni | Hafta tatili ve genel tatil dahil `Leave` (ücret doğmaz); aylıkta `UnpaidLeaveDays` | hesap + aylık view |
| Eğitime katılan | Okutması yoksa devamsız değil `Training`; okutması varsa `Work` | hesap |
| Kapalı puantaj ayı | İzin talebi ve rapor eklenemez/değiştirilemez/silinemez (51118, 51120); onay bekleyen izni olan ay kapatılamaz (51223) | guard + kayıt prosedürleri + dönem kaydı |
| İstihdam dışı | İzin tarihleri işe giriş – işten çıkış arasında olmalı (51119); puantaj istihdam dışında gün yazmaz | `Hr_S_LeaveRequestAfterSave`, hesap |
| Takvime sonradan girilen bayram | Denk gelen izinlerin gün sayısı ve puantaj hemen yenilenir | `Hr_S_WorkCalendarAfterSave` |
PDKS gövdesi:
```json
{ "Items": [ { "CardNo": "000123", "EventTime": "2026-10-05T08:02:00", "Direction": "In", "DeviceCode": "GATE-1" } ] }
```
`Items` dizi ya da JSON metni olabilir; `CardNo` yerine `UserName` gönderilebilir. Endpoint kaydı
kiracıya aittir (`CustomEndpoint` `IMultiTenant`): seed satırı host'a kurulur, kiracıda Developer Kit ›
Custom Endpoints'ten aynı SQL ve parametrelerle eklenir.
---
## 8. Akış örneği
1. İK üretim çalışanlarının kullanıcı kartına (İş sekmesi) PDKS Kart No girer; ofis çalışanlarında alan boştur (elle takip).
2. PDKS her 5 dakikada okutmaları uca gönderir; **Günlük Hareketler**'e düşer, Günlük Puantaj o hafta için güncellenir. Tanımsız kart "Kart tanımsız" görünür; numara kullanıcı kartına girilince gece işi okutmayı işler.
3. Gece 02:00'de iş dünü kurar: kartlı çalışanın okutmasız iş günü Devamsız, elle takip edilen çalışanın günü vardiya saatleriyle Varsayılan çalışma.
4. Ofis çalışanı bir gün gelmez; İK **Günlük Puantaj**'da o günü Devamsız yapar (gün Elle olur).
5. Ay sonunda İK **Aylık Puantaj**'ı kontrol eder, yıllık sınırı aşanları görür; **Puantaj Dönemleri**'nde ayı Kapalı yapar.
6. Çalışan panodaki **Puantajım**'da ayını ve yıl toplamını görür, butonla aylarına ve günlerine iner.
---
## 9. Sık sorulanlar ve bilinen sınırlar
**Okutması olmayan çalışan neden hep "geldi" görünüyor?** Kullanıcı kartında PDKS Kart No yoksa (ya da henüz PDKS okutması gelmediyse) elle takip edilir; gelinmeyen gün Günlük Puantaj'da işaretlenir. İlk PDKS okutmasından itibaren okutmayla takip edilir.
**Düzelttiğim gün neden yeniden hesapta değişmiyor?** Elle düzeltilen gün korunur. Otomatiğe döndürmek için satırı silin.
**Vardiya/takvim/ayar değişti, puantaj eski.** Gece işi dünün ve önceki ayın günlerini yeniler; hemen yansıtmak için Puantaj Dönemleri'nde ilgili ayı kaydedin.
**Kapalı aya PDKS okutması geldi.** Okutma `Kapalı dönem` hatasıyla kalır; dönem açılıp kaydedilirse hareket ekranından düzenlenerek işlenir.
**Fazla mesai hafta tatilinde neden tamamı?** Talepte istenen kural (hafta tatili ve resmî tatilde çalışma fazla mesaidir); haftalık 45 saat birikimine bu günler katılmaz.
**İzin talebinin tarihi değişti, eski günler ne olur?** Aynı kayıtta düzelir: tek çalışanlık hesap kaynağı kalmayan izinli/raporlu/eğitim günlerini aralığa katar.
**Hasta raporu alan çalışan devamsız mı yazılır?** Hayır. Rapor günleri (hafta sonu dahil) `Raporlu`dur; aynı güne izin ya da okutma düşse bile rapor önce gelir. Yıllık izindeyken rapor alındıysa o günler izinden düşmez, bakiyeye döner.
**Eğitimdeki çalışan devamsız görünüyor.** Katılımcı satırında durum "Katıldı" olmalıdır; "Katılmadı/Mazeretli" günler normal hesaplanır.
**Çalışma Takvimi kaydı sonrası hangi günler yenilenir?** Satırın tarihleri (yıllık tekrarlıda bu yıl ve gelecek yıl). Tarihi değiştirilen satırın eski günleri gece işinde ya da dönem kaydında düzelir.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_{AttendanceLog,Timesheet,TimesheetPeriod}.sql` | Tablolar |
| `configs/seeds/sql/object/Hr_T_Shift.sql` | Vardiya toleransı kolonları |
| `configs/seeds/sql/object/Hr_V_{AttendanceShift,TimesheetMonthly}.sql` | View'lar |
| `configs/seeds/sql/object/Hr_F_{WorkCalendarDayType,TimesheetPeriodClosed,LeaveNetDays}.sql` | Takvim günü türü, kapalı dönem, rapordan arınmış izin günü |
| `configs/seeds/sql/object/Hr_S_{TimesheetCalculate,AttendanceProcess,AttendanceImport,TimesheetPeriodAfterSave,TimesheetNightly}.sql` | Hesap ve tetikler |
| `configs/seeds/sql/object/Hr_S_{LeaveRequestAfterSave,LeaveRequestGuard,LeaveSickLeaveSync,LeaveDaysRefresh}.sql` | İzin/rapor → puantaj tetiği, kapalı dönem guard'ı, rapor → izin günü |
| `configs/seeds/sql/object/Hr_S_{TrainingSync,WorkCalendarAfterSave}.sql` | Eğitim ve takvim → puantaj tetiği |
| `configs/seeds/wizard/{LeaveRequest,MyLeaveRequest,HealthCardSickLeave,Training,TrainingParticipant,WorkCalendar}.json` | Tetik komutları (silme dahil) |
| `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs` (`UserSickLeave`) | Kullanıcı detayındaki istirahat listesinin silme tetiği |
| `configs/seeds/wizard/{AttendanceLog,Timesheet,TimesheetMonthly,TimesheetPeriod,MyTimesheet,MyTimesheetDay}.json` | 6 wizard |
| `configs/seeds/wizard/Shift.json` | Tolerans alanları |
| `configs/seeds/data/App.DeveloperKit.CustomComponents/1bca19aa-e144-5e4c-b23b-9eddd4dafd19.json` | `MyTimesheetWidget` |
| `configs/seeds/data/App.DeveloperKit.IntranetWidgets.json` | `my-timesheet` |
| `configs/seeds/data/App.DeveloperKit.CustomEndpoints.json` | PDKS ucu |
| `configs/seeds/data/App.BackgroundWorkers.RecurringJobs.json` | `Timesheet Nightly` |
| `configs/seeds/data/App.SettingDefinitions.json` | `App.HrSettings.Attendance.*` |
| `configs/seeds/data/{App.Menus.Menu,Abp.Identity.Permissions,App.Languages.LanguageText}.json` | Menü, yetki, dil |
| `ui/src/views/admin/user-management/{TimesheetTab,Details}.tsx` | Kullanıcı detayı › Puantaj |
Geri alma: Wizard Manager'dan altı ekran silinir; widget, endpoint, iş ve ayar satırları kendi
ekranlarından kaldırılır. İzin/rapor prosedürlerindeki `Hr_S_TimesheetCalculate` çağrısı kaldırılmadan
prosedür silinmemelidir. Tablolar düşürülmez.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Günlük Hareketler — `App.Hr.AttendanceLog`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/AttendanceLog.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Attendance` › `App.Hr.AttendanceLog` (sıra 1) |
| Yetki | `App.Hr.AttendanceLog` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Günlük Hareketler — PDKS'den (kart okuyucu), Excel aktarımından gelen ya da elle girilen ham giriş/çıkış okutmaları. |
| Kaynak | `Hr_T_AttendanceLog` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Hr.Attendance.Widget.Today`, `App.Hr.Attendance.Widget.Pdks`, `App.Hr.Attendance.Widget.Manual`, `App.Hr.Attendance.Widget.Error` |
| Komutlar | InsertAfter: `Hr_S_AttendanceProcess` · UpdateAfter: `Hr_S_AttendanceProcess` · DeleteAfter: `Hr_S_AttendanceProcess` |
| Form varsayılanları | Direction=Auto, Source=Manual |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `CardNo` | Kart No | dxTextBox | | ✓ | |
| `EventTime` | Zaman | dxDateBox | ✓ | ✓ | |
| `Direction` | Yön | dxSelectBox | ✓ | ✓ | liste: In, Out, Auto |
| `Source` | Kaynak | dxSelectBox | | ✓ | liste: Manual, Pdks, Import |
| `DeviceCode` | Cihaz | dxTextBox | | ✓ | |
| `WorkDate` | İş Günü | dxDateBox | | | |
| `ProcessError` | Hata | dxSelectBox | | | liste: UnknownCard, ClosedPeriod |
| `Note` | Not | dxTextArea | | ✓ | |
### Günlük Puantaj — `App.Hr.Timesheet`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Timesheet.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Attendance` › `App.Hr.Timesheet` (sıra 2) |
| Yetki | `App.Hr.Timesheet` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Günlük Puantaj — çalışan × gün satırı: ilk giriş, son çıkış, net çalışma süresi, mola düşümü, geç kalma (dk), erken çıkış (dk), fazla mesai (saat) ve gün tipi (çalışma, hafta tatili, resmî tatil, izinli, raporlu, eğitimde, devamsız). |
| Kaynak | `Hr_T_Timesheet` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Hr.Attendance.Widget.Absent`, `App.Hr.Attendance.Widget.Late`, `App.Hr.Attendance.Widget.Overtime`, `App.Hr.Attendance.Widget.Incomplete` |
| Komutlar | UpdateBefore: SQL · UpdateAfter: `Hr_S_TimesheetCalculate` · DeleteBefore: SQL · DeleteAfter: `Hr_S_TimesheetCalculate` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `WorkDate` | İş Günü | dxDateBox | | ✓ | |
| `DayType` | Gün Tipi | dxSelectBox | ✓ | ✓ | liste: Work, WeeklyOff, Holiday, Training, Leave, Sick, Absent |
| `LeaveTypeId` | İzin Türü | dxSelectBox | | ✓ | sorgu: Hr_T_LeaveType |
| `TrainingId` | Eğitim | dxSelectBox | | | sorgu: Hr_T_Training |
| `ShiftId` | Vardiya | dxSelectBox | | | sorgu: Hr_T_Shift |
| `FirstIn` | İlk Giriş | dxDateBox | | ✓ | |
| `LastOut` | Son Çıkış | dxDateBox | | ✓ | |
| `NetHours` | Net Çalışma (saat) | dxNumberBox | | | |
| `BreakMinutes` | Mola Düşümü (dk) | dxNumberBox | | ✓ | |
| `LateMinutes` | Geç Kalma (dk) | dxNumberBox | | | |
| `EarlyLeaveMinutes` | Erken Çıkış (dk) | dxNumberBox | | | |
| `OvertimeHours` | Fazla Mesai (saat) | dxNumberBox | | | |
| `IsAssumed` | Varsayılan | dxCheckBox | | | |
| `IsManual` | Elle | dxCheckBox | | | |
| `IsIncomplete` | Eksik Okutma | dxCheckBox | | | |
| `Note` | Not | dxTextArea | | ✓ | |
| `PeriodYear` | Yıl | dxNumberBox | | | |
| `PeriodMonth` | Ay | dxNumberBox | | | |
| `PeriodKey` | Dönem Anahtarı | dxTextBox | | | |
### Aylık Puantaj — `App.Hr.TimesheetMonthly`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/TimesheetMonthly.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Attendance` › `App.Hr.TimesheetMonthly` (sıra 3) |
| Yetki | `App.Hr.TimesheetMonthly` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Aylık Puantaj — Günlük Puantaj'ın personel bazlı aylık toplamı (salt okunur). |
| Kaynak | `Hr_V_TimesheetMonthly` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.Timesheet` (Id → PeriodKey) |
| Widget'lar | `App.Hr.Attendance.Widget.Employees`, `App.Hr.Attendance.Widget.Overtime`, `App.Hr.Attendance.Widget.Absent`, `App.Hr.Attendance.Widget.OverLimit` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `DepartmentId` | Departman | dxSelectBox | | ✓ | sorgu: Hr_T_Department |
| `JobPositionId` | Pozisyon | dxSelectBox | | ✓ | sorgu: Hr_T_JobPosition |
| `PeriodYear` | Yıl | dxNumberBox | | ✓ | |
| `PeriodMonth` | Ay | dxNumberBox | | ✓ | |
| `PeriodStatus` | Dönem Durumu | dxSelectBox | | ✓ | liste: Open, Closed |
| `WorkDays` | Çalışma Günü | dxNumberBox | | ✓ | |
| `NetHours` | Net Çalışma (saat) | dxNumberBox | | ✓ | |
| `LateMinutes` | Geç Kalma (dk) | dxNumberBox | | ✓ | |
| `LateDays` | Geç Kalınan Gün | dxNumberBox | | ✓ | |
| `EarlyLeaveMinutes` | Erken Çıkış (dk) | dxNumberBox | | ✓ | |
| `EarlyLeaveDays` | Erken Çıkılan Gün | dxNumberBox | | ✓ | |
| `OvertimeHours` | Fazla Mesai (saat) | dxNumberBox | | ✓ | |
| `OvertimePaidHours` | Zamlı Fazla Mesai (saat) | dxNumberBox | | ✓ | |
| `WeeklyOffDays` | Hafta Tatili | dxNumberBox | | ✓ | |
| `HolidayDays` | Resmî Tatil | dxNumberBox | | ✓ | |
| `LeaveDays` | İzinli Gün | dxNumberBox | | ✓ | |
| `UnpaidLeaveDays` | Ücretsiz İzinli Gün | dxNumberBox | | ✓ | |
| `TrainingDays` | Eğitim Günü | dxNumberBox | | ✓ | |
| `SickDays` | Raporlu Gün | dxNumberBox | | ✓ | |
| `AbsentDays` | Devamsız Gün | dxNumberBox | | ✓ | |
| `IncompleteDays` | Eksik Okutmalı Gün | dxNumberBox | | ✓ | |
| `AssumedDays` | Varsayılan Gün | dxNumberBox | | ✓ | |
| `YearOvertimeHours` | Yıllık Fazla Mesai (saat) | dxNumberBox | | ✓ | |
| `AnnualOvertimeLimit` | Yıllık Fazla Mesai Sınırı | dxNumberBox | | ✓ | |
| `IsOverAnnualLimit` | Yıllık Sınır Aşıldı | dxNumberBox | | | |
### Puantaj Dönemleri — `App.Hr.TimesheetPeriod`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/TimesheetPeriod.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Attendance` › `App.Hr.TimesheetPeriod` (sıra 4) |
| Yetki | `App.Hr.TimesheetPeriod` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Puantaj Dönemleri — aylık puantaj dönemleri: yeniden hesaplama ve kapanış. |
| Kaynak | `Hr_T_TimesheetPeriod` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Hr.Attendance.Widget.Open`, `App.Hr.Attendance.Widget.Closed`, `App.Widget.Total` |
| Komutlar | InsertAfter: `Hr_S_TimesheetPeriodAfterSave` · UpdateAfter: `Hr_S_TimesheetPeriodAfterSave` · DeleteBefore: SQL |
| Form varsayılanları | PeriodYear=@YEAR, PeriodMonth=@MONTH, Status=Open |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `PeriodYear` | Yıl | dxNumberBox | ✓ | ✓ | |
| `PeriodMonth` | Ay | dxNumberBox | ✓ | ✓ | |
| `Status` | Durum | dxRadioGroup | ✓ | ✓ | liste: Open, Closed |
| `CalculatedAt` | Hesaplanma | dxDateBox | | | |
| `ClosedAt` | Kapanış | dxDateBox | | | |
| `ClosedById` | Kapatan | dxSelectBox | | | sorgu: AbpUsers |
| `NightlyAt` | Gece İşi | dxDateBox | | | |
| `Note` | Not | dxTextArea | | ✓ | |
### Puantajım — `App.Hr.MyTimesheet`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/MyTimesheet.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.MyTimesheet` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Puantajım — kendi aylık puantajınız: ay ay çalışma günü, net saat, geç kalma, erken çıkış, fazla mesai ve izinli/raporlu/devamsız günler. |
| Kaynak | `Hr_V_TimesheetMonthly` · filtre `"EmployeeId" = '@USERID'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.MyTimesheetDay` (Id → PeriodKey) |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `PeriodYear` | Yıl | dxNumberBox | | ✓ | |
| `PeriodMonth` | Ay | dxNumberBox | | ✓ | |
| `PeriodStatus` | Dönem Durumu | dxSelectBox | | ✓ | liste: Open, Closed |
| `WorkDays` | Çalışma Günü | dxNumberBox | | ✓ | |
| `NetHours` | Net Çalışma (saat) | dxNumberBox | | ✓ | |
| `LateMinutes` | Geç Kalma (dk) | dxNumberBox | | ✓ | |
| `LateDays` | Geç Kalınan Gün | dxNumberBox | | ✓ | |
| `EarlyLeaveMinutes` | Erken Çıkış (dk) | dxNumberBox | | ✓ | |
| `EarlyLeaveDays` | Erken Çıkılan Gün | dxNumberBox | | ✓ | |
| `OvertimeHours` | Fazla Mesai (saat) | dxNumberBox | | ✓ | |
| `OvertimePaidHours` | Zamlı Fazla Mesai (saat) | dxNumberBox | | ✓ | |
| `WeeklyOffDays` | Hafta Tatili | dxNumberBox | | ✓ | |
| `HolidayDays` | Resmî Tatil | dxNumberBox | | ✓ | |
| `LeaveDays` | İzinli Gün | dxNumberBox | | ✓ | |
| `UnpaidLeaveDays` | Ücretsiz İzinli Gün | dxNumberBox | | ✓ | |
| `TrainingDays` | Eğitim Günü | dxNumberBox | | ✓ | |
| `SickDays` | Raporlu Gün | dxNumberBox | | ✓ | |
| `AbsentDays` | Devamsız Gün | dxNumberBox | | ✓ | |
| `IncompleteDays` | Eksik Okutmalı Gün | dxNumberBox | | ✓ | |
| `AssumedDays` | Varsayılan Gün | dxNumberBox | | ✓ | |
| `YearOvertimeHours` | Yıllık Fazla Mesai (saat) | dxNumberBox | | ✓ | |
| `AnnualOvertimeLimit` | Yıllık Fazla Mesai Sınırı | dxNumberBox | | ✓ | |
| `IsOverAnnualLimit` | Yıllık Sınır Aşıldı | dxNumberBox | | | |
### Puantaj Günlerim — `App.Hr.MyTimesheetDay`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/MyTimesheetDay.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.MyTimesheetDay` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Puantaj Günlerim — puantajınızın günleri: ilk giriş, son çıkış, net çalışma, mola, geç kalma, erken çıkış, fazla mesai ve gün tipi. |
| Kaynak | `Hr_T_Timesheet` · filtre `"IsDeleted" = 'false' AND "EmployeeId" = '@USERID'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `WorkDate` | İş Günü | dxDateBox | | | |
| `DayType` | Gün Tipi | dxSelectBox | | | liste: Work, WeeklyOff, Holiday, Training, Leave, Sick, Absent |
| `LeaveTypeId` | İzin Türü | dxSelectBox | | | sorgu: Hr_T_LeaveType |
| `TrainingId` | Eğitim | dxSelectBox | | | sorgu: Hr_T_Training |
| `FirstIn` | İlk Giriş | dxDateBox | | | |
| `LastOut` | Son Çıkış | dxDateBox | | | |
| `NetHours` | Net Çalışma (saat) | dxNumberBox | | | |
| `BreakMinutes` | Mola Düşümü (dk) | dxNumberBox | | | |
| `LateMinutes` | Geç Kalma (dk) | dxNumberBox | | | |
| `EarlyLeaveMinutes` | Erken Çıkış (dk) | dxNumberBox | | | |
| `OvertimeHours` | Fazla Mesai (saat) | dxNumberBox | | | |
| `IsAssumed` | Varsayılan | dxCheckBox | | | |
| `IsManual` | Elle | dxCheckBox | | | |
| `IsIncomplete` | Eksik Okutma | dxCheckBox | | | |
| `Note` | Not | dxTextBox | | | |
| `PeriodKey` | Dönem Anahtarı | dxTextBox | | | |
<!-- module-doc:auto:end -->

View file

@ -1,351 +0,0 @@
# İK › Tanımlamalar (`App.Hr.Definitions`)
İK modüllerinin ortak sözlükleri. Bu doküman iki işi görür: (1) Tanımlamalar menüsünün **dizinidir**
— her tanımın hangi modüle ait olduğunu ve nerede kullanıldığını gösterir; (2) belirli bir modüle ait
olmayan genel tanımları (kurum doktoru, yeterlilik belgesi, hastane, hastane bölümü, evrak türü, izin
türü, masraf türü, yan hak türü) anlatır.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Dizin
`İnsan Kaynakları › Tanımlamalar` (`App.Hr.Definitions`, ikon `FcFilingCabinet`)
| Sıra | Menü | `ListFormCode` | Kaynak | Ayrıntı |
| --- | --- | --- | --- | --- |
| 1 | Departmanlar | `App.Hr.Department` | Entity + seeder | [HrOrganization.md](HrOrganization.md) |
| 2 | Pozisyonlar | `App.Hr.JobPosition` | Entity + seeder | [HrOrganization.md](HrOrganization.md) |
| 3 | Yetenek Tipleri | `App.Hr.SkillType` | Entity + seeder | [HrOrganization.md](HrOrganization.md) |
| 4 | Yeterlilik Belgeleri | `App.Hr.Certificate` | Wizard | bu doküman |
| 5 | Evrak Türleri | `App.Hr.DocumentType` | Wizard | bu doküman |
| 6 | Aday Kaynakları | `App.Hr.CandidateSource` | Wizard | [HrRecruitment.md](HrRecruitment.md) |
| 7 | Ret Sebepleri | `App.Hr.RejectionReason` | Wizard | [HrRecruitment.md](HrRecruitment.md) |
| 8 | Değerlendirme Kriterleri | `App.Hr.EvaluationCriterion` | Wizard | [HrRecruitment.md](HrRecruitment.md) |
| 9 | Masraf Türleri | `App.Hr.ExpenseType` | Wizard | bu doküman |
| 10 | Yan Hak Türleri | `App.Hr.BenefitType` | Wizard | bu doküman |
| 11 | Vardiyalar | `App.Hr.Shift` | Wizard | [HrWorkCalendar.md](HrWorkCalendar.md) · [HrAttendance.md](HrAttendance.md) |
| 12 | İzin Türleri | `App.Hr.LeaveType` | Wizard | bu doküman |
| 13 | Kurum Doktorları | `App.Hr.Doctor` | Wizard | bu doküman · [HrHealthCard.md](HrHealthCard.md) |
| 14 | Hastaneler | `App.Hr.Hospital` | Wizard | bu doküman · [HrHealthCard.md](HrHealthCard.md) |
| 15 | Hastane Bölümleri | `App.Hr.HospitalDepartment` | Wizard | bu doküman · [HrHealthCard.md](HrHealthCard.md) |
| 16 | Performans Dereceleri | `App.Hr.PerformanceRating` | Wizard | [HrPerformance.md](HrPerformance.md) |
| 17 | Ceza Türleri | `App.Hr.DisciplinaryPenaltyType` | Wizard | [HrDisciplinaryCase.md](HrDisciplinaryCase.md) |
Tanımlamalar dışında duran tanımlar: Eğitim tipi/konusu → [HrTrainings.md](HrTrainings.md); araç
belgesi/masrafı/gidilecek yer → [HrVehicles.md](HrVehicles.md) (her alt menüde tanım ekranları en üstte);
çalışma takvimi → [HrWorkCalendar.md](HrWorkCalendar.md).
Sıra modül akışını izler: organizasyon → işe alım → zaman (vardiya, izin) → sağlık → performans → disiplin.
İK menüsünün kök sırası: Tanımlamalar · Organizasyon Şeması · İşe Alım · Çalışma Takvimi · İzin Yönetimi ·
Puantaj ve Devam Takibi · Sağlık Kartları · Eğitim · Performans · Disiplin Dosyaları · Araçlar · Ziyaretçiler ·
Olay Günlüğü · Intranet. Yetki ağacı (`AbpPermissions.Order`) aynı sırayı izler; menüsüz çalışan/yönetici
yetkileri (`MyLeaveRequest`, `MyTimesheet`, `MyPerformanceReview` …) modüllerinin hemen ardındadır.
---
## 2. Analiz ve kararlar (genel tanımlar)
> §0.13 kuralından önce üretildiler; keşif turu kaydı yoktur. Kararlar wizard ve seed dosyalarından okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Her tanım tek Wizard liste ekranı; veri seed dosyasına **aynalanır** (`SeedFilePath = data/App.Hr.{Kod}.json`, ekle/güncelle/sil senkron). Ekrandan yapılan değişiklik dosyaya yazılır, DB sıfırlanınca geri gelir |
| Aktif/Pasif | Hepsinde `IsActive` (varsayılan aktif); lookup'lar çoğunlukla yalnız aktifleri listeler |
| Kod alanı | İzin, masraf ve yan hak türlerinde iş kodu (`Code`, ör. `Annual`, `Meal`) ayrıca tutulur; yan hak kodu teklifte saklanan değerdir |
| Hazırlık tanımları | İzin türü ve masraf türü **henüz bir talep ekranına bağlı değildir** — ileride izin/masraf talep ekranlarının (onaylı talep kalıbı, `lowcode.instructions.md` §10.7) kaynağı olmak üzere tanımlanmıştır. Hastane ve hastane bölümü de şu an kullanılmıyor |
---
## 3. Ekranlar
Hepsi Grid, Card, Pivot, Chart görünümlüdür.
| Ekran | Alanlar | Widget'lar | Seed |
| --- | --- | --- | --- |
| Kurum Doktorları | Ad soyad*, diploma no/tarihi, işyeri hekimi belge no/tarihi, T.C. kimlik no, aktif | Toplam · Aktif · Pasif | 1 |
| Yeterlilik Belgeleri | Belge adı*, aktif | Toplam · Aktif · Pasif | 16 (operatör belgeleri, kaynakçı, SRC 4, forklift, mezuniyet belgeleri, çıraklık-ustalık…) |
| Hastaneler | Kurum adı*, aktif | Toplam · Aktif · Pasif | 45 |
| Hastane Bölümleri | Bölüm adı*, aktif | Toplam · Aktif · Pasif | 31 |
| Evrak Türleri | Dosya grubu* (Genel / Özlük Evrakları / Sağlık), dosya türü*, zorunlu, aktif | Toplam · Aktif · Pasif · Zorunlu | 48 |
| İzin Türleri | Kod*, ad*, cinsiyet* (All/Male/Female), yıllık en fazla gün, ücretli, yıllık izinden düşer, belge gerekir, yarım gün kullanılabilir, bakiye aşılırsa (Engelle/Uyar), aktif | Toplam · Ücretli · Ücretsiz | 9 (Yıllık, Mazeret, Ücretsiz, Hastalık, Doğum 112 gün, Babalık 5, Evlilik 3, Ölüm 3, Süt) |
| Masraf Türleri | Kod*, ad*, varsayılan tutar, para birimi, gelir vergisine tabi, SGK'ya tabi, fiş/belge gerekir, aktif | Toplam · Aktif · Pasif | 7 (yemek, yol, konaklama, akaryakıt, telefon, temsil, kırtasiye) |
| Yan Hak Türleri | Kod*, ad*, varsayılan tutar, para birimi, gelir vergisine tabi, SGK'ya tabi, aktif | Toplam · Aktif · Pasif | 10 (yemek kartı, ulaşım, özel sağlık sigortası, BES, konut, şirket aracı, telefon, çocuk, yakacak, diğer) |
`*` zorunlu. Varsayılanlar: izin türünde ücretli = evet, yıllık izinden düşer/belge/yarım gün = hayır,
cinsiyet = Tümü; masraf ve yan hakta para birimi TRY, vergi/SGK = hayır, masrafta fiş gerekir = evet.
---
## 4. Veri modeli
Tablolar `sql/object/Hr_T_{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.sql`
(entity yok); kiracı + audit + `IsActive`. Kolonlar §3'teki alanlarla aynıdır. Tablo düzeyinde tekil
kısıt yoktur.
---
## 5. İş kuralları
Hesap yoktur; yalnız form varsayılanları (§3). Seed dosyası aynalandığı için tanımlar ekrandan
değiştirildiğinde `configs/seeds/data/App.Hr.*.json` dosyası da değişir — bu dosyalar commit'lenmelidir.
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Her tanımın kökü + `.Create/.Update/.Delete/.Export/.Import/.Note`,
hepsi `App.Hr.Definitions` ara yetkisinin altında. Tanım ekranlarının yazma yetkisi İK'da kalmalıdır;
lookup olarak okunmaları ekran yetkisi gerektirmez. Hiçbir yetki role otomatik atanmaz.
---
## 7. Entegrasyonlar (kim kullanıyor)
| Tanım | Kullanan |
| --- | --- |
| Kurum Doktorları | Sağlık Kartı (periyodik muayene, protokol, sonuç hekimleri) |
| Yeterlilik Belgeleri | Kullanıcı detayı › Eğitim ve Yetkinlik (`UserCertificate`), Performans gelişim planı (sertifika aksiyonu) |
| Evrak Türleri | Eğitim dosyaları, Disiplin belgeleri |
| Yan Hak Türleri | İşe Alım › Teklif (`BenefitCodes`, kod ile) |
| İzin Türleri | İzin Yönetimi'nin tür kuralları (cinsiyet, yarım gün, belge, yıllık sınır, yıllık izinden düşme, aşım davranışı); `Sick` kodlu tür Sağlık Kartı istirahatiyle eşlenir — `HrLeave.md` |
| Hastaneler, Hastane Bölümleri, Masraf Türleri | Henüz kullanan ekran yok |
---
## 8. Akış örneği
1. İK yeni bir yan hak tanımlar: kod `Gym`, ad "Spor Salonu Üyeliği", varsayılan tutar 1.500 TRY, gelir vergisine tabi.
2. Kayıt `data/App.Hr.BenefitType.json` dosyasına da yazılır.
3. İşe Alım › Teklif formundaki **Yan Haklar** listesinde yeni hak seçilebilir.
---
## 9. Sık sorulanlar
**Pasif yaptığım tanım listelerden kayboldu.** Lookup'lar yalnız aktif kayıtları listeler; eski kayıtlarda değer korunur.
**İzin talebi nereden girilir?** Henüz izin/masraf talep ekranı yoktur; türler tanım olarak hazırdır.
**Evrak Türleri "özlük dosyası" diyor ama özlük dosyası ekranı yok.** Doğru; türler şu an eğitim ve disiplin belgelerinde kullanılır.
---
## 10. Dosyalar
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.sql` | Tablolar |
| `configs/seeds/wizard/{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.json` | 8 wizard |
| `configs/seeds/data/App.Hr.{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.json` | Aynalanan tanım verisi |
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Yeterlilik Belgeleri — `App.Hr.Certificate`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Certificate.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.Certificate` (sıra 4) |
| Yetki | `App.Hr.Certificate` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Yeterlilik Belgeleri — personelin sahip olabileceği mesleki yeterlilik ve mezuniyet belgeleri. |
| Geliştirme talebi | Human Resources -> Definitions |
| Kaynak | `Hr_T_Certificate` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Hr.Certificate.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Belge Adı | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Evrak Türleri — `App.Hr.DocumentType`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DocumentType.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.DocumentType` (sıra 5) |
| Yetki | `App.Hr.DocumentType` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Evrak Türleri — personel ve süreç belgelerinin türleri (CV, dilekçe, ibraname, sağlık raporu…), gruplarıyla. |
| Geliştirme talebi | Human Resources -> Definitions |
| Kaynak | `Hr_T_DocumentType` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive`, `App.Listform.ListformField.IsRequired` |
| Veri aynası | `data/App.Hr.DocumentType.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `GroupName` | Dosya Grubu | dxSelectBox | ✓ | ✓ | liste: Genel, Özlük Evrakları, Sağlık |
| `Name` | Dosya Türü | dxTextBox | ✓ | ✓ | |
| `IsRequired` | Zorunlu | dxCheckBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Masraf Türleri — `App.Hr.ExpenseType`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/ExpenseType.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.ExpenseType` (sıra 9) |
| Yetki | `App.Hr.ExpenseType` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Masraf Türleri — personel masraf kalemleri (yemek, yol, konaklama, akaryakıt, telefon, temsil, kırtasiye) ve vergi/SGK özellikleri. |
| Kaynak | `Hr_T_ExpenseType` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | CurrencyId=TRY, IsTaxable=false, IsSgkSubject=false, RequiresReceipt=true, IsActive=true |
| Veri aynası | `data/App.Hr.ExpenseType.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Code` | Kod | dxTextBox | ✓ | ✓ | |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `DefaultAmount` | Varsayılan Tutar | dxNumberBox | | ✓ | |
| `CurrencyId` | Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `IsTaxable` | Gelir Vergisine Tabi | dxCheckBox | ✓ | ✓ | |
| `IsSgkSubject` | SGK'ya Tabi | dxCheckBox | ✓ | ✓ | |
| `RequiresReceipt` | Fiş / Belge Gerekir | dxCheckBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Yan Hak Türleri — `App.Hr.BenefitType`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/BenefitType.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.BenefitType` (sıra 10) |
| Yetki | `App.Hr.BenefitType` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Yan Hak Türleri — çalışanlara sağlanan yan haklar (yemek kartı, ulaşım, özel sağlık sigortası, BES, konut, şirket aracı, telefon, çocuk ve yakacak yardımı…). |
| Kaynak | `Hr_T_BenefitType` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | CurrencyId=TRY, IsTaxable=false, IsSgkSubject=false, IsActive=true |
| Veri aynası | `data/App.Hr.BenefitType.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Code` | Kod | dxTextBox | ✓ | ✓ | |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `DefaultAmount` | Varsayılan Tutar | dxNumberBox | | ✓ | |
| `CurrencyId` | Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `IsTaxable` | Gelir Vergisine Tabi | dxCheckBox | ✓ | ✓ | |
| `IsSgkSubject` | SGK'ya Tabi | dxCheckBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### İzin Türleri — `App.Hr.LeaveType`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/LeaveType.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.LeaveType` (sıra 12) |
| Yetki | `App.Hr.LeaveType` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | İzin Türleri — izin çeşitleri ve kuralları (yıllık, mazeret, ücretsiz, hastalık, doğum, babalık, evlilik, ölüm, süt izni). |
| Kaynak | `Hr_T_LeaveType` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Paid`, `App.Widget.Unpaid` |
| Form varsayılanları | IsPaid=true, DeductsFromAnnualLeave=false, RequiresDocument=false, AllowHalfDay=false, Gender=All, IsActive=true, BalanceExceedAction=Block |
| Veri aynası | `data/App.Hr.LeaveType.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Code` | Kod | dxTextBox | ✓ | ✓ | |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `Gender` | Cinsiyet | dxSelectBox | ✓ | ✓ | liste: All, Male, Female |
| `MaxDaysPerYear` | Yıllık En Fazla Gün | dxNumberBox | | ✓ | |
| `IsPaid` | Ücretli | dxCheckBox | ✓ | ✓ | |
| `DeductsFromAnnualLeave` | Yıllık İzinden Düşer | dxCheckBox | ✓ | ✓ | |
| `RequiresDocument` | Belge Gerekir | dxCheckBox | ✓ | ✓ | |
| `AllowHalfDay` | Yarım Gün Kullanılabilir | dxCheckBox | ✓ | ✓ | |
| `BalanceExceedAction` | Bakiye Aşılırsa | dxRadioGroup | ✓ | ✓ | liste: Block, Warn |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Kurum Doktorları — `App.Hr.Doctor`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Doctor.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.Doctor` (sıra 13) |
| Yetki | `App.Hr.Doctor` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Kurum Doktorları — işyeri hekimleri ve belge bilgileri. |
| Geliştirme talebi | Human Resources -> Definitions |
| Kaynak | `Hr_T_Doctor` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Hr.Doctor.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `FullName` | Doktor Adı Soyadı | dxTextBox | ✓ | ✓ | |
| `DiplomaNo` | Diploma No | dxTextBox | | ✓ | |
| `DiplomaDate` | Diploma Tarihi | dxDateBox | | ✓ | |
| `OhsCertificateNo` | İşyeri Hekimi Belge No | dxTextBox | | ✓ | |
| `OhsCertificateDate` | İşyeri Hekimi Belge Tarihi | dxDateBox | | ✓ | |
| `IdentityNo` | T.C. Kimlik No | dxTextBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Hastaneler — `App.Hr.Hospital`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Hospital.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.Hospital` (sıra 14) |
| Yetki | `App.Hr.Hospital` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Hastaneler — hastane ve sağlık kurumu tanımları. |
| Geliştirme talebi | Human Resources -> Definitions |
| Kaynak | `Hr_T_Hospital` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Hr.Hospital.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Kurum Adı | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Hastane Bölümleri — `App.Hr.HospitalDepartment`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HospitalDepartment.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.HospitalDepartment` (sıra 15) |
| Yetki | `App.Hr.HospitalDepartment` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Hastane Bölümleri — hastane klinik/bölüm tanımları (acil, dahiliye, kardiyoloji…). |
| Geliştirme talebi | Human Resources -> Definitions |
| Kaynak | `Hr_T_HospitalDepartment` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Hr.HospitalDepartment.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Bölüm Adı | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,298 +0,0 @@
<!-- module-doc: claims=App.Hr.DisciplinaryPenaltyType -->
# İK › Disiplin Dosyaları (`App.Hr.DisciplinaryCase`)
Personel disiplin süreci: olay tutanağı (personel, tarih/saat/yer, tanıklar, tutanağı tutan) →
savunma talebi ve savunma → karar (ceza türü, yürürlük/tebliğ, ücret kesintisi) → belgeler. Tamamı
konfigürasyondur (Wizard + SQL); entity, migration ya da React kodu yoktur.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| İK / disiplin kurulu sekreteryası | Dosya açar, tutanağı yazar, savunma ister, savunmayı ve kararı işler, belgeleri yükler, dosyayı kapatır | Disiplin Dosyaları |
| Yönetici (tutanağı tutan) | Olayı kaydeder | Disiplin Dosyaları |
| İK yöneticisi | Ceza türü tanımlarını yönetir | İK › Tanımlar › Ceza Türleri |
Kapsam: kiracı (`IsTenant`). Satır süzmesi yoktur; disiplin kaydı hassas veridir, görünürlük yalnızca yetkiyle sınırlanır.
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar artefaktlardan (wizard,
> SQL, seed, commit `130e23bc` "Disiplin Dosyaları, Çalışma Takvimi") okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Tek menü ekranı (dosya) + üç menüsüz alt form: Savunmalar, Kararlar, Belgeler |
| Menü yeri | Dosya ekranı doğrudan `İnsan Kaynakları` kökünde; ceza türü İK › Tanımlar'da |
| Dosya durumu | Open → AwaitingDefense → Decided → Closed; **elle** değiştirilir (Todo panosunda sürükleyerek ya da formdan). Savunma/karar girilmesi durumu otomatik değiştirmez |
| Savunma süresi | Son savunma tarihi = talep tarihi + **2 gün** (formda önerilir, değiştirilebilir); talep tarihinden önce olamaz |
| Ceza türü | Ağırlık (`Severity` 1–5) sırasıyla listelenir; karar ekranında yalnız aktif türler |
| Ücret kesintisi | Kararda tutar olarak saklanır; bordroya aktarım yoktur |
| Tanıklar | Çok değerli kullanıcı seçimi (`WitnessUserIds`, `dxTagBox`) |
| Dosya numarası | `DSP-YYYY-00001` (kiracı + yıl bazında, insert sonrası SQL) |
| Onay akışı, bildirim, rapor | Yok |
---
## 3. Menü ve ekranlar
| Menü (TR / EN) | `ListFormCode` | Yer | Alt formlar | Görünümler | Widget'lar |
| --- | --- | --- | --- | --- | --- |
| Disiplin Dosyaları / Disciplinary Cases | `App.Hr.DisciplinaryCase` | `İnsan Kaynakları` (sıra 10, `FcHighPriority`) | `DisciplinaryDefense`, `DisciplinaryDecision`, `DisciplinaryFile` (`Id → CaseId`) | Grid, Card, Pivot, Chart, **Todo** | Toplam · Açık · Savunma Bekleniyor · Kapanan (Karara Bağlandı + Kapandı) |
| Ceza Türleri / Penalty Types | `App.Hr.DisciplinaryPenaltyType` | `İnsan Kaynakları › Tanımlar` | — | Grid, Card, Pivot, Chart | — (tanım listesi) |
Todo panosu: başlık `Subject`, açıklama `IncidentDescription`, tarih `IncidentDate`, sütun sırası
`Open, AwaitingDefense, Decided, Closed`.
Seed verisi (`data/App.Hr.DisciplinaryPenaltyType.json`): Sözlü Uyarı (1), Yazılı Uyarı (2), İhtar (3),
Kınama (3), Ücret Kesintisi (4), Fesih (5).
---
## 4. Veri modeli
```
AbpUsers ──< Hr_T_DisciplinaryCase ──< Hr_T_DisciplinaryDefense
(UserId, ReportedById, ├──< Hr_T_DisciplinaryDecision >── Hr_T_DisciplinaryPenaltyType
WitnessUserIds) └──< Hr_T_DisciplinaryFile >── Hr_T_DocumentType
```
| Tablo | Önemli kolonlar |
| --- | --- |
| `Hr_T_DisciplinaryPenaltyType` | `Name`, `Severity`, `Description`, `IsActive` |
| `Hr_T_DisciplinaryCase` | `CaseNo`, `UserId`, `DepartmentId`, `JobPositionId`, `IncidentDate`, `IncidentTime`, `IncidentPlace`, `ReportedById`, `Status`, `Subject`, `IncidentDescription` (HTML tutanak), `WitnessUserIds` |
| `Hr_T_DisciplinaryDefense` | `CaseId`, `RequestDate`, `DueDate`, `ResponseDate`, `Status` (Requested/Submitted/NotSubmitted), `DefenseText` (HTML) |
| `Hr_T_DisciplinaryDecision` | `CaseId`, `DecisionDate`, `PenaltyTypeId`, `EffectiveDate`, `NotificationDate`, `DeductionAmount`, `Status` (Draft/Final), `DecisionText` (HTML) |
| `Hr_T_DisciplinaryFile` | `CaseId`, `DocumentTypeId`, `FileDate`, `FileName`, `FileContent` |
Tablo düzeyinde CHECK ya da tekil indeks yoktur; kurallar formdadır.
---
## 5. İş kuralları ve hesaplar
| Yer | Kural |
| --- | --- |
| Dosya — yeni kayıt | Olay tarihi bugün, tutanağı tutan oturumdaki kullanıcı, durum `Open` |
| Dosya — personel seçimi | Departman ve pozisyon kullanıcı kaydından kopyalanır (olay anındaki hâli saklanır) |
| Dosya — insert sonrası | `CaseNo = 'DSP-' + yıl + '-' + 5 hane` (kiracı + yıl içinde `MAX + 1`, yalnız boşsa) |
| Savunma — yeni kayıt | Talep tarihi bugün, durum `Requested`, son tarih talep + 2 gün |
| Savunma — tarih kontrolü | Son tarih talep tarihinden önceyse uyarı + değişiklik geri alınır |
| Karar — yeni kayıt | Karar tarihi bugün, durum `Draft` |
| Belge — yeni kayıt | Tarih bugün |
Tavsiye edilen kullanım sırası: dosya `Open` → savunma istenince `AwaitingDefense` → karar
`Final` olunca `Decided` → tebliğ ve belgeler tamamlanınca `Closed`. Bu sıra sistemce zorlanmaz.
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. `App.Hr.DisciplinaryCase` kökü + `.Create/.Update/.Delete/.Export/.Import/.Note`;
`DisciplinaryDefense`, `DisciplinaryDecision`, `DisciplinaryFile` onun altındadır. Ceza türü
`App.Hr.Definitions` altındadır. Satır süzmesi yoktur. Hiçbir yetki role otomatik atanmaz.
| Rol | Yetkiler |
| --- | --- |
| İK disiplin sorumlusu | Dosya + üç alt form (tam) + ceza türü |
| Tutanak tutan yönetici | `App.Hr.DisciplinaryCase` (+ `.Create`) — ancak bu yetki tüm dosyaları gösterir |
Disiplin verisi kişisel ve hassas veridir; okuma yetkisi dar tutulmalıdır.
---
## 7. Entegrasyonlar
| Kaynak | Kullanım |
| --- | --- |
| `AbpUsers` | Personel, tutanağı tutan, tanıklar |
| Kullanıcı detayı › Disiplin | Disiplin Dosyaları ekranı çalışana süzülmüş açılır (`HrPersonnel.md`) |
| `Hr_T_Department`, `Hr_T_JobPosition` | Olay anındaki birim (Pivot kırılımı) |
| `Hr_T_DocumentType` | Belge türü (ortak İK tanımı) |
Bildirim, onay akışı, rapor şablonu ya da bordro bağlantısı yoktur.
---
## 8. Akış örneği
1. Vardiya amiri **Disiplin Dosyaları**'nda yeni dosya açar: personel seçilir (departman/pozisyon
dolar), olay tarihi/saati/yeri, konu, tutanak metni ve tanıklar girilir → `DSP-2026-00001`, durum Açık.
2. İK **Savunmalar** sekmesine savunma talebi ekler; son tarih 2 gün sonrası önerilir; dosya panoda
**Savunma Bekleniyor**'a çekilir.
3. Personel savunmasını verince talep **Savunma Verildi** yapılır, metin ve tarih girilir; verilmezse **Savunma Verilmedi**.
4. Kurul kararı **Kararlar** sekmesine girilir: ceza türü Yazılı Uyarı, yürürlük ve tebliğ tarihi, durum **Kesinleşti**; dosya **Karara Bağlandı**.
5. İmzalı tutanak, savunma ve tebliğ belgesi **Belgeler** sekmesine yüklenir; dosya **Kapandı** yapılır.
---
## 9. Sık sorulanlar
**Savunma/karar girdim, dosya durumu değişmedi.** Durum elle yönetilir; panoda sürükleyin ya da formdan seçin.
**Son savunma tarihi neden 2 gün sonrası?** Formdaki varsayılan öneridir; değiştirilebilir, yalnızca talep tarihinden önce olamaz.
**Ücret kesintisi bordroya yansıyor mu?** Hayır; karar kaydında bilgi olarak durur.
**Tutanak metni listede görünmüyor.** HTML alan listede gizlidir; detayda ve Todo kartında görünür.
**Bir yöneticinin yalnız kendi tuttuğu dosyaları görmesi mümkün mü?** Şu an değil (satır süzmesi yok). Gerekirse ayrı bir persona ekranı `DefaultFilter` (`"ReportedById" = '@USERID'`) ile açılır (Performans modülündeki kalıp).
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_Disciplinary{PenaltyType,Case,Defense,Decision,File}.sql` | Tablolar |
| `configs/seeds/wizard/Disciplinary{PenaltyType,Case,Defense,Decision,File}.json` | 5 wizard |
| `configs/seeds/data/App.Hr.DisciplinaryPenaltyType.json` | Ceza türleri |
| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları |
Kurulum: DB Migrate. Geri alma: Wizard Manager'dan alt formlar (`DisciplinaryDefense`, `DisciplinaryDecision`,
`DisciplinaryFile`) önce, sonra `DisciplinaryCase` ve `DisciplinaryPenaltyType` silinir; tablolar
yalnızca onayda seçilirse düşer.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Disiplin Dosyaları — `App.Hr.DisciplinaryCase`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DisciplinaryCase.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr` › `App.Hr.DisciplinaryCase` (sıra 10) |
| Yetki | `App.Hr.DisciplinaryCase` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Disiplin Dosyaları — bir personel hakkındaki olay tutanağı ile başlayan disiplin süreci: savunma, karar ve belgeler tek dosyada. |
| Kaynak | `Hr_T_DisciplinaryCase` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart, Todo (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.DisciplinaryDefense` (Id → CaseId), `App.Hr.DisciplinaryDecision` (Id → CaseId), `App.Hr.DisciplinaryFile` (Id → CaseId) |
| Widget'lar | `App.Widget.Total`, `App.Hr.DisciplinaryCase.Widget.Open`, `App.Hr.DisciplinaryCase.Widget.AwaitingDefense`, `App.Hr.DisciplinaryCase.Widget.Closed` |
| Todo | başlık `Subject`, durum `Status` (Open,AwaitingDefense,Decided,Closed) |
| Komutlar | InsertAfter: SQL |
| Form varsayılanları | IncidentDate=@DATE, ReportedById=@USERID, Status=Open |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `CaseNo` | Dosya No | dxTextBox | | | |
| `UserId` | Personel | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers · script |
| `DepartmentId` | Departman | dxSelectBox | | ✓ | sorgu: Hr_T_Department |
| `JobPositionId` | Pozisyon | dxSelectBox | | ✓ | sorgu: Hr_T_JobPosition |
| `IncidentDate` | Olay Tarihi | dxDateBox | ✓ | ✓ | |
| `IncidentTime` | Olay Saati | dxDateBox | | ✓ | |
| `IncidentPlace` | Olay Yeri | dxTextBox | | ✓ | |
| `ReportedById` | Tutanağı Tutan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `Status` | Durum | dxRadioGroup | ✓ | ✓ | liste: Open, AwaitingDefense, Decided, Closed |
| `Subject` | Konu | dxTextBox | ✓ | ✓ | |
| `IncidentDescription` | Olay Tutanağı | dxHtmlEditor | ✓ | ✓ | |
| `WitnessUserIds` | Tanıklar | dxTagBox | | ✓ | sorgu: AbpUsers |
### Ceza Türleri — `App.Hr.DisciplinaryPenaltyType`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DisciplinaryPenaltyType.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.DisciplinaryPenaltyType` (sıra 17) |
| Yetki | `App.Hr.DisciplinaryPenaltyType` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Ceza Türleri — disiplin kararlarında verilebilecek cezalar ve ağırlıkları (sözlü uyarı, yazılı uyarı, ihtar, kınama, ücret kesintisi, fesih). |
| Kaynak | `Hr_T_DisciplinaryPenaltyType` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Form varsayılanları | Severity=1, IsActive=true |
| Veri aynası | `data/App.Hr.DisciplinaryPenaltyType.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Ad | dxTextBox | ✓ | ✓ | |
| `Severity` | Ağırlık | dxNumberBox | ✓ | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Savunmalar — `App.Hr.DisciplinaryDefense`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DisciplinaryDefense.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.DisciplinaryDefense` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Savunmalar — disiplin dosyası için personelden istenen savunma ve verilen cevap. |
| Kaynak | `Hr_T_DisciplinaryDefense` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Form varsayılanları | RequestDate=@DATE, Status=Requested |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `CaseId` | Disiplin Dosyası | dxTextBox | | | |
| `RequestDate` | Talep Tarihi | dxDateBox | ✓ | ✓ | script |
| `DueDate` | Son Savunma Tarihi | dxDateBox | ✓ | ✓ | script |
| `ResponseDate` | Savunma Tarihi | dxDateBox | | ✓ | |
| `Status` | Durum | dxRadioGroup | ✓ | ✓ | liste: Requested, Submitted, NotSubmitted |
| `DefenseText` | Savunma Metni | dxHtmlEditor | | ✓ | |
### Kararlar — `App.Hr.DisciplinaryDecision`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DisciplinaryDecision.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.DisciplinaryDecision` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Kararlar — disiplin dosyası için verilen karar ve ceza. |
| Kaynak | `Hr_T_DisciplinaryDecision` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Form varsayılanları | DecisionDate=@DATE, Status=Draft |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `CaseId` | Disiplin Dosyası | dxTextBox | | | |
| `DecisionDate` | Karar Tarihi | dxDateBox | ✓ | ✓ | |
| `PenaltyTypeId` | Ceza Türü | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_DisciplinaryPenaltyType |
| `EffectiveDate` | Yürürlük Tarihi | dxDateBox | | ✓ | |
| `NotificationDate` | Tebliğ Tarihi | dxDateBox | | ✓ | |
| `DeductionAmount` | Kesinti Tutarı | dxNumberBox | | ✓ | |
| `Status` | Durum | dxRadioGroup | ✓ | ✓ | liste: Draft, Final |
| `DecisionText` | Karar Metni | dxHtmlEditor | | ✓ | |
### Belgeler — `App.Hr.DisciplinaryFile`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DisciplinaryFile.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.DisciplinaryFile` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Disiplin Belgeleri — dosyaya ait imzalı tutanak, savunma, tebliğ gibi belgeler. |
| Kaynak | `Hr_T_DisciplinaryFile` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Form varsayılanları | FileDate=@DATE |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `CaseId` | Disiplin Dosyası | dxTextBox | | | |
| `DocumentTypeId` | Dosya Türü | dxSelectBox | | ✓ | sorgu: Hr_T_DocumentType |
| `FileDate` | Tarih | dxDateBox | ✓ | ✓ | |
| `FileName` | Dosya Adı | dxTextBox | ✓ | ✓ | |
| `FileContent` | Dosya | dxImageUpload | ✓ | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,142 +0,0 @@
# İK › Olay Günlüğü (`App.Hr.EventLog`)
Vardiya amiri, güvenlik odası ve gözlem odasının tuttuğu vardiya defteri: tarih–saat, kaynak,
olayı yaşayan/kaydeden personel, vardiya, önem derecesi (1–5) ve açıklama. Tamamı konfigürasyondur
(tek Wizard ekranı).
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| Vardiya amiri / güvenlik / gözlem odası | Vardiyada yaşanan önemli olayları kaydeder | Olay Günlüğü |
| Yönetim / İK | Günün olaylarını ve önemli/kritik kayıtları izler; gerekirse disiplin dosyası açar | Olay Günlüğü (widget, Pivot) |
Kapsam: kiracı (`IsTenant`). Şube isteğe bağlıdır; satır süzmesi yoktur.
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar wizard ve SQL'den okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Tek Wizard liste ekranı |
| Kaynak | Sabit liste: Vardiya Amiri (`ShiftSupervisor`), Güvenlik Odası (`SecurityRoom`), Gözlem Odası (`ObservationRoom`) |
| Önem | 1–5 yıldız: Rutin, Düşük, Önemli, Çok Önemli, Kritik; **4 ve üstü** "önemli" kartında sayılır |
| Saat | `EventTime` `SS:dd` metin; yeni kayıtta şimdiki saat |
| Vardiya | Vardiya tanımından seçilir (`Hr_T_Shift`) |
| Disiplin bağı | Olaydan disiplin dosyasına otomatik geçiş **yoktur**; dosya elle açılır |
---
## 3. Menü ve ekran
| Menü (TR / EN) | `ListFormCode` | Yer | Görünümler | Widget'lar |
| --- | --- | --- | --- | --- |
| Olay Günlüğü / Event Log | `App.Hr.EventLog` | `İnsan Kaynakları` (sıra 13, `FcViewDetails`) | Grid, Card, Pivot, Chart | Toplam · Bugün · Önemli (önem ≥ 4) |
---
## 4. Veri modeli
`Hr_T_EventLog` (`sql/object/Hr_T_EventLog.sql`, entity yok):
| Kolon | Açıklama |
| --- | --- |
| `LogSource` | Kayıt kaynağı (zorunlu) |
| `BranchId` | Şube |
| `UserId` | Vardiya amiri / olayı yaşayan personel (zorunlu) |
| `EventDate`, `EventTime` | Olay tarihi ve saati (zorunlu) |
| `Importance` | 1–5 (varsayılan 1) |
| `ShiftId` | Vardiya |
| `Description` | Açıklama |
---
## 5. İş kuralları
| Yer | Kural |
| --- | --- |
| Yeni kayıt | Olay saati boşsa şimdiki saat yazılır |
| Önem kartı | `Importance >= 4` |
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`; `App.Hr.EventLog` + `.Create/.Update/.Delete/.Export/.Import/.Note`.
Kayıt tutanlara kök + `.Create` (ve gerekiyorsa `.Update`) yeter; silme yalnız yöneticide kalmalıdır.
Hiçbir yetki role otomatik atanmaz.
---
## 7. Entegrasyonlar
`Sas_T_Branch`, `AbpUsers`, `Hr_T_Shift` (bkz. `HrWorkCalendar.md`). Kritik olay için bildirim yoktur.
---
## 8. Akış örneği
1. Gece vardiyasında güvenlik odası "B kapısı alarmı" olayını kaynak Güvenlik Odası, önem 3, vardiya Gece ile kaydeder.
2. Vardiya amiri bir iş kazasını önem 5 ile girer → "Önemli" kartı artar.
3. Sabah İK önemli kayıtları süzer; gerekiyorsa Disiplin Dosyaları'nda elle dosya açar.
---
## 9. Sık sorulanlar
**Kritik olayda yöneticiye bildirim gidiyor mu?** Hayır; widget ve filtre ile izlenir.
**Olay disiplin dosyasına bağlanabiliyor mu?** Hayır; iki kayıt arasında bağ yoktur.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_EventLog.sql` | Tablo |
| `configs/seeds/wizard/EventLog.json` | Wizard |
Geri alma: Wizard Manager'dan silinir; tablo yalnızca onayda seçilirse düşer.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Olay Günlüğü — `App.Hr.EventLog`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/EventLog.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr` › `App.Hr.EventLog` (sıra 13) |
| Yetki | `App.Hr.EventLog` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Olay Günlüğü — vardiya amiri, güvenlik odası ve gözlem odasının vardiya sırasında kaydettiği önemli olaylar. |
| Geliştirme talebi | Human Resources -> Olay Günlüğü |
| Kaynak | `Hr_T_EventLog` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Today`, `App.Hr.EventLog.Widget.Important` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `LogSource` | Kaynak | dxSelectBox | ✓ | ✓ | liste: ShiftSupervisor, SecurityRoom, ObservationRoom |
| `BranchId` | Şube Adı | dxSelectBox | | ✓ | sorgu: Sas_T_Branch |
| `UserId` | Vardiya Amiri / Olayı Yaşayan | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers |
| `EventDate` | Olay Tarihi | dxDateBox | ✓ | ✓ | script |
| `EventTime` | Olay Saati | dxTextBox | ✓ | ✓ | |
| `Importance` | Önem Durumu | dxSelectBox | ✓ | ✓ | liste: 1, 2, 3, 4, 5 |
| `ShiftId` | Vardiya | dxSelectBox | | ✓ | sorgu: Hr_T_Shift |
| `Description` | Açıklama | dxTextArea | | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,430 +0,0 @@
# İK › Sağlık Kartları (`App.Hr.HealthCard`)
İşyeri hekimliği için çalışan sağlık kartı: soy/özgeçmiş, anamnez, laboratuvar ve fizik muayene
bulguları, çalışmaya elverişlilik sonucu (vardiya/gece çalışması dahil), periyodik muayeneler,
revir (hasta protokol defteri) başvuruları ve istirahat raporları. Kart ayrı bir tablo değildir:
tek kayıtlık bölümler doğrudan çalışanın kullanıcı satırında (`AbpUsers` genişletme kolonları),
çok kayıtlı bölümler `Adm_T_User*` tablolarındadır. Tamamı konfigürasyon + platformun kullanıcı
detay ekranıdır.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| İşyeri hekimi / sağlık personeli | Kartı doldurur, periyodik muayene ve revir başvurusu girer, elverişlilik sonucunu yazar | Sağlık Kartları (detay sekmeleri) |
| İK | Kart durumunu izler, istirahat raporlarını girer; hekim, hastane ve bölüm tanımlarını yönetir | Sağlık Kartları · İK › Tanımlar |
| Sistem yöneticisi | Aynı veriyi kullanıcı detayında görür | Kimlik › Kullanıcılar › detay › Sağlık Kartı sekmesi |
Kapsam: kiracı. Liste, kiracının **tüm kullanıcılarıdır** (aktif/pasif); satır süzmesi yoktur.
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar artefaktlardan (wizard,
> entity, `HealthCardTab.tsx`) okunmuştur.
| Konu | Karar |
| --- | --- |
| Veri yeri | Kart başına tek değer taşıyan alanlar (kan grubu, anamnez, muayene bulguları, sonuç) **`AbpUsers` genişletme kolonlarıdır** — ayrı kart tablosu yok |
| Liste | Sağlık Kartları ekranı `AbpUsers` üzerinde salt okunur listedir (ekle/düzenle/sil kapalı); kart bilgileri detaydaki sekmelerden düzenlenir |
| Tek kayıtlık sekmeler | Soy/Özgeçmiş, Laboratuvar–Fizik Muayene, Anamnez, Sonuç: aynı kullanıcı satırını düzenleyen alt formlar (`Id → Id`, yalnız güncelleme) |
| Çok kayıtlı sekmeler | Periyodik Muayeneler, Hasta Protokol Defteri, İstirahat Raporları: `Adm_T_UserPeriodicExamination`, `Adm_T_UserPatientProtocol`, `Adm_T_UserSickLeave` (`Id → UserId`) |
| İkinci giriş | Kullanıcı detayındaki **Sağlık Kartı** sekmesi (`HealthCardTab.tsx`) aynı kolonları ve aynı üç tabloyu (`App.IdentityManagement.User{PeriodicExamination,PatientProtocol,SickLeave}` listeleriyle) düzenler |
| Hekim | Kurum doktoru tanımı (`Hr_T_Doctor`); periyodik muayenede seçilince diploma no kopyalanır |
| Koşullu alanlar | Anamnezde "evet" denmeyen sorunun not alanı, protokolde sonuç "Sevk" değilse sevk yeri kilitlidir (`readOnly` script) |
---
## 3. Menü ve ekranlar
| Menü (TR / EN) | `ListFormCode` | Yer | Kaynak | Alt formlar |
| --- | --- | --- | --- | --- |
| Sağlık Kartları / Health Cards | `App.Hr.HealthCard` | `İnsan Kaynakları` (sıra 7, `FcInspection`) | `AbpUsers` | aşağıdaki 7 sekme |
| Sekme | `ListFormCode` (menüsüz) | Kaynak | İlişki | Düzenleme |
| --- | --- | --- | --- | --- |
| Soy / Özgeçmiş | `App.Hr.HealthCardHistory` | `AbpUsers` | `Id → Id` | Yalnız güncelleme |
| Laboratuvar – Fizik Muayene | `App.Hr.HealthCardLabPhysical` | `AbpUsers` | `Id → Id` | Yalnız güncelleme |
| Anamnez | `App.Hr.HealthCardAnamnesis` | `AbpUsers` | `Id → Id` | Yalnız güncelleme |
| Sonuç | `App.Hr.HealthCardResult` | `AbpUsers` | `Id → Id` | Yalnız güncelleme |
| Periyodik Muayeneler | `App.Hr.HealthCardPeriodicExamination` | `Adm_T_UserPeriodicExamination` | `Id → UserId` | Ekle/düzenle/sil |
| Hasta Protokol Defteri | `App.Hr.HealthCardPatientProtocol` | `Adm_T_UserPatientProtocol` | `Id → UserId` | Ekle/düzenle/sil |
| İstirahat Raporları | `App.Hr.HealthCardSickLeave` | `Adm_T_UserSickLeave` | `Id → UserId` | Ekle/düzenle/sil; kayıt sonrası İzin Yönetimi'ne eşlenir (`HrLeave.md` §5.5) |
Widget'lar (liste): Toplam · Aktif · Pasif kullanıcı.
Tanımlar (`İnsan Kaynakları › Tanımlamalar`):
| Menü | `ListFormCode` | Seed |
| --- | --- | --- |
| Kurum Doktorları | `App.Hr.Doctor` | 1 kayıt (diploma, işyeri hekimi belge no/tarihi, T.C. kimlik) |
| Hastaneler | `App.Hr.Hospital` | 45 kurum (devlet/özel hastaneler, sağlık ocağı, işyeri hekimliği…) |
| Hastane Bölümleri | `App.Hr.HospitalDepartment` | 31 bölüm |
---
## 4. Veri modeli
| Yer | Alanlar |
| --- | --- |
| `AbpUsers` — kimlik (liste) | `Name`, `Surname`, `IdentityNumber`, `BirthDate`, `Gender`, `BloodType`, `Email`, `PhoneNumber`, `DepartmentId`, `JobPositionId`, `HireDate`, `SskNo`, `IsActive` |
| `AbpUsers` — soy/özgeçmiş | Anne/baba/kardeş/çocuk sağlık durumu (Alive/Disease/Deceased) + not, `BloodType` (8 grup), `ChronicDisease`, tetanoz/hepatit/diğer aşı |
| `AbpUsers` — anamnez | Şikâyetler (`Complaint*`: göğüs ağrısı, balgamlı öksürük, nefes darlığı, çarpıntı, eklem/bel ağrısı, ishal/kabızlık), tanılar (`Diagnosis*`: diyabet, kalp, böbrek, sarılık, ülser, işitme, görme, sinir sistemi, deri, besin zehirlenmesi), evet/hayır + not soruları (hastanede yatış, ameliyat, iş kazası, meslek hastalığı muayenesi, malulen emeklilik, süren tedavi), sigara/alkol (None/Occasionally/Normal/Quit + not) |
| `AbpUsers` — lab/fizik | `Lab*` (biyolojik, kan, idrar, radyolojik, odyometri, SFT, psikolojik, diğer), `Exam*` (duyu, göz, KBB, deri, kalp-damar, solunum, sindirim, ürogenital, kas-iskelet, nörolojik, psikiyatrik, diğer, tansiyon, nabız, boy, kilo) |
| `AbpUsers` — sonuç | `FitForWorkJob`, `FitForWorkCondition`, `FitnessDoctorId`, `ShiftWorkFit`, `NightWorkFit`, `ShiftNightDoctorId` |
| `Adm_T_UserPeriodicExamination` | `UserId`, `ExamDate`, `Examination`, `Result`, `DoctorId`, `DoctorDiplomaNo` |
| `Adm_T_UserPatientProtocol` | `UserId`, `SequenceNo`, `ProtocolDate`, `ProtocolType` (Disease/WorkAccident/OccupationalDisease/Control/Other), `ProtocolResult` (TreatmentDischarged/Rest/Referral), `RestDays`, `ReferralPlace`, `DoctorId` |
| `Adm_T_UserSickLeave` | `UserId`, `StartDate`, `EndDate`, `Days`, `SickLeaveType` (Disease/WorkAccident/OccupationalDisease/Maternity), `Institution`, `Clinic`, `SickLeaveReason` |
| `Hr_T_Doctor` / `Hr_T_Hospital` / `Hr_T_HospitalDepartment` | Tanımlar |
Entity'ler: `Entities/Tenant/Administration/User{PeriodicExamination,PatientProtocol,SickLeave}.cs`;
genişletme kolonları `AbpIdentityUserExtensions` ve EF genişletme eşlemeleri.
---
## 5. İş kuralları
| Yer | Kural |
| --- | --- |
| Anamnez | Evet/hayır sorusu işaretli değilse not alanı salt okunur; sigara/alkol "Kullanmıyor" ise not kilitli |
| Periyodik muayene | Yeni satırda tarih bugün; hekim seçilince diploma no kopyalanır |
| Protokol | Yeni satırda tarih bugün; sonuç "Sevk" değilse sevk yeri kilitli |
| İstirahat | `Days = days(Başlangıç, Bitiş)` (bitiş dahil); bitiş başlangıçtan önceyse uyarı |
| Liste | Kayıt eklenemez/silinemez — kartın sahibi kullanıcı kaydıdır |
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. `App.Hr.HealthCard` kökü; yedi sekme onun altındadır. Tanımlar
`App.Hr.Definitions` altındadır. Kullanıcı detayındaki sekme, kullanıcı ekranının yetkileriyle
(`AbpIdentity.Users` / `.Update`) çalışır. Hiçbir yetki role otomatik atanmaz.
Sağlık verisi **özel nitelikli kişisel veridir** (KVKK md. 6): Sağlık Kartları ve alt sekmelerinin
yetkisi yalnız işyeri hekimi ve yetkili sağlık personeline verilmelidir. Kullanıcı detayına erişen
yöneticiler de bu sekmeyi görebileceği için kullanıcı yönetimi yetkisi de buna göre sınırlanmalıdır.
---
## 7. Entegrasyonlar
| Kaynak | Kullanım |
| --- | --- |
| `AbpUsers` (+ `Hr_T_Department`, `Hr_T_JobPosition`) | Kartın sahibi ve kimlik bilgisi |
| Kullanıcı detayı (`views/admin/user-management/HealthCardTab.tsx`) | Aynı verinin ikinci düzenleme yeri |
| `Hr_T_Doctor` | Periyodik muayene, protokol, sonuç hekimleri |
Hastane ve hastane bölümü tanımları **henüz hiçbir sekmeye bağlı değildir** (istirahatte kurum/klinik
serbest metindir). Bildirim, periyodik muayene hatırlatması ya da rapor şablonu yoktur.
---
## 8. Akış örneği
1. İşe giren çalışanın kartı listede hazırdır (kullanıcı açıldığı anda). Hekim **Soy/Özgeçmiş** ve
**Anamnez** sekmelerini doldurur.
2. İşe giriş muayenesi bulguları **Laboratuvar – Fizik Muayene**'ye, kararı **Sonuç**'a yazılır
(vardiyalı/gece çalışabilir + hekimler).
3. Yıllık kontrol **Periyodik Muayeneler**'e eklenir.
4. Çalışan revire gelince **Hasta Protokol Defteri**'ne kayıt açılır (tür Hastalık, sonuç İstirahat, 1 gün).
5. Dışarıdan alınan rapor **İstirahat Raporları**'na girilir; gün sayısı hesaplanır.
---
## 9. Sık sorulanlar
**Listeye neden kayıt eklenemiyor?** Kart kullanıcı kaydıdır; yeni çalışan Kimlik › Kullanıcılar'dan açılır.
**Sağlık kartı verisi nerede tutuluyor?** Tek değerli alanlar `AbpUsers` kolonlarında, muayene/protokol/istirahat ayrı tablolarda (§4).
**Kullanıcı detayındaki Sağlık Kartı sekmesiyle bu ekran aynı mı?** Evet; aynı veriyi iki yerden düzenlerler.
**Hastane tanımı nerede kullanılıyor?** Şu an hiçbir yerde; ileride istirahat/sevk alanlarına lookup olarak bağlanmak üzere duruyor.
**İstirahat raporu izinlere yansıyor mu?** Evet, tek kayıttır: buradan (ya da kullanıcı detayındaki İstirahatler'den) girilen rapor İK › İzin Yönetimi'nde onaylı Rapor (`Sick`) talebi olur; çalışanın Intranet'ten açtığı Rapor talebi de bu sekmeye düşer (`Hr_S_LeaveSickLeaveSync`, ayrıntı `HrLeave.md` §5.5). Rapor yıllık izin bakiyesinden düşmez; yıllık izindeyken alınan raporun günleri izinden sayılmaz, bakiyeye döner. Çalışanın rapor talebi sağlık kartına onaylanınca düşer. Raporun günleri (hafta sonu dahil) puantajda Raporlu olur, devamsız yazılmaz (`Hr_S_LeaveSickLeaveSync` sonunda yeniden hesap, `HrAttendance.md` §7.1). Kapalı puantaj ayına düşen rapor kaydedilemez/silinemez; önce İK dönemi açar.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/wizard/HealthCard{,History,LabPhysical,Anamnesis,Result,PeriodicExamination,PatientProtocol,SickLeave}.json` | 8 wizard |
| `configs/seeds/wizard/{Doctor,Hospital,HospitalDepartment}.json`, `data/App.Hr.{Doctor,Hospital,HospitalDepartment}.json` | Tanım ekranları ve verisi |
| `api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/User{PeriodicExamination,PatientProtocol,SickLeave}.cs` | Çok kayıtlı sekmelerin entity'leri (migration'lı) |
| `ui/src/views/admin/user-management/HealthCardTab.tsx` | Kullanıcı detayındaki sekme |
Kart sekmelerini kaldırmak wizard'ları siler; `AbpUsers` kolonları ve `Adm_T_User*` tabloları
entity/migration'a bağlı olduğu için kalır.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Sağlık Kartları — `App.Hr.HealthCard`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCard.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr` › `App.Hr.HealthCard` (sıra 7) |
| Yetki | `App.Hr.HealthCard` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Sağlık Kartları — işyeri hekimliği için her çalışanın sağlık kartı. Liste kiracının bütün kullanıcılarıdır; kart bilgileri detay sekmelerinden girilir. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `AbpUsers` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.HealthCardHistory` (Id → Id), `App.Hr.HealthCardLabPhysical` (Id → Id), `App.Hr.HealthCardAnamnesis` (Id → Id), `App.Hr.HealthCardResult` (Id → Id), `App.Hr.HealthCardPeriodicExamination` (Id → UserId), `App.Hr.HealthCardPatientProtocol` (Id → UserId), `App.Hr.HealthCardSickLeave` (Id → UserId) |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Ad | dxTextBox | | ✓ | |
| `Surname` | Soyad | dxTextBox | | ✓ | |
| `IdentityNumber` | T.C. Kimlik No | dxTextBox | | ✓ | |
| `BirthDate` | Doğum Tarihi | dxDateBox | | ✓ | |
| `Gender` | Cinsiyet | dxSelectBox | | ✓ | liste: Male, Female |
| `BloodType` | Kan Grubu | dxTextBox | | ✓ | |
| `Email` | E-posta | dxTextBox | | ✓ | |
| `PhoneNumber` | Telefon | dxTextBox | | ✓ | |
| `DepartmentId` | Departman | dxSelectBox | | ✓ | sorgu: Hr_T_Department |
| `JobPositionId` | Pozisyon | dxSelectBox | | ✓ | sorgu: Hr_T_JobPosition |
| `HireDate` | İşe Giriş Tarihi | dxDateBox | | ✓ | |
| `SskNo` | SSK No | dxTextBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
### Soy / Özgeçmiş — `App.Hr.HealthCardHistory`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCardHistory.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.HealthCardHistory` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Soy / Özgeçmiş — çalışanın aile sağlık geçmişi, kan grubu, kronik hastalıkları ve aşıları. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `AbpUsers` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `MotherHealthStatus` | Anne | dxSelectBox | | ✓ | liste: Alive, Disease, Deceased |
| `MotherHealthNote` | Anne Notu | dxTextBox | | ✓ | |
| `FatherHealthStatus` | Baba | dxSelectBox | | ✓ | liste: Alive, Disease, Deceased |
| `FatherHealthNote` | Baba Notu | dxTextBox | | ✓ | |
| `SiblingHealthStatus` | Kardeş | dxSelectBox | | ✓ | liste: Alive, Disease, Deceased |
| `SiblingHealthNote` | Kardeş Notu | dxTextBox | | ✓ | |
| `ChildHealthStatus` | Çocuk | dxSelectBox | | ✓ | liste: Alive, Disease, Deceased |
| `ChildHealthNote` | Çocuk Notu | dxTextBox | | ✓ | |
| `BloodType` | Kan Grubu | dxSelectBox | | ✓ | liste: 0 Rh+, 0 Rh-, A Rh+, A Rh-, B Rh+, B Rh-, AB Rh+, AB Rh- |
| `ChronicDisease` | Doğuştan / Kronik Hastalık | dxTextBox | | ✓ | |
| `TetanusVaccination` | Tetanoz | dxTextBox | | ✓ | |
| `HepatitisVaccination` | Hepatit | dxTextBox | | ✓ | |
| `OtherVaccination` | Diğer | dxTextBox | | ✓ | |
### Laboratuvar - Fizik Muayene — `App.Hr.HealthCardLabPhysical`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCardLabPhysical.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.HealthCardLabPhysical` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Laboratuvar – Fizik Muayene — işe giriş ya da periyodik muayenedeki tahlil ve fizik muayene bulguları. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `AbpUsers` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `LabBiologicalAnalysis` | Biyolojik Analizler | dxTextBox | | ✓ | |
| `LabBlood` | Kan | dxTextBox | | ✓ | |
| `LabUrine` | İdrar | dxTextBox | | ✓ | |
| `LabRadiological` | Radyolojik Analizler | dxTextBox | | ✓ | |
| `LabRadiologicalPhysiological` | Radyolojik Fizyolojik | dxTextBox | | ✓ | |
| `LabAudiometry` | Odyometri | dxTextBox | | ✓ | |
| `LabPulmonaryFunction` | Solunum Fonksiyon Testi | dxTextBox | | ✓ | |
| `LabPsychologicalTests` | Psikolojik Testler | dxTextBox | | ✓ | |
| `LabOther` | Diğer Laboratuvar Bulguları | dxTextBox | | ✓ | |
| `ExamSensoryOrgans` | Duyu Organları | dxTextBox | | ✓ | |
| `ExamEye` | Göz | dxTextBox | | ✓ | |
| `ExamEnt` | Kulak-Burun-Boğaz | dxTextBox | | ✓ | |
| `ExamSkin` | Deri | dxTextBox | | ✓ | |
| `ExamCardiovascular` | Kalp-Damar Sistemi Muayenesi | dxTextBox | | ✓ | |
| `ExamRespiratory` | Solunum Sistemi Muayenesi | dxTextBox | | ✓ | |
| `ExamDigestive` | Sindirim Sistemi Muayenesi | dxTextBox | | ✓ | |
| `ExamUrogenital` | Ürogenital Sistem Muayenesi | dxTextBox | | ✓ | |
| `ExamMusculoskeletal` | Kas-İskelet Sistemi Muayenesi | dxTextBox | | ✓ | |
| `ExamNeurological` | Nörolojik Muayene | dxTextBox | | ✓ | |
| `ExamPsychiatric` | Psikiyatrik Muayene | dxTextBox | | ✓ | |
| `ExamOther` | Diğer Muayene Bulguları | dxTextBox | | ✓ | |
| `ExamBloodPressure` | Tansiyon | dxTextBox | | ✓ | |
| `ExamPulse` | Nabız / dk | dxTextBox | | ✓ | |
| `ExamHeight` | Boy (cm) | dxNumberBox | | ✓ | |
| `ExamWeight` | Kilo (kg) | dxNumberBox | | ✓ | |
### Anamnez — `App.Hr.HealthCardAnamnesis`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCardAnamnesis.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.HealthCardAnamnesis` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Anamnez — çalışanın sağlık geçmişine dair soru formu. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `AbpUsers` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `ComplaintChestPain` | Göğüs Ağrısı | dxCheckBox | | ✓ | |
| `ComplaintProductiveCough` | Balgamlı Öksürük | dxCheckBox | | ✓ | |
| `ComplaintDyspnea` | Nefes Darlığı | dxCheckBox | | ✓ | |
| `ComplaintPalpitation` | Çarpıntı | dxCheckBox | | ✓ | |
| `ComplaintJointPain` | Eklem Ağrısı | dxCheckBox | | ✓ | |
| `ComplaintBackPain` | Bel Ağrısı | dxCheckBox | | ✓ | |
| `ComplaintDiarrheaConstipation` | İshal veya Kabızlık | dxCheckBox | | ✓ | |
| `DiagnosisDiabetes` | Şeker Hastalığı | dxCheckBox | | ✓ | |
| `DiagnosisHeartDisease` | Kalp Hastalığı | dxCheckBox | | ✓ | |
| `DiagnosisKidneyDisease` | Böbrek Hastalığı | dxCheckBox | | ✓ | |
| `DiagnosisJaundice` | Sarılık | dxCheckBox | | ✓ | |
| `DiagnosisPepticUlcer` | Mide / Oniki Parmak Ülseri | dxCheckBox | | ✓ | |
| `DiagnosisHearingLoss` | İşitme Kaybı | dxCheckBox | | ✓ | |
| `DiagnosisVisionImpairment` | Görme Bozukluğu | dxCheckBox | | ✓ | |
| `DiagnosisNervousSystemDisease` | Sinir Sistemi Hastalığı | dxCheckBox | | ✓ | |
| `DiagnosisSkinDisease` | Deri Hastalığı | dxCheckBox | | ✓ | |
| `DiagnosisFoodPoisoning` | Besin Zehirlenmesi | dxCheckBox | | ✓ | |
| `Hospitalized` | 3. Hastanede yattınız mı? | dxCheckBox | | ✓ | script |
| `HospitalizedNote` | Hastanede Yatış Notu | dxTextBox | | ✓ | |
| `HadSurgery` | 4. Ameliyat oldunuz mu? | dxCheckBox | | ✓ | script |
| `HadSurgeryNote` | Ameliyat Notu | dxTextBox | | ✓ | |
| `HadWorkAccident` | 5. İş kazası geçirdiniz mi? | dxCheckBox | | ✓ | script |
| `HadWorkAccidentNote` | İş Kazası Notu | dxTextBox | | ✓ | |
| `OccupationalDiseaseExam` | 6. Meslek hastalığı şüphesiyle tetkik veya muayene oldunuz mu? | dxCheckBox | | ✓ | script |
| `OccupationalDiseaseExamNote` | Meslek Hastalığı Muayene Notu | dxTextBox | | ✓ | |
| `DisabilityPension` | 7. Malulen emeklilik aldınız mı? | dxCheckBox | | ✓ | script |
| `DisabilityPensionNote` | Malulen Emeklilik Notu | dxTextBox | | ✓ | |
| `UnderTreatment` | 8. Şu anda bir tedavi görüyor musunuz? | dxCheckBox | | ✓ | script |
| `UnderTreatmentNote` | Tedavi Notu | dxTextBox | | ✓ | |
| `Smoking` | 9. Sigara kullanıyor musunuz? | dxSelectBox | | ✓ | liste: None, Occasionally, Normal, Quit · script |
| `SmokingNote` | Sigara Notu | dxTextBox | | ✓ | |
| `Alcohol` | 10. Alkol kullanıyor musunuz? | dxSelectBox | | ✓ | liste: None, Occasionally, Normal, Quit · script |
| `AlcoholNote` | Alkol Notu | dxTextBox | | ✓ | |
### Sonuç — `App.Hr.HealthCardResult`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCardResult.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.HealthCardResult` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Sonuç — işyeri hekiminin çalışmaya elverişlilik kararı. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `AbpUsers` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `FitForWorkJob` | Çalışmaya Elverişli - İş | dxTextBox | | ✓ | |
| `FitForWorkCondition` | Çalışmaya Elverişli - Koşul | dxTextBox | | ✓ | |
| `FitnessDoctorId` | Doktor | dxSelectBox | | ✓ | sorgu: Hr_T_Doctor |
| `ShiftWorkFit` | Vardiyalı Çalışabilir | dxCheckBox | | ✓ | |
| `NightWorkFit` | Gece Çalışabilir | dxCheckBox | | ✓ | |
| `ShiftNightDoctorId` | Vardiya / Gece Çalışması Doktoru | dxSelectBox | | ✓ | sorgu: Hr_T_Doctor |
### Periyodik Muayeneler — `App.Hr.HealthCardPeriodicExamination`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCardPeriodicExamination.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.HealthCardPeriodicExamination` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Periyodik Muayeneler — çalışanın yasal periyodik sağlık muayeneleri. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `Adm_T_UserPeriodicExamination` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `UserId` | Kullanıcı | dxSelectBox | | | sorgu: AbpUsers |
| `ExamDate` | Tarih | dxDateBox | ✓ | ✓ | script |
| `Examination` | Muayene | dxTextBox | ✓ | ✓ | |
| `Result` | Sonuç | dxTextBox | | ✓ | |
| `DoctorId` | Doktor | dxSelectBox | | ✓ | sorgu: Hr_T_Doctor · script |
| `DoctorDiplomaNo` | Diploma No | dxTextBox | | ✓ | |
### Hasta Protokol Defteri — `App.Hr.HealthCardPatientProtocol`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCardPatientProtocol.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.HealthCardPatientProtocol` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Hasta Protokol Defteri — çalışanın revir başvuruları. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `Adm_T_UserPatientProtocol` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `UserId` | Kullanıcı | dxSelectBox | | | sorgu: AbpUsers |
| `SequenceNo` | Sıra No | dxNumberBox | | ✓ | |
| `ProtocolDate` | Tarih | dxDateBox | ✓ | ✓ | script |
| `ProtocolType` | Protokol Türü | dxSelectBox | ✓ | ✓ | liste: Disease, WorkAccident, OccupationalDisease, Control, Other |
| `ProtocolResult` | Sonuç | dxSelectBox | | ✓ | liste: TreatmentDischarged, Rest, Referral · script |
| `RestDays` | İstirahat Günü | dxNumberBox | | ✓ | |
| `ReferralPlace` | Sevk Yeri | dxTextBox | | ✓ | |
| `DoctorId` | Doktor | dxSelectBox | | ✓ | sorgu: Hr_T_Doctor |
### İstirahat Raporları — `App.Hr.HealthCardSickLeave`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/HealthCardSickLeave.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.HealthCardSickLeave` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | İstirahat Raporları — çalışanın aldığı istirahat (rapor) kayıtları. |
| Geliştirme talebi | Human Resources -> Health Card |
| Kaynak | `Adm_T_UserSickLeave` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Komutlar | InsertAfter: `Hr_S_LeaveSickLeaveSync` · UpdateAfter: `Hr_S_LeaveSickLeaveSync` · DeleteAfter: `Hr_S_LeaveSickLeaveSync` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `UserId` | Kullanıcı | dxSelectBox | | | sorgu: AbpUsers |
| `StartDate` | Başlangıç Tarihi | dxDateBox | ✓ | ✓ | script |
| `EndDate` | Bitiş Tarihi | dxDateBox | ✓ | ✓ | script |
| `Days` | Gün | dxNumberBox | ✓ | ✓ | |
| `SickLeaveType` | İstirahat Türü | dxSelectBox | ✓ | ✓ | liste: Disease, WorkAccident, OccupationalDisease, Maternity |
| `Institution` | Kurum | dxTextBox | | ✓ | |
| `Clinic` | Klinik | dxTextBox | | ✓ | |
| `SickLeaveReason` | İstirahat Nedeni | dxTextBox | | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,219 +0,0 @@
# İK › Intranet (`App.Hr.Intranet`)
Kurum içi iletişim: ana sayfa panosu (bugün doğanlar, dokümanlar, aktif anketler, sosyal duvar,
duyurular, yaklaşan etkinlikler), bunların yönetim ekranları (duyuru, anket, sosyal paylaşım,
etkinlik), doküman kütüphanesi ve kullanıcılar arası mesajlaşma. Bu modül **entity + C# seeder +
özel React sayfalarıyla** kuruludur (wizard değil); pano kutuları Developer Kit › Intranet Widgets
kaydıyla yönetilir.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| Tüm çalışanlar | Panoyu görür; duyuru okur/beğenir/yorumlar, anket doldurur, paylaşım yapar, beğenir, yorumlar, ankete oy verir, etkinliği beğenir/yorumlar, doküman indirir, mesajlaşır | `/admin/dashboard` · mesajlaşma penceresi |
| İK / iç iletişim | Duyuru, anket (soru/seçenek), etkinlik ve tanımlarını yönetir; yanıtları ve etkileşimleri izler | İK › Intranet › Duyurular · Anketler · Sosyal Paylaşımlar · Etkinlik Türleri/Kategorileri · Etkinlikler |
| İK / doküman sorumlusu | Doküman yükler, yayına alır/kaldırır | İK › Intranet › Dokümanlar (`/admin/intranet-documents`) |
| Sistem yöneticisi | Pano kutularını (hazır ya da custom component) tanımlar, sırasını ve yetkisini belirler | Developer Kit › Intranet Widgets |
Kapsam: kiracı. Bütün tablolar `IMultiTenant`; satır süzmesi yoktur.
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar koddan
> (`ListFormSeeder_Hr.cs`, `Entities/Tenant/Hr/Intranet`, `IntranetAppService`, `views/intranet`) okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Yönetim ekranları entity + seeder ListForm'u; pano ve etkileşimler özel React + `IntranetAppService` |
| Modül yeri | Intranet İK'dadır: menü `App.Hr.Intranet`, yetkiler `App.Hr.Intranet.*`, tablolar `Hr_T_*`; pano kutusu kaydı (`Sas_T_IntranetWidget`) Saas › Developer Kit'te kalır |
| Pano kutuları | Veritabanı kaydıdır (kod, kolon, sıra, yetki, aktiflik). **Builtin** kutu koda kayıtlı React bileşenidir (`id` = kayıt `Code`); **Custom** kutu Developer Kit custom component'idir (Props JSON ile) |
| Kullanıcı yerleşimi | Pano tasarım modunda sürükle-bırak, gizle/göster, kolon oranı hazır ayarları; tercih **tarayıcıda (localStorage)** saklanır, sunucuya yazılmaz |
| Doğum günleri | Her pano isteğinde kullanıcıların `BirthDate` genişletme alanından canlı hesaplanır (gün + ay) |
| Duyuru panoda | Kutu yalnız **sabitlenmiş** (`IsPinned`) duyuruları, en fazla 3 tane gösterir |
| Anket | `Status = active` anketler; kullanıcı yanıtını sonradan **güncelleyebilir**; anonim ankette yanıt `UserId` taşımaz |
| Sosyal medya | Bir paylaşımda tek medya kaydı (resim(ler), video ya da anket); yüklenen dosya **base64 data URL** olarak `SocialMedia.Urls`'e yazılır |
| Doküman yayını | Fiziksel CDN klasörleri: `intranet` (yayında) ve `intranet/_passive` (yayında değil); yayına alma = klasörler arası taşıma |
| Mesajlaşma | Yalnız tablo öneki ile İK'dadır (`Hr_T_Messenger*`); menüsü/yetkisi yoktur, kabuk düzenine gömülü pencere + SignalR |
| Bildirim / zamanlanmış iş | Yok |
---
## 3. Menü ve ekranlar
`İnsan Kaynakları › Intranet` (`App.Hr.Intranet`, sıra 14)
| Sıra | Menü | `ListFormCode` / rota | Alt formlar | Widget'lar |
| --- | --- | --- | --- | --- |
| 1 | Etkinlik Türleri | `App.Hr.Intranet.Events.EventType` | — | — |
| 2 | Etkinlik Kategorisi | `App.Hr.Intranet.Events.EventCategory` | — | — |
| 3 | Duyurular | `App.Hr.Intranet.Announcement` | `…Announcement.AnnouncementLike`, `…Announcement.AnnouncementComment` (`Id → AnnouncementId`) | Aktif · Sabitlenmiş · Süresi dolan · Toplam görüntülenme |
| 4 | Anketler | `App.Hr.Intranet.Survey` | `SurveyQuestion` (→ `SurveyQuestionOption`), `SurveyResponse` (→ `SurveyAnswer`) — iki seviyeli | Aktif · Pasif · Toplam yanıt |
| 5 | Sosyal Paylaşımlar | `App.Hr.Intranet.SocialPost` | `SocialComment`, `SocialMedia` (→ `SocialPollOption`), `SocialLike` (`Id → SocialPostId`) | Toplam · Bugün · Toplam beğeni |
| 6 | Etkinlikler | `App.Hr.Intranet.Events.Event` | `EventLike`, `EventComment` (`Id → EventId`) | Yaklaşan · Yayında · Taslak · İptal |
| 7 | Dokümanlar | `/admin/intranet-documents` (özel sayfa) | — | — |
Diğer giriş noktaları:
| Ne | Nerede |
| --- | --- |
| Pano | `/admin/dashboard` (`views/Dashboard.tsx` → `views/intranet/Dashboard.tsx`); rota yetkisizdir, menüde ayrı kaydı yoktur |
| Pano kutusu tanımı | Developer Kit › Intranet Widgets (`App.DeveloperKit.IntranetWidgets`, `Sas_T_IntranetWidget`); veri `data/App.DeveloperKit.IntranetWidgets.json`'a aynalanır |
| Mesajlaşma | Uygulama kabuğundaki `MessengerWidget` |
Pano kutuları (seed):
| Kod | Kolon / sıra | Yetki | Ne gösterir |
| --- | --- | --- | --- |
| `today-birthdays` | Sol / 10 | `AbpIdentity.Users.Widget` | Bugün doğanlar: avatar, ad, yaş, departman |
| `documents` | Sol / 20 | `App.Files.Widget` | Yayındaki ilk 3 doküman (tür, boyut, tarih, indir) |
| `active-surveys` | Sol / 30 | `App.Hr.Intranet.Survey.Widget` | Aktif anketler (kalan gün, soru/yanıt sayısı, doldurdum/doldur) |
| `my-leave` | Sol / 35 | `App.Hr.MyLeaveRequest` | Custom `MyLeaveWidget`: izin bakiyesi, süren talepler → İzin Talebi Oluştur · Taleplerim · Bakiyem |
| `team-leave` | Sol / 36 | `App.Hr.TeamLeaveRequest` | Custom `TeamLeaveWidget`: müdürün onayını bekleyen izin talepleri, bugün izinde olanlar → Onay Bekleyenler |
| `my-timesheet` | Sol / 37 | `App.Hr.MyTimesheet` | Custom `MyTimesheetWidget`: seçilen ayın puantajı (net saat, geç/erken dk, fazla mesai, devamsız/izinli/raporlu) ve yıl toplamı, 270 saatlik fazla mesai çubuğu → Puantajım |
| `social-wall` | Orta / 40 | `App.Hr.Intranet.SocialPost.Widget` | Sosyal duvar (Tümü / Paylaşımlarım, sonsuz kaydırma 10'arlı) |
| `announcements` | Sağ / 50 | `App.Hr.Intranet.Announcement.Widget` | Sabitlenmiş duyurular (en fazla 3) |
| `upcoming-events` | Sağ / 60 | `App.Hr.Intranet.Events.Event.Widget` | Bugünden sonraki yayınlanmış ilk 3 etkinlik |
| `my-performance` | Sağ / 70 | `App.Hr.MyPerformanceReview` | Custom `MyPerformanceWidget`: aktif performans değerlendirmesi, puanlar, gelişim aksiyonları → Değerlendirmelerim |
| `team-performance` | Sağ / 80 | `App.Hr.TeamPerformanceReview` | Custom `TeamPerformanceWidget`: yöneticinin ekip değerlendirmeleri → Ekibimin Değerlendirmeleri |
| `my-360-feedback` | Sağ / 90 | `App.Hr.My360Feedback` | Custom `My360FeedbackWidget`: kullanıcıya atanan 360 talepleri → 360 Taleplerim |
İlk altı kutu Builtin'dir (koddaki `Code`). `my-leave` / `team-leave` İzin Yönetimi'nin (`HrLeave.md` §3.3), `my-timesheet` Puantaj ve Devam Takibi'nin (`HrAttendance.md` §3.3), son üçü Performans Yönetimi'nin çalışana ait özetleridir:
İK menüsü İK departmanınındır, çalışan kendi değerlendirmesini buradan takip eder; listeler menüsüzdür ve
kutudaki bağlantıyla açılır. Ayrıntı: `HrPerformance.md` §3.3.
---
## 4. Veri modeli
Entity'ler `Entities/Tenant/Hr/Intranet` (`FullAuditedEntity<Guid>, IMultiTenant`); tablolar migration'lıdır.
| Tablo | Önemli kolonlar | Kural |
| --- | --- | --- |
| `Hr_T_Announcement` | `Title`, `Excerpt`, `Content` (HTML), `ImageUrl`, `Category` (general/event/urgent/announcement), `UserId`, `PublishDate`, `ExpiryDate`, `IsPinned`, `ViewCount`, `Likes`, `Attachments` | Başlık kiracıda tekil |
| `Hr_T_AnnouncementComment` / `Hr_T_AnnouncementLike` | `AnnouncementId`, `UserId`, (`Content`) | Beğeni kullanıcı başına tekil |
| `Hr_T_Survey` | `Title`, `Description`, `Deadline`, `Responses`, `Status`, `IsAnonymous` | Başlık kiracıda tekil |
| `Hr_T_SurveyQuestion` | `SurveyId`, `QuestionText`, `Type` (rating/multiple-choice/text/textarea/yes-no), `Order`, `IsRequired` | — |
| `Hr_T_SurveyQuestionOption` | `QuestionId`, `Text`, `Order` | — |
| `Hr_T_SurveyResponse` / `Hr_T_SurveyAnswer` | `SurveyId`, `UserId`, `SubmissionTime` / `ResponseId`, `QuestionId`, `QuestionType`, `Value` | — |
| `Hr_T_SocialPost` | `UserId`, `Content`, `LikeCount`, `IsLiked`, `IsOwnPost` | — |
| `Hr_T_SocialMedia` | `SocialPostId` (1:1), `Type` (image/video/poll), `Urls`, `PollQuestion`, `PollTotalVotes`, `PollEndsAt`, `PollUserVoteId` | — |
| `Hr_T_SocialPollOption` | `SocialMediaId`, `Text`, `Votes` | — |
| `Hr_T_SocialComment` / `Hr_T_SocialLike` | `SocialPostId`, `UserId`, (`Content`) | Beğeni kullanıcı başına tekil |
| `Hr_T_SocialLocation` | Konum bilgisi | Kullanan ekran/servis yok |
| `Hr_T_EventType` / `Hr_T_EventCategory` | `Name` | Kiracıda tekil |
| `Hr_T_Event` | `CategoryId`, `TypeId`, `Date`, `Name`, `Description`, `Place`, `Status` (published/draft/cancelled), `UserId`, `ParticipantsCount`, `Likes`, `isPublished`, `Photos` | `(CategoryId, TypeId, Name)` tekil |
| `Hr_T_EventComment` / `Hr_T_EventLike` | `EventId`, `UserId`, (`Content`) | Beğeni tekil |
| `Sas_T_IntranetWidget` | `Code`, `Name`, `LabelKey`, `SourceType` (0 Builtin / 1 Custom), `ComponentName`, `PermissionName`, `Column` (0 sol / 1 orta / 2 sağ), `Order`, `Props`, `IsActive` | Kod kiracıda tekil |
| `Hr_T_MessengerConversation` / `Hr_T_MessengerConversationMessage` | Konuşma (katılımcılar, son mesaj) / mesaj (gönderen, alıcılar, metin, ekler, zaman) | — |
---
## 5. İş kuralları (`IntranetAppService`)
| İşlem | Kural |
| --- | --- |
| Pano verisi | Doğum günleri (bugün), `intranet` klasöründeki dosyalar (`_passive` hariç), son 50 duyuru, `active` anketler + kullanıcının yanıtı, `isPublished` son 50 etkinlik, aktif ve yetkisi verilmiş pano kutuları. Duyuru için tarih/sabitleme süzmesi, anket için son tarih süzmesi **sunucuda yoktur** |
| Duyuru aç | Her açılışta `ViewCount + 1` |
| Beğeni (duyuru, paylaşım, etkinlik) | Aç/kapa: beğeni satırı eklenir/silinir, sayaç ±1 |
| Yorum | Satır eklenir; listelenirken yazar bilgisi doldurulur |
| Anket yanıtı | Kullanıcının önceki yanıtı varsa cevaplar güncellenir; yoksa yeni yanıt + `Responses + 1`; anonimde `UserId` boş |
| Paylaşım | İsteğe bağlı tek medya ya da 2–6 seçenekli anket; yalnız sahibi silebilir |
| Ankete oy | Seçenek ve toplam oy +1, `PollUserVoteId` = seçenek; tekrar oy UI'da engellenir |
| Doküman | Yüklemede görünen ad zorunlu (uzantı korunur); yayına al/kaldır = klasör taşıma; silme toplu |
| Mesaj | Aynı kiracıdaki aktif kullanıcılara (en fazla 100 kişi listelenir); en fazla 10 ek; çevrimiçi durumu SignalR (`/messengerhub`) |
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Kök `App.Hr.Intranet`; her yönetim ekranının kökü + standart alt
yetkiler (`.Create/.Update/.Delete/.Export/.Import`, ana ekranlarda `.Note`). Duyuru, Anket, Sosyal
Paylaşım ve Etkinlik ayrıca **`.Widget`** taşır — pano kutusunu görme yetkisidir. Dokümanlar:
`App.Hr.Intranet.Document` + `.Create` (yükle) / `.Update` (yayına al/kaldır) / `.Delete`.
Intranet Widgets: `App.DeveloperKit.IntranetWidgets` (+ CRUD). Hiçbir yetki role otomatik atanmaz.
Önerilen dağıtım:
| Rol | Yetkiler |
| --- | --- |
| Tüm çalışanlar | `AbpIdentity.Users.Widget`, `App.Files.Widget`, dört `.Widget` yetkisi, `App.Hr.Intranet.Document` (okuma); performans kutuları için `HrPerformance.md` §6 |
| İç iletişim | `App.Hr.Intranet` dalının tamamı |
| Sistem yöneticisi | `App.DeveloperKit.IntranetWidgets` |
**Kontrol edilmeli — güvenlik:** `IntranetAppService`, `MessengerAppService` ve dosya yöneticisi servisi
yalnız oturum ister (`[Authorize]`); `App.Hr.Intranet.*` yetkilerini sunucuda denetlemez. Pano
kutularının yetkisi (`PermissionName`) sunucuda süzülür, doküman yetkileri ise **yalnız arayüzde**
uygulanır.
---
## 7. Entegrasyonlar
| Kaynak | Kullanım |
| --- | --- |
| `AbpUsers` (+ departman, pozisyon) | Doğum günleri, yazar/kullanıcı kartları, mesajlaşma rehberi |
| Dosya yöneticisi / CDN (`intranet`, `intranet/_passive`) | Doküman kütüphanesi ve pano doküman kutusu |
| Developer Kit › Custom Components | Custom pano kutuları |
| SignalR (`/messengerhub`) | Mesajlaşma ve çevrimiçi durumu |
| Organizasyon Şeması | Kullanıcı satırlarında sosyal duvarın `UserProfileCard` bileşenini kullanır |
---
## 8. Akış örneği
1. İç iletişim **Duyurular**'a "Yeni ofis açılışı"nı görsel ve HTML içerikle ekler, **sabitler** → panonun sağ kolonunda görünür.
2. **Anketler**'de "Çalışan Memnuniyet Anketi"ni anonim ve aktif açar; **Sorular** sekmesine puanlama, çoktan seçmeli (seçenekleriyle) ve metin soruları ekler.
3. Çalışan panoda anketi doldurur, sonradan yanıtını günceller; İK **Yanıtlar** sekmesinden sonuçları görür (anonimde kişi boş).
4. **Etkinlikler**'de "Jazz Akşamı" tarih, yer ve fotoğraflarla yayınlanır → "Yaklaşan Etkinlikler" kutusunda çıkar; çalışanlar beğenir/yorumlar.
5. Çalışan sosyal duvarda iki seçenekli bir anket paylaşır; diğerleri oy verir.
6. Doküman sorumlusu "İzin Prosedürü.pdf"i yükler ve yayına alır → pano doküman kutusunda indirilebilir olur.
7. Sistem yöneticisi Intranet Widgets'a bir custom component'i (ör. kurum KPI kartı) orta kolona ekler.
---
## 9. Sık sorulanlar ve bilinen sınırlar
**Duyuru panoda görünmüyor.** Kutu yalnız sabitlenmiş duyuruları (en fazla 3) gösterir; listelenmek için **Sabitle** işaretli olmalı.
**Süresi dolmuş duyuru/anket hâlâ görünüyor.** Sunucuda son tarih süzmesi yoktur; duyuru sabitlemesi kaldırılmalı, anket `passive` yapılmalıdır.
**Pano yerleşimim başka bilgisayarda yok.** Yerleşim tarayıcıda saklanır.
**Pano kutusu görünmüyor.** Kayıt aktif olmalı ve kullanıcıda `PermissionName` yetkisi bulunmalı; Custom kutuda bileşen aktif olmalı.
**Yanıtlarımı değiştirebilir miyim?** Evet; anketi tekrar açıp kaydetmek önceki yanıtı günceller.
Kontrol edilmeli (koddan tespit edilenler):
- Ankete oy sunucuda kullanıcı bazında tutulmaz (`PollUserVoteId` tek kolon); aynı kullanıcı API'yi doğrudan çağırarak tekrar oy verebilir.
- `SocialPost.IsLiked` beğenen son kullanıcıya göre veritabanına yazılır; listelerken sunucu kullanıcıya göre yeniden hesaplar.
- Duyuru modalındaki ekler bölümü dizi bekler, `Attachments` metindir; ekler görünmeyebilir.
- Anket entity açıklaması `draft|active|closed` der, ekran `active|passive` sunar.
- Sosyal paylaşımda yüklenen medya base64 olarak veritabanına yazılır; büyük dosyalar tabloyu büyütür.
- `Hr_T_EventPhoto` tablosu ve `sql/object/Hr_T_EventPhoto.sql` yoktur (referans dokümanda geçer); etkinlik fotoğrafları `Hr_T_Event.Photos` kolonundadır.
---
## 10. Dosyalar
| Dosya | İçerik |
| --- | --- |
| `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Hr.cs` | Yönetim ekranları |
| `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs` | Intranet Widgets ekranı |
| `api/src/Sozsoft.Platform.Domain/Entities/Tenant/Hr/Intranet/*.cs`, `Entities/Tenant/Administration/IntranetWidget.cs`, `Entities/Tenant/Messenger*.cs` | Entity'ler |
| `api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs`, `Messenger/MessengerAppService.cs` | Servisler |
| `ui/src/views/intranet/Dashboard.tsx`, `widgets/*`, `SocialWall/*`, `documents/IntranetDocuments.tsx` | Pano, kutular, sosyal duvar, doküman sayfası |
| `ui/src/components/template/MessengerWidget.tsx` | Mesajlaşma penceresi |
| `configs/seeds/data/App.Hr.Intranet.*.json`, `App.DeveloperKit.IntranetWidgets.json` | Örnek veri ve pano kutuları |
Örnek veri:
- 5 duyuru (3'ü sabitlenmiş).
- 3 anket, 10 soru ve 13 seçenek.
- 6 paylaşım: 4 medya (2 resim, 1 video, 1 anket), 8 yorum.
- 50 etkinlik türü, 5 kategori, 3 etkinlik ve 9 yorum.

View file

@ -1,593 +0,0 @@
# İK › İzin Yönetimi (`App.Hr.Leave`)
İzin talebi, iki aşamalı onay (departman müdürü → İK), yıllık izin bakiyesi ve rapor (istirahat)
eşlemesi. **İnsan Kaynakları menüsü yalnız İK içindir**: tüm talepleri ve bakiyeleri İK izler.
Çalışan ve müdür izinle ilgili her şeyi **Intranet › Dashboard**'daki widget'lardan yapar. Wizard
ekranları, SQL nesneleri ve custom component widget'larıyla kuruludur. Çerçeve dışında iki ek
vardır (kullanıcı onayıyla): Grid Workflow'da kayıttan okunan onaycı (`@Kolon`) ve kullanıcı
detayındaki **İzinler** sekmesi.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| 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 · 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ı |
Kapsam: kiracı (`IsTenant`). Şube kırılımı yoktur.
---
## 2. Analiz ve kararlar
### 2.1 Talep
> İnsan Kaynakları › İzin Yönetimi. Kullanıcı Intranet Dashboard'dan kendi bakiye raporunu izler,
> izin talebi oluşturur, bir butonla taleplerini ve onay durumlarını takip eder (Performansım
> widget'ındaki Değerlendirmelerim gibi). İzin türüne göre gerekli alanlar doldurulur; bakiye
> yetmezse talep engellenir veya uyarı verilir; aynı tarihlerde çakışan talep oluşturulamaz.
> Sağlık Kartı › İstirahatler ile Rapor türündeki izin tek kayıttır. İzin bakiyesi kullanıcı
> kartında gösterilir. Alanlar: izin türü, başlangıç/bitiş tarihi ve saati, yarım gün, gün sayısı
> (otomatik), açıklama, ek belge, durum. Yıllık hak çalışma yılına göre hesaplanır. Onay önce
> departman müdürü (İş Pozisyonları'ndaki departman müdürü), sonra İK. Talep gelince yöneticiye
> bildirim gider. İK menüsü yalnız İK kullanır; widget'lar Aktif Anketler'in altına (sol kolon).
### 2.2 Depodan okunanlar (sorulmadı)
- İzin türleri `Hr_T_LeaveType` (İK › Tanımlamalar): cinsiyet, yıllık en fazla gün, ücretli, yıllık izinden düşer, belge gerekir, yarım gün.
- Rapor kayıtları `Adm_T_UserSickLeave`; iki ekran kullanır: Sağlık Kartı (`App.Hr.HealthCardSickLeave`) ve kullanıcı detayı (`App.IdentityManagement.UserSickLeave`).
- Kıdem `AbpUsers.SeniorityStartDate` (boşsa `HireDate`), doğum `BirthDate`, cinsiyet `Gender`, departman/pozisyon `DepartmentId`/`JobPositionId`.
- Tatiller `Hr_T_WorkCalendar` (`Holiday` / `HalfDay`, `FREQ=YEARLY` tekrar).
- Pozisyon ağacı `Hr_T_JobPosition` (`DepartmentId` + `ParentId`); departmanda "müdür" alanı yoktur.
- Grid Workflow onaycıyı düğümde sabit tutuyordu — kayda göre değişen müdür için motor eki gerekti.
### 2.3 Keşif turu cevapları
| # | Soru | Seçilen |
| --- | --- | --- |
| 1 | Onay akışı | Grid Workflow + motor eki: düğüm onaycısı `@Kolon` ise kayıttan okunur |
| 2 | Müdür nasıl bulunur | Departmanın en üst pozisyonu (üst pozisyonu başka departmanda ya da üstü olmayan); çalışan o pozisyondaysa bir üst pozisyonun sahibi; birden çok müdür varsa herhangi biri |
| 3 | Yıllık hak | İş Kanunu md.53 kademeleri + devir (kullanılmayan izin devreder) |
| 4 | Bakiye yetersiz | İzin türü bazında İK seçer: `BalanceExceedAction` = Engelle / Uyar ve kaydet |
| 5 | İK onaycısı | Düğümde sabit kullanıcı(lar) (İzin Talepleri akışının "İK Onayı" düğümü) |
| 6 | Gün sayısı | Ayarlar › İnsan Kaynakları › İzin'deki çalışma günleri + Çalışma Takvimi tatilleri (kullanıcı kararı: tüm İK ayarları bu grupta) |
| 7 | Rapor talebi | Onay akışından geçer; onaylanınca Sağlık Kartı'nda görünür (onay beklerken sağlık kartına ve puantaja düşmez), reddedilirse kalkar |
| 8 | Kullanıcı kartı | Kullanıcı detayına **İzinler** sekmesi (bakiye özeti + talepler + defter) |
| 9 | Üstü olmayan pozisyon (Genel Müdür) | Müdür adımı atlanır, talep doğrudan İK Onayı'na gider (üretim sırasında soruldu) |
### 2.4 Üretimde verilen kararlar
| Konu | Karar | Neden |
| --- | --- | --- |
| Persona ekranında akış | **İzin Taleplerim** ekranında Workflow yoktur; ilk adımı (`Müdür Onayı` + müdür kullanıcı adları) kayıt sonrası prosedür yazar | Workflow alanlarını yazabilen persona ekranı, çalışanın durumu API ile "Onaylandı" yapmasına izin verirdi; alanlar İK yetkisine kilitli |
| Müdür ekranı | **Ekibimin İzin Talepleri** ayrı ekran, aynı akış başlıklarıyla; satır `ManagerIds` ile süzülür | Akış düğümleri ekrana bağlıdır, durum başlıkla eşleşir (`FindCurrentCriteria`) |
| İK onaycısının tek kaynağı | Kayıt sonrası prosedür İK adımına girerken onaycıyı **İzin Talepleri** akışının "İK Onayı" düğümünden okur | Ekibimin İzin Talepleri akışındaki İK düğümü ayrı düzenlenirse sapma olmasın |
| Yöneticinin geçişleri | Müdür yalnız Müdür Onayı → İK Onayı / Reddedildi yapabilir; başka geçiş geri alınır | Workflow alanları müdür ekranında yazılabilir olmak zorunda (motor ekranın kayıt yolunu kullanır) |
| Bakiye defteri | Hak ediş yılı başına satır; 0. yıl düzeltmeler ve ilk hak edişten önceki kullanım | Devirli kümülatif bakiyenin denetlenebilir dökümü |
| Açılış bakiyesi | **Bakiye Düzeltmeleri** (± gün) | Devirli hesapta sistem öncesi kullanım girilmezse geçmiş bütün yıllar kullanılmamış görünür |
| Bildirim | Uygulama içi + toast (`IzinTalebiOnayBekliyor`, `IzinTalebiSonuclandi`); Workflow `Inform` düğümü kullanılmadı | Inform yalnız e-posta gönderir ve onaycısı sabittir |
| Ek belge | `dxImageUpload` (base64, `nvarchar(MAX)`), listede gizli | Platform standardı |
| En üst pozisyon | Prosedür `RequiresManagerApproval`'ı yazar (1 = müdür onayı gerekir, 0 = atlanır); İzin Talepleri akışında Başlangıç ile Müdür Onayı arasında bu kolona bakan karşılaştırma düğümü vardır, çalışanın kendi talebinde prosedür durumu doğrudan İK Onayı yapar | Motorun karşılaştırma düğümü yalnız sayısal kolon okur (`EvaluateCondition`) ve satırı ekranın sorgusundan alır; kolon İzin Talepleri'nde gizli alandır |
| Form günü | Gün formda tarayıcıda önizlenir (hafta içi gün, yarım günde 0,5), kalıcı değer prosedürün hesabıdır; İzin Türü lookup'ı cascade değildir | Cascade ebeveyni çocuğu kilitler ve değiştiğinde temizler; ayrıntı §5.1 |
| Bildirim başlığı | Bildirim panelinde ve etkinlik kaydında tip adı yerine kaydın `Subject`'i gösterilir (bütün modüller) | Tip adı (`PerformansDurumDegisti`) koddur; konu çevrilmiş metindir |
### 2.5 Varsayılan kalanlar
- Çalışma günleri Pzt–Cum; kademeler 14 / 20 / 26, genç/yaşlı en az 20 (ayarlarda değiştirilir).
- İzin türü varsayılanı: yıllık izinden düşen ya da yıllık sınırı olan türler **Engelle**, diğerleri **Uyar ve kaydet** (seed).
- Rapor kaydı `SickLeaveType = Disease`, gün sayısı takvim günü (Sağlık Kartı kuralı); izin talebindeki gün çalışma günüdür.
---
## 3. Menü ve ekranlar
### 3.1 İK menüsü
`İnsan Kaynakları › İzin Yönetimi` (`App.Hr.Leave`, sıra 14, `FcOvertime`) — yalnız İK.
| Sıra | Menü (TR / EN) | `ListFormCode` | Wizard | Kaynak | Alt formlar | Widget'lar |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | İzin Talepleri / Leave Requests | `App.Hr.LeaveRequest` | `LeaveRequest.json` | `Hr_T_LeaveRequest` | — | Müdür Onayında · İK Onayında · Onaylanan (bu yıl) · Reddedilen (bu yıl) |
| 2 | İzin Bakiyeleri / Leave Balances | `App.Hr.LeaveBalance` | `LeaveBalance.json` | `Hr_V_LeaveSummary` (salt okunur) | `LeaveBalanceAdjustment`, `LeaveBalanceYear` (`Id → EmployeeId`) | Çalışan · Toplam Kalan Gün · Eksi Bakiye · Onay Bekleyen Gün |
Menüsüz alt formlar: **Bakiye Düzeltmeleri** (`App.Hr.LeaveBalanceAdjustment`, `Hr_T_LeaveBalanceAdjustment`),
**İzin Defteri** (`App.Hr.LeaveBalanceYear`, `Hr_V_LeaveBalanceYear`, salt okunur).
Görünümler: hepsi Grid, Card, Pivot, Chart.
### 3.2 Çalışana ve müdüre ait ekranlar (menüsüz)
| Ekran | `ListFormCode` | Satır süzmesi | Yapılabilen |
| --- | --- | --- | --- |
| İzin Taleplerim | `App.Hr.MyLeaveRequest` | `EmployeeId = @USERID` | Ekle; karar verilmeden düzelt/sil. `EmployeeId` sunucuda `@USERID` ile yazılır |
| Bakiyem | `App.Hr.MyLeaveBalance` | `EmployeeId = @USERID` | Defteri görür |
| Ekibimin İzin Talepleri | `App.Hr.TeamLeaveRequest` | `ManagerIds` içinde kullanıcı | Müdür adımında Onayla / Reddet |
Talep formu doğrudan açılır: `/admin/form/App.Hr.MyLeaveRequest`.
### 3.3 Intranet › Dashboard widget'ları
Sol kolon (`Column: 0`), Aktif Anketler'in (30) altında; kayıtlar `data/App.DeveloperKit.IntranetWidgets.json`.
| `Code` · sıra | Bileşen | `PermissionName` | Gösterir | Butonlar |
| --- | --- | --- | --- | --- |
| `my-leave` · 35 | `MyLeaveWidget` | `App.Hr.MyLeaveRequest` | Kalan / kullanılan / onay bekleyen gün, sonraki hak ediş, süren ilk 3 talep ve durumu | İzin Talebi Oluştur · Taleplerim · Bakiyem |
| `team-leave` · 36 | `TeamLeaveWidget` | `App.Hr.TeamLeaveRequest` | Onayımı bekleyen talep sayısı ve ilk 5'i, bugün izinde olanlar | Onay Bekleyenler |
Bileşenler code modunda, rotasızdır; veriyi ekranların kendi sorgusundan okur
(`GET /api/app/list-form-select/select`). Bakiye kartları `App.Hr.MyLeaveBalance` yetkisi yoksa gizlenir.
### 3.4 Kullanıcı detayı › İzinler
`ui/src/views/admin/user-management/LeaveTab.tsx`: İzin Bakiyeleri satırından altı kart (hak edilen,
düzeltme, kullanılan, bekleyen, kalan, kullanılabilir), altında kullanıcıya süzülmüş **İzin Talepleri**
ve **İzin Defteri** listeleri. Her parça kendi ekran yetkisiyle görünür; İK buradan çalışan adına talep
ekleyebilir (yeni satırın `EmployeeId`'si süzgeçten dolar).
### 3.5 Ayarlar
Ayarlar › İnsan Kaynakları › İzin (`App.HrSettings` › `App.HrSettings.Leave`, sağlayıcılar `T|G|D`):
| Kod | Varsayılan |
| --- | --- |
| `App.HrSettings.Leave.Work{Monday…Sunday}` | Pzt–Cum `True`, Cmt–Paz `False` |
| `App.HrSettings.Leave.AnnualDays1To5` / `AnnualDays5To15` / `AnnualDays15Plus` | 14 / 20 / 26 |
| `App.HrSettings.Leave.MinDaysYoungOld` | 20 |
SQL tarafı ayarı `dbo.Hr_F_HrSetting` ile ABP'nin sırasıyla okur (kiracı → genel → tanım varsayılanı).
---
## 4. Veri modeli
```
AbpUsers ──< Hr_T_LeaveRequest >── Hr_T_LeaveType
│ └── Adm_T_UserSickLeave (SickLeaveId, Rapor türünde)
└──< Hr_T_LeaveBalanceAdjustment
Hr_V_LeaveBalanceYear (defter) ──▶ Hr_V_LeaveSummary (çalışan başına toplam)
```
| Nesne | Önemli kolonlar | Kural |
| --- | --- | --- |
| `Hr_T_LeaveRequest` | `RequestNo`, `EmployeeId`/`EmployeeName`/`DepartmentId`/`JobPositionId`, `LeaveTypeId`, `StartDate`/`EndDate` (tarih+saat), `IsHalfDay`, `HalfDayPart` (Morning/Afternoon), `Days` (0,5 adımlı), `Description`, `DocumentFileName`/`DocumentContent`, `BalanceWarning`, `SickLeaveId`, `ManagerIds`/`ManagerUserNames`, `RequiresManagerApproval` (int, 1/0), `ApprovalStatus`/`ApprovalUser`/`ApprovalDate`/`ApprovalDescription`, `LastNotifiedStatus`, `IsActive` | `EndDate ≥ StartDate`; kiracı + tam audit |
| `Hr_T_LeaveBalanceAdjustment` | `EmployeeId`, `AdjustmentDate`, `Days` (≠ 0, ±), `Reason`, `IsActive` | — |
| `Hr_T_LeaveType` (+ kolon) | `BalanceExceedAction` (`Block`/`Warn`, varsayılan `Block`) | Kayıpsız `ALTER ADD` |
| `Hr_V_LeaveBalanceYear` | `Id` (`{EmployeeId}-{YearNo}`), `YearNo`, `AccrualDate`, `PeriodEndDate`, `EntitledDays`, `UsedDays`, `PendingDays`, `RemainingDays`, `CumulativeRemainingDays`, `NextAccrualDate`, `NextAccrualDays` | Görünüm |
| `Hr_V_LeaveSummary` | `Id` (= kullanıcı), `ServiceYears`, `EntitledDays`, `AdjustmentDays`, `UsedDays`, `PendingDays`, `RemainingDays`, `AvailableDays`, `NextAccrualDate/Days`, `IsActive` | Görünüm |
Durum değerleri (Workflow düğüm başlıkları): `Onaya Gönderildi` · `Müdür Onayı` · `İK Onayı` · `Onaylandı` · `Reddedildi`.
---
## 5. İş kuralları ve hesaplar
### 5.1 Gün sayısı (`Hr_F_LeaveWorkDays`)
Başlangıç–bitiş tarihleri (saat dikkate alınmaz) arasındaki her gün: ayarlarda çalışma günü değilse 0;
Çalışma Takvimi'nde `Holiday` ise 0, `HalfDay` ise 0,5; aksi 1. Yıllık tekrarlı satır başlangıç
yılından itibaren her yıl aynı gün/aya düşer. Yarım gün talepte sonuç en fazla 0,5'tir.
Formda gün veri girerken dolar (Başlangıç, Bitiş ve Yarım Gün alanlarının `EditorScript`'i, `custom` tarifi).
Bu bir önizlemedir: iki tarih arasındaki hafta içi günler sayılır, yarım günde 0,5 yazılır; Çalışma Günleri
ayarı ve Çalışma Takvimi tatilleri formda düşülmez. Kayıtta prosedür günü `Hr_F_LeaveWorkDays` ile yeniden
hesaplar ve kayıtlı değer odur.
| Olay | Formda |
| --- | --- |
| Başlangıç / Bitiş değişir | Gün yeniden hesaplanır; yarım gün açıkken Başlangıç değişirse Bitiş onu izler |
| Yarım Gün işaretlenir | Bitiş = Başlangıç, Gün 0,5, Yarım Gün Dilimi açılır |
| Yarım Gün kaldırılır | Dilim temizlenip kilitlenir, Gün tam gün olarak hesaplanır |
Notlar:
- **Dilim kilidi `custom` kuralıdır** (`readOnly` tarifi değil). Grid popup'ında editörler yeniden çizilince
kural açılış olayıyla (`isReady`) tekrar çalışır; o anda form verisi güncel değildir (`form.getEditor` popup
editörünü döndürmez) ve tarif Yarım Gün'ü eski hâliyle okuyup kilidi geri kapatırdı. Kural bu çalışmada
değeri grid'in düzenlenen satırından (`editor.component.cellValue`) okur; form sayfasında form verisinden.
- **Dilimin script'i yoktur.** Form sayfası script'li bir editör değişince `formData`'yı baştan yazar; bu sırada
açılır liste temizlenip yeniden dolar ve script'i olsaydı kendini tetikleyip sonsuz döngüye girerdi.
Tutarlılığı prosedür sağlar: yarım gün değilse dilim silinir, yarım günde boş dilim `Morning` olur.
- **İzin Türü düz bir lookup'tır**, cascade kullanılmaz: bool ebeveyn `false` iken çocuğu kilitler, tarih
değişince değeri silerdi.
### 5.2 Yıllık hak ve bakiye (`Hr_V_LeaveBalanceYear`)
| Değer | Formül |
| --- | --- |
| Hak ediş tarihi | Kıdem başlangıcı + n yıl (n = 1, 2, …), bugün ve (varsa) işten çıkış tarihine kadar |
| Hak edilen | n ≤ 5 → `AnnualDays1To5`; 6 ≤ n ≤ 14 → `AnnualDays5To15`; n ≥ 15 → `AnnualDays15Plus`. Hak ediş tarihindeki yaş ≤ 18 ya da ≥ 50 ise en az `MinDaysYoungOld` |
| Kullanılan / bekleyen | Yıllık izinden düşen türdeki onaylı / onay bekleyen taleplerin günü; talep başlangıcının düştüğü hak ediş dönemine yazılır |
| 0. yıl | Düzeltmelerin toplamı ve ilk hak edişten önce başlayan talepler |
| Kalan | Σ(hak edilen + düzeltme) − Σ kullanılan (devirli) |
| Kullanılabilir | Kalan − onay bekleyen |
### 5.3 Kayıt sonrası (`Hr_S_LeaveRequestAfterSave`)
Her ekleme/güncellemede (Workflow kararı dahil) çalışır; `THROW` kaydı geri alır.
1. Çalışan adı, departman, pozisyon kullanıcı kartından; talep no `IZN-YYYY-00001`. (İK formunda çalışan seçilince departman ve pozisyon formda da hemen dolar: Çalışan lookup'ı iki kolonu döndürür, `copy` tarifi yazar.)
2. Müdür (§2.3 karar 2) bulunur → `ManagerIds`, `ManagerUserNames`. Bulunamazsa: çalışanın pozisyonunun üst pozisyonu yoksa (Genel Müdür) `RequiresManagerApproval = 0` yazılır ve müdür adımı atlanır; değilse ve talep müdür adımına kadar ise `THROW 51110` (yapılandırma eksik).
3. Müdür ekranından izinsiz geçiş önceki duruma geri alınır.
4. Çalışanın yeni talebi `Müdür Onayı`'na (müdür adımı atlanıyorsa `İK Onayı`'na) alınır; adıma girerken onaycı yazılır (müdür adımı → `ManagerUserNames`, İK adımı → İzin Talepleri akışının "İK Onayı" düğümü).
5. Gün sayısı hesaplanır.
6. Talep sonuçlanmamışsa doğrulanır:
| Kod | Kural |
| --- | --- |
| 51111 | Tür cinsiyeti çalışana uymuyor |
| 51112 | Tür yarım güne izin vermiyor |
| 51113 | Yarım günde başlangıç ve bitiş aynı gün olmalı |
| 51114 | Tür belge istiyor, belge yok |
| 51115 | Aralıkta çalışma günü yok |
| 51116 | Reddedilmemiş başka bir talep ya da talebe bağlı olmayan istirahat aynı güne düşüyor (aynı günün iki ayrı yarısı serbest) |
| 51117 | Yıllık izinden düşen türde gün > kullanılabilir bakiye, ya da türün yıllık sınırı aşılıyor — tür `Block` ise; `Warn` ise kayıt yapılır, uyarı `BalanceWarning`'e yazılır |
| 51118 | Talebin (yeni ya da eski) tarihleri kapalı puantaj dönemine düşüyor — ekleme, düzeltme, karar ve silme reddedilir (`Hr_F_TimesheetPeriodClosed`; eski tarihleri `Hr_S_LeaveRequestGuard` denetler) |
| 51119 | Tarihler çalışanın işe giriş – işten çıkış aralığının dışında |
7. Rapor (`Code = Sick`) türünde talep **onaylandığında** istirahat satırı açılır/güncellenir; reddedilen, onay bekleyen ya da türü değişen talebin istirahati kalkar. Rapor türü dışındaki talebin gün sayısı rapora denk gelen günleri saymaz (`Hr_F_LeaveNetDays`); rapor değişince çalışanın diğer taleplerinin gün sayısı yenilenir (`Hr_S_LeaveDaysRefresh`).
8. Silinen talep (silme sonrası komut aynı prosedürü çağırır): bağlı istirahat kalkar, puantaj yeniden hesaplanır.
9. Durum bildirim taşıyan bir değere geçtiyse bir kez bildirim gider, `LastNotifiedStatus` güncellenir.
10. Puantaj: talebin günleri yeniden hesaplanır; tarihi değişen talebin eski günlerini hesap kendisi aralığa katar.
### 5.4 Satır koruması (`Hr_S_LeaveRequestGuard`, before-command)
| Aktör | Kural |
| --- | --- |
| Çalışan | Yalnız kendi talebi (`51100`); durum `Onaya Gönderildi`/`Müdür Onayı` değilse düzeltme/silme yok (`51101`) |
| Müdür | Silemez (`51102`); `ManagerIds` içinde olmalı (`51103`); talep `Müdür Onayı`'nda olmalı (`51104`) |
| İK | Serbest |
| Hepsi | Talebin kayıttaki (eski) tarihleri kapalı puantaj dönemine düşüyorsa düzeltme/silme yok (`51118`) |
Alan kilidi: persona ekranlarında çalışanın/müdürün değiştirmemesi gereken alanların yazma yetkisi
`App.Hr.LeaveRequest.Create/.Update`'e bağlıdır; sunucu yetkisi olmayan alanı düşürür. Bu yüzden
çalışan/müdür rolüne İzin Talepleri'nin `.Create/.Update` yetkisi verilmez.
### 5.5 Rapor ↔ izin (tek kayıt)
| Nereden | Ne olur |
| --- | --- |
| İzin talebi, tür Rapor | Onayda `Adm_T_UserSickLeave` satırı açılır (`SickLeaveId`); onaylı talep güncellenince güncellenir, silinince/reddedilince kalkar |
| Sağlık Kartı › İstirahat Raporları ya da kullanıcı detayı › İstirahatler | `Hr_S_LeaveSickLeaveSync`: onaylı Rapor talebi açılır (onay akışına girmez), güncellemede tarih/gün, silmede (silme sonrası komut aynı prosedürü çağırır) talep kalkar. Rapora denk gelen yıllık izin günleri bakiyeye döner. Kapalı puantaj ayına düşen rapor kaydedilmez (`51120`). Kiracıda `Sick` kodlu tür yoksa eşleme yapılmaz |
| Çalışma Takvimi | Takvim satırı kaydedilince denk gelen taleplerin gün sayısı yeniden hesaplanır (`Hr_S_WorkCalendarAfterSave`) |
### 5.6 Onay akışı (Grid Workflow)
`Onaya Gönderildi` (Start) → `Müdür Onayı Gerekli mi?` (Compare, `RequiresManagerApproval = 1`) →
evet: `Müdür Onayı` (Approval, `@ManagerUserNames`) / hayır: doğrudan `İK Onayı` (Approval, seed'de
`system@sozsoft.com`) → `Onaylandı` (End); iki onay adımının reddi → `Reddedildi` (End).
Karşılaştırma düğümü yalnız İzin Talepleri akışındadır (akış İK grid'inden eklenen kayıtta orada başlar);
Ekibimin İzin Talepleri akışı başlatılmaz, yalnız müdür adımından ilerler. Akış iki ekranda aynı başlıklarla durur. İK onaycısı İzin
Talepleri › ListForm › Workflow › "İK Onayı" düğümünden değiştirilir (§2.4).
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Dal `App.Hr.Leave` → `App.Hr.LeaveRequest`, `App.Hr.LeaveBalance`
(altında `LeaveBalanceAdjustment`, `LeaveBalanceYear`). Kökte menüsüz `App.Hr.MyLeaveRequest`,
`App.Hr.MyLeaveBalance`, `App.Hr.TeamLeaveRequest`. Ayar: `App.HrSettings` › `App.HrSettings.Leave`
(Administration grubu, `App.Setting` altında). Her ekranda `.Create/.Update/.Delete/.Export/.Import/.Note`.
**Hiçbir yetki role otomatik atanmaz.**
| Rol | Verilecek yetkiler |
| --- | --- |
| Tüm çalışanlar | `App.Hr.MyLeaveRequest` (+ `.Create/.Update/.Delete`), `App.Hr.MyLeaveBalance` |
| Müdürler | Yukarıdakiler + `App.Hr.TeamLeaveRequest` (+ `.Update` — Workflow kararı güncelleme yetkisi ister) |
| İK | `App.Hr.Leave` dalının tamamı + `App.HrSettings.Leave` |
Widget'lar liste okuma yetkisiyle görünür (İzinlerim `App.Hr.MyLeaveRequest`, Ekibimin İzinleri `App.Hr.TeamLeaveRequest`).
---
## 7. Entegrasyonlar
| Kaynak | Kullanım |
| --- | --- |
| `AbpUsers` | Çalışan, kıdem, doğum tarihi, cinsiyet, departman/pozisyon; onaycı kullanıcı adları |
| `Hr_T_JobPosition` / `Hr_T_Department` | Departman müdürü çözümü |
| `Hr_T_LeaveType` | Tür kuralları (cinsiyet, yarım gün, belge, yıllık sınır, yıllık izinden düşme, aşım davranışı) |
| `Hr_T_WorkCalendar` | Tatil ve yarım günler |
| `AbpSettings` / `Sas_H_SettingDefinition` | Çalışma günleri ve kademeler |
| `Adm_T_UserSickLeave` | Rapor eşlemesi |
| Puantaj ve Devam Takibi | `Hr_S_LeaveRequestAfterSave` sonunda talebin günleri `Hr_S_TimesheetCalculate` ile yeniden hesaplanır: onaylı izin İzinli, rapor Raporlu gün olur; ücretli izne denk gelen tatil İzinli değil tatil, ücretsiz izin hafta sonu dahil İzinli yazılır. Tarihi değişen ya da silinen talebin eski günleri aynı işlemde düzelir (`HrAttendance.md` §7.1). Kapalı puantaj ayına düşen talep değiştirilemez; onay bekleyen talebi olan ay kapatılamaz. Çalışma günü ayarları puantajda hafta tatilini de belirler |
| Bildirimler | `IzinTalebiOnayBekliyor` (müdür / İK onaycısı), `IzinTalebiSonuclandi` (çalışan); kurallar `Custom` + `UiActivity`/`UiToast`. Metinler `App.Hr.Leave.Notification.{Approval,Result}.{Subject,Message}` (TR), yer tutucular `[Employee]`, `[LeaveType]`, `[StartDate]`, `[EndDate]`, `[Days]`, `[Status]`. Yazan: `Hr_S_LeaveNotify` |
---
## 8. Akış örneği
1. İK Ayarlar › İnsan Kaynakları › İzin'de Cumartesi'yi çalışma günü yapar (gerekirse); eski çalışanlar için İzin Bakiyeleri › Bakiye Düzeltmeleri'ne sistem öncesi kullanılan günleri eksi olarak girer.
2. Muhasebe Şefi panodaki **İzinlerim** widget'ında kalan 18 günü görür, **İzin Talebi Oluştur** ile 3 günlük yıllık izin açar.
3. Gün kayıtta 3 hesaplanır, talep `Müdür Onayı`'na düşer, Muhasebe Müdürü bildirim alır.
4. Müdür **Ekibimin İzinleri** → Onay Bekleyenler'den talebi seçip **Onayla** der → talep `İK Onayı`'na geçer, İK onaycısı bildirim alır.
5. İK İzin Talepleri'nde onaylar → `Onaylandı`; çalışan bildirim alır, bakiye 15'e iner.
6. Çalışan rapor alır, Rapor türünde talep açıp raporu ekler → Sağlık Kartı › İstirahat Raporları'nda görünür; aynı onay akışından geçer.
7. Sağlık birimi başka bir raporu doğrudan Sağlık Kartı'na girer → İzin Talepleri'nde onaylı Rapor talebi olarak görünür.
---
## 9. Sık sorulanlar
**"Departman müdürü bulunamadı" hatası.** Çalışanın kullanıcı kartında departman ve pozisyon dolu olmalı; departmanın en üst pozisyonunda (ya da üst pozisyonlarda) aktif bir kullanıcı bulunmalı (§5.3 adım 2). Üst pozisyonu olmayan çalışan (Genel Müdür) bu hatayı almaz; talebi doğrudan İK onayına gider.
**Genel Müdür'ün talebini kim onaylar?** Müdür adımı atlanır, yalnız İK onaylar (§2.3 karar 9).
**Gün sayısı beklenenden az.** Hafta sonu, ayarlarda kapalı gün ve Çalışma Takvimi tatilleri sayılmaz; arife 0,5 sayılır.
**Bakiye çok yüksek görünüyor.** Devirli hesap kıdem başlangıcından bugüne bütün yılları sayar; sistem öncesi kullanılan izin Bakiye Düzeltmeleri'ne eksi olarak girilmelidir.
**Bakiyem/İzinlerim'de kıdem uyarısı.** Kullanıcı kartında Kıdem Başlangıç ya da İşe Giriş tarihi yok.
**Talebimi değiştiremiyorum.** Müdür karar verdikten sonra çalışan değiştiremez/silemez (`51101`); İK ile görüşülür.
**Müdür Onayla butonunu görmüyor.** Rolünde `App.Hr.TeamLeaveRequest.Update` olmalı; talep `Müdür Onayı`'nda ve kullanıcı o departmanın müdürü olmalı.
**İK onaycısı nasıl değişir?** İzin Talepleri › ListForm › Workflow › "İK Onayı" düğümü. Ekibimin İzin Talepleri'ndeki aynı düğüm akışı ilerletir; onaycı kayda İzin Talepleri düğümünden yazılır.
**Bildirim gelmiyor.** Kiracıda `IzinTalebiOnayBekliyor` / `IzinTalebiSonuclandi` tipi ve `Custom` kuralları olmalı (yeni kiracıya kopyalanır, mevcut kiracıda Administration › Bildirimler'den eklenir).
**Kullanıcı detayındaki İstirahatler'den girilen rapor izinlere düşmüyor.** O ekranın kancaları C# seeder'dadır (`ListFormSeeder_Administration`); daha önce kurulmuş bir veritabanında ekran yeniden kurulmadıkça kanca yoktur. Sağlık Kartı ekranı wizard'dır ve migrate ile güncellenir.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_LeaveRequest.sql`, `Hr_T_LeaveBalanceAdjustment.sql`, `Hr_T_LeaveType.sql` (`BalanceExceedAction`) | Tablolar |
| `…/Hr_F_HrSetting.sql`, `Hr_F_LeaveWorkDays.sql` | Ayar okuma, gün sayısı |
| `…/Hr_V_LeaveBalanceYear.sql`, `Hr_V_LeaveSummary.sql` | Bakiye görünümleri |
| `…/Hr_S_LeaveRequestAfterSave.sql`, `Hr_S_LeaveRequestGuard.sql`, `Hr_S_LeaveNotify.sql`, `Hr_S_LeaveSickLeaveSync.sql` | Prosedürler |
| `configs/seeds/wizard/{LeaveRequest,MyLeaveRequest,TeamLeaveRequest,LeaveBalance,LeaveBalanceAdjustment,LeaveBalanceYear,MyLeaveBalance}.json` | 7 wizard; `LeaveType.json` (alan), `HealthCardSickLeave.json` (kancalar) |
| `configs/seeds/data/App.DeveloperKit.CustomComponents/{23744edc-eda9-512e-8079-fe2e8e5fd2a8,3b7b16c6-aa96-5765-8e19-fe5e727feb52}.json` | `MyLeaveWidget`, `TeamLeaveWidget` |
| `configs/seeds/data/App.DeveloperKit.IntranetWidgets.json`, `App.SettingDefinitions.json`, `App.Notifications.Notification{Types,Rules}.json`, `App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json`, `App.Hr.LeaveType.json` | Seed satırları |
| `api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs` | `@Kolon` onaycı |
| `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs` | Kullanıcı detayı istirahat kancaları |
| `ui/src/views/admin/listForm/workflow/WorkflowCriteria.tsx` | Tasarımcıda alan onaycısı seçeneği |
| `ui/src/views/admin/user-management/LeaveTab.tsx`, `Details.tsx` | İzinler sekmesi |
Kurulum: DB Migrate (SQL nesneleri, ekranlar `WizardDataSeeder`, satırlar `ListFormSeedDataApplier`),
ardından rollere yetki (§6). Ayar tanımları uygulama yeniden başlayınca ayar ekranında görünür.
Geri alma: Wizard Manager'dan 7 wizard silinir (alt formlar önce); tabloları düşürmek onayda seçilirse olur.
Widget bileşenleri ve pano kayıtları, bildirim tip/kural satırları, ayar tanımları, `Hr_F_*`/`Hr_V_*`/`Hr_S_Leave*`
nesneleri elle kaldırılır. `BalanceExceedAction` kolonu kalır (kayıpsız ekleme geri alınmaz).
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### İzin Talepleri — `App.Hr.LeaveRequest`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/LeaveRequest.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Leave` › `App.Hr.LeaveRequest` (sıra 1) |
| Yetki | `App.Hr.LeaveRequest` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | İzin Talepleri — kurumdaki bütün izin talepleri ve onay durumları. Yalnız İnsan Kaynakları kullanır. |
| Kaynak | `Hr_T_LeaveRequest` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Hr.Leave.Widget.ManagerApproval`, `App.Hr.Leave.Widget.HrApproval`, `App.Hr.Leave.Widget.Approved`, `App.Hr.Leave.Widget.Rejected` |
| Komutlar | InsertAfter: `Hr_S_LeaveRequestAfterSave` · UpdateBefore: `Hr_S_LeaveRequestGuard` · UpdateAfter: `Hr_S_LeaveRequestAfterSave` · DeleteBefore: `Hr_S_LeaveRequestGuard` · DeleteAfter: `Hr_S_LeaveRequestAfterSave` |
| Form varsayılanları | IsHalfDay=false, StartDate=@DATE, EndDate=@DATE |
| Onay akışı | Start 'Onaya Gönderildi' → Compare 'Müdür Onayı Gerekli mi?' → Approval 'Müdür Onayı' → Approval 'İK Onayı' → End 'Onaylandı' → End 'Reddedildi' |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `RequestNo` | Talep No | dxTextBox | | ✓ | |
| `EmployeeId` | Çalışan | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers · script |
| `DepartmentId` | Departman | dxSelectBox | | ✓ | sorgu: Hr_T_Department |
| `JobPositionId` | İş Pozisyonu | dxSelectBox | | ✓ | sorgu: Hr_T_JobPosition |
| `LeaveTypeId` | İzin Türü | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_LeaveType |
| `StartDate` | Başlangıç Tarihi | dxDateBox | ✓ | ✓ | script |
| `EndDate` | Bitiş Tarihi | dxDateBox | ✓ | ✓ | script |
| `IsHalfDay` | Yarım Gün | dxCheckBox | | ✓ | script |
| `HalfDayPart` | Yarım Gün Dilimi | dxSelectBox | | ✓ | liste: Morning, Afternoon |
| `Days` | Gün | dxNumberBox | | ✓ | |
| `BalanceWarning` | Bakiye Uyarısı | dxTextBox | | ✓ | |
| `ManagerUserNames` | Departman Müdürleri | dxTextBox | | | |
| `RequiresManagerApproval` | Müdür Onayı Gerekli | dxNumberBox | | | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `DocumentFileName` | Belge Adı | dxTextBox | | ✓ | |
| `DocumentContent` | Belge (rapor vb.) | dxImageUpload | | ✓ | |
| `ApprovalStatus` | Onay Durumu | dxTextBox | | ✓ | |
| `ApprovalUser` | Onaylayan | dxTextBox | | ✓ | |
| `ApprovalDate` | Onay Tarihi | dxDateBox | | ✓ | |
| `ApprovalDescription` | Onay Açıklaması | dxTextBox | | ✓ | |
### İzin Bakiyeleri — `App.Hr.LeaveBalance`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/LeaveBalance.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Leave` › `App.Hr.LeaveBalance` (sıra 2) |
| Yetki | `App.Hr.LeaveBalance` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | İzin Bakiyeleri — her çalışanın yıllık izin bakiyesi. |
| Kaynak | `Hr_V_LeaveSummary` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.LeaveBalanceAdjustment` (Id → EmployeeId), `App.Hr.LeaveBalanceYear` (Id → EmployeeId) |
| Widget'lar | `App.Widget.Total`, `App.Hr.Leave.Widget.RemainingDays`, `App.Hr.Leave.Widget.NegativeBalance`, `App.Hr.Leave.Widget.PendingDays` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeName` | Çalışan Adı | dxTextBox | | ✓ | |
| `DepartmentId` | Departman | dxSelectBox | | ✓ | sorgu: Hr_T_Department |
| `JobPositionId` | İş Pozisyonu | dxSelectBox | | ✓ | sorgu: Hr_T_JobPosition |
| `SeniorityStartDate` | Kıdem Başlangıcı | dxDateBox | | ✓ | |
| `ServiceYears` | Kıdem Yılı | dxNumberBox | | ✓ | |
| `EntitledDays` | Hak Edilen Gün | dxNumberBox | | ✓ | |
| `AdjustmentDays` | Düzeltme Günü | dxNumberBox | | ✓ | |
| `UsedDays` | Kullanılan Gün | dxNumberBox | | ✓ | |
| `PendingDays` | Onay Bekleyen Gün | dxNumberBox | | ✓ | |
| `RemainingDays` | Kalan Gün | dxNumberBox | | ✓ | |
| `AvailableDays` | Kullanılabilir Gün | dxNumberBox | | ✓ | |
| `NextAccrualDate` | Sonraki Hak Ediş | dxDateBox | | ✓ | |
| `NextAccrualDays` | Sonraki Hak Ediş Günü | dxNumberBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
### Bakiye Düzeltmeleri — `App.Hr.LeaveBalanceAdjustment`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/LeaveBalanceAdjustment.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.LeaveBalanceAdjustment` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Bakiye Düzeltmeleri — çalışanın yıllık izin bakiyesine elle yapılan ekleme ve düşümler. |
| Kaynak | `Hr_T_LeaveBalanceAdjustment` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Form varsayılanları | IsActive=true, AdjustmentDate=@DATE |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `AdjustmentDate` | Düzeltme Tarihi | dxDateBox | ✓ | ✓ | |
| `Days` | Gün | dxNumberBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
| `Reason` | Neden | dxTextArea | ✓ | ✓ | |
### İzin Defteri — `App.Hr.LeaveBalanceYear`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/LeaveBalanceYear.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.LeaveBalanceYear` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | İzin Defteri — çalışanın yıllık izninin yıl yıl dökümü. |
| Kaynak | `Hr_V_LeaveBalanceYear` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `YearNo` | Hizmet Yılı | dxNumberBox | | ✓ | |
| `AccrualDate` | Hak Ediş Tarihi | dxDateBox | | ✓ | |
| `PeriodEndDate` | Dönem Sonu | dxDateBox | | ✓ | |
| `EntitledDays` | Hak Edilen Gün | dxNumberBox | | ✓ | |
| `UsedDays` | Kullanılan Gün | dxNumberBox | | ✓ | |
| `PendingDays` | Onay Bekleyen Gün | dxNumberBox | | ✓ | |
| `RemainingDays` | Kalan Gün | dxNumberBox | | ✓ | |
| `CumulativeRemainingDays` | Kümülatif Bakiye | dxNumberBox | | ✓ | |
| `NextAccrualDate` | Sonraki Hak Ediş | dxDateBox | | ✓ | |
| `NextAccrualDays` | Sonraki Hak Ediş Günü | dxNumberBox | | ✓ | |
### İzin Taleplerim — `App.Hr.MyLeaveRequest`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/MyLeaveRequest.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.MyLeaveRequest` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | İzin Taleplerim — oturum açan çalışanın kendi izin talepleri ve onay durumları. |
| Kaynak | `Hr_T_LeaveRequest` · filtre `"IsDeleted" = 'false' AND "EmployeeId" = '@USERID'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Komutlar | InsertAfter: `Hr_S_LeaveRequestAfterSave` · UpdateBefore: `Hr_S_LeaveRequestGuard` · UpdateAfter: `Hr_S_LeaveRequestAfterSave` · DeleteBefore: `Hr_S_LeaveRequestGuard` · DeleteAfter: `Hr_S_LeaveRequestAfterSave` |
| Form varsayılanları | IsHalfDay=false, StartDate=@DATE, EndDate=@DATE |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `RequestNo` | Talep No | dxTextBox | | ✓ | |
| `LeaveTypeId` | İzin Türü | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_LeaveType |
| `StartDate` | Başlangıç Tarihi | dxDateBox | ✓ | ✓ | script |
| `EndDate` | Bitiş Tarihi | dxDateBox | ✓ | ✓ | script |
| `IsHalfDay` | Yarım Gün | dxCheckBox | | ✓ | script |
| `HalfDayPart` | Yarım Gün Dilimi | dxSelectBox | | ✓ | liste: Morning, Afternoon |
| `Days` | Gün | dxNumberBox | | ✓ | |
| `BalanceWarning` | Bakiye Uyarısı | dxTextBox | | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `DocumentFileName` | Belge Adı | dxTextBox | | ✓ | |
| `DocumentContent` | Belge (rapor vb.) | dxImageUpload | | ✓ | |
| `ApprovalStatus` | Onay Durumu | dxTextBox | | ✓ | |
| `ApprovalUser` | Onaylayan | dxTextBox | | ✓ | |
| `ApprovalDate` | Onay Tarihi | dxDateBox | | ✓ | |
| `ApprovalDescription` | Onay Açıklaması | dxTextBox | | ✓ | |
### Ekibimin İzin Talepleri — `App.Hr.TeamLeaveRequest`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/TeamLeaveRequest.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.TeamLeaveRequest` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Ekibimin İzin Talepleri — müdürü olduğunuz departmandaki çalışanların izin talepleri. |
| Kaynak | `Hr_T_LeaveRequest` · filtre `"IsDeleted" = 'false' AND '\|' + "ManagerIds" + '\|' LIKE '%\|@USERID\|%'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Komutlar | UpdateBefore: `Hr_S_LeaveRequestGuard` · UpdateAfter: `Hr_S_LeaveRequestAfterSave` |
| Onay akışı | Start 'Onaya Gönderildi' → Approval 'Müdür Onayı' → Approval 'İK Onayı' → End 'Onaylandı' → End 'Reddedildi' |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `RequestNo` | Talep No | dxTextBox | | ✓ | |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `DepartmentId` | Departman | dxSelectBox | | ✓ | sorgu: Hr_T_Department |
| `JobPositionId` | İş Pozisyonu | dxSelectBox | | ✓ | sorgu: Hr_T_JobPosition |
| `LeaveTypeId` | İzin Türü | dxSelectBox | | ✓ | sorgu: Hr_T_LeaveType |
| `StartDate` | Başlangıç Tarihi | dxDateBox | | ✓ | |
| `EndDate` | Bitiş Tarihi | dxDateBox | | ✓ | |
| `IsHalfDay` | Yarım Gün | dxCheckBox | | ✓ | |
| `HalfDayPart` | Yarım Gün Dilimi | dxSelectBox | | ✓ | liste: Morning, Afternoon |
| `Days` | Gün | dxNumberBox | | ✓ | |
| `BalanceWarning` | Bakiye Uyarısı | dxTextBox | | ✓ | |
| `ManagerUserNames` | Departman Müdürleri | dxTextBox | | | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `DocumentFileName` | Belge Adı | dxTextBox | | ✓ | |
| `DocumentContent` | Belge (rapor vb.) | dxImageUpload | | ✓ | |
| `ApprovalStatus` | Onay Durumu | dxTextBox | | ✓ | |
| `ApprovalUser` | Onaylayan | dxTextBox | | ✓ | |
| `ApprovalDate` | Onay Tarihi | dxDateBox | | ✓ | |
| `ApprovalDescription` | Onay Açıklaması | dxTextArea | | ✓ | |
### Bakiyem — `App.Hr.MyLeaveBalance`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/MyLeaveBalance.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.MyLeaveBalance` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Bakiyem — yıllık izin hakkınızın ve kullanımınızın yıl yıl dökümü. |
| Kaynak | `Hr_V_LeaveBalanceYear` · filtre `"EmployeeId" = '@USERID'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✗ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `EmployeeId` | Çalışan | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `YearNo` | Hizmet Yılı | dxNumberBox | | ✓ | |
| `AccrualDate` | Hak Ediş Tarihi | dxDateBox | | ✓ | |
| `PeriodEndDate` | Dönem Sonu | dxDateBox | | ✓ | |
| `EntitledDays` | Hak Edilen Gün | dxNumberBox | | ✓ | |
| `UsedDays` | Kullanılan Gün | dxNumberBox | | ✓ | |
| `PendingDays` | Onay Bekleyen Gün | dxNumberBox | | ✓ | |
| `RemainingDays` | Kalan Gün | dxNumberBox | | ✓ | |
| `CumulativeRemainingDays` | Kümülatif Bakiye | dxNumberBox | | ✓ | |
| `NextAccrualDate` | Sonraki Hak Ediş | dxDateBox | | ✓ | |
| `NextAccrualDays` | Sonraki Hak Ediş Günü | dxNumberBox | | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,187 +0,0 @@
# İK › Organizasyon ve Yetkinlik (Departman, Pozisyon, Organizasyon Şeması, Yetenek Tipleri)
Şirketin organizasyon iskeleti ve yetkinlik sözlüğü: şubeye bağlı departman ağacı, departmana bağlı
pozisyon ağacı, bunların görsel organizasyon şeması ve yetenek tipi → yetenek → seviye sözlüğü.
Çalışanın departman/pozisyon/işe giriş/vardiya bilgileri ve yetkinlikleri kullanıcı kaydında tutulur.
Diğer bütün İK modülleri (İşe Alım, Eğitim, Performans, Disiplin, Sağlık Kartı…) bu tanımları lookup
olarak kullanır. Bu bölüm **entity + C# seeder** ile kuruludur (wizard değil); Organizasyon Şeması
özel bir React sayfasıdır.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| İK | Departman ve pozisyon ağacını, yetenek sözlüğünü yönetir | İK › Tanımlamalar › Departmanlar · Pozisyonlar · Yetenek Tipleri |
| İK / sistem yöneticisi | Çalışanın departman, pozisyon, işe giriş, vardiya, yetkinlik ve dil bilgilerini girer | Kimlik › Kullanıcılar › detay (İş Bilgileri, Eğitim ve Yetkinlik sekmeleri) |
| Tüm kullanıcılar (yetkili) | Organizasyonu departman ya da pozisyon bazında görür, JPG olarak dışa aktarır | İK › Organizasyon Şeması |
Kapsam: kiracı. Departman ve pozisyon **şube bazlıdır** (`BranchId` zorunlu); ekranlarda satır süzmesi yoktur.
---
## 2. Analiz ve kararlar
> Bu bölüm §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar koddan
> (`ListFormSeeder_Hr.cs`, entity'ler, `OrgChartAppService`, `OrgChart.tsx`) okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Entity + `ListFormSeeder_Hr.cs` (migration'lı tablolar); şema için özel React sayfası + AppService |
| Departman | Ağaç (`ParentId`), şubeye bağlı; varsayılan görünüm **Tree**; aynı şubede ad tekil |
| Pozisyon | Ağaç (`ParentId` = üst pozisyon), departmana ve şubeye bağlı; varsayılan görünüm **Tree**; aynı departmanda ad tekil |
| Yönetici ilişkisi | Departmanda yönetici alanı **yoktur**; hiyerarşi pozisyon ağacından okunur |
| Host şubesi | Host varsayılan şubesi sabit kimlikle kurulur (`PlatformConsts.Branches.BranchIdDefaultValue = 1c125bf3-…`); seed departman/pozisyon satırları buna bağlıdır, kiracı şubeleri yeni GUID alır |
| Admin varsayılanı | Admin kullanıcı departman **Yönetim**, pozisyon **Genel Müdür** ile kurulur |
| Yetkinlik sözlüğü | Yetenek Tipi ekranının iki alt formu: Seviyeler (ad + `Progress` 0–100 kaydırıcı + varsayılan) ve Yetenekler |
| Çalışan yetkinliği | İki ayrı ölçek: **diller** `Hr_T_SkillLevel` seviyesiyle (`UserLanguage`), **diğer yetkinlikler** sabit 1–5 ile (`Adm_T_UserSkill`) |
| Tip adı bağımlılığı | Bazı lookup'lar tip adını sabit yazar (`'Languages'`, `'Technical Skills'`) — bu tipler yeniden adlandırılmamalıdır |
---
## 3. Menü ve ekranlar
| Menü (TR) | `ListFormCode` / rota | Yer | Görünüm | Alt formlar |
| --- | --- | --- | --- | --- |
| Yetenek Tipleri | `App.Hr.SkillType` | Tanımlamalar (1) | Grid | `App.Hr.SkillLevel`, `App.Hr.Skill` (`Id → SkillTypeId`) |
| Departmanlar | `App.Hr.Department` | Tanımlamalar (2) | **Tree** (`Id`/`ParentId`, tümü açık) | — |
| Pozisyonlar | `App.Hr.JobPosition` | Tanımlamalar (3) | **Tree** | — |
| Organizasyon Şeması | `/admin/organization` (`@/views/admin/hr/OrgChart`) | `İnsan Kaynakları` (4) | Özel sayfa | — |
Bu ekranlarda widget yoktur ve veri seed dosyasına aynalanmaz (`SeedSync*` kapalı).
**Organizasyon Şeması** (`ui/src/views/admin/hr/OrgChart.tsx`):
- **Mod:** Departman ya da Pozisyon.
- **Kullanıcıları göster:** Açık/kapalı seçilebilir.
- **Yakınlaştırma:** %30–300 arası, Ctrl + tekerlek ile de.
- **Dışa aktarma:** JPG olarak.
- **Kartlar:** Derinliğe göre renklenir, alt sayısı ve aç/kapa düğmesi taşır, sürüklenerek yeri değiştirilir (yer kaydedilmez).
- **Kullanıcı satırı:** Avatar gösterir; üzerine gelince profil kartı açılır (unvan, departman, e-posta, telefon).
---
## 4. Veri modeli
```
Sas_T_Branch ──< Hr_T_Department (ParentId ağacı) ──< Hr_T_JobPosition (ParentId ağacı)
▲
AbpUsers.DepartmentId / JobPositionId / HireDate / ShiftId ┘
Hr_T_SkillType ──< Hr_T_SkillLevel (Progress, IsDefault)
└────────< Hr_T_Skill ──< Adm_T_UserSkill (Level 1–5)
└────< UserLanguage (SkillLevelId)
```
| Tablo | Kolonlar | Kural |
| --- | --- | --- |
| `Hr_T_Department` | `BranchId` (zorunlu), `Name` (64), `ParentId` | `UX (TenantId, BranchId, Name)` |
| `Hr_T_JobPosition` | `BranchId` (zorunlu), `DepartmentId` (zorunlu, FK Restrict), `Name` (64), `ParentId` | `UX (TenantId, Name, DepartmentId)` |
| `Hr_T_SkillType` | `Name` (128) | `UX (TenantId, Name)`; silinince yetenek/seviye kademeli silinir |
| `Hr_T_SkillLevel` | `SkillTypeId`, `Name`, `Progress` (int, 0), `IsDefault` | `UX (TenantId, SkillTypeId, Name)` |
| `Hr_T_Skill` | `SkillTypeId`, `Name` | `UX (TenantId, SkillTypeId, Name)` |
| `Adm_T_UserSkill` | `UserId`, `SkillId`, `Level`, `Note` (512) | `CHECK Level BETWEEN 1 AND 5`; `UX (TenantId, UserId, SkillId)` |
| `AbpUsers` (genişletme) | `DepartmentId`, `JobPositionId`, `HireDate`, `ShiftId`, sözleşme/fesih/ehliyet alanları | `PlatformModuleExtensionConfigurator`, `AbpIdentityUserExtensions` |
Entity'ler: `Entities/Tenant/Hr/Definitions/{SkillType,Skill,SkillLevel,Department,JobPosition}.cs`
(`FullAuditedEntity<Guid>, IMultiTenant`); EF yapılandırması `PlatformDbContext.cs`.
Seed (`configs/seeds/data/`):
| Dosya | İçerik |
| --- | --- |
| `App.Hr.Department.json` | Yönetim (kök) → Üretim, Satış, Finans (→ Muhasebe), Bilgi İşlem |
| `App.Hr.JobPosition.json` | Genel Müdür (kök) → BT, İhracat, İç Piyasa, Üretim, Finans müdürleri; Finans Müdürü → Muhasebe Müdürü → Muhasebe Şefi |
| `App.Hr.SkillType.json` | Languages, Soft Skills, Technical Skills |
| `App.Hr.Skill.json` | 31 yetenek: 21 dil, 3 soft skill, 7 teknik |
| `App.Hr.SkillLevel.json` | Diller C2 100 · C1 85 · B2 75 · B1 60 · A2 40 · A1 10 (varsayılan); Soft Skills Expert 100 · Advanced 80 · Intermediate 50 · Elementary 25 · Beginner 15 (varsayılan); **Technical Skills için seviye yok** |
---
## 5. İş kuralları
| Yer | Kural |
| --- | --- |
| Departman formu | Ad → üst departman → şube; şube değişince üst departman temizlenir; üst departman listesi şubeye göre süzülür |
| Pozisyon formu | Ad → üst pozisyon → şube → departman; şube değişince departman ve üst pozisyon, departman değişince üst pozisyon temizlenir. Üst pozisyon listesi seçilen departmanın **ve üst departmanlarının** pozisyonları + tüm kök pozisyonlardır (`JobPositionFullValues`) |
| Seviye — yeni kayıt | `IsDefault = true` (tek varsayılan zorlanmaz) |
| Kullanıcı — İş Bilgileri | Departman değişince pozisyon temizlenir, pozisyon listesi departmana göre süzülür; deneme süresi sonu işe girişten önce ya da 4 aydan sonra olamaz (`PlatformIdentityAppService`) |
| Şema — Departman modu | Her departman kartında o departmanın **en üst pozisyonu** ve yalnızca o pozisyondaki kullanıcılar gösterilir (üst pozisyonu olmayan ya da üstü başka departmanda olan; ada göre ilk) |
| Şema — Pozisyon modu | Her pozisyon bir kart; o pozisyondaki tüm kullanıcılar |
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`:
| Yetki | Üst | Alt yetkiler |
| --- | --- | --- |
| `App.Hr.SkillType` | `App.Hr.Definitions` | Create, Update, Delete, Export, Import, Note |
| `App.Hr.SkillLevel`, `App.Hr.Skill` | `App.Hr.Definitions` | Create, Update, Delete, Export, Import |
| `App.Hr.Department`, `App.Hr.JobPosition` | `App.Hr.Definitions` | Create, Update, Delete, Export, Import |
| `App.Hr.OrgChart` | grup kökü | — (menü ve rota bu yetkiyi ister) |
Çalışanın iş bilgileri ve yetkinlikleri kullanıcı ekranının yetkileriyle (`AbpIdentity.Users.*`) düzenlenir.
Hiçbir yetki role otomatik atanmaz.
---
## 7. Entegrasyonlar (kim kullanıyor)
| Tanım | Kullanan modüller |
| --- | --- |
| Departman / Pozisyon | İşe Alım (talep, ilan, teklif), Eğitim katılımcısı, Disiplin dosyası, Sağlık Kartı listesi, Performans değerlendirmesi, kullanıcı ekranı, Organizasyon Şeması |
| Yetenek / Seviye | Aday yetkinlikleri, eğitimin kazandırdıkları, Performans yetkinlik değerlendirmesi (seviye `Progress` = puan), kullanıcı dil ve yetkinlik listeleri |
| `Adm_T_UserSkill` | Performans Yönetimi tamamlanınca yönetici seviyesini buraya yazar (bkz. `HrPerformance.md`) |
| Vardiya (`ShiftId`) | Bkz. `HrWorkCalendar.md` |
---
## 8. Akış örneği
1. İK **Departmanlar**'a "Kalite" departmanını şube Merkez, üst departman Üretim olarak ekler (ağaçta Üretim'in altında görünür).
2. **Pozisyonlar**'a "Kalite Şefi"ni departman Kalite, üst pozisyon Üretim Müdürü olarak ekler.
3. Kullanıcı detayının **İş Bilgileri** sekmesinde çalışanın departmanı Kalite, pozisyonu Kalite Şefi, işe giriş tarihi ve vardiyası girilir; **Eğitim ve Yetkinlik** sekmesine İngilizce B2 ve "Kaynak Uzmanı 4/5" eklenir.
4. **Organizasyon Şeması** Pozisyon modunda Kalite Şefi kartı Üretim Müdürü'nün altında, çalışanla birlikte görünür; şema JPG olarak dışa aktarılır.
---
## 9. Sık sorulanlar ve bilinen sınırlar
**Departman yöneticisi nerede?** Yok; hiyerarşi pozisyon ağacındadır. (Performans modülü bu yüzden yöneticiyi değerlendirme kaydında tutar.)
**Şemada departman kartında neden tüm çalışanlar yok?** Departman modunda yalnız en üst pozisyondakiler gösterilir; herkes için Pozisyon modunu kullanın.
**Şemadaki kart yerleri kaydedilmiyor.** Sürükleme yalnız görünümü değiştirir.
**Teknik yetenekler için seviye seçilemiyor.** Seed'de Technical Skills tipine seviye tanımlı değil; kullanıcı yetkinliği 1–5 ölçeğiyle girilir. Performans modülünde bu tipe seviye eklenmesi gerekir.
**"Languages" tipinin adını değiştirdim, dil listesi boşaldı.** Lookup'lar tip adını sabit kullanır (`TenantLanguageSkillValues`, `TechnicalSkillsValues`).
Kontrol edilmeli (koddan tespit edilen tutarsızlıklar):
- Organizasyon Şeması API'si (`api/app/org-chart`) yalnız oturum ister, `App.Hr.OrgChart` yetkisini denetlemez; menü ve rota yetkiyle korunur.
- `BranchDepartmentValues` ve `JobPositionFullValues` lookup'larında açık kiracı koşulu yoktur; `JobPositionFullValues` departmandan bağımsız tüm kök pozisyonları da listeler.
- Yetenek alt formunun popup başlığı Seviye ekranının anahtarını kullanır (`ListFormSeeder_Hr.cs`).
- Yetenek Tipi/Seviye/Yetenek yetkileri `MultiTenancySide 2`, Departman/Pozisyon `3`'tür.
- Bir tipte birden çok varsayılan seviye işaretlenebilir.
---
## 10. Dosyalar
| Dosya | İçerik |
| --- | --- |
| `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Hr.cs` | Beş ekranın ListForm tanımı |
| `api/src/Sozsoft.Platform.Domain/Entities/Tenant/Hr/Definitions/*.cs` | Entity'ler |
| `api/src/Sozsoft.Platform.Application/OrgChart/OrgChartAppService.cs`, `Application.Contracts/OrgChart/OrgChartDto.cs` | Şema API'si (`GET departments`, `GET job-positions`) |
| `ui/src/views/admin/hr/OrgChart.tsx`, `ui/src/services/orgChart.service.ts` | Şema sayfası |
| `ui/src/views/admin/user-management/Details.tsx`, `EducationSkillsTab.tsx` | Kullanıcı iş bilgileri ve yetkinlik sekmeleri |
| `api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs` | Şube/departman/pozisyon/yetenek lookup'ları |
| `configs/seeds/data/App.Hr.{Department,JobPosition,SkillType,Skill,SkillLevel}.json` | Seed |
| `configs/seeds/data/App.Menus.Routes.json` (`admin.hr.organization`) | Şema rotası |
Bu tablolar migration'lıdır; ekranlar seeder'dan kurulduğu için Wizard Manager'dan silinmez.

File diff suppressed because it is too large Load diff

View file

@ -1,186 +0,0 @@
# İK › Personel Kaydı (Kimlik › Kullanıcılar › detay)
Çalışanın özlük kaydı ayrı bir İK ekranında değil, kullanıcı detay sayfasındadır
(`/admin/users/detail/:userId`): iletişim ve ek bilgiler, askerlik, engellilik, nüfus/kimlik, doğum ve
aile, yabancı uyruklu belgeleri, organizasyon (departman/pozisyon/vardiya), sözleşme ve deneme süresi,
işten çıkış, sürücü belgesi, eğitim geçmişi, yabancı diller, yetenekler, yeterlilik belgeleri, eş ve
çocuklar, acil durum kişileri, banka ve ücret bilgileri, yan haklar, iş deneyimi. (Sağlık Kartı sekmesi
için bkz. [HrHealthCard.md](HrHealthCard.md).) Tek değerli alanlar `AbpUsers` genişletme kolonlarında,
çok kayıtlılar `Adm_T_User*` tablolarındadır; diğer İK modüllerinin hepsi çalışanı buradan okur.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| İK / özlük | Personelin bütün özlük, sözleşme, eğitim, aile ve mali bilgisini girer | Kimlik › Kullanıcılar › Detay |
| Sistem yöneticisi | Kullanıcıyı açar; listede temel alanları (departman, pozisyon, cinsiyet, adres…) düzenler | Kimlik › Kullanıcılar |
| Yönetici | Deneme süresi bitimine 15 gün kala bildirim alır | Bildirimler |
Kapsam: kiracı. Rota yetkisi `AbpIdentity.Users.Update`.
---
## 2. Analiz ve kararlar
> §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar koddan (`Details.tsx` ve sekme
> bileşenleri, `PlatformIdentityAppService`, `ListFormSeeder_Administration.cs`, entity'ler) okunmuştur.
| Konu | Karar |
| --- | --- |
| Yer | Ayrı "personel" tablosu yok; özlük = kullanıcı. Tek değerli alanlar ABP extra property olarak `AbpUsers` kolonları |
| Çok kayıtlı bilgiler | `Adm_T_User{WorkExperience,Education,Language,Skill,Certificate,FamilyMember,EmergencyContact,Benefit}`; detay sekmelerinde gömülü liste (`App.IdentityManagement.*`), yeni satırın `UserId`'si sayfa filtresinden dolar |
| Kaydetme | Her form sekmesi kendi formuyla bütün modeli `PUT /api/app/platform-identity/user` (`UpdateUserAsync`) ile gönderir; başka sekmede kaydedilmemiş değişiklik gitmez |
| Mali bilgiler | Ayrı DTO ve uç (`GetFinancialAsync` / `UpdateFinancialAsync`, `Users.Update` yetkisi) — intranet listelerine akan `UserInfoViewModel`'de taşınmasın diye |
| Deneme süresi | İşe giriş ile işe giriş + **4 ay** arasında olmalı (4857 s. İK md. 15; toplu sözleşmeyle 4 ay) — hem formda hem sunucuda |
| Koşullu alanlar | Tecil bitişi yalnız "Tecilli"de, sözleşme bitişi yalnız "Belirli Süreli"de, yabancı uyruklu bölümü yalnız TR dışı uyrukta, engellilik oranı/raporu yalnız "Engelli"de **görünür**; gizlenince değer silinmez |
| Ölçekler | Dil seviyesi `Hr_T_SkillLevel` ile, diğer yetenekler sabit 1–5 ile (bkz. [HrOrganization.md](HrOrganization.md)) |
| Saklama biçimleri | Engellilik raporu ve avatar dışındaki dosyalar base64 kolonda; ehliyet sınıfları `|` ile birleştirilmiş metin; medeni durum ve eğitim düzeyi Türkçe anahtar |
---
## 3. Ekranlar ve sekmeler
| Sekme (TR) | İçerik | Kayıt |
| --- | --- | --- |
| Genel | İletişim (e-posta, ikinci e-posta, ad, soyad, telefon, ikinci telefon, kullanıcı tipi İç/Dış), ek bilgiler (ev adresi, uyruk, cinsiyet), askerlik (durum: Yapıldı/Muaf/Tecilli/Yapılmadı + tecil bitişi), avatar (kırpma, ≤ 2 MB), engellilik (engelli, oran %, rapor PDF/JPG/PNG ≤ 5 MB) | `UpdateUserAsync` (+ avatar `UpdateAvatarAsync`) |
| Kimlik Bilgileri | Nüfus cüzdanı (kimlik no, seri no, veriliş yeri/tarihi, il, ilçe, köy, cilt, aile sıra, sıra no), doğum ve aile (doğum yeri/tarihi, baba/anne adı, medeni durum Bekar/Evli/Boşanmış/Dul, evlenme tarihi), yabancı uyruklu (pasaport no/geçerlilik, çalışma izni no/bitiş) | `UpdateUserAsync` |
| İş | Organizasyon (departman → pozisyon, sicil no, SGK no, vardiya grubu, PDKS kart no — sicil/kart no ve vardiya puantajın kişi eşlemesi ve vardiyasıdır, `HrAttendance.md`), sözleşme (çalışma şekli Tam/Yarı Zamanlı, sözleşme türü Belirsiz/Belirli, işe başlama, sözleşme bitişi, deneme süresi bitişi, kıdem başlangıcı), işten çıkış (tarih, SGK çıkış kodu, açıklama), sürücü belgesi (sınıflar çoklu M…G, no, veriliş, geçerlilik), iletişim kanalları (Rocket, Telegram) + **Daha Önce Çalıştığı Yerler** listesi | `UpdateUserAsync` + `App.IdentityManagement.UserWorkExperience` |
| Eğitim ve Yetenekler | En yüksek eğitim seviyesi + **Eğitim Geçmişi**, **Yabancı Diller**, **Yetenekler**, **Yeterlilik Belgeleri** listeleri | `UpdateUserAsync` + 4 liste |
| Aile / Acil Durum | **Eş ve Çocuklar**, **Acil Durum Kişileri** listeleri | 2 liste |
| Finansal | Banka adı, IBAN; ücret tipi Aylık/Günlük/Saatlik, brüt ücret, para birimi + **Yan Haklar** listesi | `UpdateFinancialAsync` + `App.IdentityManagement.UserBenefit` |
| Sağlık Kartı | Bkz. [HrHealthCard.md](HrHealthCard.md) | — |
| Puantaj | Yılın puantaj kartları + Aylık Puantaj, Günlük Puantaj, Günlük Hareketler listeleri; bkz. [HrAttendance.md](HrAttendance.md) §3.4 | — |
| Eğitimler | Çalışanın eğitim katılımları (`App.Hr.TrainingParticipant`, `UserId` süzgeci); bkz. [HrTrainings.md](HrTrainings.md) | — |
| Performans | Değerlendirmeler (`App.Hr.PerformanceReview`) ve gelişim planları (`App.Hr.DevelopmentPlan`), `EmployeeId` süzgeci; bkz. [HrPerformance.md](HrPerformance.md) | — |
| Disiplin | Disiplin dosyaları (`App.Hr.DisciplinaryCase`, `UserId` süzgeci); bkz. [HrDisciplinaryCase.md](HrDisciplinaryCase.md) | — |
Gömülü listeler (hepsi menüsüz alt liste, `Adm_T_*`):
| Liste | Alanlar | Kural |
| --- | --- | --- |
| `UserWorkExperience` | Firma*, iş kolu* (sektör), giriş*, çıkış, yaptığı iş (HTML) | Giriş tarihine göre azalan |
| `UserEducation` | Eğitim düzeyi* (İlkokul…Doktora), okul*, bölüm, başlangıç, mezuniyet, mezun (varsayılan evet) | — |
| `UserLanguage` | Dil* ("Languages" tipi yetenekler), seviye (dilin seviyeleri), not | Kullanıcı × dil tekil |
| `UserSkill` | Yetenek* (dil dışı tipler), seviye* 1–5 (Başlangıç…Uzman, varsayılan 3), not | `CHECK 1–5`; kullanıcı × yetenek tekil |
| `UserCertificate` | Belge* (`Hr_T_Certificate`), belge no, veren kurum, düzenleme, son kullanma, not | Son kullanmaya göre artan |
| `UserFamilyMember` | Yakınlık* (Eş/Çocuk), ad*, soyad, TC kimlik, doğum tarihi, çalışma durumu (Çalışıyor/Çalışmıyor/Öğrenci/Emekli), sigorta kapsamında, yan hak kapsamında | — |
| `UserEmergencyContact` | Ad soyad*, yakınlık* (Eş, Anne/Baba, Kardeş, Çocuk, Akraba, Arkadaş, Diğer), telefon* | — |
| `UserBenefit` | Yan hak türü* (`Hr_T_BenefitType`, **kod** saklanır), tutar, başlangıç, bitiş, not | — |
Kullanıcı listesi (`AbpIdentity.Users`) popup'ında düzenlenen İK alanları: departman*, pozisyon*,
cinsiyet, ev adresi (+ avatar, e-posta, ad, soyad, tip, telefon, çalışma saati). Diğer özlük alanları
listede gizli kolon olarak durur.
---
## 4. Veri modeli
| Yer | İçerik |
| --- | --- |
| `AbpUsers` genişletme kolonları | Bütün form sekmesi alanları; sabitler `PlatformConsts.AbpIdentity.User.*`, erişim `AbpIdentityUserExtensions`, kolon tipleri `PlatformEfCoreEntityExtensionMappings` |
| `Adm_T_UserWorkExperience` | `CompanyName`, `SectorId` (FK Sector), `StartDate`, `EndDate`, `JobDescription` |
| `Adm_T_UserEducation` | `EducationLevel`, `SchoolName`, `Department`, `StartDate`, `EndDate`, `IsGraduated` |
| `Adm_T_UserLanguage` | `SkillId` (FK Skill), `SkillLevelId` (FK SkillLevel), `Note` |
| `Adm_T_UserSkill` | `SkillId`, `Level` (1–5), `Note` |
| `Adm_T_UserCertificate` | `CertificateId` (`Hr_T_Certificate`, FK'siz), `CertificateNo`, `IssuedBy`, `IssueDate`, `ExpiryDate`, `Note` |
| `Adm_T_UserFamilyMember` | `Relationship`, `Name`, `Surname`, `IdentityNumber`, `BirthDate`, `EmploymentStatus`, `IsInsuranceCovered`, `IsBenefitCovered` |
| `Adm_T_UserEmergencyContact` | `Name`, `Relationship`, `PhoneNumber` |
| `Adm_T_UserBenefit` | `BenefitType` (kod), `Amount`, `StartDate`, `EndDate`, `Note` |
Entity'ler `Entities/Tenant/Administration/User*.cs` (`FullAuditedEntity<Guid>, IMultiTenant`), EF
yapılandırması `PlatformDbContext.cs`.
---
## 5. İş kuralları
| Yer | Kural |
| --- | --- |
| Deneme süresi | `HireDate ≤ ProbationEndDate ≤ HireDate + 4 ay`; formda gün bazında, sunucuda (`UpdateUserAsync`) tam zamanla |
| Departman değişimi | Pozisyon temizlenir; pozisyon listesi departmana göre süzülür |
| IBAN | Formda boşluk silinir, büyük harfe çevrilir, `^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$` deseni; sunucu yalnız normalleştirir |
| Engellilik raporu | ≤ 5 MB (yalnız formda), base64 olarak kolonda |
| Avatar | `{userId}.jpg` avatar blob kabına yazılır; boş gönderim siler |
| Deneme süresi hatırlatması | `ProbationReminderWorker` günlük çalışır; aktif, çıkışı olmayan ve deneme bitişi **bugün + 15 gün** olan kullanıcılar için `DenemeSuresiBitiyor` bildirimi üretir (tip yoksa atlar) |
| Doğum günü | `BirthDate` intranet panosundaki "Bugün Doğanlar" kutusunu besler (bkz. [HrIntranet.md](HrIntranet.md)) |
---
## 6. Yetki ve görünürlük
| İşlem | Yetki |
| --- | --- |
| Detay sayfası (rota), liste "Detay" butonu | `AbpIdentity.Users.Update` |
| Gömülü listeler | okuma `AbpIdentity.Users`; ekle/güncelle/sil `AbpIdentity.Users.Update` (Yan Haklar alanlarında ekleme `Users.Create`) |
| Mali bilgiler, avatar | `AbpIdentity.Users.Update` (sunucuda) |
| Kullanıcı listesi ekle/güncelle | `Users.Create` / `Users.Update` |
Özlük verisi kişisel veri, sağlık ve engellilik verisi özel nitelikli kişisel veridir (KVKK). Kullanıcı
yönetimi yetkisi yalnız İK ve sistem yöneticilerinde olmalıdır.
**Kontrol edilmeli — güvenlik (koddan tespit edildi):**
- `PlatformIdentityAppService.GetByIdAsync` ve `UpdateUserAsync` yalnız oturum ister (`[Authorize]`); `Users.Update` yalnız arayüz rotasında denetlenir. Oturum açmış herhangi bir kullanıcı API'yi doğrudan çağırarak başka birinin özlük kaydını (engellilik raporu dahil) okuyup güncelleyebilir.
- Banka, IBAN ve ücret alanları `AbpIdentity.Users` listesinde gizli ama okunabilir/dışa aktarılabilir kolon olarak durur; liste okuma yetkisi olan kullanıcı kolon seçiciyle görebilir.
- Intranet doğum günü listesine giden `UserInfoViewModel` kimlik no, SGK no ve sağlık kartı alanlarını da taşır.
---
## 7. Entegrasyonlar
| Kullanan | Okuduğu |
| --- | --- |
| Tüm İK modülleri | Çalışan, departman, pozisyon |
| Organizasyon Şeması | Pozisyondaki kullanıcılar |
| Vardiyalar / Olay Günlüğü | `ShiftId` |
| Yeterlilik Belgeleri, Yan Hak Türleri, Sektörler, SGK çıkış kodları, Para Birimleri | Lookup kaynakları |
| Performans Yönetimi | Tamamlanan değerlendirme `Adm_T_UserSkill`'e yazar |
| İşe Alım | İşe alınan adayın kaydı kullanıcıya `UserId` ile bağlanır |
| Intranet | Doğum günleri, kullanıcı kartları |
| Bildirimler | Deneme süresi hatırlatması (`DenemeSuresiBitiyor`) |
---
## 8. Akış örneği
1. BT kullanıcıyı listeden açar (ad, e-posta, departman, pozisyon). İK **Detay**'a girer.
2. **Kimlik Bilgileri**: nüfus ve aile bilgileri; **Genel**: adres, uyruk, askerlik, avatar.
3. **İş**: sicil/SGK no, vardiya, sözleşme türü Belirsiz, işe başlama 01.10, deneme süresi bitişi 01.12 (4 ayı aşarsa kayıt reddedilir); önceki iş yerleri listesi.
4. **Eğitim ve Yetenekler**: lisans mezuniyeti, İngilizce B2, "Kaynak Uzmanı 4", forklift belgesi.
5. **Aile / Acil Durum**: eş ve çocuk (sigorta kapsamında), acil durum kişisi.
6. **Finansal**: banka/IBAN, aylık brüt ücret TRY; yemek kartı yan hakkı.
7. 15 Kasım'da yöneticilere deneme süresinin bitmek üzere olduğu bildirimi gider.
---
## 9. Sık sorulanlar ve bilinen sınırlar
**Detayda e-postayı değiştirdim, kaydolmadı.** `UpdateUserAsync` e-postayı yazmaz; e-posta kullanıcı listesindeki düzenlemeden değişir.
**Başka sekmede yaptığım değişiklik kayboldu.** Her sekme ayrı kaydedilir; sekme değiştirmeden önce kaydedin.
**Sözleşmeyi Belirsiz yaptım, bitiş tarihi hâlâ kayıtlı.** Koşullu alanlar yalnız gizlenir, değer silinmez.
**Pozisyon listede zorunlu, detayda değil.** Doğru; kullanıcı listesi popup'ı zorunlu tutar.
**Acil durum kişisinde ad kolonu "Doktor Adı Soyadı" görünüyor.** Alan başlığı yanlış dil anahtarını (`App.Listform.ListformField.FullName`) kullanıyor; düzeltilmeli.
**Aile üyelerindeki sigorta/yan hak işaretleri bir yere gidiyor mu?** Şu an bunları okuyan bir ekran/rapor bulunmadı.
---
## 10. Dosyalar
| Dosya | İçerik |
| --- | --- |
| `ui/src/views/admin/user-management/Details.tsx`, `EducationSkillsTab.tsx`, `FamilyEmergencyTab.tsx`, `FinancialTab.tsx`, `userFormFields.tsx`, `useUserFieldLookup.ts` | Detay sayfası ve sekmeler |
| `ui/src/views/admin/user-management/HrRecordsTab.tsx`, `hrRecordTabs.ts` | Eğitimler / Performans / Disiplin sekmeleri: İK ekranları çalışana süzülmüş açılır; sekme, ekranlarından birini görme yetkisi varsa çizilir. Yeni bir İK modülü sekmesi `HR_RECORD_TABS`'a satır eklemekle açılır |
| `api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs` | `GetByIdAsync`, `UpdateUserAsync`, `Get/UpdateFinancialAsync`, `UpdateAvatarAsync` |
| `api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs`, `EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs` | Genişletme alanları |
| `api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/User*.cs` | Çok kayıtlı entity'ler |
| `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs` | `AbpIdentity.Users` ve `App.IdentityManagement.*` listeleri |
| `api/src/Sozsoft.Platform.Domain/BackgroundWorkers/ProbationReminderWorker.cs` | Deneme süresi hatırlatması |

View file

@ -1,573 +0,0 @@
<!-- module-doc: claims=App.Hr.CandidateSource, App.Hr.RejectionReason, App.Hr.EvaluationCriterion -->
# İK › İşe Alım (`App.Hr.Recruitment`)
Kadro talebinden işe alıma kadar uçtan uca süreç: onaylı kadro talebi → iş ilanı → aday havuzu
(CV, KVKK açık rızası) → başvuru (aşama panosu) → mülakat (takvim, kriter ağırlıklı puan) →
teklif (iç onay, teklif mektubu). Tamamı konfigürasyondur (Wizard + SQL + rapor şablonu); entity,
migration ya da React kodu yoktur.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| Talep eden yönetici | Kadro talebi açar, onaya gönderir | Kadro Talepleri |
| Onaycı | Kadro talebini ve teklifi onaylar / reddeder | Kadro Talepleri · Teklifler (Grid Workflow butonları) |
| İK / işe alım uzmanı | İlan açar, aday ve başvuruları yönetir, mülakat planlar, teklif hazırlar, aday kaydını personele bağlar | İlanlar · Adaylar · Başvurular · Mülakatlar · Teklifler |
| Mülakatçı | Kendisine atanan mülakatta kriter bazlı puan verir | Mülakatlar (+ bildirim) |
| İK yöneticisi | Aday kaynağı, ret sebebi, değerlendirme kriteri tanımlarını yönetir | İK › Tanımlar |
Kapsam: kiracı (`IsTenant`). Şube, kayıtta veri olarak tutulur (satır süzmesi yoktur).
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Aşağıdaki kararlar üretilen
> artefaktlardan (wizard, SQL, seed, commit `16fd5df0` "İşe Alım modülü") okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Her adım ayrı Wizard ekranı; ilan ve aday ekranlarında başvuru alt formu, başvuruda mülakat ve teklif alt formu |
| Kadro talebi ve teklif onayı | **Grid Workflow** (`WorkflowDto`): Start → Approval (`Onay Bekliyor`) → Inform → End `Onaylandı` / `Reddedildi`. Onaycı **düğümün `Approver` değeridir** (seed'de platform admin e-postası); motor onaycıyı kayıttaki alandan okumaz — ListForm › Workflow sekmesinden değiştirilir |
| İlan kaynağı | İlan yalnızca **onaylı** (`ApprovalStatus = 'Onaylandı'`) kadro talebinden açılır; şube/departman/pozisyon/adet/çalışma şekli talepten `copy` ile dolar |
| Aday kimliği | E-posta kiracı içinde tekildir (filtreli unique index) |
| KVKK | Açık rıza **zorunlu**: formda zorunlu alan + tabloda `CHECK (KvkkConsent = 1)`; rıza tarihi/yöntemi/imzalı belge saklanır; satır butonu **KVKK Açık Rıza Formu** raporunu açar |
| Başvuru | Aday × ilan çifti tekildir; aşama **Todo (kanban)** panosunda yönetilir |
| Aşama ilerlemesi | Mülakat eklenince `Interview`, teklif eklenince `Offer`, teklif `Accepted` olunca `Hired` — SQL ile, **geri gitmez** |
| Mülakat puanı | Kriter başına 1–5; `OverallScore = Σ(puan × kriter ağırlığı) / Σ ağırlık`; başvurunun `AverageScore`'u mülakatların ortalaması |
| Mülakatçı bildirimi | Mülakat eklenince mülakatçıya `IseAlimMulakatAtandi` bildirimi (kanallar `UiActivity` + `Mail`) |
| Belge numaraları | `KT-YYYY-00001` (talep), `ILN-…` (ilan), `TKL-…` (teklif) — insert sonrası SQL, kiracı + yıl bazında |
| Personele dönüşüm | İşe alınan adayın kullanıcı (personel) kaydını **BT elle açar**; İK aday kaydını `UserId` ile o kullanıcıya bağlar (otomatik kullanıcı açılmaz) |
| Kanban başlığı | `CandidateName` / `PostingTitle` başvuruya metin olarak kopyalanır (Todo başlığı metin kolon ister) |
---
## 3. Menü ve ekranlar
`İnsan Kaynakları › İşe Alım` (`App.Hr.Recruitment`, ikon `FcManager`)
| Sıra | Menü (TR / EN) | `ListFormCode` | Alt formlar | Görünümler | Widget'lar |
| --- | --- | --- | --- | --- | --- |
| 1 | Kadro Talepleri / Staff Requests | `App.Hr.StaffRequest` | — | Grid, Card, Pivot, Chart | Toplam · Onay Bekliyor · Onaylanan · Onaylı kadro adedi (Σ Headcount) |
| 2 | İlanlar / Job Postings | `App.Hr.JobPosting` | `JobApplication` (`Id → JobPostingId`) | Grid, Card, Pivot, Chart | Toplam · Yayında · Taslak · Kapalı |
| 3 | Adaylar / Candidates | `App.Hr.Candidate` | `JobApplication` (`Id → CandidateId`) | Grid, Card, Pivot, Chart | Toplam · Aktif · Pasif · Personele Dönüşen |
| 4 | Başvurular / Applications | `App.Hr.JobApplication` | `Interview`, `JobOffer` (`Id → ApplicationId`) | Grid, Card, Pivot, Chart, **Todo** | — (alt form olarak da kullanıldığı için yok) |
| 5 | Mülakatlar / Interviews | `App.Hr.Interview` | `InterviewScore` (`Id → InterviewId`, menüsüz) | Grid, Card, Pivot, Chart, **Scheduler** | — |
| 6 | Teklifler / Job Offers | `App.Hr.JobOffer` | — | Grid, Card, Pivot, Chart | — |
Tanımlar `İnsan Kaynakları › Tanımlar` (`App.Hr.Definitions`) altında, verileri seed dosyasıyla aynalı:
| Menü | `ListFormCode` | Seed |
| --- | --- | --- |
| Aday Kaynakları | `App.Hr.CandidateSource` | 7 kayıt: LinkedIn, Kariyer.net, Yenibiris, Şirket Web Sitesi, Referans, Kariyer Fuarı, Doğrudan Başvuru |
| Ret Sebepleri | `App.Hr.RejectionReason` | 8 kayıt; `RejectedBy` = Company / Candidate |
| Değerlendirme Kriterleri | `App.Hr.EvaluationCriterion` | 6 kayıt (ağırlıklı): İletişim 1, Teknik Yetkinlik 2, Problem Çözme 1.5, Takım Çalışması 1, Motivasyon 1, Kurum Kültürüne Uyum 1 |
Satır butonları (`DynamicReport`, `Raporlar` kategorisi):
| Ekran | Buton | Rapor şablonu | Görünürlük |
| --- | --- | --- | --- |
| Adaylar | KVKK Açık Rıza Formu | `475c1ab4-4600-5ef2-a43e-1366ed89b0ed` | Her satır |
| Teklifler | Teklif Mektubu | `80d38666-423d-5e1d-8766-01f2c46be768` | `ApprovalStatus === 'Onaylandı'` |
---
## 4. Veri modeli
```
Hr_T_StaffRequest ──< Hr_T_JobPosting ──< Hr_T_JobApplication >── Hr_T_Candidate ──> AbpUsers (UserId, personel)
│
├──< Hr_T_Interview ──< Hr_T_InterviewScore >── Hr_T_EvaluationCriterion
└──< Hr_T_JobOffer
```
| Tablo | Önemli kolonlar | Kural |
| --- | --- | --- |
| `Hr_T_StaffRequest` | `RequestNo`, `RequestDate`, `RequestedById`, `BranchId` → `DepartmentId` → `JobPositionId` (cascade), `Headcount`, `EmploymentType`, `ExpectedStartDate`, `Reason` (NewPosition/Replacement), `ReplacedUserId`, ücret aralığı + `CurrencyId`, `Qualifications` (HTML), onay alanları | `Reason ≠ Replacement` ise `ReplacedUserId` temizlenir (script) |
| `Hr_T_JobPosting` | `PostingNo`, `StaffRequestId`, `Title`, `Status` (Draft/Published/Closed/Cancelled), şube/departman/pozisyon, `Headcount`, `EmploymentType`, `WorkModel` (Office/Hybrid/Remote), `Location`, `PublishDate`, `ClosingDate`, `SourceIds` (yayın kanalları, çok değerli), açıklama/nitelikler (HTML) | Başlık boşsa pozisyon adı kopyalanır |
| `Hr_T_Candidate` | Kişisel bilgiler, fotoğraf, ülke → şehir, eğitim (`EducationLevel`, okul, bölüm, mezuniyet), deneyim, ücret beklentisi, `SourceId`, `SkillIds` (`Hr_T_Skill`, çok değerli), `CvDocument`, `UserId`, KVKK alanları | `UX (TenantId, Email)`; `CHECK (KvkkConsent = 1)` |
| `Hr_T_JobApplication` | `CandidateId`, `JobPostingId`, `CandidateName`, `PostingTitle`, `ApplicationDate`, `SourceId`, `OwnerUserId`, `AverageScore`, `Stage`, `RejectionReasonId`, `RejectionNote`, `SortOrder` | `UX (TenantId, CandidateId, JobPostingId)`; aşama `Rejected` değilse ret sebebi/notu temizlenir |
| `Hr_T_Interview` | `ApplicationId`, `Title`, `InterviewType` (Phone/Video/FaceToFace/Technical/Hr), `InterviewerUserId`, `StartDate`, `EndDate`, `Location`, `MeetingUrl`, `Status` (Planned/Completed/Cancelled/NoShow), `Recommendation` (Hire/Hold/Reject), `OverallScore` | Bitiş boşsa başlangıç + 1 saat (script) |
| `Hr_T_InterviewScore` | `InterviewId`, `CriterionId`, `Score`, `Comment` | `CHECK (Score BETWEEN 1 AND 5)`; `UX (InterviewId, CriterionId)` |
| `Hr_T_JobOffer` | `OfferNo`, `ApplicationId`, `CandidateName`, `OfferDate`, `ValidUntil`, `StartDate`, `GrossSalary` + `CurrencyId`, `Status` (Draft/Sent/Accepted/Declined/Expired), `ResponseDate`, pozisyon bilgileri, `BenefitCodes` (`Hr_T_BenefitType`), onay alanları | `CHECK (GrossSalary >= 0)`; durum Accepted/Declined olunca yanıt tarihi bugün (script) |
---
## 5. İş kuralları ve hesaplar
### 5.1 Başvuru aşamaları
```
Applied → Screening → Interview → Offer → Hired
└──→ Rejected (her aşamadan, elle)
```
| Tetik | Etki (after-command SQL) |
| --- | --- |
| Mülakat eklendi | Başvuru `Applied`/`Screening` ise → `Interview` |
| Teklif eklendi | Başvuru `Applied`/`Screening`/`Interview` ise → `Offer`; teklif zaten `Accepted` ise → `Hired` |
| Teklif güncellendi (`Accepted`) | Başvuru henüz `Hired` değilse → `Hired` |
| Elle | Panodan sürükleme ya da formdan her aşama seçilebilir |
Otomatik ilerleme yalnızca ileri gider; geri almak elle yapılır.
### 5.2 Puanlar
- `Interview.OverallScore = Σ(Score × Criterion.Weight) / Σ Weight` (silinmemiş puanlar).
- `JobApplication.AverageScore = AVG(Interview.OverallScore)` (puanı olan mülakatlar).
- Puan eklenince, güncellenince ya da silinince ikisi de yeniden hesaplanır.
### 5.3 Metin kopyaları
`CandidateName`, `PostingTitle` (başvuru), `Title` (mülakat = aday – ilan), `CandidateName`
(teklif) after-command ile yazılır. Aday, ilan ya da başvuru güncellendiğinde **tüm** değişmiş
kopyalar topluca düzeltilir (sorgu `@Id` ile değil fark koşuluyla çalışır).
### 5.4 Numaratörler
`KT-`, `ILN-`, `TKL-` + yıl + 5 hane; kiracı ve yıl bazında `MAX + 1`. Numara yalnızca boşsa yazılır.
### 5.5 Onay akışı (Kadro Talebi, Teklif)
Grid Workflow; durum `ApprovalStatus`, onaylayan `ApprovalUser`, tarih `ApprovalDate`, açıklama
`ApprovalDescription`. Onay/Ret butonlarını akış kendisi çizer. Onaycı düğümde sabittir
(`system@sozsoft.com` seed değeri) — gerçek onaycı ListForm › Workflow'dan girilmelidir.
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Yetki ağacı menüyü izler: `App.Hr.Recruitment` ara yetkisinin
altında her ekranın kökü + `.Create/.Update/.Delete/.Export/.Import/.Note`; `App.Hr.InterviewScore`
mülakat yetkisinin altındadır. Tanımlar `App.Hr.Definitions` altındadır. Satır süzmesi yoktur —
ekran yetkisi olan tüm kayıtları görür. Hiçbir yetki role otomatik atanmaz.
Önerilen dağıtım:
| Rol | Yetkiler |
| --- | --- |
| Talep eden yöneticiler | `App.Hr.StaffRequest` (+ `.Create/.Update`) |
| Mülakatçılar | `App.Hr.Interview` (+ `.Update`), `App.Hr.InterviewScore` (+ `.Create/.Update/.Delete`) |
| İşe alım uzmanı | `App.Hr.Recruitment` dalının tamamı + üç tanım ekranı |
| Onaycı | Onaylayacağı ekranın kökü + `.Update` |
KVKK: aday verisi kişisel veridir; Adaylar ekranının yetkisi yalnızca işe alım ekibine verilmelidir.
---
## 7. Entegrasyonlar
| Kaynak | Kullanım |
| --- | --- |
| `Sas_T_Branch`, `Hr_T_Department`, `Hr_T_JobPosition` | Şube → departman → pozisyon cascade (talep, ilan, teklif) |
| `Sas_H_Currency` | Ücret para birimi (varsayılan TRY) |
| `Sas_H_Country`, `Sas_H_City` | Aday adresi (ülke → şehir cascade) |
| `Hr_T_Skill` / `Hr_T_SkillType` | Aday yetkinlikleri (`SkillIds`) |
| `Hr_T_BenefitType` | Teklifteki yan haklar (`BenefitCodes`) |
| `AbpUsers` | Talep eden, ayrılan personel, sorumlu, mülakatçı, personele dönüşen aday |
| Bildirimler | Tip `IseAlimMulakatAtandi`; kurallar `Custom` + `UiActivity`/`Mail`; metin `App.Hr.Interview.Notification.{Subject,Message}`, yer tutucular `[Candidate]`, `[Posting]`, `[Date]` |
| Raporlar | KVKK Açık Rıza Formu, Teklif Mektubu (`DynamicReport`) |
---
## 8. Akış örneği
1. Satış müdürü **Kadro Talepleri**'nde "Satış Temsilcisi, 2 kişi, yeni pozisyon" talebini açar →
`KT-2026-00001`; onaya gönderir, onaycı onaylar → `Onaylandı`.
2. İK **İlanlar**'da talebi seçer; şube/departman/pozisyon/adet dolar, başlık pozisyon adı olur;
yayın kanallarını (LinkedIn, Kariyer.net) seçip durumu **Published** yapar → `ILN-2026-00001`.
3. Gelen adaylar **Adaylar**'a CV ve KVKK rızasıyla girilir; KVKK formu satır butonundan basılır.
4. İlanın **Başvurular** sekmesinden aday eklenir (`Applied`); kanban panosunda **Screening**'e çekilir.
5. Başvurunun **Mülakatlar** sekmesinden teknik mülakat planlanır → başvuru `Interview` olur,
mülakatçıya bildirim gider; mülakatçı kriterleri puanlar → genel ve ortalama puan hesaplanır.
6. **Teklifler**'de teklif hazırlanır → başvuru `Offer`; teklif onaya gider, onaylanınca
**Teklif Mektubu** basılır.
7. Aday kabul eder, teklif **Accepted** yapılır → başvuru `Hired`. BT kullanıcıyı açar, İK aday
kaydının **Personel** alanına bağlar ("Personele Dönüşen" kartı artar).
---
## 9. Sık sorulanlar
**İlan açarken kadro talebi listede yok.** Yalnızca onaylanmış (`ApprovalStatus = 'Onaylandı'`) talepler listelenir.
**Onay butonu kimde görünüyor?** Akış düğümündeki `Approver`'da (seed'de admin). ListForm › Workflow'dan gerçek onaycıyı yazın.
**Başvuru aşaması geri gitmedi.** Otomatik ilerleme yalnız ileri gider; geri almayı panodan ya da formdan elle yapın.
**Ret sebebi alanı temizlendi.** Ret sebebi ve notu yalnızca `Rejected` aşamasında tutulur.
**Aday kaydı kaydedilmiyor.** KVKK rızası işaretli olmalı (tablo da zorlar) ve e-posta kiracıda tekil olmalı.
**Aynı aday aynı ilana ikinci kez eklenemiyor.** Aday × ilan tekildir.
**Mülakat puanı nasıl hesaplanıyor?** Kriter ağırlıklı ortalama (§5.2); ağırlıklar İK › Tanımlar › Değerlendirme Kriterleri'nden.
**İşe alınan aday otomatik personel olmuyor mu?** Hayır; kullanıcıyı BT açar, İK aday kaydına bağlar.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_{StaffRequest,JobPosting,Candidate,JobApplication,Interview,InterviewScore,JobOffer,CandidateSource,RejectionReason,EvaluationCriterion}.sql` | Tablolar, CHECK ve tekil indeksler |
| `configs/seeds/wizard/{StaffRequest,JobPosting,Candidate,JobApplication,Interview,InterviewScore,JobOffer,CandidateSource,RejectionReason,EvaluationCriterion}.json` | 10 wizard |
| `configs/seeds/data/App.Hr.{CandidateSource,RejectionReason,EvaluationCriterion}.json` | Tanım verisi |
| `configs/seeds/data/App.Reports.ReportTemplates.json` | KVKK formu ve teklif mektubu şablonları |
| `configs/seeds/data/App.Notifications.Notification{Types,Rules}.json` | `IseAlimMulakatAtandi` tipi ve 2 kuralı |
| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları |
Kurulum: DB Migrate. Geri alma: Wizard Manager'dan wizard'lar silinir (alt formlar önce: `InterviewScore`
→ `Interview`/`JobOffer` → `JobApplication` → diğerleri); tablolar yalnızca onayda seçilirse düşer.
Rapor şablonları ve bildirim satırları elle kaldırılır.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Kadro Talepleri — `App.Hr.StaffRequest`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/StaffRequest.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Recruitment` › `App.Hr.StaffRequest` (sıra 1) |
| Yetki | `App.Hr.StaffRequest` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Kadro Talepleri — bir departmanın yeni ya da boşalan bir pozisyon için personel istemesi ve bu isteğin onay süreci. İşe alım sürecinin ilk adımıdır; ilan yalnızca onaylanmış talepten açılır. |
| Kaynak | `Hr_T_StaffRequest` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Pending`, `App.Hr.StaffRequest.Widget.Approved`, `App.Hr.StaffRequest.Widget.Headcount` |
| Komutlar | InsertAfter: SQL |
| Form varsayılanları | IsActive=true, RequestDate=@DATE, RequestedById=@USERID, Headcount=1, EmploymentType=FullTime, Reason=NewPosition, CurrencyId=TRY |
| Onay akışı | Start 'Onaya Gönderildi' → Approval 'Onay Bekliyor' → Inform 'Onay Bilgisi' → Inform 'Ret Bilgisi' → End 'Onaylandı' → End 'Reddedildi' |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `RequestNo` | Talep No | dxTextBox | | ✓ | |
| `RequestDate` | Talep Tarihi | dxDateBox | ✓ | ✓ | |
| `RequestedById` | Talep Eden | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `BranchId` | Şube Adı | dxSelectBox | ✓ | ✓ | sorgu: Sas_T_Branch |
| `DepartmentId` | Departman | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_Department (← BranchId) |
| `JobPositionId` | İş Pozisyonu | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_JobPosition (← DepartmentId) |
| `Headcount` | Kadro Adedi | dxNumberBox | ✓ | ✓ | |
| `EmploymentType` | Çalışma Şekli | dxSelectBox | ✓ | ✓ | liste: FullTime, PartTime, Contract, Intern, Seasonal |
| `ExpectedStartDate` | Beklenen Başlangıç | dxDateBox | | ✓ | |
| `Reason` | Talep Nedeni | dxRadioGroup | ✓ | ✓ | liste: NewPosition, Replacement · script |
| `ReplacedUserId` | Ayrılan Personel | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
| `MinSalary` | Asgari Ücret | dxNumberBox | | ✓ | |
| `MaxSalary` | Azami Ücret | dxNumberBox | | ✓ | |
| `CurrencyId` | Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `Qualifications` | Aranan Nitelikler | dxHtmlEditor | | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `ApprovalStatus` | Onay Durumu | dxTextBox | | ✓ | |
| `ApprovalUser` | Onaylayan | dxTextBox | | ✓ | |
| `ApprovalDate` | Onay Tarihi | dxDateBox | | ✓ | |
| `ApprovalDescription` | Onay Açıklaması | dxTextArea | | ✓ | |
### İlanlar — `App.Hr.JobPosting`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/JobPosting.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Recruitment` › `App.Hr.JobPosting` (sıra 2) |
| Yetki | `App.Hr.JobPosting` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | İlanlar — onaylanmış kadro taleplerinden açılan iş ilanları ve ilana gelen başvurular. |
| Kaynak | `Hr_T_JobPosting` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.JobApplication` (Id → JobPostingId) |
| Widget'lar | `App.Widget.Total`, `App.Widget.Published`, `App.Widget.Draft`, `App.Hr.JobPosting.Widget.Closed` |
| Komutlar | InsertAfter: SQL · UpdateAfter: SQL |
| Form varsayılanları | IsActive=true, Status=Draft, Headcount=1, EmploymentType=FullTime, WorkModel=Office, PublishDate=@DATE |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `PostingNo` | İlan No | dxTextBox | | ✓ | |
| `StaffRequestId` | Kadro Talebi | dxSelectBox | | ✓ | sorgu: Hr_T_StaffRequest · script |
| `Title` | İlan Başlığı | dxTextBox | ✓ | ✓ | |
| `Status` | Durum | dxSelectBox | ✓ | ✓ | liste: Draft, Published, Closed, Cancelled |
| `BranchId` | Şube Adı | dxSelectBox | ✓ | ✓ | sorgu: Sas_T_Branch |
| `DepartmentId` | Departman | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_Department (← BranchId) |
| `JobPositionId` | İş Pozisyonu | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_JobPosition (← DepartmentId) |
| `Headcount` | Kadro Adedi | dxNumberBox | ✓ | ✓ | |
| `EmploymentType` | Çalışma Şekli | dxSelectBox | ✓ | ✓ | liste: FullTime, PartTime, Contract, Intern, Seasonal |
| `WorkModel` | Çalışma Modeli | dxSelectBox | ✓ | ✓ | liste: Office, Hybrid, Remote |
| `Location` | Lokasyon | dxTextBox | | ✓ | |
| `PublishDate` | Yayın Tarihi | dxDateBox | | ✓ | |
| `ClosingDate` | Son Başvuru Tarihi | dxDateBox | | ✓ | |
| `SourceIds` | Yayın Kanalları | dxTagBox | | ✓ | sorgu: Hr_T_CandidateSource |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
| `Description` | Açıklama | dxHtmlEditor | | ✓ | |
| `Requirements` | Aranan Nitelikler | dxHtmlEditor | | ✓ | |
### Adaylar — `App.Hr.Candidate`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Candidate.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Recruitment` › `App.Hr.Candidate` (sıra 3) |
| Yetki | `App.Hr.Candidate` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Adaylar — CV ve KVKK açık rızası ile tutulan aday havuzu. Bir aday birden fazla ilana başvurabilir. |
| Kaynak | `Hr_T_Candidate` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.JobApplication` (Id → CandidateId) |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive`, `App.Hr.Candidate.Widget.Converted` |
| Komutlar | UpdateAfter: SQL |
| Form varsayılanları | IsActive=true, KvkkConsentDate=@NOW, KvkkConsentMethod=Written, CurrencyId=TRY |
| Butonlar | `App.Hr.Candidate.KvkkConsentForm` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Photo` | Fotoğraf | dxImageUpload | | ✓ | |
| `FirstName` | Adı | dxTextBox | ✓ | ✓ | |
| `LastName` | Soyadı | dxTextBox | ✓ | ✓ | |
| `Email` | E-posta | dxTextBox | ✓ | ✓ | |
| `PhoneNumber` | Telefon | dxTextBox | | ✓ | |
| `Gender` | Cinsiyet | dxSelectBox | | ✓ | liste: Male, Female |
| `BirthDate` | Doğum Tarihi | dxDateBox | | ✓ | |
| `Country` | Ülke | dxSelectBox | | ✓ | sorgu: Sas_H_Country |
| `City` | Şehir | dxSelectBox | | ✓ | sorgu: Sas_H_City (← Country) |
| `Address` | Adres | dxTextBox | | ✓ | |
| `EducationLevel` | Eğitim Düzeyi | dxSelectBox | | ✓ | liste: Primary, HighSchool, Associate, Bachelor, Master, Doctorate |
| `School` | Okul | dxTextBox | | ✓ | |
| `Major` | Bölüm | dxTextBox | | ✓ | |
| `GraduationYear` | Mezuniyet Yılı | dxNumberBox | | ✓ | |
| `ExperienceYears` | Deneyim (Yıl) | dxNumberBox | | ✓ | |
| `CurrentCompany` | Mevcut Firma | dxTextBox | | ✓ | |
| `CurrentTitle` | Mevcut Ünvan | dxTextBox | | ✓ | |
| `ExpectedSalary` | Ücret Beklentisi | dxNumberBox | | ✓ | |
| `CurrencyId` | Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `SourceId` | Kaynak | dxSelectBox | | ✓ | sorgu: Hr_T_CandidateSource |
| `LinkedInUrl` | LinkedIn | dxTextBox | | ✓ | |
| `UserId` | Personel | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `SkillIds` | Yetkinlikler | dxTagBox | | ✓ | sorgu: Hr_T_Skill |
| `CvDocument` | CV | dxImageUpload | | ✓ | |
| `Notes` | Notlar | dxTextBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
| `KvkkConsent` | Adayın KVKK kapsamında açık rızası alındı | dxCheckBox | ✓ | ✓ | |
| `KvkkConsentDate` | Rıza Tarihi | dxDateBox | ✓ | ✓ | |
| `KvkkConsentMethod` | Rıza Alma Yöntemi | dxSelectBox | ✓ | ✓ | liste: Written, Electronic, Email |
| `KvkkConsentDocument` | İmzalı Rıza Belgesi | dxImageUpload | | ✓ | |
### Başvurular — `App.Hr.JobApplication`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/JobApplication.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Recruitment` › `App.Hr.JobApplication` (sıra 4) |
| Yetki | `App.Hr.JobApplication` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Başvurular — bir adayın bir ilana başvurusu ve başvurunun aşama takibi (kanban panosu). |
| Kaynak | `Hr_T_JobApplication` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart, Todo (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.Interview` (Id → ApplicationId), `App.Hr.JobOffer` (Id → ApplicationId) |
| Todo | başlık `CandidateName`, durum `Stage` (Applied,Screening,Interview,Offer,Hired,Rejected) |
| Komutlar | InsertAfter: SQL · UpdateAfter: SQL |
| Form varsayılanları | IsActive=true, ApplicationDate=@DATE, Stage=Applied, OwnerUserId=@USERID |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `CandidateId` | Aday | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_Candidate · script |
| `JobPostingId` | İlan | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_JobPosting |
| `CandidateName` | Aday | dxTextBox | | | |
| `PostingTitle` | İlan | dxTextBox | | | |
| `ApplicationDate` | Başvuru Tarihi | dxDateBox | ✓ | ✓ | |
| `SourceId` | Kaynak | dxSelectBox | | ✓ | sorgu: Hr_T_CandidateSource |
| `OwnerUserId` | Sorumlu | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `AverageScore` | Ortalama Puan | dxNumberBox | | ✓ | |
| `Stage` | Aşama | dxSelectBox | ✓ | ✓ | liste: Applied, Screening, Interview, Offer, Hired, Rejected · script |
| `RejectionReasonId` | Ret Sebebi | dxSelectBox | | ✓ | sorgu: Hr_T_RejectionReason |
| `RejectionNote` | Ret Açıklaması | dxTextArea | | ✓ | |
| `SortOrder` | Sıralama | dxNumberBox | | | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
| `Notes` | Notlar | dxTextArea | | ✓ | |
### Mülakatlar — `App.Hr.Interview`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Interview.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Recruitment` › `App.Hr.Interview` (sıra 5) |
| Yetki | `App.Hr.Interview` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Mülakatlar — başvuru için mülakat planlama, mülakatçı ataması ve kriter bazlı puanlama. |
| Kaynak | `Hr_T_Interview` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart, Scheduler (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.InterviewScore` (Id → InterviewId) |
| Komutlar | InsertAfter: SQL · UpdateAfter: SQL · DeleteAfter: SQL |
| Form varsayılanları | IsActive=true, InterviewType=FaceToFace, Status=Planned, InterviewerUserId=@USERID |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `ApplicationId` | Başvuru | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_JobApplication |
| `Title` | Başlık | dxTextBox | | | |
| `InterviewType` | Mülakat Türü | dxSelectBox | ✓ | ✓ | liste: Phone, Video, FaceToFace, Technical, Hr |
| `InterviewerUserId` | Mülakatçı | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers |
| `StartDate` | Başlangıç | dxDateBox | ✓ | ✓ | script |
| `EndDate` | Bitiş | dxDateBox | ✓ | ✓ | |
| `Location` | Yer | dxTextBox | | ✓ | |
| `MeetingUrl` | Toplantı Bağlantısı | dxTextBox | | ✓ | |
| `Status` | Durum | dxSelectBox | ✓ | ✓ | liste: Planned, Completed, Cancelled, NoShow |
| `Recommendation` | Öneri | dxSelectBox | | ✓ | liste: Hire, Hold, Reject |
| `OverallScore` | Genel Puan | dxNumberBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
| `Notes` | Notlar | dxTextArea | | ✓ | |
### Aday Kaynakları — `App.Hr.CandidateSource`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/CandidateSource.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.CandidateSource` (sıra 6) |
| Yetki | `App.Hr.CandidateSource` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Aday Kaynakları — adayların ve ilanların hangi kanaldan geldiğini/yayınlandığını tanımlayan liste (LinkedIn, Kariyer.net, referans, kariyer fuarı…). |
| Kaynak | `Hr_T_CandidateSource` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | IsActive=true |
| Veri aynası | `data/App.Hr.CandidateSource.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
### Teklifler — `App.Hr.JobOffer`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/JobOffer.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Recruitment` › `App.Hr.JobOffer` (sıra 6) |
| Yetki | `App.Hr.JobOffer` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Teklifler — işe alınması düşünülen adaya yapılan ücret ve şart teklifi, iç onayı ve teklif mektubu. |
| Kaynak | `Hr_T_JobOffer` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Komutlar | InsertAfter: SQL · UpdateAfter: SQL |
| Form varsayılanları | IsActive=true, OfferDate=@DATE, Status=Draft, CurrencyId=TRY, EmploymentType=FullTime, WorkModel=Office |
| Butonlar | `App.Hr.JobOffer.OfferLetter` |
| Onay akışı | Start 'Onaya Gönderildi' → Approval 'Onay Bekliyor' → Inform 'Onay Bilgisi' → Inform 'Ret Bilgisi' → End 'Onaylandı' → End 'Reddedildi' |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `OfferNo` | Teklif No | dxTextBox | | ✓ | |
| `ApplicationId` | Başvuru | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_JobApplication · script |
| `CandidateName` | Aday | dxTextBox | | | |
| `OfferDate` | Teklif Tarihi | dxDateBox | ✓ | ✓ | |
| `ValidUntil` | Geçerlilik Tarihi | dxDateBox | | ✓ | |
| `StartDate` | İşe Başlama Tarihi | dxDateBox | | ✓ | |
| `GrossSalary` | Brüt Maaş | dxNumberBox | ✓ | ✓ | |
| `CurrencyId` | Para Birimi | dxSelectBox | ✓ | ✓ | sorgu: Sas_H_Currency |
| `Status` | Durum | dxSelectBox | ✓ | ✓ | liste: Draft, Sent, Accepted, Declined, Expired · script |
| `ResponseDate` | Yanıt Tarihi | dxDateBox | | ✓ | |
| `BranchId` | Şube Adı | dxSelectBox | ✓ | ✓ | sorgu: Sas_T_Branch |
| `DepartmentId` | Departman | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_Department (← BranchId) |
| `JobPositionId` | İş Pozisyonu | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_JobPosition (← DepartmentId) |
| `EmploymentType` | Çalışma Şekli | dxSelectBox | ✓ | ✓ | liste: FullTime, PartTime, Contract, Intern, Seasonal |
| `WorkModel` | Çalışma Modeli | dxSelectBox | ✓ | ✓ | liste: Office, Hybrid, Remote |
| `BenefitCodes` | Yan Haklar | dxTagBox | | ✓ | sorgu: Hr_T_BenefitType |
| `Notes` | Notlar | dxTextBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
| `ApprovalStatus` | Onay Durumu | dxTextBox | | ✓ | |
| `ApprovalUser` | Onaylayan | dxTextBox | | ✓ | |
| `ApprovalDate` | Onay Tarihi | dxDateBox | | ✓ | |
| `ApprovalDescription` | Onay Açıklaması | dxTextBox | | ✓ | |
### Ret Sebepleri — `App.Hr.RejectionReason`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/RejectionReason.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.RejectionReason` (sıra 7) |
| Yetki | `App.Hr.RejectionReason` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Ret Sebepleri — bir başvurunun şirket ya da aday tarafından neden kapatıldığını sınıflandıran liste. |
| Kaynak | `Hr_T_RejectionReason` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | IsActive=true, RejectedBy=Company |
| Veri aynası | `data/App.Hr.RejectionReason.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `RejectedBy` | Reddeden | dxRadioGroup | ✓ | ✓ | liste: Company, Candidate |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
### Değerlendirme Kriterleri — `App.Hr.EvaluationCriterion`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/EvaluationCriterion.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.EvaluationCriterion` (sıra 8) |
| Yetki | `App.Hr.EvaluationCriterion` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Değerlendirme Kriterleri — mülakatlarda puanlanan kriterler ve ağırlıkları (iletişim, teknik yetkinlik, problem çözme…). |
| Kaynak | `Hr_T_EvaluationCriterion` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | IsActive=true, Weight=1 |
| Veri aynası | `data/App.Hr.EvaluationCriterion.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `Weight` | Ağırlık | dxNumberBox | ✓ | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
### Mülakat Puanları — `App.Hr.InterviewScore`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/InterviewScore.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.InterviewScore` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Mülakat Puanları — mülakatta her değerlendirme kriterine verilen 1–5 puan. |
| Kaynak | `Hr_T_InterviewScore` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Komutlar | InsertAfter: SQL · UpdateAfter: SQL · DeleteAfter: SQL |
| Form varsayılanları | IsActive=true |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `InterviewId` | Mülakat | dxTextBox | | | |
| `CriterionId` | Kriter | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_EvaluationCriterion |
| `Score` | Puan | dxSelectBox | ✓ | ✓ | liste: 1, 2, 3, 4, 5 |
| `Comment` | Yorum | dxTextArea | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,319 +0,0 @@
# İK › Eğitimler (`App.Hr.Trainings`)
Şirket içi ve dışı eğitimlerin kaydı: eğitim tipi → konu tanımları, eğitim oturumu (tarih/saat,
süre, yer, eğitmen, maliyet, kazandırdığı yetkinlikler), katılımcılar (katılım durumu, puan,
katılım belgesi) ve eğitim dosyaları. Tamamı konfigürasyondur (Wizard + rapor şablonu); entity,
migration ya da React kodu yoktur.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| İK / eğitim sorumlusu | Tip ve konu tanımlar, eğitim açar, katılımcı ekler, katılım/puan girer, dosya yükler, katılım belgesi basar | Eğitim Tipleri · Eğitim Konuları · Eğitimler |
| Yönetim | Eğitim adedi, açık/kapalı durum ve toplam maliyeti izler (widget, Pivot, Chart) | Eğitimler |
Kapsam: kiracı (`IsTenant`). Eğitim bir şubeye bağlıdır (`BranchId` zorunlu); satır süzmesi yoktur.
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar artefaktlardan
> (wizard, SQL, seed, commit `36546d0c` "Human Resources -> Eğitim bilgileri") okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Wizard ekranları; eğitim kaydının altında Katılımcılar ve Dosyalar sekmeleri (menüsüz alt formlar) |
| Tanımlar | Tip ve konu tanımları İK › Tanımlar'da değil **Eğitimler** dalının içindedir; konu bir tipe bağlıdır ve tip seçimine göre süzülür |
| Süre | `Toplam Süre (saat) = gün sayısı (bitiş dahil) × günlük saat (bitiş – başlangıç saati)` — formda hesaplanır |
| Maliyet | Altı kalem (yer, eğitmen, konaklama, ulaşım, ikram, diğer) toplanarak `CostTotal` formda hesaplanır |
| Yetkinlik | Eğitimin kazandırdığı yetkinlikler `SkillIds` (çok değerli, `Hr_T_Skill`) olarak saklanır; çalışan profiline (`Adm_T_UserSkill`) **yazılmaz** |
| Katılımcı | Personel seçilince işe giriş tarihi, departman ve pozisyon kullanıcı kaydından kopyalanır (eğitim anındaki hâli saklanır) |
| Katılım durumu | Attended / NotAttended / Excused; varsayılan **Attended** |
| Katılım belgesi | Yalnızca `Attended` satırda görünen satır butonu → **Katılım Belgesi** raporu (`DynamicReport`) |
| Dosyalar | Belge türü `Hr_T_DocumentType` (ortak İK tanımı); dosya içeriği base64 (`dxImageUpload`) |
---
## 3. Menü ve ekranlar
`İnsan Kaynakları › Eğitimler` (`App.Hr.Trainings`, ikon `FcGraduationCap`)
| Sıra | Menü (TR / EN) | `ListFormCode` | Alt formlar | Görünümler | Widget'lar |
| --- | --- | --- | --- | --- | --- |
| 1 | Eğitim Tipleri / Training Types | `App.Hr.TrainingType` | — | Grid, Card, Pivot, Chart | Toplam · Aktif · Pasif |
| 2 | Eğitim Konuları / Training Subjects | `App.Hr.TrainingSubject` | — | Grid, Card, Pivot, Chart | Toplam · Aktif · Pasif |
| 3 | Eğitimler / Trainings | `App.Hr.Training` | `TrainingParticipant`, `TrainingFile` (`Id → TrainingId`, menüsüz) | Grid, Card, Pivot, Chart | Toplam · Açık · Kapalı · Toplam Maliyet |
Seed verisi: 8 eğitim tipi (Oryantasyon, İş Başı, Teknik Oryantasyon, Grup, Firma Dışı, Eğitim
İçerikli Sosyal Faaliyet, Akademi, Rotasyon) ve 18 eğitim konusu (`data/App.Hr.{TrainingType,TrainingSubject}.json`).
Satır butonu: Katılımcılar › **Katılım Belgesi** — rapor `3840a20d-3233-4d17-bc06-ccfd8d87b32a`,
görünürlük `AttendanceStatus === 'Attended'`.
---
## 4. Veri modeli
```
Hr_T_TrainingType ──< Hr_T_TrainingSubject
│ │
└──────< Hr_T_Training >──── Sas_T_Branch
├──< Hr_T_TrainingParticipant >── AbpUsers (+ Department, JobPosition)
└──< Hr_T_TrainingFile >── Hr_T_DocumentType
```
| Tablo | Önemli kolonlar | Kural |
| --- | --- | --- |
| `Hr_T_TrainingType` | `Name`, `IsActive` | — |
| `Hr_T_TrainingSubject` | `TrainingTypeId`, `Name`, `IsEvaluated` (değerlendirilsin mi), `IsActive` | — |
| `Hr_T_Training` | `TrainingTypeId`, `TrainingSubjectId` (tipe göre cascade), `EntryDate`, `BranchId`, `StartDate`, `EndDate`, `StartTime`, `EndTime`, `TotalHours`, `Status` (Open/Closed), `TrainerName`, `TrainingPlace`, `TrainingInstitution`, `Description`, `SkillIds`, `CostPlace`, `CostTrainer`, `CostAccommodation`, `CostTransport`, `CostCatering`, `CostOther`, `CostTotal` | Bitiş başlangıçtan önce olamaz (form) |
| `Hr_T_TrainingParticipant` | `TrainingId`, `UserId`, `HireDate`, `DepartmentId`, `JobPositionId`, `AttendanceStatus`, `EvaluationScore`, `ExamScore`, `LeaveDate`, `Note` | — |
| `Hr_T_TrainingFile` | `TrainingId`, `DocumentTypeId`, `FileDate`, `FileName`, `FileContent` | Dosya zorunlu |
Tablo düzeyinde CHECK ya da tekil indeks yoktur; kurallar formdadır.
---
## 5. İş kuralları ve hesaplar
Hesapların tamamı `EditorScript`'tir (sunucu tarafında after-command yoktur):
| Alan | Kural |
| --- | --- |
| `EntryDate` | Yeni kayıtta bugün |
| `Status` | Yeni kayıtta `Open` |
| `TotalHours` | Başlangıç/bitiş tarihi ya da saatlerinden biri değişince `round(days(StartDate, EndDate) × hours(StartTime, EndTime), 2)`; dört alan dolu olmalı |
| `EndDate` | Başlangıçtan önceyse uyarı + değişiklik geri alınır |
| `CostTotal` | Altı maliyet kaleminden biri değişince toplam |
| Katılımcı `UserId` | Seçilince `HireDate`, `DepartmentId`, `JobPositionId` kopyalanır |
| `AttendanceStatus` | Yeni satırda `Attended` |
| Dosya `FileDate` | Yeni satırda bugün |
| Tip/Konu `IsActive`, `IsEvaluated` | Yeni kayıtta `true` |
Saat hesabı aynı günlük saat aralığının her gün tekrarlandığını varsayar; gün sayısına bitiş günü dahildir.
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Yetki ağacı menüyü izler: `App.Hr.Trainings` ara yetkisinin
altında üç ekranın kökü + `.Create/.Update/.Delete/.Export/.Import/.Note`; `TrainingParticipant` ve
`TrainingFile` `App.Hr.Training` altındadır. Satır süzmesi yoktur. Hiçbir yetki role otomatik atanmaz.
| Rol | Yetkiler |
| --- | --- |
| Eğitim sorumlusu | `App.Hr.Trainings` dalının tamamı |
| Bölüm yöneticisi (izleme) | `App.Hr.Training` + `App.Hr.TrainingParticipant` (salt okuma) |
---
## 7. Entegrasyonlar
| Kaynak | Kullanım |
| --- | --- |
| `Sas_T_Branch` | Eğitimin şubesi |
| `AbpUsers` | Katılımcı; işe giriş tarihi, departman, pozisyon kopyası |
| Kullanıcı detayı › Eğitimler | Katılımcılar ekranı çalışana süzülmüş açılır; satır eklemek çalışanı eğitime katılımcı yapar (`HrPersonnel.md`) |
| `Hr_T_Department`, `Hr_T_JobPosition` | Katılımcının o anki birimi (Pivot kırılımı) |
| `Hr_T_Skill` / `Hr_T_SkillType` | Eğitimin kazandırdığı yetkinlikler |
| `Hr_T_DocumentType` | Eğitim dosyası türü |
| Raporlar | Katılım Belgesi (`DynamicReport`, `Raporlar` kategorisi) |
| Performans Yönetimi | Gelişim planı aksiyonu bir eğitime bağlanabilir (`Hr_T_DevelopmentPlan.TrainingId`); çalışan o eğitimde **Attended** ise aksiyon kendiliğinden tamamlanır: plan kaydında ve katılımcı kaydında (`Hr_S_DevelopmentPlanSync`, `Hr_S_TrainingSync`; bkz. `HrPerformance.md`) |
| Puantaj ve Devam Takibi | **Katıldı** işaretli katılımcının başlangıç–bitiş arasındaki iş günleri puantajda `Eğitimde` (vardiya saatleriyle) yazılır, okutması olmadığı için devamsız sayılmaz. Eğitim ya da katılımcı eklenince, değişince, silinince `Hr_S_TrainingSync` günleri hemen yeniden hesaplar (`HrAttendance.md` §7.1) |
---
## 8. Akış örneği
1. İK **Eğitimler**'de "İş Sağlığı ve Güvenliği" kaydını açar: tip Grup Eğitimi, konu İSG, şube
Merkez, 3–4 Kasım 09:00–17:00 → toplam süre 16 saat; eğitmen ve yer maliyetini girer → toplam
maliyet hesaplanır; kazandırdığı yetkinliği (İSG) seçer.
2. **Katılımcılar** sekmesine personeli ekler; departman/pozisyon kopyalanır, durum Katıldı.
3. Gelmeyen personeli **Katılmadı**, raporlu olanı **Mazeretli** yapar; sınav/değerlendirme puanlarını girer.
4. Katılanların satırından **Katılım Belgesi** basılır; imzalı liste **Dosyalar** sekmesine yüklenir.
5. Eğitim **Kapalı** yapılır; widget'taki toplam maliyete eklenir.
---
## 9. Sık sorulanlar
**Toplam süre hesaplanmıyor.** Başlangıç/bitiş tarihi ve başlangıç/bitiş saatinin dördü de dolu olmalı.
**Katılım belgesi butonu görünmüyor.** Yalnızca katılım durumu "Katıldı" olan satırda çıkar.
**Eğitim konusu listesi boş.** Konu, seçilen eğitim tipine göre süzülür; önce tip seçin.
**Eğitimdeki yetkinlik çalışan profiline yazılıyor mu?** Hayır; `SkillIds` eğitimin bilgisidir. Profil, Performans Yönetimi'nde tamamlanan değerlendirmeden güncellenir.
**Katılımcının departmanı sonradan değişti, eğitimde eski görünüyor.** Bilinçli: eğitim anındaki birim saklanır.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_{TrainingType,TrainingSubject,Training,TrainingParticipant,TrainingFile}.sql` | Tablolar |
| `configs/seeds/wizard/{TrainingType,TrainingSubject,Training,TrainingParticipant,TrainingFile}.json` | 5 wizard |
| `configs/seeds/data/App.Hr.{TrainingType,TrainingSubject}.json` | Tanım verisi |
| `configs/seeds/data/App.Reports.ReportTemplates.json` | Katılım Belgesi şablonu |
| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları |
Kurulum: DB Migrate. Geri alma: Wizard Manager'dan alt formlar (`TrainingParticipant`, `TrainingFile`)
önce, sonra `Training`, `TrainingSubject`, `TrainingType` silinir; tablolar yalnızca onayda seçilirse
düşer. Gelişim planlarındaki `TrainingId` bağları silinmez, boşa düşer.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Eğitim Tipleri — `App.Hr.TrainingType`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/TrainingType.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Trainings` › `App.Hr.TrainingType` (sıra 1) |
| Yetki | `App.Hr.TrainingType` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Eğitim Tipleri — eğitimlerin türü (oryantasyon, iş başı, grup, firma dışı, akademi, rotasyon…). |
| Geliştirme talebi | Human Resources -> Eğitim |
| Kaynak | `Hr_T_TrainingType` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Hr.TrainingType.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | script |
### Eğitim Konuları — `App.Hr.TrainingSubject`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/TrainingSubject.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Trainings` › `App.Hr.TrainingSubject` (sıra 2) |
| Yetki | `App.Hr.TrainingSubject` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Eğitim Konuları — bir eğitim tipine bağlı konu başlıkları (İSG, siber güvenlik, iş başı eğitimi…). |
| Geliştirme talebi | Human Resources -> Eğitim |
| Kaynak | `Hr_T_TrainingSubject` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Hr.TrainingSubject.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `TrainingTypeId` | Eğitim Tipi | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_TrainingType |
| `Name` | Eğitim Konusu | dxTextBox | ✓ | ✓ | |
| `IsEvaluated` | Değerlendirilsin mi? | dxCheckBox | ✓ | ✓ | script |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | script |
### Eğitimler — `App.Hr.Training`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Training.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Trainings` › `App.Hr.Training` (sıra 3) |
| Yetki | `App.Hr.Training` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Eğitimler — düzenlenen her eğitim oturumu: tarih/saat, süre, yer, eğitmen, maliyet, kazandırdığı yetkinlikler, katılımcılar ve dosyalar. |
| Geliştirme talebi | Human Resources -> Eğitim |
| Kaynak | `Hr_T_Training` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.TrainingParticipant` (Id → TrainingId), `App.Hr.TrainingFile` (Id → TrainingId) |
| Widget'lar | `App.Widget.Total`, `App.Hr.Training.Widget.Open`, `App.Hr.Training.Widget.Closed`, `App.Hr.Training.Widget.Cost` |
| Komutlar | UpdateAfter: `Hr_S_TrainingSync` · DeleteAfter: `Hr_S_TrainingSync` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `TrainingTypeId` | Eğitim Tipi | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_TrainingType |
| `TrainingSubjectId` | Eğitim Konusu | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_TrainingSubject (← TrainingTypeId) |
| `EntryDate` | Giriş Tarihi | dxDateBox | ✓ | ✓ | script |
| `BranchId` | Şube Adı | dxSelectBox | ✓ | ✓ | sorgu: Sas_T_Branch |
| `StartDate` | Eğitim Baş. Tarihi | dxDateBox | ✓ | ✓ | script |
| `EndDate` | Bit. Tarihi | dxDateBox | ✓ | ✓ | script |
| `StartTime` | Eğitim Baş. Saati | dxDateBox | | ✓ | script |
| `EndTime` | Bit. Saati | dxDateBox | | ✓ | script |
| `TotalHours` | Top. Eğitim Süresi (Saat) | dxNumberBox | | ✓ | |
| `Status` | Durum | dxRadioGroup | ✓ | ✓ | liste: Open, Closed · script |
| `TrainerName` | Eğitim Veren Kişi | dxTextBox | | ✓ | |
| `TrainingPlace` | Eğitim Yeri | dxTextBox | | ✓ | |
| `TrainingInstitution` | Eğitim Veren Kurum | dxTextBox | | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
| `SkillIds` | Yetkinlikler | dxTagBox | | ✓ | sorgu: Hr_T_Skill |
| `CostPlace` | Eğitim Yeri Maliyeti | dxNumberBox | | ✓ | script |
| `CostTrainer` | Eğitmen Maliyeti | dxNumberBox | | ✓ | script |
| `CostAccommodation` | Konaklama | dxNumberBox | | ✓ | script |
| `CostTransport` | Ulaşım | dxNumberBox | | ✓ | script |
| `CostCatering` | İkram | dxNumberBox | | ✓ | script |
| `CostOther` | Diğer | dxNumberBox | | ✓ | script |
| `CostTotal` | Toplam Maliyet | dxNumberBox | | ✓ | |
### Katılımcılar — `App.Hr.TrainingParticipant`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/TrainingParticipant.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.TrainingParticipant` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Katılımcılar — eğitime katılan personel ve katılım sonucu. |
| Geliştirme talebi | Human Resources -> Eğitim |
| Kaynak | `Hr_T_TrainingParticipant` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Komutlar | InsertAfter: `Hr_S_TrainingSync` · UpdateAfter: `Hr_S_TrainingSync` · DeleteAfter: `Hr_S_TrainingSync` |
| Butonlar | `App.Hr.TrainingParticipant.Certificate` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `TrainingId` | Eğitim | dxSelectBox | | | sorgu: Hr_T_Training |
| `UserId` | Adı Soyadı | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers · script |
| `HireDate` | İşe Giriş Tarihi | dxDateBox | | ✓ | |
| `DepartmentId` | Bölüm | dxSelectBox | | ✓ | sorgu: Hr_T_Department |
| `JobPositionId` | Görevi | dxSelectBox | | ✓ | sorgu: Hr_T_JobPosition |
| `AttendanceStatus` | Katılım Durumu | dxSelectBox | ✓ | ✓ | liste: Attended, NotAttended, Excused · script |
| `EvaluationScore` | Değerlendirme Puanı | dxNumberBox | | ✓ | |
| `ExamScore` | Sınav Notu | dxNumberBox | | ✓ | |
| `LeaveDate` | Ayrılma Tarihi | dxDateBox | | ✓ | |
| `Note` | Not | dxTextArea | | ✓ | |
### Dosyalar — `App.Hr.TrainingFile`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/TrainingFile.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.TrainingFile` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Eğitim Dosyaları — eğitime ait doküman ve dijital kaynaklar (sunum, imzalı katılım listesi, sertifika örneği…). |
| Geliştirme talebi | Human Resources -> Eğitim |
| Kaynak | `Hr_T_TrainingFile` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `TrainingId` | Eğitim | dxTextBox | | | |
| `DocumentTypeId` | Dosya Türü | dxSelectBox | | ✓ | sorgu: Hr_T_DocumentType |
| `FileDate` | Tarih | dxDateBox | ✓ | ✓ | script |
| `FileName` | Dosya Adı | dxTextBox | ✓ | ✓ | |
| `FileContent` | Dosya | dxImageUpload | ✓ | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,545 +0,0 @@
# İK › Araçlar (`App.Hr.Vehicles`)
Şirket araç filosu yönetimi: araç ve şoför kayıtları, araç/şoför belgeleri (ruhsat, sigorta,
muayene, ehliyet, psikoteknik), trafik cezaları ve kazalar, fatura bazlı araç masrafları ve
güvenlik kulübesinden araç giriş–çıkış takibi (km, süre, gidilen yer). Tamamı konfigürasyondur
(Wizard + SQL); entity, migration ya da React kodu yoktur.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| Filo / idari işler sorumlusu | Araç, şoför, belge ve masraf tanımlarını yönetir; araç belgelerini ve masraf faturalarını girer | Araç Belgeleri · Araçlar · Gidilecek Yerler · Şoför Tanımlama · Masraf Tanımlama · Masraf Girişleri |
| Güvenlik personeli | Aracın çıkışını ve dönüşünü (km, saat, kullanan personel) kaydeder | Araç Giriş-Çıkış Takip |
| Yönetim | Dışarıdaki araç sayısı, toplam km ve masrafı izler | Widget'lar, Pivot, Chart |
Kapsam: kiracı (`IsTenant`). Araç bir şubeye (tesis) bağlıdır; satır süzmesi yoktur.
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar artefaktlardan (wizard,
> SQL, seed, commit'ler `7be0b4ed` "Human Resources -> Araçlar" ve `021a8a34` "Araçlar düzenlemesi")
> okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Wizard ekranları; aracın altında Belgeler, şoförün altında Belgeler / Cezalar / Kazalar, masraf girişinin altında Kalemler (menüsüz alt formlar) |
| Belge tanımı | Tek tanım tablosu (`Hr_T_VehicleDocument`) iki eksenle ayrılır: **grup** (Vehicle / Driver) ve **tip** (Insurance, Document, Tax, Toll, Inspection, Maintenance, Penalty, Accident). Her alt form kendi kümesini süzer |
| Araç durumu | `Arrived` (Geldi — şirkette) / `Left` (Gitti — dışarıda); giriş–çıkış kaydından güncellenir |
| Araç km'si | `CurrentKm` giriş–çıkış kaydının giriş km'sinden güncellenir; yeni çıkışta önerilen çıkış km'si budur |
| Zimmet | Şoföre en fazla **5 araç** zimmetlenir (`Vehicle1Id`…`Vehicle5Id` sabit kolonlar) |
| Şoför km'si | Şoförün `TotalKm`'si, kullandığı tüm hareketlerin yapılan km toplamıdır (SQL) |
| Masraf | Fatura başlığı + kalemler; `Net Tutar = Miktar × Tutar × (1 − İskonto% / 100)` formda hesaplanır |
| Muayene/sigorta tarihleri | Araç kaydında başlangıç–bitiş tarihleri olarak tutulur; **hatırlatma/bildirim yoktur** |
| Hareket numarası | `MovementNo` kiracı bazında `MAX + 1` (insert sonrası SQL) |
---
## 3. Menü ve ekranlar
`İnsan Kaynakları › Araçlar` (`App.Hr.Vehicles`, ikon `FcAutomotive`)
| Sıra | Menü (TR / EN) | `ListFormCode` | Alt formlar | Widget'lar |
| --- | --- | --- | --- | --- |
| 1 | Araç Belgeleri / Vehicle Documents | `App.Hr.VehicleDocument` | — | Toplam · Aktif · Pasif |
| 2 | Masraf Tanımlama / Expense Definitions | `App.Hr.VehicleExpenseDefinition` | — | Toplam · Aktif · Pasif |
| 3 | Gidilecek Yerler / Destinations | `App.Hr.VehicleDestination` | — | Toplam · Aktif · Pasif |
| 4 | Araçlar / Vehicles | `App.Hr.Vehicle` | `VehicleFile` (`Id → VehicleId`) | Toplam · Geldi · Gitti · Pasif |
| 5 | Şoför Tanımlama / Drivers | `App.Hr.Driver` | `DriverDocument`, `DriverPenalty`, `DriverAccident` (`Id → DriverId`) | Toplam · Zimmetli Araçlı · Toplam Km |
| 6 | Araç Giriş-Çıkış Takip / Vehicle Gate Log | `App.Hr.VehicleMovement` | — | Toplam · Gitti · Bugün |
| 7 | Masraf Girişleri / Vehicle Expenses | `App.Hr.VehicleExpense` | `VehicleExpenseLine` (`Id → VehicleExpenseId`) | Toplam · Bugün · Toplam Tutar (net kalemler) |
Tüm ekranlar Grid, Card, Pivot, Chart görünümleriyle gelir; özel görünüm (Todo, Scheduler) yoktur.
Seed verisi:
- 17 belge tanımı (`data/App.Hr.VehicleDocument.json`). Örnekler: trafik sigortası, ruhsat, K2, MTV, OGS/HGS, egzoz/genel muayene, takograf (araç); sürücü belgesi, psikoteknik, SRC 4, kırmızı ışık ve hız cezası (şoför).
- 9 masraf tanımı (`data/App.Hr.VehicleExpenseDefinition.json`): sigorta, vergi, bakım, muayene ×2, geçiş ×2, ceza, yakıt.
---
## 4. Veri modeli
```
Sas_T_Branch ──< Hr_T_Vehicle ──< Hr_T_VehicleFile >── Hr_T_VehicleDocument (grup Vehicle)
│ ▲
│ └── Hr_T_Driver.Vehicle1..5Id ── AbpUsers (UserId)
│ ├──< Hr_T_DriverDocument >── VehicleDocument (grup Driver, ceza/kaza hariç)
│ ├──< Hr_T_DriverPenalty >── VehicleDocument (tip Penalty)
│ └──< Hr_T_DriverAccident >── VehicleDocument (tip Accident)
├──< Hr_T_VehicleExpense ──< Hr_T_VehicleExpenseLine >── Hr_T_VehicleExpenseDefinition
│ └── Hr_T_VehicleDestination (firma)
└──< Hr_T_VehicleMovement >── AbpUsers (gönderen, güvenlik, kullanan), Hr_T_VehicleDestination
```
| Tablo | Önemli kolonlar |
| --- | --- |
| `Hr_T_VehicleDocument` | `DocumentGroup` (Vehicle/Driver), `DocumentType` (8 tip), `Name`, `IsActive` |
| `Hr_T_Vehicle` | `BranchId` (tesis), `Name`, `Brand`, `Model`, `BodyType`, `UsagePlace`, `RegistrationDate`, `PlateNumber`, `Status` (Arrived/Left), `CurrentKm`, muayene / sigorta / egzoz / takograf başlangıç–bitiş tarihleri, `IsActive` |
| `Hr_T_VehicleFile` | `VehicleId`, `VehicleDocumentId`, `FileName`, `FileContent` |
| `Hr_T_VehicleDestination` | `Name`, `Distance` (km), `IsActive` |
| `Hr_T_Driver` | `UserId`, `Vehicle1Id`…`Vehicle5Id`, `TotalKm` |
| `Hr_T_DriverDocument` | `DriverId`, `VehicleDocumentId`, `FileName`, `FileContent` |
| `Hr_T_DriverPenalty` | `DriverId`, `PenaltyDate`, `Amount`, `Description`, `VehicleDocumentId`, dosya |
| `Hr_T_DriverAccident` | `DriverId`, `AccidentDate`, `VehicleDocumentId`, `Description`, dosya |
| `Hr_T_VehicleExpenseDefinition` | `ExpenseType`, `Name`, `IsActive` |
| `Hr_T_VehicleExpense` | `VehicleId`, `InvoiceNo`, `InvoiceDate`, `DestinationId` (firma), `VehicleDocumentId`, dosya |
| `Hr_T_VehicleExpenseLine` | `VehicleExpenseId`, `ExpenseDefinitionId`, `DetailName`, `Quantity`, `Amount`, `DiscountRate`, `NetAmount`, `Description` |
| `Hr_T_VehicleMovement` | `MovementNo`, `ExitDate`, `ExitTime` (SS:dd metin), `BranchId`, `ExitKm`, `SenderUserId`, `SecurityUserId`, `DriverUserId`, `VehicleId`, `DestinationId`, `Reason`, `Location`, `EntrySecurityUserId`, `EntryDate`, `EntryBranchId`, `EntryTime`, `EntryKm`, `TravelledKm`, `Status`, `DurationMinutes`, `ExpectedKm` |
Tablo düzeyinde CHECK ya da tekil indeks yoktur (plaka tekilliği zorlanmaz); kurallar form ve SQL'dedir.
---
## 5. İş kuralları ve hesaplar
### 5.1 Giriş–çıkış (`App.Hr.VehicleMovement`)
Form script'leri:
| Olay | Kural |
| --- | --- |
| Form açılışı | Çıkış tarihi bugün, çıkış saati şimdi, durum `Left` |
| Araç seçimi | `ExpectedKm` = aracın `CurrentKm`'si; çıkış km boşsa aynı değer yazılır |
| Çıkış km | `ExpectedKm`'den küçükse uyarı + `ExpectedKm` yazılır; giriş km varsa yapılan km yeniden hesaplanır |
| Giriş km | Çıkış km'den küçükse uyarı + geri alınır. Doluysa durum `Arrived`, `TravelledKm = EntryKm − ExitKm`, giriş tarihi/saati boşsa şimdi. Boşsa durum `Left`, yapılan km boş |
| Tarih/saat değişimi | `DurationMinutes` = giriş − çıkış (dakika; saat `SS:dd` biçiminde olmalı) |
Sunucu komutları:
| Komut | Etki |
| --- | --- |
| Insert sonrası | `MovementNo` atanır; aracın `Status`'u ve (giriş km varsa) `CurrentKm`'si hareketten yazılır; kullanan personelin şoför kaydındaki `TotalKm` yeniden toplanır |
| Delete sonrası | Şoför `TotalKm` yeniden toplanır; aracın `CurrentKm`'si son girişin km'sine, `Status`'u son hareketin durumuna döner (hareket yoksa `Arrived`) |
| Update sonrası | **Yok** — bkz. §9 |
### 5.2 Masraf
`NetAmount = round(Quantity × Amount × (1 − DiscountRate / 100), 2)`; miktar, tutar ya da iskonto
değişince formda hesaplanır. Fatura tarihi yeni kayıtta bugündür. "Toplam Tutar" widget'ı
silinmemiş tüm kalemlerin toplamıdır.
### 5.3 Belge süzmesi
| Ekran | Listelenen belge tanımları |
| --- | --- |
| Araç › Belgeler, Masraf Girişi | `DocumentGroup = 'Vehicle'` |
| Şoför › Belgeler | `DocumentGroup = 'Driver'` ve tip `Penalty`/`Accident` dışı |
| Şoför › Cezalar | `DocumentType = 'Penalty'` |
| Şoför › Kazalar | `DocumentType = 'Accident'` |
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Yetki ağacı menüyü izler: `App.Hr.Vehicles` ara yetkisinin
altında yedi ekranın kökü + `.Create/.Update/.Delete/.Export/.Import/.Note`; alt formlar (`VehicleFile`,
`DriverDocument`, `DriverPenalty`, `DriverAccident`, `VehicleExpenseLine`) ana ekranlarının altındadır.
Satır süzmesi yoktur. Hiçbir yetki role otomatik atanmaz.
| Rol | Yetkiler |
| --- | --- |
| Filo sorumlusu | `App.Hr.Vehicles` dalının tamamı |
| Güvenlik | `App.Hr.VehicleMovement` (+ `.Create/.Update`) |
| Muhasebe (masraf) | `App.Hr.VehicleExpense`, `App.Hr.VehicleExpenseLine` |
---
## 7. Entegrasyonlar
| Kaynak | Kullanım |
| --- | --- |
| `Sas_T_Branch` | Aracın tesisi; hareketin çıkış/giriş şubesi |
| `AbpUsers` | Şoför, gönderen personel, güvenlik personeli, kullanan personel |
Bildirim, zamanlanmış iş ya da rapor şablonu yoktur.
---
## 8. Akış örneği
1. Filo sorumlusu **Araçlar**'a "34 ABC 123"ü girer (şube Merkez, km 45.000, muayene/sigorta bitiş
tarihleri); **Belgeler** sekmesine ruhsat ve trafik sigortasını yükler.
2. **Şoför Tanımlama**'da personeli şoför yapar, aracı zimmet 1'e bağlar; sürücü belgesini yükler.
3. Güvenlik **Araç Giriş-Çıkış Takip**'te yeni kayıt açar: tarih/saat dolar, araç seçilince çıkış
km 45.000 olur; kullanan personel ve gidilen yer seçilir → araç **Dışarıda**.
4. Dönüşte giriş km'si (45.180) girilir → durum Şirkette, yapılan km 180, süre dakika olarak hesaplanır.
5. Yakıt faturası **Masraf Girişleri**'ne başlık + kalem (yakıt, 40 lt × 45 ₺) olarak girilir.
6. Gelen hız cezası şoförün **Cezalar** sekmesine tutar ve belgeyle eklenir.
---
## 9. Sık sorulanlar ve bilinen sınırlar
**Plakası aynı iki araç girilebiliyor mu?** Evet; tabloda tekil kısıt yoktur.
**Muayene/sigorta bitişi yaklaşınca uyarı geliyor mu?** Hayır; tarihler yalnızca kayıttadır.
Hatırlatma için zamanlanmış iş + bildirim tipi eklenmelidir (yok).
**Bir şoföre 5'ten fazla araç zimmetlenebilir mi?** Hayır; kolonlar sabittir.
**Masraf girişindeki "Firma Adı" neden Gidilecek Yerler'den geliyor?** Masraf firması ayrı bir
tanım olarak açılmamış, gidilecek yer tanımı kullanılmıştır.
**Kontrol edilmeli — dönüş güncellemeyle girildiğinde araç/şoför güncellenmiyor.** Giriş–çıkış
ekranında yalnızca insert ve delete sonrası komut vardır. Çıkış kaydı açılıp dönüş **aynı kaydı
düzenleyerek** girildiğinde aracın `Status`/`CurrentKm`'si ve şoförün `TotalKm`'si güncellenmez
(bir sonraki ekleme ya da silmeye kadar).
**Kontrol edilmeli — yalnız çıkış girilen kayıtta aracın km'si.** Formda `EntryKm` varsayılanı
`0`'dır. Insert sonrası komut `@EntryKm` boş değilse aracın `CurrentKm`'sini ona eşitler. Giriş km
temizlenmeden kaydedilen bir çıkış kaydı aracın km'sini 0'a çekebilir.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_{VehicleDocument,Vehicle,VehicleFile,VehicleDestination,Driver,DriverDocument,DriverPenalty,DriverAccident,VehicleExpenseDefinition,VehicleExpense,VehicleExpenseLine,VehicleMovement}.sql` | Tablolar |
| `configs/seeds/wizard/{VehicleDocument,Vehicle,VehicleFile,VehicleDestination,Driver,DriverDocument,DriverPenalty,DriverAccident,VehicleExpenseDefinition,VehicleExpense,VehicleExpenseLine,VehicleMovement}.json` | 12 wizard |
| `configs/seeds/data/App.Hr.{VehicleDocument,VehicleExpenseDefinition}.json` | Tanım verisi |
| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları |
Kurulum: DB Migrate. Geri alma: Wizard Manager'dan alt formlar önce, ana ekranlar sonra silinir;
tablolar yalnızca onayda seçilirse düşer.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Araç Belgeleri — `App.Hr.VehicleDocument`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/VehicleDocument.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Vehicles` › `App.Hr.VehicleDocument` (sıra 1) |
| Yetki | `App.Hr.VehicleDocument` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Araç Belgeleri — araç ve şoför kayıtlarında kullanılan belge tanımları (ruhsat, sigorta, muayene, MTV, OGS/HGS, ehliyet, psikoteknik, ceza ve kaza belgeleri). |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_VehicleDocument` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | IsActive=true |
| Veri aynası | `data/App.Hr.VehicleDocument.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `DocumentGroup` | Belge Grubu | dxSelectBox | ✓ | ✓ | liste: Vehicle, Driver |
| `DocumentType` | Belge Tipi | dxSelectBox | ✓ | ✓ | liste: Insurance, Document, Tax, Toll, Inspection, Maintenance, Penalty, Accident |
| `Name` | Belge Adı | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Masraf Tanımlama — `App.Hr.VehicleExpenseDefinition`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/VehicleExpenseDefinition.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Vehicles` › `App.Hr.VehicleExpenseDefinition` (sıra 2) |
| Yetki | `App.Hr.VehicleExpenseDefinition` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Masraf Tanımlama — araç masraf kalemleri (sigorta, vergi, bakım, muayene, geçiş, ceza, yakıt…). |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_VehicleExpenseDefinition` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | IsActive=true |
| Veri aynası | `data/App.Hr.VehicleExpenseDefinition.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `ExpenseType` | Masraf Tipi | dxTextBox | ✓ | ✓ | |
| `Name` | Masraf Adı | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Gidilecek Yerler — `App.Hr.VehicleDestination`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/VehicleDestination.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Vehicles` › `App.Hr.VehicleDestination` (sıra 3) |
| Yetki | `App.Hr.VehicleDestination` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Gidilecek Yerler — araçlarla gidilen tanımlı yerler ve uzaklıkları. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_VehicleDestination` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | IsActive=true |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Gidilecek Yerin Adı | dxTextBox | ✓ | ✓ | |
| `Distance` | Uzaklığı (km) | dxNumberBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Araçlar — `App.Hr.Vehicle`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Vehicle.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Vehicles` › `App.Hr.Vehicle` (sıra 4) |
| Yetki | `App.Hr.Vehicle` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Araçlar — şubeye ait şirket araçları, anlık durumu (şirkette/dışarıda), güncel km'si ve muayene/sigorta tarihleri. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_Vehicle` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.VehicleFile` (Id → VehicleId) |
| Widget'lar | `App.Widget.Total`, `App.Hr.Vehicle.Widget.Arrived`, `App.Hr.Vehicle.Widget.Left`, `App.Widget.Passive` |
| Form varsayılanları | IsActive=true |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `BranchId` | Tesis Adı | dxSelectBox | ✓ | ✓ | sorgu: Sas_T_Branch |
| `Name` | Araç Adı | dxTextBox | ✓ | ✓ | |
| `Brand` | Markası | dxTextBox | | ✓ | |
| `Model` | Modeli | dxTextBox | | ✓ | |
| `BodyType` | Cinsi | dxTextBox | | ✓ | |
| `UsagePlace` | Kullanıldığı Yer | dxTextBox | | ✓ | |
| `RegistrationDate` | Tescil Tarihi | dxDateBox | | ✓ | |
| `PlateNumber` | Plakası | dxTextBox | ✓ | ✓ | |
| `Status` | Durumu | dxSelectBox | | ✓ | liste: Arrived, Left |
| `CurrentKm` | Araç Km'si | dxNumberBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
| `InspectionStart` | Muayene Başlangıç | dxDateBox | | ✓ | |
| `InspectionEnd` | Muayene Bitiş | dxDateBox | | ✓ | |
| `InsuranceStart` | Sigorta Başlangıç | dxDateBox | | ✓ | |
| `InsuranceEnd` | Sigorta Bitiş | dxDateBox | | ✓ | |
| `EmissionStart` | Egzoz Muayene Başlangıç | dxDateBox | | ✓ | |
| `EmissionEnd` | Egzoz Muayene Bitiş | dxDateBox | | ✓ | |
| `TachographStart` | Takograf Kalib. Başlangıç | dxDateBox | | ✓ | |
| `TachographEnd` | Takograf Kalib. Bitiş | dxDateBox | | ✓ | |
### Şoför Tanımlama — `App.Hr.Driver`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Driver.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Vehicles` › `App.Hr.Driver` (sıra 5) |
| Yetki | `App.Hr.Driver` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Şoför Tanımlama — şirket araçlarını kullanan personel, zimmetli araçları ve sürüş geçmişi. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_Driver` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.DriverDocument` (Id → DriverId), `App.Hr.DriverPenalty` (Id → DriverId), `App.Hr.DriverAccident` (Id → DriverId) |
| Widget'lar | `App.Widget.Total`, `App.Hr.Driver.Widget.WithVehicle`, `App.Hr.Driver.Widget.TotalKm` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `UserId` | Sicil No / Adı Soyadı | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers |
| `Vehicle1Id` | Zimmetli Araç 1 | dxSelectBox | | ✓ | sorgu: Hr_T_Vehicle |
| `Vehicle2Id` | Zimmetli Araç 2 | dxSelectBox | | ✓ | sorgu: Hr_T_Vehicle |
| `Vehicle3Id` | Zimmetli Araç 3 | dxSelectBox | | ✓ | sorgu: Hr_T_Vehicle |
| `Vehicle4Id` | Zimmetli Araç 4 | dxSelectBox | | ✓ | sorgu: Hr_T_Vehicle |
| `Vehicle5Id` | Zimmetli Araç 5 | dxSelectBox | | ✓ | sorgu: Hr_T_Vehicle |
| `TotalKm` | Yapılan KM | dxNumberBox | | ✓ | |
### Araç Giriş-Çıkış Takip — `App.Hr.VehicleMovement`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/VehicleMovement.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Vehicles` › `App.Hr.VehicleMovement` (sıra 6) |
| Yetki | `App.Hr.VehicleMovement` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Araç Giriş-Çıkış Takip — güvenliğin tuttuğu araç hareket defteri: araç şirketten ne zaman, kimle, hangi km ile çıktı, ne zaman döndü. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_VehicleMovement` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Hr.Vehicle.Widget.Left`, `App.Widget.Today` |
| Komutlar | InsertAfter: SQL · DeleteAfter: SQL |
| Form varsayılanları | EntryKm=0 |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `MovementNo` | Hareket No | dxNumberBox | | | |
| `ExitDate` | Çıkış Tarihi | dxDateBox | ✓ | ✓ | script |
| `BranchId` | Çıkış Şube | dxSelectBox | | ✓ | sorgu: Sas_T_Branch |
| `ExitTime` | Çıkış Saati | dxTextBox | ✓ | ✓ | script |
| `ExitKm` | Çıkış Km | dxNumberBox | ✓ | ✓ | script |
| `SenderUserId` | Gönderen Personel | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `SecurityUserId` | Güvenlik Personeli | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `DriverUserId` | Kullanan Personel | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers |
| `VehicleId` | Araç Adı / Plaka | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_Vehicle · script |
| `DestinationId` | Gidilen Yer | dxSelectBox | | ✓ | sorgu: Hr_T_VehicleDestination |
| `Reason` | Gidiş Sebebi | dxTextBox | | ✓ | |
| `Location` | Konum | dxTextBox | | ✓ | |
| `EntrySecurityUserId` | Giriş Yapan Güvenlik Per. | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `EntryDate` | Giriş Tarihi | dxDateBox | | ✓ | script |
| `EntryBranchId` | Giriş Şube | dxSelectBox | | ✓ | sorgu: Sas_T_Branch |
| `EntryTime` | Giriş Saati | dxTextBox | | ✓ | script |
| `EntryKm` | Giriş Km | dxNumberBox | | ✓ | script |
| `TravelledKm` | Yapılan Km | dxNumberBox | | ✓ | |
| `Status` | Durum | dxSelectBox | | ✓ | liste: Arrived, Left |
| `DurationMinutes` | Gezinti Süresi / Dk | dxNumberBox | | ✓ | |
| `ExpectedKm` | Olması Gereken Km | dxNumberBox | | ✓ | |
### Masraf Girişleri — `App.Hr.VehicleExpense`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/VehicleExpense.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Vehicles` › `App.Hr.VehicleExpense` (sıra 7) |
| Yetki | `App.Hr.VehicleExpense` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Masraf Girişleri — araç için gelen her fatura/fiş: başlık bilgileri ve kalemleri. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_VehicleExpense` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Hr.VehicleExpenseLine` (Id → VehicleExpenseId) |
| Widget'lar | `App.Widget.Total`, `App.Widget.Today`, `App.Widget.TotalRevenue` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `VehicleId` | Araç Adı | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_Vehicle |
| `InvoiceNo` | Fatura No | dxTextBox | ✓ | ✓ | |
| `InvoiceDate` | Fatura Tarihi | dxDateBox | ✓ | ✓ | script |
| `DestinationId` | Firma Adı | dxSelectBox | | ✓ | sorgu: Hr_T_VehicleDestination |
| `VehicleDocumentId` | Belge Adı | dxSelectBox | | ✓ | sorgu: Hr_T_VehicleDocument |
| `FileName` | Dosya Adı | dxTextBox | | ✓ | |
| `FileContent` | Dosya | dxImageUpload | | ✓ | |
### Araç Belgeleri — `App.Hr.VehicleFile`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/VehicleFile.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.VehicleFile` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Araç Dosyaları — araca yüklenen belgeler (ruhsat, poliçe, muayene raporu…). |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_VehicleFile` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `VehicleId` | Araç | dxSelectBox | | | sorgu: Hr_T_Vehicle |
| `VehicleDocumentId` | Belge Adı | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_VehicleDocument |
| `FileName` | Dosya Adı | dxTextBox | ✓ | ✓ | |
| `FileContent` | Dosya | dxImageUpload | ✓ | ✓ | |
### Şoför Belgeleri — `App.Hr.DriverDocument`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DriverDocument.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.DriverDocument` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Şoför Belgeleri — şoföre ait belgeler (sürücü belgesi, psikoteknik, SRC…). |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_DriverDocument` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `DriverId` | Şoför | dxSelectBox | | | sorgu: Hr_T_Driver |
| `VehicleDocumentId` | Belge Adı | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_VehicleDocument |
| `FileName` | Dosya Adı | dxTextBox | ✓ | ✓ | |
| `FileContent` | Dosya | dxImageUpload | ✓ | ✓ | |
### Ceza Belgeleri — `App.Hr.DriverPenalty`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DriverPenalty.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.DriverPenalty` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Şoför Cezaları — şoföre kesilen trafik cezaları. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_DriverPenalty` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `DriverId` | Şoför | dxSelectBox | | | sorgu: Hr_T_Driver |
| `PenaltyDate` | Ceza Tarihi | dxDateBox | ✓ | ✓ | |
| `Amount` | Tutarı | dxNumberBox | | ✓ | |
| `Description` | Ceza Açıklama | dxTextBox | | ✓ | |
| `VehicleDocumentId` | Belge Adı | dxSelectBox | | ✓ | sorgu: Hr_T_VehicleDocument |
| `FileName` | Dosya Adı | dxTextBox | | ✓ | |
| `FileContent` | Dosya | dxImageUpload | | ✓ | |
### Kaza Belgeleri — `App.Hr.DriverAccident`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DriverAccident.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.DriverAccident` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Şoför Kazaları — şoförün karıştığı trafik kazaları ve belgeleri. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_DriverAccident` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `DriverId` | Şoför | dxSelectBox | | | sorgu: Hr_T_Driver |
| `AccidentDate` | Kaza Tarihi | dxDateBox | ✓ | ✓ | |
| `VehicleDocumentId` | Belge Adı | dxSelectBox | | ✓ | sorgu: Hr_T_VehicleDocument |
| `Description` | Kaza Açıklama | dxTextBox | | ✓ | |
| `FileName` | Dosya Adı | dxTextBox | | ✓ | |
| `FileContent` | Dosya | dxImageUpload | | ✓ | |
### Masraf Kalemleri — `App.Hr.VehicleExpenseLine`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/VehicleExpenseLine.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Hr.VehicleExpenseLine` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Masraf Kalemleri — bir masraf faturasının satırları. |
| Geliştirme talebi | Human Resources -> Araçlar |
| Kaynak | `Hr_T_VehicleExpenseLine` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `VehicleExpenseId` | Masraf Girişi | dxTextBox | | | |
| `ExpenseDefinitionId` | Masraf Adı | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_VehicleExpenseDefinition |
| `DetailName` | Masraf Detay Adı | dxTextBox | | ✓ | |
| `Quantity` | Miktar | dxNumberBox | ✓ | ✓ | script |
| `Amount` | Tutarı | dxNumberBox | ✓ | ✓ | script |
| `DiscountRate` | İskonto Oranı (%) | dxNumberBox | ✓ | ✓ | script |
| `NetAmount` | Net Tutar | dxNumberBox | ✓ | ✓ | |
| `Description` | Masraf Açıklama | dxTextBox | | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -1,150 +0,0 @@
# İK › Ziyaretçiler (`App.Hr.Visitor`)
Güvenlik/resepsiyon için ziyaretçi giriş–çıkış defteri: kim, hangi firmadan, kimi ziyarete, ne
zaman geldi, hangi kartı aldı, ne zaman çıktı. Tamamı konfigürasyondur (tek Wizard ekranı).
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| Güvenlik / resepsiyon | Ziyaretçiyi girişte kaydeder, kart verir, çıkışta çıkış saatini işler | Ziyaretçiler |
| İK / idari işler | Günlük ziyaretçi sayısını, içeride kalanları ve geçmişi izler | Ziyaretçiler (widget, Pivot) |
Kapsam: kiracı (`IsTenant`). Her kayıt bir şubeye bağlıdır; satır süzmesi yoktur.
---
## 2. Analiz ve kararlar
> Modül §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar wizard ve SQL'den okunmuştur.
| Konu | Karar |
| --- | --- |
| Yol | Tek Wizard liste ekranı |
| Ziyaretçi kimliği | Ziyaretçi ayrı bir kişi kartı değildir; her ziyaret satırında ad soyad serbest metindir (tekrar gelen ziyaretçi yeniden yazılır) |
| Firma | Serbest metin + **otomatik tamamlama** (daha önce girilen firma adlarından) |
| Ziyaretçi tipi | Sabit liste: Ziyaretçi, Müşteri, Tedarikçi, Taşeron, Kurye (değerler Türkçe anahtardır) |
| İçeride mi? | `ExitTime` boşsa ziyaretçi içeride sayılır |
| Bildirim | Ziyaret edilen personele bildirim **gönderilmez** |
---
## 3. Menü ve ekran
| Menü (TR / EN) | `ListFormCode` | Yer | Görünümler | Widget'lar (bugün) |
| --- | --- | --- | --- | --- |
| Ziyaretçiler / Visitors | `App.Hr.Visitor` | `İnsan Kaynakları` (sıra 12, `FcBusinessman`) | Grid, Card, Pivot, Chart | Bugün Gelen · İçeride · Çıkış Yapan |
Widget'lar yalnız **bugünün** (`VisitDate = bugün`) kayıtlarını sayar.
---
## 4. Veri modeli
`Hr_T_Visitor` (`sql/object/Hr_T_Visitor.sql`, entity yok):
| Kolon | Açıklama |
| --- | --- |
| `BranchId` | Şube (zorunlu) |
| `VisitorType` | Ziyaretçi tipi (varsayılan "Ziyaretçi") |
| `VisitDate` | Ziyaret tarihi |
| `EntryTime` / `ExitTime` | Giriş / çıkış zamanı (`datetime`) |
| `CardNo` | Verilen ziyaretçi kartı |
| `VisitorFullName` | Ziyaretçi adı soyadı (zorunlu) |
| `VisitedUserId` | Ziyaret edilen personel (`AbpUsers`, zorunlu) |
| `CompanyName` | Firma |
| `VisitReason` | Ziyaret sebebi |
---
## 5. İş kuralları
| Yer | Kural |
| --- | --- |
| Yeni kayıt | Ziyaret tarihi bugün, giriş saati şimdi, tip "Ziyaretçi" |
| Çıkış | Kayıt düzenlenip çıkış saati girilir; otomatik çıkış yoktur |
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`; `App.Hr.Visitor` + `.Create/.Update/.Delete/.Export/.Import/.Note`.
Güvenliğe kök + `.Create/.Update` yeter. Hiçbir yetki role otomatik atanmaz. Ziyaretçi adı kişisel
veridir; saklama süresi için periyodik temizlik yoktur.
---
## 7. Entegrasyonlar
`Sas_T_Branch` (şube), `AbpUsers` (ziyaret edilen personel). Bildirim, rapor ya da zamanlanmış iş yoktur.
---
## 8. Akış örneği
1. Tedarikçi temsilcisi gelir; güvenlik yeni kayıt açar (tarih/saat dolu), tip Tedarikçi, ad soyad,
firma (otomatik tamamlamadan), ziyaret edilen satın alma uzmanı, kart no 12.
2. "İçeride" kartı 1 artar.
3. Çıkışta kayıt açılıp çıkış saati girilir; "Çıkış Yapan" kartı artar.
---
## 9. Sık sorulanlar
**Tekrar gelen ziyaretçinin bilgileri neden dolmuyor?** Ziyaretçi kartı yoktur; yalnız firma adı otomatik tamamlanır.
**Gün sonunda içeride kalanlar nasıl görülür?** Bugünün "İçeride" kartı ya da `ExitTime` boş filtresi.
**Ziyaret edilen kişiye haber gidiyor mu?** Hayır.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_Visitor.sql` | Tablo |
| `configs/seeds/wizard/Visitor.json` | Wizard |
Geri alma: Wizard Manager'dan silinir; tablo yalnızca onayda seçilirse düşer.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Ziyaretçiler — `App.Hr.Visitor`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Visitor.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr` › `App.Hr.Visitor` (sıra 12) |
| Yetki | `App.Hr.Visitor` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Ziyaretçiler — güvenlik/resepsiyonun tuttuğu ziyaretçi giriş-çıkış defteri. |
| Geliştirme talebi | Human Resources -> Ziyaretçi Takip Listesi |
| Kaynak | `Hr_T_Visitor` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Hr.Visitor.Widget.Today`, `App.Hr.Visitor.Widget.Inside`, `App.Hr.Visitor.Widget.Exited` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `VisitDate` | Ziyaret Tarihi | dxDateBox | ✓ | ✓ | script |
| `VisitorType` | Ziyaretçi Tipi | dxSelectBox | ✓ | ✓ | liste: Ziyaretçi, Müşteri, Tedarikçi, Taşeron, Kurye · script |
| `EntryTime` | Giriş Saati | dxDateBox | ✓ | ✓ | script |
| `ExitTime` | Çıkış Saati | dxDateBox | | ✓ | |
| `CardNo` | Kart No | dxTextBox | | ✓ | |
| `VisitedUserId` | Ziyaret Edilen Personel | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers |
| `VisitorFullName` | Ziyaretçi Adı Soyadı | dxTextBox | ✓ | ✓ | |
| `CompanyName` | Firma Adı | dxAutocomplete | | ✓ | sorgu: Hr_T_Visitor |
| `VisitReason` | Ziyaret Sebebi | dxTextArea | | ✓ | |
| `BranchId` | Şube Adı | dxSelectBox | ✓ | ✓ | sorgu: Sas_T_Branch |
<!-- module-doc:auto:end -->

View file

@ -1,205 +0,0 @@
<!-- module-doc: claims=App.Hr.Shift -->
# İK › Çalışma Takvimi ve Vardiyalar (`App.Hr.WorkCalendar`, `App.Hr.Shift`)
Çalışma zamanının iki temel tanımı: resmi tatil / yarım gün takvimi (yıllık tekrarlı ya da tek
seferlik) ve vardiya tanımları (saat aralığı, mola, gece vardiyası). Diğer modüller bu tanımları
lookup olarak kullanır. Tamamı konfigürasyondur (Wizard + seed); vardiya ayrıca kullanıcı kaydına
bağlanır.
> Bu doküman modülün tek "nasıl çalışır" kaynağıdır. Modülde yapılan her değişiklik aynı commit'te
> buraya yansır (`lowcode.instructions.md` §0.13).
---
## 1. Amaç ve kapsam
| Persona | Ne yapar | Ekran |
| --- | --- | --- |
| İK | Yılın tatil ve yarım günlerini girer, takvim görünümünden kontrol eder | Çalışma Takvimi |
| İK / üretim planlama | Vardiya tanımlarını yönetir, çalışanı vardiyaya bağlar | Vardiyalar · Kimlik › Kullanıcılar |
Kapsam: kiracı (`IsTenant`). Şube kırılımı yoktur — takvim kiracının tamamı için geçerlidir.
---
## 2. Analiz ve kararlar
> Modüller §0.13 kuralından önce üretildi; keşif turu kaydı yoktur. Kararlar artefaktlardan
> (wizard, seed, commit `130e23bc` "Disiplin Dosyaları, Çalışma Takvimi") okunmuştur.
| Konu | Karar |
| --- | --- |
| Tatil tekrarı | Sabit tarihli resmi tatiller `RecurrenceRule = FREQ=YEARLY` ile **bir kez** girilir, her yıl tekrarlar; dini bayramlar ay takvimine bağlı olduğu için **her yıl ayrı** satırdır (tekrar kuralı boş) |
| Gün türü | `Holiday` (tam gün tatil) / `HalfDay` (arife, yarım gün) |
| Görünüm | Liste görünümlerine ek olarak **Scheduler** (takvim) görünümü; `AllDay` her zaman `true` |
| Vardiya saatleri | `StartTime` / `EndTime` `SS:dd` metin; çalışma süresi elle girilir (otomatik hesap yok) |
| Gece vardiyası | Bayrak (`IsNightShift`) — sağlık kartındaki "gece çalışabilir" sonucuyla birlikte değerlendirilir, otomatik kontrol yoktur |
| Vardiya bağı | Kullanıcı kaydında `ShiftId` alanı (`LookupQueryValues.TenantShiftValues`: "Ad (başlangıç-bitiş)") |
| Menü yeri | Çalışma Takvimi `İnsan Kaynakları` kökünde; Vardiyalar bir tanım olduğu için `İnsan Kaynakları › Tanımlamalar` altında (yetkisi de `App.Hr.Definitions`'ın altındadır) |
---
## 3. Menü ve ekranlar
| Menü (TR / EN) | `ListFormCode` | Yer | Görünümler | Widget'lar |
| --- | --- | --- | --- | --- |
| Çalışma Takvimi / Work Calendar | `App.Hr.WorkCalendar` | `İnsan Kaynakları` (sıra 4, `FcCalendar`) | Grid, Card, Pivot, Chart, **Scheduler** | Toplam · Tatil · Yarım Gün |
| Vardiyalar / Shifts | `App.Hr.Shift` | `İnsan Kaynakları › Tanımlar` (sıra 11, `FcClock`) | Grid, Card, Pivot, Chart | Toplam · Aktif · Pasif |
Scheduler eşlemesi: metin `Name`, başlangıç `StartDate`, bitiş `EndDate`, tüm gün `AllDay`,
tekrar `RecurrenceRule`.
Seed verisi:
- Çalışma takvimi, 16 satır (`data/App.Hr.WorkCalendar.json`):
- Yıllık tekrarlı tatiller: Yılbaşı, 23 Nisan, 1 Mayıs, 19 Mayıs, 15 Temmuz, 30 Ağustos, 29 Ekim.
- Yıllık tekrarlı yarım gün: 28 Ekim arifesi.
- Yıla özel satırlar: 2026 ve 2027 Ramazan/Kurban bayramları ve arifeleri.
- Vardiyalar, 3 satır (`data/App.Hr.Shift.json`): Sabah 08:00–16:00, Akşam 16:00–00:00, Gece 00:00–08:00 (gece vardiyası).
---
## 4. Veri modeli
| Tablo | Kolonlar |
| --- | --- |
| `Hr_T_WorkCalendar` | `Name`, `StartDate`, `EndDate`, `DayType` (Holiday/HalfDay), `RecurrenceRule` (boş / `FREQ=YEARLY`), `AllDay`, `IsActive` |
| `Hr_T_Shift` | `Name`, `StartTime`, `EndTime`, `BreakMinutes`, `LateToleranceMinutes`, `EarlyLeaveToleranceMinutes`, `WorkingHours`, `IsNightShift`, `IsActive` |
| `AbpUsers.ShiftId` | Çalışanın vardiyası (genişletme kolonu) |
Entity yoktur (wizard tabloları); `sql/object/Hr_T_{WorkCalendar,Shift}.sql`.
---
## 5. İş kuralları
| Yer | Kural |
| --- | --- |
| Takvim — yeni kayıt | Gün türü `Holiday`, tekrar `FREQ=YEARLY`, tüm gün, aktif |
| Takvim — çok günlü tatil | Başlangıç–bitiş aralığı olarak tek satırda girilebilir |
| Vardiya — yeni kayıt | Gece vardiyası hayır, aktif |
| Vardiya — toleranslar | Geç kalma / erken çıkış toleransı (dk); boşsa Ayarlar › İnsan Kaynakları › Puantaj'daki genel değer |
Takvim İzin Yönetimi'nin gün sayısını (`HrLeave.md`), takvim ve vardiya Puantaj ve Devam Takibi'nin
gün tipini, vardiya saatini, molasını ve toleransını (`HrAttendance.md`) besler.
---
## 6. Yetki ve görünürlük
İzin grubu `App.Hr`, modül `Sas|Hr`. Her ekranın kökü + `.Create/.Update/.Delete/.Export/.Import/.Note`.
Hiçbir yetki role otomatik atanmaz. Takvimin okunması tüm çalışanlara açılabilir; düzenleme İK'da kalmalıdır.
---
## 7. Entegrasyonlar
| Kullanan | Nasıl |
| --- | --- |
| Kimlik › Kullanıcılar | `ShiftId` alanı vardiya lookup'ı |
| Olay Günlüğü (`App.Hr.EventLog`) | Olay kaydında vardiya seçimi |
| Sağlık Kartları | Vardiyalı / gece çalışabilir sonucu (bilgi; vardiya tanımıyla otomatik eşleşmez) |
| İzin Yönetimi | Takvim tatilleri izin gününden düşülür (`Hr_F_LeaveWorkDays`). Takvim satırı eklenince/değişince/silinince denk gelen izinlerin gün sayısı yeniden hesaplanır (`Hr_S_WorkCalendarAfterSave`) |
| Puantaj ve Devam Takibi | Tatil → Resmî tatil, arife → yarım vardiya (`Hr_F_WorkCalendarDayType`); vardiya saat/mola/tolerans → `Hr_V_AttendanceShift`. Takvim kaydı ilgili günleri hemen yeniden hesaplar (kapalı ay hariç); vardiya değişikliği gece işinde ya da Puantaj Dönemleri kaydında yansır |
---
## 8. Akış örneği
1. Yıl başında İK **Çalışma Takvimi**'nde o yılın dini bayram satırlarını ekler (sabit tatiller zaten yıllık tekrarlıdır) ve Scheduler görünümünde yılı kontrol eder.
2. Üretim için **Vardiyalar**'a "Hafta Sonu 08:00–20:00, mola 60 dk, çalışma 11 saat" eklenir.
3. Kullanıcı ekranında çalışanların **Vardiya** alanı güncellenir; Olay Günlüğü kayıtlarında da bu vardiyalar seçilir.
---
## 9. Sık sorulanlar ve bilinen sınırlar
**Ramazan Bayramı gelecek yıl görünmüyor.** Dini bayramlar yıla özeldir; her yıl yeni satır girilir.
**Çalışma süresi neden hesaplanmıyor?** Vardiya saatleri metindir, süre elle girilir.
**Takvim izin/puantaj hesabını etkiliyor mu?** Evet: izin gün sayısından tatiller düşülür, puantajda tatil
günü Resmî tatil, arife yarım gün sayılır. Takvim satırı kaydedilince denk gelen günlerin izin gün sayısı ve
puantajı hemen yeniden hesaplanır (yıllık tekrarlı satırda bu yıl ve gelecek yıl; kapalı puantaj ayı yazılmaz).
Bir satırın tarihi değiştirildiğinde eski tarihlerin puantajı gece işinde ya da Puantaj Dönemleri kaydında düzelir.
**Vardiyalar nerede?** `İnsan Kaynakları › Tanımlamalar` altında (sıra 11); menü satırı, wizard dosyası ve
yetki ağacı aynı yeri gösterir.
---
## 10. Dosyalar ve geri alma
| Dosya | İçerik |
| --- | --- |
| `configs/seeds/sql/object/Hr_T_{WorkCalendar,Shift}.sql` | Tablolar |
| `configs/seeds/wizard/{WorkCalendar,Shift}.json` | 2 wizard |
| `configs/seeds/data/App.Hr.{WorkCalendar,Shift}.json` | Takvim ve vardiya verisi (ekran kayıtları dosyaya aynalanır) |
| `api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs` (`TenantShiftValues`) | Kullanıcı ekranındaki vardiya lookup'ı |
Geri alma: Wizard Manager'dan silinir; vardiya tablosu kullanıcı ekranının lookup'ı tarafından okunduğu için düşürülmemelidir.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Çalışma Takvimi — `App.Hr.WorkCalendar`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/WorkCalendar.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr` › `App.Hr.WorkCalendar` (sıra 4) |
| Yetki | `App.Hr.WorkCalendar` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Çalışma Takvimi — resmi tatiller, arife (yarım gün) ve çalışılmayan günler. |
| Kaynak | `Hr_T_WorkCalendar` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart, Scheduler (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Hr.WorkCalendar.DayType.Holiday`, `App.Hr.WorkCalendar.DayType.HalfDay` |
| Komutlar | InsertAfter: `Hr_S_WorkCalendarAfterSave` · UpdateAfter: `Hr_S_WorkCalendarAfterSave` · DeleteAfter: `Hr_S_WorkCalendarAfterSave` |
| Form varsayılanları | DayType=Holiday, RecurrenceRule=FREQ=YEARLY, AllDay=true, IsActive=true |
| Veri aynası | `data/App.Hr.WorkCalendar.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `StartDate` | Başlangıç Tarihi | dxDateBox | ✓ | ✓ | |
| `EndDate` | Bitiş Tarihi | dxDateBox | ✓ | ✓ | |
| `DayType` | Gün Türü | dxSelectBox | ✓ | ✓ | liste: Holiday, HalfDay |
| `RecurrenceRule` | Tekrarlama Kuralı | dxSelectBox | | ✓ | liste: FREQ=YEARLY |
| `AllDay` | Tüm Gün | dxCheckBox | | | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Vardiyalar — `App.Hr.Shift`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Shift.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Hr.Definitions` › `App.Hr.Shift` (sıra 11) |
| Yetki | `App.Hr.Shift` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Vardiyalar — çalışma vardiyalarının tanımı (sabah, akşam, gece…). |
| Geliştirme talebi | Human Resources -> Olay Günlüğü |
| Kaynak | `Hr_T_Shift` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | IsNightShift=false, IsActive=true |
| Veri aynası | `data/App.Hr.Shift.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Vardiya Adı | dxTextBox | ✓ | ✓ | |
| `StartTime` | Başlangıç Saati | dxTextBox | | ✓ | |
| `EndTime` | Bitiş Saati | dxTextBox | | ✓ | |
| `BreakMinutes` | Mola (dk) | dxNumberBox | | ✓ | |
| `LateToleranceMinutes` | Geç Kalma Toleransı (dk) | dxNumberBox | | ✓ | |
| `EarlyLeaveToleranceMinutes` | Erken Çıkış Toleransı (dk) | dxNumberBox | | ✓ | |
| `WorkingHours` | Çalışma Süresi (Saat) | dxNumberBox | | ✓ | |
| `IsNightShift` | Gece Vardiyası | dxCheckBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -32,8 +32,8 @@
"MenuTr": "Tüm Değerlendirmeler", "MenuTr": "Tüm Değerlendirmeler",
"TitleEn": "All Reviews", "TitleEn": "All Reviews",
"TitleTr": "Tüm Değerlendirmeler", "TitleTr": "Tüm Değerlendirmeler",
"DescEn": "All Reviews — every employee review of every period; HR follows and manages the process end to end here.\nWhere: Human Resources › Performance Management › All Reviews (HR only). The detail has Goals, Competencies, 360 Feedback and Development Plan tabs; the Todo view groups reviews by stage.\nStages: Goal Setting → Self Review → Manager Review → Calibration → Completed.\nFields:\n• Period, employee (department/position copied), manager → HR selects the reviewing manager\n• Scores → goal, competency, 360 average (info), overall, calibrated score and rating; calculated automatically\n• Self, manager and calibration comments; submission and completion dates\nAutomatic: scores are recalculated on every save. HR may make any stage change; leaving Goal Setting requires at least one goal with weights totalling 100. Transitions notify the employee/manager; on Completed manager levels are written to the employee's skill profile.\nRelated: My Reviews (employee), Team Reviews (manager), Calibration, Development Plans. Details: docs/modules/HrPerformance.md.", "DescEn": "All Reviews — every employee review of every period; HR follows and manages the process end to end here.\nWhere: Human Resources › Performance Management › All Reviews (HR only). The detail has Goals, Competencies, 360 Feedback and Development Plan tabs; the Todo view groups reviews by stage.\nStages: Goal Setting → Self Review → Manager Review → Calibration → Completed.\nFields:\n• Period, employee (department/position copied), manager → HR selects the reviewing manager\n• Scores → goal, competency, 360 average (info), overall, calibrated score and rating; calculated automatically\n• Self, manager and calibration comments; submission and completion dates\nAutomatic: scores are recalculated on every save. HR may make any stage change; leaving Goal Setting requires at least one goal with weights totalling 100. Transitions notify the employee/manager; on Completed manager levels are written to the employee's skill profile.\nRelated: My Reviews (employee), Team Reviews (manager), Calibration, Development Plans. Details: docs/modules/App.Hr.md › Performans Yönetimi.",
"DescTr": "Tüm Değerlendirmeler — bütün dönemlerin bütün çalışan değerlendirmeleri; İK süreci uçtan uca buradan izler ve yönetir.\nNerede: İnsan Kaynakları › Performans Yönetimi › Tüm Değerlendirmeler (yalnız İK). Detayda Hedefler, Yetkinlikler, 360 Derece ve Gelişim Planı sekmeleri; Todo görünümünde değerlendirmeler aşama sütunlarındadır.\nAşamalar: Hedef Belirleme → Öz Değerlendirme → Yönetici Değerlendirmesi → Kalibrasyon → Tamamlandı.\nAlanlar:\n• Dönem, çalışan (departman/pozisyon çalışandan dolar), yönetici → değerlendiren yöneticiyi İK seçer\n• Puanlar → hedef puanı, yetkinlik puanı, 360 ortalaması (bilgi), genel puan, kalibre puan ve derece; hesaplar otomatiktir\n• Öz değerlendirme, yönetici ve kalibrasyon yorumları; gönderim ve tamamlanma tarihleri\nOtomatik: her kayıtta puanlar yeniden hesaplanır. İK her aşama geçişini yapabilir; Hedef Belirleme'den çıkmak için ağırlık toplamı 100 olan en az bir hedef gerekir. Geçişlerde çalışana/yöneticiye bildirim gider; Tamamlandı'da yönetici seviyeleri çalışanın yetkinlik profiline yazılır.\nİlişkili: Değerlendirmelerim (çalışan), Ekibimin Değerlendirmeleri (yönetici), Kalibrasyon, Gelişim Planları. Ayrıntı: docs/modules/HrPerformance.md.", "DescTr": "Tüm Değerlendirmeler — bütün dönemlerin bütün çalışan değerlendirmeleri; İK süreci uçtan uca buradan izler ve yönetir.\nNerede: İnsan Kaynakları › Performans Yönetimi › Tüm Değerlendirmeler (yalnız İK). Detayda Hedefler, Yetkinlikler, 360 Derece ve Gelişim Planı sekmeleri; Todo görünümünde değerlendirmeler aşama sütunlarındadır.\nAşamalar: Hedef Belirleme → Öz Değerlendirme → Yönetici Değerlendirmesi → Kalibrasyon → Tamamlandı.\nAlanlar:\n• Dönem, çalışan (departman/pozisyon çalışandan dolar), yönetici → değerlendiren yöneticiyi İK seçer\n• Puanlar → hedef puanı, yetkinlik puanı, 360 ortalaması (bilgi), genel puan, kalibre puan ve derece; hesaplar otomatiktir\n• Öz değerlendirme, yönetici ve kalibrasyon yorumları; gönderim ve tamamlanma tarihleri\nOtomatik: her kayıtta puanlar yeniden hesaplanır. İK her aşama geçişini yapabilir; Hedef Belirleme'den çıkmak için ağırlık toplamı 100 olan en az bir hedef gerekir. Geçişlerde çalışana/yöneticiye bildirim gider; Tamamlandı'da yönetici seviyeleri çalışanın yetkinlik profiline yazılır.\nİlişkili: Değerlendirmelerim (çalışan), Ekibimin Değerlendirmeleri (yönetici), Kalibrasyon, Gelişim Planları. Ayrıntı: docs/modules/App.Hr.md › Performans Yönetimi.",
"Texts": [ "Texts": [
{ {
"Key": "App.Hr.PerformanceReview.Status.GoalSetting", "Key": "App.Hr.PerformanceReview.Status.GoalSetting",