Postggre güncellemeleri ve colorbox
This commit is contained in:
parent
68dd9b28e6
commit
35118ab2d9
134 changed files with 6203 additions and 1694 deletions
6
.github/instructions/ai.instructions.md
vendored
6
.github/instructions/ai.instructions.md
vendored
|
|
@ -308,9 +308,13 @@ designer. Aggregations available in the Group By column: `GroupBy`, `Where`, `SU
|
|||
`COUNT_DISTINCT`, `AVG`, `MIN`, `MAX`.
|
||||
|
||||
Persistence: only the **table designer's** deploy writes a seed file automatically
|
||||
(`{sql|postgres}/object/{TableName}.sql`, full CREATE script, via `save-table-script`). View
|
||||
(`sql/object/{TableName}.sql`, full CREATE script, via `save-table-script`; its PostgreSQL counterpart
|
||||
`postgres/object/{TableName}.sql` is generated from it by `PostgreSqlTableScript`). View
|
||||
Designer deploys and any DDL run from the SQL editor (views, procedures, functions) reach the
|
||||
database only — their seed files under `{sql|postgres}/object/` must be written explicitly.
|
||||
Every seed script is written for **both** providers: a `sql/…/X.sql` change always comes with its
|
||||
`postgres/…/X.sql` counterpart (schema `public`, same name and parameter order) — the platform runs on
|
||||
SQL Server and PostgreSQL alike (`lowcode.instructions.md` §8.3.1, CLAUDE.md standing default 16).
|
||||
Full mechanics and the SP/function production patterns: `lowcode.instructions.md` §8.1–§8.2.1
|
||||
and §9.5.
|
||||
|
||||
|
|
|
|||
8
.github/instructions/dotnet.instructions.md
vendored
8
.github/instructions/dotnet.instructions.md
vendored
|
|
@ -122,6 +122,14 @@ Ek kurallar:
|
|||
- **Repository**: özel sorgu gerekiyorsa Domain'de arayüz + EntityFrameworkCore'da
|
||||
implementasyon. AppService içinde `DbContext` doğrudan kullanılmaz.
|
||||
- **Dinamik SQL**: parametreli çalıştırılır; string birleştirme ile sorgu kurulmaz.
|
||||
- **İki sağlayıcı**: uygulama SQL Server ve PostgreSQL'de çalışır (`DefaultDatabaseProvider`).
|
||||
EF modeli SQL Server sözdizimiyle yazılır (`HasColumnType("nvarchar(max)")`,
|
||||
`HasFilter("[IsDeleted] = 0")`, `HasCheckConstraint`, `HasDefaultValueSql("NEWID()")`);
|
||||
PostgreSQL'de `PlatformDbContext.ConfigureProviderSpecificModel` bunları `PostgreSqlModelSyntax`
|
||||
ile çevirir. Çeviride karşılığı olmayan bir sözdizimi kullanılırsa `PostgreSqlModelSyntax`'a
|
||||
eklenir; immutable olmayan hesaplanan kolon (`GETDATE()`'e bağlı) PG'de yazılamaz, sağlayıcıya
|
||||
göre dallanır. C#'ta üretilen SQL tırnaklı tanımlayıcı ve ANSI ifadelerle yazılır ya da
|
||||
`DataSourceType`'a göre dallanır; yalnızca T-SQL'de çalışan metin eklenmez.
|
||||
- **Mapping**: Mapperly (`[Mapper]` partial class) kullanılır; AutoMapper eklenmez.
|
||||
Mapper'lar servis klasöründeki `{Alan}Mappers.cs` dosyasındadır (modülde
|
||||
`{Modül}ApplicationMappers.cs`), ABP'nin `MapperBase<TSource, TDest>` /
|
||||
|
|
|
|||
55
.github/instructions/lowcode.instructions.md
vendored
55
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -50,7 +50,7 @@ ve onay, en son dosyalar.**
|
|||
configs/seeds/
|
||||
├── sql/object/{Nesne}.sql ← tablo / view / fonksiyon / prosedür (SQL Server)
|
||||
├── sql/execute/{Ad}.sql ← bir kez çalışacak script (veri dolumu, migrasyon)
|
||||
├── postgres/object|execute/… ← aynısının PostgreSQL diyalekti (hedef PG ise)
|
||||
├── postgres/object|execute/… ← aynısının PostgreSQL diyalekti (her zaman, §8.3.1)
|
||||
├── {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ı
|
||||
├── wizard/{WizardName}.json ← ekran + menü + yetki + dil anahtarları (List yolunda sunucu yazar, §4.1)
|
||||
|
|
@ -509,7 +509,7 @@ ister ve kiracı kullanıcısına hiçbiri açılmaz.
|
|||
| Araç | Ne yapar | Not |
|
||||
| --- | --- | --- |
|
||||
| `get_dev_guide` | Bu dosyanın (`lowcode`) ya da `reference`'ın başlık listesini / bir bölümünü döner | Bölüm numarayla (`4.3`) ya da başlık metniyle istenir; aynı numarayı iki başlık taşımamalıdır. Bölüm önce istenen belgede, sonra diğerinde aranır; başlık olmayan bir terim (`dxImageUpload`) terimin geçtiği bölümlerin listesini döndürür |
|
||||
| `create_table` | Yeni SQL Server tablosu kurar, `sql/object/{Tablo}.sql` dosyasını yazar | Ek olarak `App.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz |
|
||||
| `create_table` | Yeni tabloyu kurar (SQL Server ya da PostgreSQL veri kaynağı), `sql/object/{Tablo}.sql` ve PG eşi `postgres/object/{Tablo}.sql` dosyalarını yazar | Ek olarak `App.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz |
|
||||
| `list_wizards` · `get_wizard` | Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner | Var olan menü kökünü/izin grubunu bulmak için önce `list_wizards` |
|
||||
| `deploy_wizard` | Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) | Girdi §4.4'teki düz cevap ya da `get_wizard` çıktısıyla aynı dosya biçimi; enum'lar sayı. `replaceFileName` = `EditFileName` (§4.5). List ekranında `Widgets` dolu gelir — alt form olarak üretilen ekran hariç (`Widgets: []`) — en az 3 kart, kiracı tablosunda `"TenantId" = '@TENANTID'` (§4.6.1); kartlardaki her dil anahtarı var olmalı ya da `Language.Texts`'te EN + TR verilmeli, yoksa deploy reddeder. `MenuOrder` yazılmaz — sunucu yeni ekranı kardeşlerinin sonuna koyar, güncellemede yerini korur. Talep bağı (`DevRequest`) da yazılmaz — sunucu etkin talepten doldurur (§4.0). İstek demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1). Satır/toolbar butonları `CommandColumns` (§4.9). Bağımlı lookup öğedeki `Cascade*` alanları (§4.3.4). Çıkıştaki `isPending: true` ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır; model migrate'i beklemez |
|
||||
| `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) |
|
||||
|
|
@ -1399,6 +1399,14 @@ gibi yüklendiği için öğe anahtarları **küçük harf** (`key`, `name`, cas
|
|||
`name` değeri `::` öneki eklenerek çevrilir — dil anahtarı da yazılabilir. `Query` sonucunda ise
|
||||
istemci `Key`/`Name`/`Group` kolonlarını `key`/`name`/`group`'a eşler; sorgu kolonları bu adlarla
|
||||
döndürür (`SELECT Id AS [Key], Name AS [Name] …`) ve `ValueExpr`/`DisplayExpr` `Key`/`Name` kalır.
|
||||
Lookup öğesi renk taşıyorsa (etiket tanımındaki `dxColorBox` kolonu gibi) sorgu onu `AS [Color]`
|
||||
takma adıyla döndürür (`StaticData`'da `color`); `dxTagBox` etiketleri formda ve grid hücresinde bu
|
||||
renkle çizilir (`views/shared/lookupColor.tsx`), renk yoksa varsayılan rozet rengi kalır. Dinamik
|
||||
raporlar (`DynamicGrid`/`DynamicTree`/`DynamicForm`) `Query` lookup'ında takma adı görünce rapor
|
||||
SQL'ine `{Alan}__LookupColor` kolonu ekler ve değeri renkli rozet olarak basar
|
||||
(`DynamicReportColorHelper`); sorgu rapor kurulurken çalıştırılmadığı için takma ad yazılmazsa
|
||||
rapor düz metin kalır. `dxColorBox` editörlü kolon grid'de ve raporlarda değerin yanında renk
|
||||
örneğiyle gösterilir.
|
||||
|
||||
`LookupDataSourceType` `1` (StaticData) ise `LookupQuery` bir JSON dizisi, `2` (Query) ise SQL,
|
||||
`3` (WebService) ise noktalı virgülle ayrılmış bir çağrı tarifidir. Tam sözleşme ve cascade
|
||||
|
|
@ -3564,8 +3572,8 @@ tabloları için yalnız `T`/`D` üretir.)
|
|||
1. Üretilen SQL hedef veri kaynağında çalıştırılır (yeni tabloda `CREATE`, düzenleme modunda
|
||||
**`ALTER` diff'i**).
|
||||
2. Seed dosyası kaydedilir: her zaman **tam `CREATE TABLE` script'i** (diff değil),
|
||||
`save-table-script` ucu ile `configs/seeds/{sql|postgres}/object/{TabloAdı}.sql`
|
||||
altına (`SeedPathResolver`). Dosya kaydı başarısız olsa deploy geri alınmaz — log'a düşer.
|
||||
`save-table-script` ucu ile `configs/seeds/sql/object/{TabloAdı}.sql` altına; PostgreSQL eşi
|
||||
aynı script'ten `PostgreSqlTableScript` ile üretilip `postgres/object/{TabloAdı}.sql` altına yazılır. Dosya kaydı başarısız olsa deploy geri alınmaz — log'a düşer.
|
||||
3. CRUD adımında işaretlendiyse endpoint'ler üretilir (`crud/{Entity}.json`); onay kutusu
|
||||
**varsayılan kapalıdır**, Custom Component'in kullanacağı tabloda açılır.
|
||||
|
||||
|
|
@ -3656,10 +3664,10 @@ vardır; seed dosyası yazarken aynı iskeleti kullan ve §8.3'ün idempotency k
|
|||
| --- | --- |
|
||||
| Dosya adı | Nesne adıyla **birebir aynı**: `Hr_T_AdvanceRequest.sql` |
|
||||
| Klasör | `object` = idempotent nesne tanımı · `execute` = nesne olarak da oluşturulur, ayrıca tüm migration'lar bittikten sonra çalıştırılır |
|
||||
| Sağlayıcı | Hedef SQL Server ise `sql/`, PostgreSQL ise `postgres/`. **İkisini birden destekliyorsan iki dosya da yazılır**; içerikler aynı nesnenin iki diyalektidir. |
|
||||
| İdempotency | SQL Server: `CREATE OR ALTER VIEW/PROCEDURE/FUNCTION`, tablolar için `IF NOT EXISTS` koruması. PostgreSQL: `CREATE OR REPLACE VIEW/FUNCTION`, `CREATE TABLE IF NOT EXISTS`. |
|
||||
| Şema | `dbo` kullanılır; PostgreSQL tarafında tanımlayıcılar çift tırnaklıdır (`dbo."Hr_T_AdvanceRequest"`) çünkü kolon adları PascalCase'tir. |
|
||||
| Toplu ayraç | SQL Server'da prosedür sonunda `GO` kullanılabilir; PostgreSQL'de kullanılmaz. |
|
||||
| Sağlayıcı | **Her nesne iki dosyadır**: `sql/…/X.sql` (SQL Server) ve `postgres/…/X.sql` (PostgreSQL), aynı göreli yol ve ad. Biri yazılıp/değiştirilip diğeri bırakılmaz (§8.3.1). |
|
||||
| İdempotency | SQL Server: `CREATE OR ALTER VIEW/PROCEDURE/FUNCTION`, tablolar için `IF NOT EXISTS` koruması. PostgreSQL: `CREATE OR REPLACE VIEW/FUNCTION/PROCEDURE`, `CREATE TABLE IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, constraint için `pg_constraint` kontrollü `DO $$` bloğu. |
|
||||
| Şema | SQL Server'da `dbo`; PostgreSQL'de `public` (EF tabloları oradadır, `dbo` şeması yoktur). PG'de tanımlayıcılar çift tırnaklıdır (`public."Hr_T_AdvanceRequest"`, `"TenantId"`) çünkü adlar PascalCase'tir. |
|
||||
| Toplu ayraç | SQL Server'da batch'ler `GO` ile ayrılır. PostgreSQL dosyasında `GO` gerekmez; dosya tek komut metni olarak da çalışır. |
|
||||
| Yorum | Dosyanın ilk satırında ne ürettiğini yaz (`-- Create View`). |
|
||||
|
||||
Tablo yazarken §8.1'deki varsayılan kolonlar dahil edilir. Örnek iskelet (SQL Server):
|
||||
|
|
@ -3686,14 +3694,37 @@ END
|
|||
> `Id` tipini ne seçtiysen wizard dosyasındaki `KeyFieldDbSourceType` ile aynı olmalıdır
|
||||
> (`UNIQUEIDENTIFIER` → `9`, `INT IDENTITY` → `11`). Uyumsuzluk sessiz veri hatası üretir.
|
||||
|
||||
#### 8.3.1 PostgreSQL eşi
|
||||
|
||||
Uygulama iki sağlayıcıda da çalışır; `sql/` altına yazılan ya da değiştirilen her dosyanın
|
||||
`postgres/` eşi **aynı işte** yazılır (CLAUDE.md standing default 16). Eş, aynı nesnenin aynı
|
||||
davranışla PG diyalektidir: aynı satırları yazar, aynı doğrulamayı yapar, aynı (Türkçe) mesajı verir.
|
||||
|
||||
| Konu | Kural |
|
||||
| --- | --- |
|
||||
| Rutin dili | Fonksiyon ve prosedürler `LANGUAGE plpgsql`. Dosyalar alfabetik çalıştığı için `Hr_S_*` tablolardan (`Hr_T_*`) önce kurulur; `plpgsql` gövdesi kurulumda doğrulanmaz, `LANGUAGE sql` doğrulanır ve düşer. |
|
||||
| Rutin türü | Skaler/tablo fonksiyonu → `FUNCTION … RETURNS <tip>` / `RETURNS TABLE(…)`. Sonuç kümesi döndürmeyen prosedür → `PROCEDURE` (çağrı `CALL`). Sonunda satır döndüren T-SQL prosedürü → `FUNCTION … RETURNS TABLE` (PG prosedürü sonuç kümesi döndüremez). `execute/` dosyası her zaman `PROCEDURE`'dür (`FinalSeeder` yalnızca `prokind = 'p'` çağırır). Zamanlanmış işin `BeforeSp`'si (`SpRepository`) PG'de katalogdan türe bakar: prosedür `CALL`, fonksiyon `SELECT * FROM` ile, ad tırnaklı `public."Ad"` olarak çağrılır. |
|
||||
| Parametre | Ad ve **sıra** T-SQL ile aynı (çağıranlar sırayla geçer); `@Ad` → `p_Ad`, yerel değişken `v_Ad` — kolon adlarıyla belirsizlik olmasın diye. |
|
||||
| Transaction | `BEGIN TRAN/COMMIT`, `SET NOCOUNT/XACT_ABORT` yazılmaz; çağıranın transaction'ı atomikliği sağlar. |
|
||||
| Hata | `THROW 51100, N'Mesaj', 1` → `RAISE EXCEPTION 'Mesaj' USING ERRCODE = 'P0001', DETAIL = '51100'` (mesajdaki `%` → `%%`). |
|
||||
| Tipler | `uniqueidentifier`→`uuid`, `nvarchar(n)`→`varchar(n)`, `nvarchar(MAX)`→`text`, `bit`→`boolean`, `datetime2`→`timestamp`, `decimal`→`numeric`, `int`→`integer`, `tinyint`→`smallint`, `float`→`double precision`. |
|
||||
| Varsayılan | `NEWID()`→`gen_random_uuid()`, `GETDATE()`→`LOCALTIMESTAMP`, `GETUTCDATE()`→`(NOW() AT TIME ZONE 'UTC')`, bit `0/1`→`FALSE/TRUE`. |
|
||||
| Boolean | `bit` kolon karşılaştırması `= 0/1` değil `= FALSE/TRUE` (check ve index filtresi dahil). |
|
||||
| İfadeler | `ISNULL`→`COALESCE`, `TOP n`→`LIMIT n`, `+` (metin)→`\|\|`, `OUTER/CROSS APPLY`→`LEFT/CROSS JOIN LATERAL`, `DATEADD/DATEDIFF`→interval/tarih farkı, `CHARINDEX`→`strpos`, `STRING_SPLIT`→`string_to_table`, `@@ROWCOUNT`→`GET DIAGNOSTICS`, tablo değişkeni/`#temp`→`CREATE TEMP TABLE … ON COMMIT DROP`, `MERGE`→`INSERT … ON CONFLICT`. |
|
||||
| Ad uzunluğu | PG tanımlayıcısı en fazla 63 karakterdir; uzun constraint/index adı sessizce kısalır ve varlık kontrolü bir dahaki migrate'te tutmaz. Adı 63 karakterin altında tut. |
|
||||
| Büyük/küçük harf | SQL Server karşılaştırması harf duyarsızdır, PG duyarlıdır; kullanıcı girdisi/ad karşılaştırmasında `lower()`/`ILIKE` kullan. |
|
||||
|
||||
Doğrulama: PG eşi bir PostgreSQL veritabanında **iki kez** çalıştırılır (idempotent) ve her rutin
|
||||
gerçekçi veriyle en az bir kez çağrılır — `plpgsql` tip/belirsiz kolon hatalarını yalnızca çalışırken verir.
|
||||
|
||||
### 8.4 Seeder bu dosyaları nasıl çalıştırır
|
||||
|
||||
Yeni bir entity/tablo `sql/object/` altına yazılır ve veritabanı `SqlDataSeeder` tarafından bu
|
||||
dosyalardan kurulur. Mekanik:
|
||||
|
||||
1. **Yalnızca aktif sağlayıcının klasörü çalışır.** `DefaultDatabaseProvider` SQL Server ise
|
||||
`sql/`, PostgreSQL ise `postgres/`. İki dosya da yazmak taşınabilirlik
|
||||
içindir; aynı anda ikisi birden çalışmaz.
|
||||
`sql/`, PostgreSQL ise `postgres/`. Aynı anda ikisi birden çalışmaz ama iki dosya da her
|
||||
zaman yazılır (§8.3.1); yazılmayan eş, o sağlayıcıdaki kurulumu ilk migrate'te kırar.
|
||||
2. Sıra: `object/` → `execute/`, her klasörün içinde **dosya adına göre alfabetik**. Bir tablo
|
||||
başka bir tabloya FK ile bağlıysa dosya adları o sırayı vermelidir (ör. `Hr_T_Employee.sql`
|
||||
`Hr_T_EmployeeAdvance.sql`'den önce gelir).
|
||||
|
|
@ -3727,7 +3758,7 @@ içindir.
|
|||
### 8.5 Yeni entity/tablo eklerken izlenecek yol
|
||||
|
||||
1. `sql/object/{Modul}_T_{Entity}.sql` — tabloyu §8.3 iskeletiyle yaz (tenant + audit kolonları
|
||||
varsayılan). PostgreSQL hedefi de destekleniyorsa `postgres/object/` altına diyalekt eşini yaz.
|
||||
varsayılan) ve `postgres/object/` altına diyalekt eşini yaz (§8.3.1).
|
||||
2. İndeks ve FK'leri **aynı dosyada**, varlık kontrolüyle ekle (`IF NOT EXISTS`); ayrı dosya
|
||||
sıralamayı kırılgan hâle getirir.
|
||||
3. Ekran bu tabloyu doğrudan kullanacaksa CRUD endpoint gerekmez — ListForm `SelectCommandType: 1`
|
||||
|
|
@ -4689,7 +4720,7 @@ Bu tür isteklerin tamamı aynı iskeleti kullanır. Kullanıcı "avans talep fo
|
|||
|
||||
```
|
||||
configs/seeds/
|
||||
├── sql/object/Hr_T_AdvanceRequest.sql ← tablo (+ PostgreSQL hedefse postgres/object/…)
|
||||
├── sql/object/Hr_T_AdvanceRequest.sql ← tablo (+ postgres/object/… PG eşi, §8.3.1)
|
||||
├── crud/Hr_T_AdvanceRequest.json ← yalnızca Custom Component yolunda gerekir
|
||||
├── data/App.DeveloperKit.CustomComponents/{Id}.json ← yalnızca serbest yerleşim isteniyorsa
|
||||
├── wizard/AdvanceRequestApprovals.json ← (varsa) menüsüz alt ekran, küçük SeededAt
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -230,6 +230,10 @@ Uygulama `https://localhost:44344` adresinde açılır. Swagger UI kök adreste
|
|||
- `GET /api/setup/application-status` — veritabanının hazır olup olmadığını döner (anonim).
|
||||
- UI tarafında `/setup` route'u (`views/setup/DatabaseSetup.tsx`) migration'ı tetikleyen ekranı gösterir.
|
||||
- Migration için `Setup:MigrationPassword` yapılandırması veya `App.Setup.Migrate` yetkisi gerekir.
|
||||
- Ekran DbMigrator'ü `SetupMigrationRunner` ile çalıştırır. `Setup:MigratorPath` publish çıktısını
|
||||
gösteriyorsa DLL doğrudan çalışır; kaynak proje klasöründe (geliştirme) `dotnet run` kullanılmaz —
|
||||
`bin/Debug` altındaki DLL `api/` kaynaklarından (ve son başarılı derlemenin `obj/setup-migrator.stamp`
|
||||
damgasından) yeniyse derleme atlanır, değilse `dotnet build` çıktısı ekrandaki loga akar.
|
||||
|
||||
Alternatif olarak migration + seed'i doğrudan çalıştırabilirsiniz:
|
||||
|
||||
|
|
@ -280,7 +284,12 @@ migrate'te bu, kendi veritabanı olan son tenant'tır; öyle bir tenant yoksa ho
|
|||
kapansaydı, ekranın eski sürümü kurulu olan tenant ekranı "kurulu" sayıp güncellemezdi. Tek
|
||||
tenant seed'i (Tenants › Seed) bayrağı kapatmaz.
|
||||
|
||||
Aktif veritabanı sağlayıcısına göre `sql/` (SQL Server) ya da `postgres/` okunur. Her
|
||||
Aktif veritabanı sağlayıcısına göre `sql/` (SQL Server) ya da `postgres/` okunur. İki klasör
|
||||
birebir eştir: her `sql/…/X.sql` dosyasının aynı adlı `postgres/…/X.sql` karşılığı vardır (PG'de şema
|
||||
`public`; çeviri kuralları `lowcode.instructions.md` §8.3.1). EF modeli SQL Server sözdizimiyle
|
||||
yazılır, PostgreSQL'de `PostgreSqlModelSyntax` ile çevrilir (`PlatformDbContext.ConfigureProviderSpecificModel`);
|
||||
`Order.IsActive` SQL Server'da lisans bitişinden hesaplanan kolondur, PG'de hesaplanan kolon
|
||||
`CURRENT_DATE` alamadığı için saklanan kolondur (varsayılan `true`). Her
|
||||
`.sql` dosyası amacını belirten bir klasörün altındadır; klasör köklerinde dosya bulunmaz:
|
||||
|
||||
| Klasör | Ne zaman çalışır |
|
||||
|
|
@ -1558,7 +1567,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
|
|||
| `run_query` | Kayıtlı bir nesneyi ya da Custom Endpoint'i parametreleriyle çağırır; yetkiler `list_queries` ile aynı. |
|
||||
| `insert_row` / `update_row` / `delete_row` | ListForm ekranının insert/update/delete sözleşmesinden geçer. |
|
||||
| `get_dev_guide` | Üretim sözleşmesi: `lowcode` / `reference` belgelerinin başlık listesi ya da istenen bölümü (numarayla, örn. `4.2`, ya da başlık metniyle). Bölüm önce istenen, sonra diğer belgede aranır; başlıkla eşleşmeyen terimde (örn. `dxImageUpload`) terimin geçtiği bölümler (en çok 15, `DevGuideSearchMatches`), hiç geçmiyorsa başlık listesi döner — hata atılmaz. Çok büyük bir üst bölüm yalnızca giriş metni + alt başlıklarıyla döner. |
|
||||
| `create_table` | Yeni tabloyu SQL Query Manager tablo tasarımcısıyla aynı script biçimiyle kurar (`McpTableCreator`): `Id` + `TenantId` + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. Script `sql/object/{Tablo}.sql` dosyasına yazılır. Yalnızca oluşturur — tablo varsa reddeder; yalnızca SQL Server. `.Write` + `App.SqlQueryManager` ister. |
|
||||
| `create_table` | Yeni tabloyu SQL Query Manager tablo tasarımcısıyla aynı script biçimiyle kurar (`McpTableCreator`): `Id` + `TenantId` + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. Script `sql/object/{Tablo}.sql` dosyasına, PostgreSQL eşi (`PostgreSqlTableScript`) `postgres/object/{Tablo}.sql` dosyasına yazılır; PG veri kaynağında çalıştırılan da o eştir. Yalnızca oluşturur — tablo varsa reddeder. `.Write` + `App.SqlQueryManager` ister. |
|
||||
| `list_wizards` / `get_wizard` | Wizard File Manager dosyaları; `get_wizard` seed dosyasını diskteki biçimiyle döndürü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). |
|
||||
|
|
|
|||
|
|
@ -76,5 +76,4 @@ public static class McpErrorCodes
|
|||
public const string InvalidDefaultValue = Prefix + "InvalidDefaultValue";
|
||||
public const string DuplicateColumn = Prefix + "DuplicateColumn";
|
||||
public const string TableAlreadyExists = Prefix + "TableAlreadyExists";
|
||||
public const string TableProviderNotSupported = Prefix + "TableProviderNotSupported";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Sozsoft.Mcp.Services;
|
|||
/// <summary>
|
||||
/// Yeni tabloyu SQL Query Manager'daki tablo tasarimcisiyla ayni sozlesmeyle kurar: script
|
||||
/// tasarimcinin <c>generateCreateTableSql</c> ciktisiyla ayni bicimdedir ve tasarimcinin deploy
|
||||
/// sonrasi yazdigi yere (<c>{kapsam}/sql/object/{Tablo}.sql</c>) yazilir; boylece tablo
|
||||
/// sonrasi yazdigi yerlere (<c>sql/object/{Tablo}.sql</c> ve PostgreSQL esi <c>postgres/object/{Tablo}.sql</c>) yazilir; boylece tablo
|
||||
/// tasarimcida acilip duzenlenebilir ve veritabani sifirlandiginda seed ile geri gelir.
|
||||
/// <para>
|
||||
/// Yalnizca olusturma yapar. Var olan tabloya dokunulmaz — kolon ekleme/degistirme tasarimcinin
|
||||
|
|
@ -95,29 +95,32 @@ public class McpTableCreator(
|
|||
var columns = BuildColumns(definition);
|
||||
|
||||
var context = await queryExecutor.ResolveDataSourceAsync();
|
||||
if (context.DataSourceType != DataSourceTypeEnum.Mssql)
|
||||
{
|
||||
throw new UserFriendlyException(localizer[McpErrorCodes.TableProviderNotSupported, context.DataSourceType.ToString()]);
|
||||
}
|
||||
var isPostgreSql = context.DataSourceType == DataSourceTypeEnum.Postgresql;
|
||||
|
||||
var existing = await context.Repository.ExecuteScalarAsync<int?>(
|
||||
"SELECT OBJECT_ID(@name, 'U')",
|
||||
var existing = await context.Repository.ExecuteScalarAsync<object?>(
|
||||
isPostgreSql ? "SELECT to_regclass(@name)::text" : "SELECT OBJECT_ID(@name, 'U')",
|
||||
context.ConnectionString,
|
||||
new Dictionary<string, object> { ["name"] = $"{SchemaName}.{tableName}" });
|
||||
new Dictionary<string, object> { ["name"] = isPostgreSql ? $"public.\"{tableName}\"" : $"{SchemaName}.{tableName}" });
|
||||
|
||||
if (existing is not null)
|
||||
if (existing is not null and not DBNull)
|
||||
{
|
||||
throw new UserFriendlyException(localizer[McpErrorCodes.TableAlreadyExists, tableName]);
|
||||
}
|
||||
|
||||
// Dosya biçimi tasarımcınınkiyle aynı T-SQL'dir; PostgreSQL eşi ondan üretilir ve PG veri
|
||||
// kaynağında çalıştırılan da o eştir.
|
||||
var script = BuildScript(tableName, columns);
|
||||
if (!PostgreSqlTableScript.TryConvert(script, out var postgreSqlScript))
|
||||
{
|
||||
throw new InvalidOperationException($"PostgreSQL script could not be generated for {tableName}.");
|
||||
}
|
||||
|
||||
// GO yalnizca SSMS ayiricidir; tek batch oldugu icin calistirirken atilir, dosyada kalir.
|
||||
await context.Repository.ExecuteAsync(
|
||||
string.Join('\n', script.Split('\n').Where(line => line.Trim() != "GO")),
|
||||
isPostgreSql ? postgreSqlScript : string.Join('\n', script.Split('\n').Where(line => line.Trim() != "GO")),
|
||||
context.ConnectionString);
|
||||
|
||||
var seedFilePath = SaveSeedScript(tableName, script);
|
||||
var (seedFilePath, _) = PostgreSqlTableScript.WriteSeedFiles(configuration, tableName, script);
|
||||
|
||||
return new McpCreatedTable(tableName, script, seedFilePath);
|
||||
}
|
||||
|
|
@ -264,25 +267,6 @@ public class McpTableCreator(
|
|||
return $" [{column.Name}] {type} {nullPart}{defaultPart}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tasarimcinin <c>SaveTableScriptAsync</c> ile yazdigi yol.
|
||||
/// Dosyasiz tablo veritabani sifirlandiginda geri gelmezdi; yazilamazsa islem hata verir.
|
||||
/// </summary>
|
||||
private string SaveSeedScript(string tableName, string script)
|
||||
{
|
||||
var outputPath = SeedPathResolver.GetPath(
|
||||
configuration,
|
||||
SeedPathResolver.GetProviderFolderName(isPostgreSql: false),
|
||||
SeedPathResolver.ObjectFolder);
|
||||
|
||||
Directory.CreateDirectory(outputPath);
|
||||
|
||||
var filePath = Path.Combine(outputPath, $"{tableName}.sql");
|
||||
File.WriteAllText(filePath, script);
|
||||
|
||||
return $"{SeedPathResolver.SqlFolder}/{SeedPathResolver.ObjectFolder}/{tableName}.sql";
|
||||
}
|
||||
|
||||
private void EnsureSafeIdentifier(string identifier)
|
||||
{
|
||||
if (!SafeIdentifierRegex.IsMatch(identifier))
|
||||
|
|
|
|||
|
|
@ -671,14 +671,15 @@ FROM (
|
|||
|
||||
try
|
||||
{
|
||||
var outputPath = ResolveSqlDataOutputPath();
|
||||
Directory.CreateDirectory(outputPath);
|
||||
|
||||
// Tasarımcı scripti T-SQL'dir; uygulama iki sağlayıcıda da kurulabildiği için PostgreSQL
|
||||
// eşi aynı anda yazılır (postgres/object).
|
||||
var safeFileName = string.Concat(input.FileName.Trim().Split(Path.GetInvalidFileNameChars()));
|
||||
var filePath = Path.Combine(outputPath, $"{safeFileName}.sql");
|
||||
File.WriteAllText(filePath, input.SqlScript);
|
||||
var (sqlServerPath, postgreSqlPath) = PostgreSqlTableScript.WriteSeedFiles(_configuration, safeFileName, input.SqlScript);
|
||||
|
||||
_logger.LogInformation("SQL seed file saved: {FilePath}", filePath);
|
||||
_logger.LogInformation("SQL seed file saved: {FilePath}", sqlServerPath);
|
||||
|
||||
if (postgreSqlPath is null)
|
||||
_logger.LogWarning("PostgreSQL counterpart of {FilePath} could not be generated; write it by hand.", sqlServerPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -771,20 +772,15 @@ FROM (
|
|||
{
|
||||
try
|
||||
{
|
||||
var outputPath = ResolveSqlDataOutputPath();
|
||||
if (!Directory.Exists(outputPath))
|
||||
return;
|
||||
|
||||
var safeFileName = string.Concat(fileName.Split(Path.GetInvalidFileNameChars()));
|
||||
if (string.IsNullOrWhiteSpace(safeFileName))
|
||||
return;
|
||||
|
||||
var filePath = Path.Combine(outputPath, $"{safeFileName}.sql");
|
||||
if (!File.Exists(filePath))
|
||||
return;
|
||||
|
||||
File.Delete(filePath);
|
||||
_logger.LogInformation("SQL object script deleted: {FilePath}", filePath);
|
||||
foreach (var filePath in ResolveSeedObjectPaths().Select(path => Path.Combine(path, $"{safeFileName}.sql")).Where(File.Exists))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
_logger.LogInformation("SQL object script deleted: {FilePath}", filePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -922,10 +918,6 @@ FROM (
|
|||
|
||||
try
|
||||
{
|
||||
var outputPath = ResolveSqlDataOutputPath();
|
||||
if (!Directory.Exists(outputPath))
|
||||
return Task.CompletedTask;
|
||||
|
||||
foreach (var rawName in input.FileNames.Distinct())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawName))
|
||||
|
|
@ -939,12 +931,11 @@ FROM (
|
|||
if (string.IsNullOrWhiteSpace(safeFileName))
|
||||
continue;
|
||||
|
||||
var filePath = Path.Combine(outputPath, $"{safeFileName}.sql");
|
||||
if (!File.Exists(filePath))
|
||||
continue;
|
||||
|
||||
File.Delete(filePath);
|
||||
_logger.LogInformation("SQL seed file deleted: {FilePath}", filePath);
|
||||
foreach (var filePath in ResolveSeedObjectPaths().Select(path => Path.Combine(path, $"{safeFileName}.sql")).Where(File.Exists))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
_logger.LogInformation("SQL seed file deleted: {FilePath}", filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -971,4 +962,14 @@ FROM (
|
|||
return SeedPathResolver.GetPath(_configuration, providerDirectoryName, SeedPathResolver.ObjectFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// İki sağlayıcının <c>object</c> klasörü. Düşürülen nesnenin dosyası ikisinden de silinir; yalnızca
|
||||
/// aktif sağlayıcıdan silinseydi diğer sağlayıcıdaki kurulum nesneyi yeniden yaratırdı.
|
||||
/// </summary>
|
||||
private string[] ResolveSeedObjectPaths() =>
|
||||
[
|
||||
SeedPathResolver.GetPath(_configuration, SeedPathResolver.SqlFolder, SeedPathResolver.ObjectFolder),
|
||||
SeedPathResolver.GetPath(_configuration, SeedPathResolver.PostgresFolder, SeedPathResolver.ObjectFolder),
|
||||
];
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,13 +177,19 @@ public class WizardDataSeeder(
|
|||
seedFile.ListForm.ApplyTo(listForm);
|
||||
await repoListForm.InsertAsync(listForm, autoSave: true);
|
||||
|
||||
foreach (var fieldDto in seedFile.Fields)
|
||||
// Alanlar ve is akisi tek SaveChanges ile yazilir; satir basina kayit, izlenen varlik sayisi
|
||||
// buyudukce her kayitta degisiklik taramasini tekrarlar ve seed suresini katlar.
|
||||
var fields = seedFile.Fields.Select(fieldDto =>
|
||||
{
|
||||
var field = new ListFormField();
|
||||
fieldDto.ApplyTo(field);
|
||||
await repoListFormField.InsertAsync(field, autoSave: true);
|
||||
}
|
||||
return field;
|
||||
}).ToList();
|
||||
|
||||
if (fields.Count > 0)
|
||||
await repoListFormField.InsertManyAsync(fields, autoSave: true);
|
||||
|
||||
var workflows = new List<ListFormWorkflow>();
|
||||
foreach (var criteria in seedFile.ListForm.WorkflowDto.Criteria)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(criteria.Id))
|
||||
|
|
@ -192,7 +198,7 @@ public class WizardDataSeeder(
|
|||
continue;
|
||||
}
|
||||
|
||||
await repoListFormWorkflow.InsertAsync(new ListFormWorkflow(criteria.Id)
|
||||
workflows.Add(new ListFormWorkflow(criteria.Id)
|
||||
{
|
||||
ListFormCode = string.IsNullOrWhiteSpace(criteria.ListFormCode) ? listFormCode : criteria.ListFormCode,
|
||||
Kind = criteria.Kind,
|
||||
|
|
@ -209,8 +215,11 @@ public class WizardDataSeeder(
|
|||
PositionX = criteria.PositionX,
|
||||
PositionY = criteria.PositionY,
|
||||
CompareOutcomesJson = JsonSerializer.Serialize(criteria.CompareOutcomes ?? []),
|
||||
}, autoSave: true);
|
||||
});
|
||||
}
|
||||
|
||||
if (workflows.Count > 0)
|
||||
await repoListFormWorkflow.InsertManyAsync(workflows, autoSave: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Sozsoft.Platform.EntityFrameworkCore;
|
||||
using Volo.Abp.Autofac;
|
||||
using Volo.Abp.Caching;
|
||||
|
|
@ -22,9 +21,10 @@ public class PlatformDbMigratorModule : AbpModule
|
|||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var hostEnvironment = context.Services.GetSingletonInstance<IHostEnvironment>();
|
||||
|
||||
ConfigureVirtualFileSystem(context, hostEnvironment);
|
||||
Configure<AbpVirtualFileSystemOptions>(options =>
|
||||
{
|
||||
options.FileSets.AddEmbedded<PlatformDbMigratorModule>();
|
||||
});
|
||||
Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "Platform:"; });
|
||||
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
|
|
@ -35,21 +35,6 @@ public class PlatformDbMigratorModule : AbpModule
|
|||
options.ConnectionStrings.Default = connectionString;
|
||||
});
|
||||
}
|
||||
|
||||
private void ConfigureVirtualFileSystem(ServiceConfigurationContext context, IHostEnvironment hostEnvironment)
|
||||
{
|
||||
Configure<AbpVirtualFileSystemOptions>(options =>
|
||||
{
|
||||
options.FileSets.AddEmbedded<PlatformDbMigratorModule>();
|
||||
// if (hostEnvironment.IsDevelopment())
|
||||
// {
|
||||
// options.FileSets.ReplaceEmbeddedByPhysical<PlatformDbMigratorModule>(
|
||||
// Path.Combine(hostEnvironment.ContentRootPath,
|
||||
// // $"..{Path.DirectorySeparatorChar}Sozsoft.Platform.DbMigrator"));
|
||||
// $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}"));
|
||||
// }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ public class ListFormSeedDataApplier(
|
|||
listForm, fields, keyFieldName, row, repository, connectionString, dataSourceType,
|
||||
overwriteExisting, existingKeys, batch);
|
||||
|
||||
if (batch.ParameterCount >= InsertBatch.MaxParameterCount)
|
||||
if (batch.IsFull)
|
||||
await batch.FlushAsync(repository, connectionString);
|
||||
|
||||
switch (outcome)
|
||||
|
|
@ -508,10 +508,16 @@ public class ListFormSeedDataApplier(
|
|||
/// <c>@p{sira}_{ad}</c> olarak yeniden adlandirilir; boylece ekranin kendi insert komutu
|
||||
/// (<c>InsertCommand</c>) da degismeden toplu calisir. Komut basina parametre sayisi SQL Server
|
||||
/// sinirinin (2100) altinda tutulur.
|
||||
/// <para>
|
||||
/// Satir sayisi da sinirlidir: komutun derlenme suresi satir sayisiyla buyur ve bos tablo
|
||||
/// doldukca istatistik guncellemesi plani tekrar tekrar yeniden derletir. 400 satirlik komutla
|
||||
/// 12 bin dil metni ~25 sn, 25 satirlik komutla ~4 sn surer.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private sealed class InsertBatch
|
||||
{
|
||||
public const int MaxParameterCount = 2000;
|
||||
private const int MaxParameterCount = 2000;
|
||||
private const int MaxRowCount = 25;
|
||||
|
||||
private static readonly Regex ParameterRegex = new(@"(?<![@\w])@(\w+)", RegexOptions.Compiled);
|
||||
|
||||
|
|
@ -519,7 +525,7 @@ public class ListFormSeedDataApplier(
|
|||
private readonly Dictionary<string, object> parameters = [];
|
||||
private int rowCount;
|
||||
|
||||
public int ParameterCount => parameters.Count;
|
||||
public bool IsFull => rowCount >= MaxRowCount || parameters.Count >= MaxParameterCount;
|
||||
|
||||
public void Add(string rowSql, Dictionary<string, object> rowParameters)
|
||||
{
|
||||
|
|
|
|||
366
api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs
Normal file
366
api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Sozsoft.Platform.Data.Seeds;
|
||||
|
||||
/// <summary>
|
||||
/// Tablo tasarımcısının ve MCP <c>create_table</c>'ın ürettiği SQL Server tablo scriptini
|
||||
/// (<c>sql/object/{Tablo}.sql</c>) aynı tablonun PostgreSQL eşine (<c>postgres/object/{Tablo}.sql</c>)
|
||||
/// çevirir. Uygulama iki sağlayıcıda da çalıştığı için uygulama içinden yazılan her tablo scripti
|
||||
/// iki dosya olarak yazılır; elle yazılan seed dosyasıyla aynı kurala uyar (idempotent, şema
|
||||
/// <c>public</c>, constraint <c>pg_constraint</c> kontrollü).
|
||||
/// <para>
|
||||
/// Yalnızca tablo scripti biçimi tanınır: <c>CREATE TABLE</c>, <c>ADD CONSTRAINT</c> (CHECK / UNIQUE /
|
||||
/// FOREIGN KEY) ve <c>CREATE INDEX</c> batch'leri. Görünüm, prosedür ve fonksiyon gövdesi diyalektler
|
||||
/// arasında güvenle çevrilemez; tanınmayan batch'te <see cref="TryConvert"/> false döner.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static partial class PostgreSqlTableScript
|
||||
{
|
||||
/// <summary>
|
||||
/// Tablo scriptini iki sağlayıcının <c>object</c> klasörüne yazar ve seed köküne göreli yollarını
|
||||
/// döndürür. PostgreSQL eşi çevrilemezse (tanınmayan batch) yalnızca SQL Server dosyası yazılır ve
|
||||
/// ikinci yol <c>null</c> döner; çağıran bunu bildirir.
|
||||
/// </summary>
|
||||
public static (string SqlServerPath, string? PostgreSqlPath) WriteSeedFiles(
|
||||
IConfiguration? configuration,
|
||||
string tableName,
|
||||
string sqlServerScript)
|
||||
{
|
||||
var fileName = $"{tableName}.sql";
|
||||
|
||||
var sqlServerFolder = SeedPathResolver.GetPath(configuration, SeedPathResolver.SqlFolder, SeedPathResolver.ObjectFolder);
|
||||
Directory.CreateDirectory(sqlServerFolder);
|
||||
File.WriteAllText(Path.Combine(sqlServerFolder, fileName), sqlServerScript);
|
||||
var sqlServerPath = $"{SeedPathResolver.SqlFolder}/{SeedPathResolver.ObjectFolder}/{fileName}";
|
||||
|
||||
if (!TryConvert(sqlServerScript, out var postgreSqlScript))
|
||||
return (sqlServerPath, null);
|
||||
|
||||
var postgreSqlFolder = SeedPathResolver.GetPath(configuration, SeedPathResolver.PostgresFolder, SeedPathResolver.ObjectFolder);
|
||||
Directory.CreateDirectory(postgreSqlFolder);
|
||||
File.WriteAllText(Path.Combine(postgreSqlFolder, fileName), postgreSqlScript);
|
||||
|
||||
return (sqlServerPath, $"{SeedPathResolver.PostgresFolder}/{SeedPathResolver.ObjectFolder}/{fileName}");
|
||||
}
|
||||
|
||||
public static bool TryConvert(string sqlServerScript, out string postgreSqlScript)
|
||||
{
|
||||
postgreSqlScript = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(sqlServerScript))
|
||||
return false;
|
||||
|
||||
var script = UseStatementRegex().Replace(sqlServerScript, string.Empty);
|
||||
var bitColumns = BitColumnRegex().Matches(script)
|
||||
.Select(m => m.Groups["name"].Value)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var statements = new List<string>();
|
||||
foreach (var rawBatch in BatchSeparatorRegex().Split(script))
|
||||
{
|
||||
var batch = BlockCommentRegex().Replace(rawBatch, " ");
|
||||
batch = LineCommentRegex().Replace(batch, string.Empty).Trim();
|
||||
if (batch.Length == 0)
|
||||
continue;
|
||||
|
||||
// Tasarımcı birden çok ALTER/INDEX deyimini aynı batch'e yazar; her biri ayrı çevrilir.
|
||||
foreach (var statement in SplitStatements(batch))
|
||||
{
|
||||
var converted = ConvertStatement(statement, bitColumns);
|
||||
if (converted is null)
|
||||
return false;
|
||||
|
||||
statements.Add(converted);
|
||||
}
|
||||
}
|
||||
|
||||
if (statements.Count == 0)
|
||||
return false;
|
||||
|
||||
postgreSqlScript = string.Join("\n\n", statements) + "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch'i deyimlere ayırır: <c>IF … BEGIN … END</c> koruması kaldırılır, kalan metin
|
||||
/// <c>ALTER TABLE</c> / <c>CREATE TABLE</c> / <c>CREATE INDEX</c> başlangıçlarından bölünür.
|
||||
/// </summary>
|
||||
private static IEnumerable<string> SplitStatements(string batch)
|
||||
{
|
||||
var body = GuardRegex().Replace(batch, string.Empty);
|
||||
body = BeginEndRegex().Replace(body, " ");
|
||||
|
||||
return StatementStartRegex()
|
||||
.Split(body)
|
||||
.Select(a => a.Trim().TrimEnd(';').Trim())
|
||||
.Where(a => a.Length > 0);
|
||||
}
|
||||
|
||||
private static string? ConvertStatement(string statement, HashSet<string> bitColumns)
|
||||
{
|
||||
if (statement.StartsWith("CREATE TABLE", StringComparison.OrdinalIgnoreCase))
|
||||
return ConvertCreateTable(statement);
|
||||
|
||||
var constraint = AddConstraintRegex().Match(statement);
|
||||
if (constraint.Success)
|
||||
{
|
||||
var table = Identifier(constraint.Groups["table"].Value);
|
||||
var name = constraint.Groups["name"].Value;
|
||||
var definition = ConvertConstraintDefinition(constraint.Groups["definition"].Value.Trim(), bitColumns);
|
||||
|
||||
return "DO $$\nBEGIN\n" +
|
||||
$" IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = '{name.Replace("'", "''")}') THEN\n" +
|
||||
$" ALTER TABLE {table} ADD CONSTRAINT {Quote(name)} {definition};\n" +
|
||||
" END IF;\nEND\n$$;";
|
||||
}
|
||||
|
||||
var index = CreateIndexRegex().Match(statement);
|
||||
if (index.Success)
|
||||
{
|
||||
var sql = new StringBuilder("CREATE ");
|
||||
if (index.Groups["unique"].Success)
|
||||
sql.Append("UNIQUE ");
|
||||
|
||||
sql.Append($"INDEX IF NOT EXISTS {Quote(index.Groups["name"].Value)} ON {Identifier(index.Groups["table"].Value)} ({ColumnList(index.Groups["columns"].Value)})");
|
||||
|
||||
if (index.Groups["include"].Success)
|
||||
sql.Append($" INCLUDE ({ColumnList(index.Groups["include"].Value)})");
|
||||
|
||||
if (index.Groups["where"].Success)
|
||||
sql.Append(" WHERE ").Append(Expression(index.Groups["where"].Value.Trim(), bitColumns));
|
||||
|
||||
return sql.Append(';').ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? ConvertCreateTable(string statement)
|
||||
{
|
||||
var match = CreateTableRegex().Match(statement);
|
||||
if (!match.Success)
|
||||
return null;
|
||||
|
||||
var inner = match.Groups["body"].Value;
|
||||
var primaryKey = PrimaryKeyRegex().Match(inner);
|
||||
var columnPart = primaryKey.Success ? inner[..primaryKey.Index] : inner;
|
||||
|
||||
var lines = new List<string>();
|
||||
foreach (var rawLine in columnPart.Split('\n'))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawLine))
|
||||
continue;
|
||||
|
||||
var column = ColumnRegex().Match(rawLine);
|
||||
if (!column.Success)
|
||||
return null;
|
||||
|
||||
var type = ColumnType(column.Groups["type"].Value);
|
||||
if (type is null)
|
||||
return null;
|
||||
|
||||
var line = new StringBuilder($" {Quote(column.Groups["name"].Value)} {type}");
|
||||
if (column.Groups["identity"].Success)
|
||||
line.Append(" GENERATED BY DEFAULT AS IDENTITY");
|
||||
|
||||
if (column.Groups["null"].Success)
|
||||
line.Append(' ').Append(column.Groups["null"].Value.Trim().ToUpperInvariant());
|
||||
|
||||
if (column.Groups["default"].Success)
|
||||
line.Append(" DEFAULT ").Append(DefaultValue(column.Groups["default"].Value, type));
|
||||
|
||||
lines.Add(line.ToString());
|
||||
}
|
||||
|
||||
if (primaryKey.Success)
|
||||
{
|
||||
var columns = string.Join(", ", primaryKey.Groups["columns"].Value
|
||||
.Split(',')
|
||||
.Select(a => Quote(SortOrderRegex().Replace(a.Trim(), string.Empty).Trim('[', ']', ' '))));
|
||||
|
||||
lines.Add($" CONSTRAINT {Quote(primaryKey.Groups["name"].Value)} PRIMARY KEY ({columns})");
|
||||
}
|
||||
|
||||
return $"CREATE TABLE IF NOT EXISTS {Identifier(match.Groups["table"].Value)}\n(\n{string.Join(",\n", lines)}\n);";
|
||||
}
|
||||
|
||||
private static string ConvertConstraintDefinition(string definition, HashSet<string> bitColumns)
|
||||
{
|
||||
definition = WhitespaceRegex().Replace(definition, " ");
|
||||
definition = ClusteringRegex().Replace(definition, string.Empty);
|
||||
definition = SortOrderInListRegex().Replace(definition, "$1");
|
||||
|
||||
return Expression(definition, bitColumns);
|
||||
}
|
||||
|
||||
private static string ColumnList(string columns) =>
|
||||
string.Join(", ", columns.Split(',')
|
||||
.Select(a => a.Trim())
|
||||
.Where(a => a.Length > 0)
|
||||
.Select(a =>
|
||||
{
|
||||
var descending = a.EndsWith(" DESC", StringComparison.OrdinalIgnoreCase);
|
||||
var name = Quote(SortOrderRegex().Replace(a, string.Empty).Trim('[', ']', ' '));
|
||||
return descending ? name + " DESC" : name;
|
||||
}));
|
||||
|
||||
/// <summary><c>[x]</c> → <c>"x"</c>, <c>N'..'</c> → <c>'..'</c>, bit kolon <c>= 0/1</c> → <c>= FALSE/TRUE</c>.</summary>
|
||||
private static string Expression(string expression, HashSet<string> bitColumns)
|
||||
{
|
||||
expression = Identifier(expression);
|
||||
expression = UnicodeLiteralRegex().Replace(expression, "'");
|
||||
|
||||
return BitComparisonRegex().Replace(expression, m =>
|
||||
bitColumns.Contains(m.Groups["column"].Value)
|
||||
|| m.Groups["column"].Value.StartsWith("Is", StringComparison.Ordinal)
|
||||
|| m.Groups["column"].Value.StartsWith("Has", StringComparison.Ordinal)
|
||||
? $"\"{m.Groups["column"].Value}\" = {(m.Groups["value"].Value == "1" ? "TRUE" : "FALSE")}"
|
||||
: m.Value);
|
||||
}
|
||||
|
||||
/// <summary><c>[dbo].[X]</c> → <c>public."X"</c>, kalan <c>[x]</c> → <c>"x"</c>.</summary>
|
||||
private static string Identifier(string text)
|
||||
{
|
||||
text = DboIdentifierRegex().Replace(text, m => "public." + Quote(m.Groups["name"].Value));
|
||||
return BracketIdentifierRegex().Replace(text, m => Quote(m.Groups["name"].Value));
|
||||
}
|
||||
|
||||
private static string Quote(string name) => "\"" + name.Replace("\"", "\"\"") + "\"";
|
||||
|
||||
private static string? ColumnType(string sqlServerType)
|
||||
{
|
||||
var type = WhitespaceRegex().Replace(sqlServerType.Trim().ToLowerInvariant(), string.Empty);
|
||||
|
||||
if (type is "nvarchar(max)" or "varchar(max)")
|
||||
return "text";
|
||||
|
||||
var sized = SizedTypeRegex().Match(type);
|
||||
if (sized.Success)
|
||||
{
|
||||
var size = sized.Groups["size"].Value;
|
||||
return sized.Groups["name"].Value switch
|
||||
{
|
||||
"nvarchar" or "varchar" => $"varchar({size})",
|
||||
"nchar" or "char" => $"char({size})",
|
||||
"decimal" or "numeric" => $"numeric({size.Replace(",", ", ")})",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
return type switch
|
||||
{
|
||||
"uniqueidentifier" => "uuid",
|
||||
"datetime2" or "datetime" or "smalldatetime" => "timestamp",
|
||||
"datetimeoffset" => "timestamp with time zone",
|
||||
"date" => "date",
|
||||
"time" => "time",
|
||||
"bit" => "boolean",
|
||||
"int" => "integer",
|
||||
"bigint" => "bigint",
|
||||
"smallint" or "tinyint" => "smallint",
|
||||
"float" => "double precision",
|
||||
"real" => "real",
|
||||
"money" => "numeric(19, 4)",
|
||||
"varbinary(max)" or "image" => "bytea",
|
||||
"decimal" => "numeric(18, 0)",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static string DefaultValue(string value, string postgreSqlType)
|
||||
{
|
||||
var text = value.Trim();
|
||||
while (text.StartsWith('(') && text.EndsWith(')'))
|
||||
text = text[1..^1].Trim();
|
||||
|
||||
switch (text.ToUpperInvariant())
|
||||
{
|
||||
case "NEWID()":
|
||||
case "NEWSEQUENTIALID()":
|
||||
return "gen_random_uuid()";
|
||||
case "GETUTCDATE()":
|
||||
case "SYSUTCDATETIME()":
|
||||
return "(NOW() AT TIME ZONE 'UTC')";
|
||||
case "GETDATE()":
|
||||
case "SYSDATETIME()":
|
||||
return "LOCALTIMESTAMP";
|
||||
}
|
||||
|
||||
if (postgreSqlType == "boolean")
|
||||
return text == "1" ? "TRUE" : "FALSE";
|
||||
|
||||
return UnicodeLiteralRegex().Replace(text, "'");
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^\s*USE\s+\[[^\]]+\]\s*$", RegexOptions.Multiline | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex UseStatementRegex();
|
||||
|
||||
[GeneratedRegex(@"^\s*GO\s*$", RegexOptions.Multiline | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex BatchSeparatorRegex();
|
||||
|
||||
[GeneratedRegex(@"/\*.*?\*/", RegexOptions.Singleline)]
|
||||
private static partial Regex BlockCommentRegex();
|
||||
|
||||
[GeneratedRegex(@"--[^\r\n]*")]
|
||||
private static partial Regex LineCommentRegex();
|
||||
|
||||
[GeneratedRegex(@"^\s*IF\s+(?:NOT\s+EXISTS\s*\((?>[^()]+|\((?<d>)|\)(?<-d>))*\)|(?:OBJECT_ID|INDEXPROPERTY)\s*\((?>[^()]+|\((?<d>)|\)(?<-d>))*\)\s+IS\s+(?:NOT\s+)?NULL)", RegexOptions.IgnoreCase | RegexOptions.Multiline)]
|
||||
private static partial Regex GuardRegex();
|
||||
|
||||
[GeneratedRegex(@"^\s*(?:BEGIN|END)\s*;?\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline)]
|
||||
private static partial Regex BeginEndRegex();
|
||||
|
||||
[GeneratedRegex(@"(?=\bALTER\s+TABLE\b|\bCREATE\s+TABLE\b|\bCREATE\s+(?:UNIQUE\s+)?(?:(?:NON)?CLUSTERED\s+)?INDEX\b)", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex StatementStartRegex();
|
||||
|
||||
[GeneratedRegex(@"^CREATE\s+TABLE\s+(?<table>\S+)\s*\((?<body>.*)\)\s*(?:ON\s+\[PRIMARY\])?\s*$", RegexOptions.Singleline | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex CreateTableRegex();
|
||||
|
||||
[GeneratedRegex(@"CONSTRAINT\s+\[(?<name>[^\]]+)\]\s+PRIMARY\s+KEY(?:\s+(?:NON)?CLUSTERED)?\s*\((?<columns>[^)]*)\)", RegexOptions.Singleline | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex PrimaryKeyRegex();
|
||||
|
||||
[GeneratedRegex(@"^\s*\[(?<name>[^\]]+)\]\s+(?<type>[A-Za-z0-9]+(?:\s*\([^)]*\))?)(?<identity>\s+IDENTITY\s*\(\s*\d+\s*,\s*\d+\s*\))?(?<null>\s+NOT\s+NULL|\s+NULL)?(?:\s+DEFAULT\s+(?<default>.+?))?\s*,?\s*$", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex ColumnRegex();
|
||||
|
||||
[GeneratedRegex(@"^ALTER\s+TABLE\s+(?<table>\S+)\s+ADD\s+CONSTRAINT\s+\[(?<name>[^\]]+)\]\s+(?<definition>(?:CHECK|UNIQUE|FOREIGN\s+KEY)\b.*)$", RegexOptions.Singleline | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex AddConstraintRegex();
|
||||
|
||||
[GeneratedRegex(@"^CREATE\s+(?<unique>UNIQUE\s+)?(?:(?:NON)?CLUSTERED\s+)?INDEX\s+\[(?<name>[^\]]+)\]\s+ON\s+(?<table>\S+)\s*\((?<columns>[^)]*)\)(?:\s+INCLUDE\s*\((?<include>[^)]*)\))?(?:\s+WHERE\s+(?<where>.+?))?\s*$", RegexOptions.Singleline | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex CreateIndexRegex();
|
||||
|
||||
[GeneratedRegex(@"\b(?:NON)?CLUSTERED\b\s*", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex ClusteringRegex();
|
||||
|
||||
[GeneratedRegex(@"(\])\s+(?:ASC|DESC)\b", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex SortOrderInListRegex();
|
||||
|
||||
[GeneratedRegex(@"\s+(?:ASC|DESC)$", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex SortOrderRegex();
|
||||
|
||||
[GeneratedRegex(@"\[dbo\]\.\[(?<name>[^\]]+)\]", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex DboIdentifierRegex();
|
||||
|
||||
[GeneratedRegex(@"\[(?<name>[^\]]+)\]")]
|
||||
private static partial Regex BracketIdentifierRegex();
|
||||
|
||||
[GeneratedRegex(@"\bN'")]
|
||||
private static partial Regex UnicodeLiteralRegex();
|
||||
|
||||
[GeneratedRegex(@"""(?<column>[A-Za-z0-9_]+)""\s*=\s*(?<value>[01])\b")]
|
||||
private static partial Regex BitComparisonRegex();
|
||||
|
||||
[GeneratedRegex(@"\[(?<name>[^\]]+)\]\s+bit\b", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex BitColumnRegex();
|
||||
|
||||
[GeneratedRegex(@"^(?<name>[a-z]+)\((?<size>[0-9,]+)\)$")]
|
||||
private static partial Regex SizedTypeRegex();
|
||||
|
||||
[GeneratedRegex(@"\s+")]
|
||||
private static partial Regex WhitespaceRegex();
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
|||
else if (defaultField.Value == PlatformConsts.DefaultValues.UserName)
|
||||
value = CurrentUser.UserName;
|
||||
else if (defaultField.Value == PlatformConsts.DefaultValues.Roles)
|
||||
value = CurrentUser.Roles; //.JoinAsString("','");
|
||||
value = CurrentUser.Roles;
|
||||
else if (defaultField.Value == PlatformConsts.DefaultValues.Date)
|
||||
value = Clock.Now.Date;
|
||||
else if (defaultField.Value == PlatformConsts.DefaultValues.Now)
|
||||
|
|
@ -117,8 +117,6 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
|||
}
|
||||
}
|
||||
}
|
||||
//value = field.Value;
|
||||
//TODO: artirilabilir
|
||||
break;
|
||||
case FieldCustomValueTypeEnum.QueryParams:
|
||||
if (queryParameters != null)
|
||||
|
|
|
|||
|
|
@ -169,7 +169,6 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
|||
// oncelik Command alanindadir, dolu ise silme islemi buradaki sorguya yonlendirilir
|
||||
if (!string.IsNullOrEmpty(command))
|
||||
{
|
||||
// sql = NormalizeCollectionParameterCommand(command, parameters, dataSourceType);
|
||||
sql = command;
|
||||
}
|
||||
else
|
||||
|
|
@ -256,78 +255,5 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
|||
_ => "1 = 0",
|
||||
};
|
||||
}
|
||||
|
||||
// private static string NormalizeCollectionParameterCommand(
|
||||
// string command,
|
||||
// Dictionary<string, object> parameters,
|
||||
// DataSourceTypeEnum dataSourceType)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(command) || parameters == null || parameters.Count == 0)
|
||||
// {
|
||||
// return command;
|
||||
// }
|
||||
|
||||
// var sql = command;
|
||||
// foreach (var parameter in parameters)
|
||||
// {
|
||||
// if (!IsCollectionParameter(parameter.Value))
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// var escapedParameterName = Regex.Escape(parameter.Key);
|
||||
// sql = dataSourceType switch
|
||||
// {
|
||||
// DataSourceTypeEnum.Mssql => NormalizeMssqlCollectionParameter(sql, escapedParameterName, parameter.Key),
|
||||
// DataSourceTypeEnum.Postgresql => NormalizePostgresqlCollectionParameter(sql, escapedParameterName, parameter.Key),
|
||||
// _ => sql
|
||||
// };
|
||||
// }
|
||||
|
||||
// return sql;
|
||||
// }
|
||||
|
||||
// private static bool IsCollectionParameter(object value)
|
||||
// {
|
||||
// return value is System.Collections.IEnumerable
|
||||
// && value is not string
|
||||
// && value is not byte[];
|
||||
// }
|
||||
|
||||
// private static string NormalizeMssqlCollectionParameter(
|
||||
// string sql,
|
||||
// string escapedParameterName,
|
||||
// string parameterName)
|
||||
// {
|
||||
// sql = Regex.Replace(
|
||||
// sql,
|
||||
// $@"IN\s*\(\s*@{escapedParameterName}\s*\)",
|
||||
// $"IN @{parameterName}",
|
||||
// RegexOptions.IgnoreCase);
|
||||
|
||||
// return Regex.Replace(
|
||||
// sql,
|
||||
// $@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b",
|
||||
// match => $"{match.Groups["column"].Value} IN @{parameterName}",
|
||||
// RegexOptions.IgnoreCase);
|
||||
// }
|
||||
|
||||
// private static string NormalizePostgresqlCollectionParameter(
|
||||
// string sql,
|
||||
// string escapedParameterName,
|
||||
// string parameterName)
|
||||
// {
|
||||
// sql = Regex.Replace(
|
||||
// sql,
|
||||
// $@"IN\s*\(\s*@{escapedParameterName}\s*\)",
|
||||
// $"= ANY(@{parameterName})",
|
||||
// RegexOptions.IgnoreCase);
|
||||
|
||||
// return Regex.Replace(
|
||||
// sql,
|
||||
// $@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b",
|
||||
// match => $"{match.Groups["column"].Value} = ANY(@{parameterName})",
|
||||
// RegexOptions.IgnoreCase);
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,12 @@ public class SpRepository : ISpRepository
|
|||
|
||||
await using var connection = CreateDbConnection(dataSourceType, connectionString);
|
||||
await connection.OpenAsync();
|
||||
await using var command = CreateDbCommand(connection, sp, dataSourceType, parameters);
|
||||
|
||||
// PostgreSQL'de prosedür `CALL`, fonksiyon `SELECT` ile çağrılır; tür adından bilinmez, katalogdan okunur.
|
||||
var isPostgreSqlProcedure = dataSourceType == DataSourceTypeEnum.Postgresql
|
||||
&& await IsPostgreSqlProcedureAsync(connection, PostgreSqlRoutineName(sp));
|
||||
|
||||
await using var command = CreateDbCommand(connection, sp, dataSourceType, parameters, isPostgreSqlProcedure);
|
||||
|
||||
return await ExecuteQueryAsync(command);
|
||||
}
|
||||
|
|
@ -55,7 +60,16 @@ public class SpRepository : ISpRepository
|
|||
};
|
||||
}
|
||||
|
||||
private DbCommand CreateDbCommand(DbConnection connection, string sp, DataSourceTypeEnum dataSourceType, Dictionary<string, object> parameters)
|
||||
private static async Task<bool> IsPostgreSqlProcedureAsync(DbConnection connection, string name)
|
||||
{
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT p.prokind FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = 'public' AND p.proname = @name LIMIT 1";
|
||||
command.Parameters.Add(new NpgsqlParameter("@name", name));
|
||||
|
||||
return await command.ExecuteScalarAsync() is char kind && kind == 'p';
|
||||
}
|
||||
|
||||
private DbCommand CreateDbCommand(DbConnection connection, string sp, DataSourceTypeEnum dataSourceType, Dictionary<string, object> parameters, bool isPostgreSqlProcedure)
|
||||
{
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandTimeout = 300; // 5 dakika timeout
|
||||
|
|
@ -64,7 +78,7 @@ public class SpRepository : ISpRepository
|
|||
command.CommandText = dataSourceType switch
|
||||
{
|
||||
DataSourceTypeEnum.Mssql => BuildSqlForMssql(sp, parameters),
|
||||
DataSourceTypeEnum.Postgresql => BuildSqlForPostgresql(sp, parameters),
|
||||
DataSourceTypeEnum.Postgresql => BuildSqlForPostgresql(sp, parameters, isPostgreSqlProcedure),
|
||||
_ => throw new Exception("Unsupported database type")
|
||||
};
|
||||
|
||||
|
|
@ -79,11 +93,28 @@ public class SpRepository : ISpRepository
|
|||
: $"EXEC {sp}";
|
||||
}
|
||||
|
||||
private string BuildSqlForPostgresql(string sp, Dictionary<string, object> parameters)
|
||||
private static string BuildSqlForPostgresql(string sp, Dictionary<string, object> parameters, bool isProcedure)
|
||||
{
|
||||
return parameters != null && parameters.Count > 0
|
||||
? $"SELECT * FROM public.{sp}({string.Join(", ", parameters.Keys.Select((k, i) => $"@p{i}"))})"
|
||||
: $"SELECT * FROM public.{sp}()";
|
||||
var arguments = parameters != null && parameters.Count > 0
|
||||
? string.Join(", ", parameters.Keys.Select((k, i) => $"@p{i}"))
|
||||
: string.Empty;
|
||||
|
||||
var routine = $"public.\"{PostgreSqlRoutineName(sp)}\"({arguments})";
|
||||
|
||||
return isProcedure ? $"CALL {routine}" : $"SELECT * FROM {routine}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rutin adı SQL Server'daki gibi PascalCase yazılır (`Hr_S_TimesheetNightly`); PostgreSQL tırnaksız adı
|
||||
/// küçük harfe indirdiği için ad tırnaklanarak kullanılır. `dbo.` öneki SQL Server şemasıdır, PG'de `public`.
|
||||
/// </summary>
|
||||
private static string PostgreSqlRoutineName(string sp)
|
||||
{
|
||||
var name = sp.Split('.').Last().Trim('[', ']', '"');
|
||||
if (name.Length == 0 || !name.All(c => char.IsLetterOrDigit(c) || c == '_'))
|
||||
throw new ArgumentException($"Invalid procedure name: {sp}", nameof(sp));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private void AddParameters(DbCommand command, DataSourceTypeEnum dataSourceType, Dictionary<string, object> parameters)
|
||||
|
|
|
|||
|
|
@ -1225,12 +1225,18 @@ public class PlatformDbContext :
|
|||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Order)), Prefix.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
|
||||
var isActiveComputedColumnSql = _isPostgreSql
|
||||
? "CASE WHEN \"LicenseEndTime\" IS NOT NULL AND CURRENT_DATE > \"LicenseEndTime\"::date THEN false ELSE true END"
|
||||
: "CONVERT(bit, CASE WHEN [LicenseEndTime] IS NOT NULL AND CONVERT(date, GETDATE()) > CONVERT(date, [LicenseEndTime]) THEN 0 ELSE 1 END)";
|
||||
|
||||
b.Property(o => o.IsActive)
|
||||
.HasComputedColumnSql(isActiveComputedColumnSql, stored: false);
|
||||
// PostgreSQL'de hesaplanan kolon yalnızca immutable ifade alır; CURRENT_DATE'e bağlı lisans
|
||||
// kontrolü kolon olarak yazılamaz. Orada değer satırla saklanır, varsayılanı aktiftir.
|
||||
if (_isPostgreSql)
|
||||
{
|
||||
b.Property(o => o.IsActive).HasDefaultValue(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.Property(o => o.IsActive).HasComputedColumnSql(
|
||||
"CONVERT(bit, CASE WHEN [LicenseEndTime] IS NOT NULL AND CONVERT(date, GETDATE()) > CONVERT(date, [LicenseEndTime]) THEN 0 ELSE 1 END)",
|
||||
stored: false);
|
||||
}
|
||||
b.Property(o => o.IsExisting).IsRequired().HasDefaultValue(false);
|
||||
b.Property(o => o.OrganizationName).HasMaxLength(128);
|
||||
b.Property(o => o.Founder).HasMaxLength(128);
|
||||
|
|
@ -1841,6 +1847,11 @@ public class PlatformDbContext :
|
|||
ConfigureProviderSpecificModel(builder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Model SQL Server sözdizimiyle yazılır (kolon tipi, index filtresi, varsayılan değer). PostgreSQL'de
|
||||
/// aynı model bu sözdizimini sağlayıcının karşılığına çevirerek kurulur; SQL Server modeli ve
|
||||
/// migration'ları bundan etkilenmez.
|
||||
/// </summary>
|
||||
private void ConfigureProviderSpecificModel(ModelBuilder builder)
|
||||
{
|
||||
if (!_isPostgreSql)
|
||||
|
|
@ -1848,11 +1859,36 @@ public class PlatformDbContext :
|
|||
return;
|
||||
}
|
||||
|
||||
foreach (var index in builder.Model.GetEntityTypes().SelectMany(entityType => entityType.GetIndexes()))
|
||||
foreach (var entityType in builder.Model.GetEntityTypes())
|
||||
{
|
||||
if (index.GetFilter() == "[IsDeleted] = 0")
|
||||
foreach (var property in entityType.GetProperties())
|
||||
{
|
||||
index.SetFilter("\"IsDeleted\" = FALSE");
|
||||
var columnType = property.GetColumnType();
|
||||
if (columnType is not null)
|
||||
{
|
||||
property.SetColumnType(PostgreSqlModelSyntax.ColumnType(columnType));
|
||||
}
|
||||
|
||||
var defaultValueSql = property.GetDefaultValueSql();
|
||||
if (defaultValueSql is not null)
|
||||
{
|
||||
property.SetDefaultValueSql(PostgreSqlModelSyntax.DefaultValueSql(defaultValueSql));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var index in entityType.GetIndexes())
|
||||
{
|
||||
var filter = index.GetFilter();
|
||||
if (filter is not null)
|
||||
{
|
||||
index.SetFilter(PostgreSqlModelSyntax.Expression(filter));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var checkConstraint in entityType.GetCheckConstraints().ToList())
|
||||
{
|
||||
entityType.RemoveCheckConstraint(checkConstraint.ModelName);
|
||||
entityType.AddCheckConstraint(checkConstraint.ModelName, PostgreSqlModelSyntax.Expression(checkConstraint.Sql));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Sozsoft.Platform.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// EF modelinde SQL Server sözdizimiyle yazılmış kolon tipi, varsayılan değer ve index filtresinin
|
||||
/// PostgreSQL karşılığı. Model tek yerde yazılır; PostgreSQL'de <see cref="PlatformDbContext"/> bu
|
||||
/// çeviriyle kurulur.
|
||||
/// </summary>
|
||||
internal static partial class PostgreSqlModelSyntax
|
||||
{
|
||||
public static string ColumnType(string sqlServerType)
|
||||
{
|
||||
var type = sqlServerType.Trim().ToLowerInvariant();
|
||||
|
||||
if (type is "nvarchar(max)" or "varchar(max)" or "ntext" or "text")
|
||||
return "text";
|
||||
|
||||
var sized = SizedStringRegex().Match(type);
|
||||
if (sized.Success)
|
||||
return $"character varying({sized.Groups["size"].Value})";
|
||||
|
||||
return type switch
|
||||
{
|
||||
"uniqueidentifier" => "uuid",
|
||||
"datetime2" or "datetime" or "smalldatetime" => "timestamp without time zone",
|
||||
"datetimeoffset" => "timestamp with time zone",
|
||||
"bit" => "boolean",
|
||||
"tinyint" => "smallint",
|
||||
"float" => "double precision",
|
||||
"real" => "real",
|
||||
"varbinary(max)" or "image" => "bytea",
|
||||
_ => sqlServerType,
|
||||
};
|
||||
}
|
||||
|
||||
public static string DefaultValueSql(string sqlServerSql)
|
||||
{
|
||||
return sqlServerSql.Trim().ToUpperInvariant() switch
|
||||
{
|
||||
"NEWID()" or "NEWSEQUENTIALID()" => "gen_random_uuid()",
|
||||
"SYSDATETIME()" or "GETDATE()" => "LOCALTIMESTAMP",
|
||||
"SYSUTCDATETIME()" or "GETUTCDATE()" => "(NOW() AT TIME ZONE 'UTC')",
|
||||
_ => sqlServerSql,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Index filtresi ya da check constraint ifadesi: köşeli parantezli kolon adını tırnaklı ada, bit
|
||||
/// karşılaştırmasını boolean karşılaştırmaya çevirir
|
||||
/// (<c>[IsDeleted] = 0</c> → <c>"IsDeleted" = FALSE</c>).
|
||||
/// </summary>
|
||||
public static string Expression(string sqlServerExpression)
|
||||
{
|
||||
var expression = BracketIdentifierRegex().Replace(sqlServerExpression, "\"$1\"");
|
||||
|
||||
return BitComparisonRegex().Replace(
|
||||
expression,
|
||||
match => $"{match.Groups["column"].Value} = {(match.Groups["value"].Value == "1" ? "TRUE" : "FALSE")}");
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^n?varchar\((?<size>\d+)\)$")]
|
||||
private static partial Regex SizedStringRegex();
|
||||
|
||||
[GeneratedRegex(@"\[([^\]]+)\]")]
|
||||
private static partial Regex BracketIdentifierRegex();
|
||||
|
||||
[GeneratedRegex(@"(?<column>""Is[A-Za-z]+"")\s*=\s*(?<value>[01])\b")]
|
||||
private static partial Regex BitComparisonRegex();
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
@ -38,6 +39,23 @@ public static class SetupMigrationRunner
|
|||
/// <summary>Alt surece seed klasorunu bildiren ortam degiskeni (App:SeedsPath karsiligi).</summary>
|
||||
private const string SeedsPathEnvironmentVariable = "App__SeedsPath";
|
||||
|
||||
private const string BuildStampFileName = "setup-migrator.stamp";
|
||||
|
||||
/// <summary>
|
||||
/// Kaynak projeden çalışırken derleme çıktısını eskiten dosyalar. Seed klasörü çalışma anında
|
||||
/// okunduğu için burada değildir.
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> SourceExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".cs", ".csproj", ".props", ".targets", ".json", ".resx"
|
||||
};
|
||||
|
||||
/// <summary>Derleme çıktısı ya da çalışma anında yazılan klasörler; tazelik kararına girmez.</summary>
|
||||
private static readonly HashSet<string> IgnoredSourceDirectories = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"bin", "obj", "Logs", "artifacts", "wwwroot", "node_modules", ".vs", ".git"
|
||||
};
|
||||
|
||||
/// <param name="processCancellationToken">
|
||||
/// Sürecin yaşam süresini kontrol eden token. Bu <b>istemci isteğinin</b> token'ı DEĞİL,
|
||||
/// uygulamanın <c>ApplicationStopping</c> token'ı olmalıdır. Aksi halde kullanıcı sekmeyi
|
||||
|
|
@ -86,8 +104,6 @@ public static class SetupMigrationRunner
|
|||
return MigrationOutcome.MigratorNotFound;
|
||||
}
|
||||
|
||||
await stream.InfoAsync($"Running: {command!.FileName} {command.Arguments}");
|
||||
|
||||
var timeout = TimeSpan.FromMinutes(
|
||||
configuration.GetValue<int?>("Setup:MigratorTimeoutMinutes") ?? DefaultTimeoutMinutes);
|
||||
|
||||
|
|
@ -95,39 +111,229 @@ public static class SetupMigrationRunner
|
|||
timeoutCts.CancelAfter(timeout);
|
||||
var token = timeoutCts.Token;
|
||||
|
||||
Process? process = null;
|
||||
try
|
||||
{
|
||||
process = new Process
|
||||
if (command!.ProjectPath is not null)
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
var migratorDll = await PrepareSourceMigratorAsync(command.ProjectPath, stream, token);
|
||||
if (migratorDll is null)
|
||||
{
|
||||
FileName = command.FileName,
|
||||
Arguments = command.Arguments,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = command.WorkingDirectory,
|
||||
await stream.DoneAsync("Failed.");
|
||||
return MigrationOutcome.Failed;
|
||||
}
|
||||
|
||||
command = command with { Arguments = $"\"{migratorDll}\" {command.Arguments}" };
|
||||
}
|
||||
|
||||
await stream.InfoAsync($"Running: {command.FileName} {command.Arguments}");
|
||||
|
||||
var processEnvironment = new Dictionary<string, string>
|
||||
{
|
||||
// Seed klasoru artik migrator publish ciktisinin icinde degil, mount edilen ortak
|
||||
// klasorde. Alt surec API'nin cozdugu yolu birebir kullansin ki uygulamadan
|
||||
// tetiklenen migrate, File Manager'da gorunen dosyalari islesin.
|
||||
[SeedsPathEnvironmentVariable] = SeedPathResolver.GetRootPath(configuration),
|
||||
};
|
||||
|
||||
// Seed klasoru artik migrator publish ciktisinin icinde degil, mount edilen ortak
|
||||
// klasorde. Alt surec API'nin cozdugu yolu birebir kullansin ki uygulamadan
|
||||
// tetiklenen migrate, File Manager'da gorunen dosyalari islesin.
|
||||
process.StartInfo.Environment[SeedsPathEnvironmentVariable] =
|
||||
SeedPathResolver.GetRootPath(configuration);
|
||||
// DLL doğrudan çalıştığında launchSettings okunmaz; kaynak projeden çalışma onun ortamını taşır.
|
||||
if (command.ProjectPath is not null)
|
||||
processEnvironment["DOTNET_ENVIRONMENT"] = "Development";
|
||||
|
||||
if (environment is not null)
|
||||
{
|
||||
foreach (var (name, value) in environment)
|
||||
{
|
||||
process.StartInfo.Environment[name] = value;
|
||||
processEnvironment[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
process.Start();
|
||||
var exitCode = await RunProcessAsync(command, processEnvironment, stream, token);
|
||||
|
||||
// Şema değişmiş olabilir — bir sonraki durum sorgusu taze veri görsün.
|
||||
DatabaseReadinessChecker.Invalidate();
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
return MigrationOutcome.Succeeded;
|
||||
}
|
||||
|
||||
await stream.ErrorAsync($"Migration failed. Exit code: {exitCode}");
|
||||
await stream.DoneAsync("Failed.");
|
||||
return MigrationOutcome.Failed;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
DatabaseReadinessChecker.Invalidate();
|
||||
|
||||
var timedOut = !processCancellationToken.IsCancellationRequested;
|
||||
await stream.ErrorAsync(timedOut
|
||||
? $"Migration timed out after {timeout.TotalMinutes:0} minutes and was terminated."
|
||||
: "Migration was canceled because the application is shutting down.");
|
||||
await stream.DoneAsync("Failed.");
|
||||
return timedOut ? MigrationOutcome.TimedOut : MigrationOutcome.Failed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DatabaseReadinessChecker.Invalidate();
|
||||
|
||||
await stream.ErrorAsync($"Migration failed: {ex.Message}");
|
||||
await stream.DoneAsync("Failed.");
|
||||
return MigrationOutcome.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kaynak projeden çalışırken migrator DLL'ini hazırlar. <c>dotnet run</c> her seferinde
|
||||
/// çıktısını gizleyerek derler; değişiklik yokken bile bu ~25 sn sessiz bekleme demektir.
|
||||
/// DLL kaynaklardan yeniyse derleme atlanır, değilse derleme çıktısı kurulum loguna akar.
|
||||
/// </summary>
|
||||
private static async Task<string?> PrepareSourceMigratorAsync(
|
||||
string projectPath,
|
||||
SetupEventStream stream,
|
||||
CancellationToken token)
|
||||
{
|
||||
// MSBuild değişmeyen çıktıyı yeniden yazmaz; derlemeye girmeyen bir dosya değiştiğinde DLL'in
|
||||
// tarihi eski kalır ve her çalıştırma yeniden derlerdi. Son başarılı derlemenin anı damgadadır.
|
||||
var stampPath = Path.Combine(projectPath, "obj", BuildStampFileName);
|
||||
|
||||
var migratorDll = FindBuiltMigrator(projectPath);
|
||||
if (migratorDll is not null && IsUpToDate(projectPath, LastBuildTimeUtc(migratorDll, stampPath)))
|
||||
{
|
||||
await stream.InfoAsync("DbMigrator is up to date, build skipped.");
|
||||
return migratorDll;
|
||||
}
|
||||
|
||||
await stream.InfoAsync("Building DbMigrator...");
|
||||
|
||||
var exitCode = await RunProcessAsync(
|
||||
new MigratorCommand("dotnet", $"build \"{projectPath}\" -nologo -v:m", projectPath),
|
||||
null,
|
||||
stream,
|
||||
token);
|
||||
|
||||
if (exitCode != 0)
|
||||
{
|
||||
await stream.ErrorAsync($"DbMigrator build failed. Exit code: {exitCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
migratorDll = FindBuiltMigrator(projectPath);
|
||||
if (migratorDll is null)
|
||||
{
|
||||
await stream.ErrorAsync($"DbMigrator build output not found under {projectPath}.");
|
||||
return null;
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(stampPath, string.Empty, token);
|
||||
|
||||
return migratorDll;
|
||||
}
|
||||
|
||||
private static DateTime LastBuildTimeUtc(string migratorDll, string stampPath)
|
||||
{
|
||||
var builtAt = File.GetLastWriteTimeUtc(migratorDll);
|
||||
|
||||
return File.Exists(stampPath) && File.GetLastWriteTimeUtc(stampPath) > builtAt
|
||||
? File.GetLastWriteTimeUtc(stampPath)
|
||||
: builtAt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug derleme çıktısındaki migrator DLL'i; birden çok hedef çerçeve klasörü varsa en yenisi
|
||||
/// (eski çerçevenin kalıntısı seçilmez).
|
||||
/// </summary>
|
||||
private static string? FindBuiltMigrator(string projectPath)
|
||||
{
|
||||
var outputRoot = Path.Combine(projectPath, "bin", "Debug");
|
||||
if (!Directory.Exists(outputRoot))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dllName = Path.GetFileName(projectPath) + ".dll";
|
||||
|
||||
return Directory.GetDirectories(outputRoot)
|
||||
.Select(directory => Path.Combine(directory, dllName))
|
||||
.Where(File.Exists)
|
||||
.OrderByDescending(File.GetLastWriteTimeUtc)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migrator'ün bağlı olduğu kaynaklar (api/src, api/modules ve kök props dosyaları) DLL'den
|
||||
/// sonra değişmediyse true. Yanlış "değişti" kararı yalnızca gereksiz bir derlemeye mal olur.
|
||||
/// </summary>
|
||||
private static bool IsUpToDate(string projectPath, DateTime builtAtUtc)
|
||||
{
|
||||
var apiRoot = Directory.GetParent(projectPath)?.Parent;
|
||||
if (apiRoot is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !EnumerateSourceFiles(apiRoot).Any(file => File.GetLastWriteTimeUtc(file) > builtAtUtc);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateSourceFiles(DirectoryInfo root)
|
||||
{
|
||||
var pending = new Stack<DirectoryInfo>();
|
||||
pending.Push(root);
|
||||
|
||||
while (pending.Count > 0)
|
||||
{
|
||||
var directory = pending.Pop();
|
||||
|
||||
foreach (var file in directory.EnumerateFiles())
|
||||
{
|
||||
if (SourceExtensions.Contains(file.Extension))
|
||||
{
|
||||
yield return file.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var child in directory.EnumerateDirectories())
|
||||
{
|
||||
if (!IgnoredSourceDirectories.Contains(child.Name))
|
||||
{
|
||||
pending.Push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Süreci çalıştırır, stdout/stderr satırlarını kurulum loguna aktarır ve çıkış kodunu döndürür.</summary>
|
||||
private static async Task<int> RunProcessAsync(
|
||||
MigratorCommand command,
|
||||
IReadOnlyDictionary<string, string>? environment,
|
||||
SetupEventStream stream,
|
||||
CancellationToken token)
|
||||
{
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = command.FileName,
|
||||
Arguments = command.Arguments,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = command.WorkingDirectory,
|
||||
}
|
||||
};
|
||||
|
||||
if (environment is not null)
|
||||
{
|
||||
foreach (var (name, value) in environment)
|
||||
{
|
||||
process.StartInfo.Environment[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
process.Start();
|
||||
|
||||
try
|
||||
{
|
||||
async Task PumpAsync(StreamReader reader, string level)
|
||||
{
|
||||
try
|
||||
|
|
@ -149,50 +355,20 @@ public static class SetupMigrationRunner
|
|||
|
||||
await process.WaitForExitAsync(token);
|
||||
|
||||
// Şema değişmiş olabilir — bir sonraki durum sorgusu taze veri görsün.
|
||||
DatabaseReadinessChecker.Invalidate();
|
||||
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
return MigrationOutcome.Succeeded;
|
||||
}
|
||||
|
||||
await stream.ErrorAsync($"Migration failed. Exit code: {process.ExitCode}");
|
||||
await stream.DoneAsync("Failed.");
|
||||
return MigrationOutcome.Failed;
|
||||
return process.ExitCode;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
catch
|
||||
{
|
||||
KillQuietly(process);
|
||||
DatabaseReadinessChecker.Invalidate();
|
||||
|
||||
var timedOut = !processCancellationToken.IsCancellationRequested;
|
||||
await stream.ErrorAsync(timedOut
|
||||
? $"Migration timed out after {timeout.TotalMinutes:0} minutes and was terminated."
|
||||
: "Migration was canceled because the application is shutting down.");
|
||||
await stream.DoneAsync("Failed.");
|
||||
return timedOut ? MigrationOutcome.TimedOut : MigrationOutcome.Failed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
KillQuietly(process);
|
||||
DatabaseReadinessChecker.Invalidate();
|
||||
|
||||
await stream.ErrorAsync($"Migration failed: {ex.Message}");
|
||||
await stream.DoneAsync("Failed.");
|
||||
return MigrationOutcome.Failed;
|
||||
}
|
||||
finally
|
||||
{
|
||||
process?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void KillQuietly(Process? process)
|
||||
private static void KillQuietly(Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (process is { HasExited: false })
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill(entireProcessTree: true);
|
||||
}
|
||||
|
|
@ -227,7 +403,6 @@ public static class SetupMigrationRunner
|
|||
|
||||
if (migratorPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && File.Exists(migratorPath))
|
||||
{
|
||||
// Doğrudan DLL yolu — "--" separator yok, argümanlar doğrudan geçer.
|
||||
command = new MigratorCommand(
|
||||
"dotnet",
|
||||
$"\"{migratorPath}\" {extraArgs}",
|
||||
|
|
@ -246,8 +421,8 @@ public static class SetupMigrationRunner
|
|||
command = dllFiles.Length > 0
|
||||
// Publish çıktısı — SDK gerekmez.
|
||||
? new MigratorCommand("dotnet", $"\"{dllFiles[0]}\" {extraArgs}", migratorPath)
|
||||
// Kaynak proje klasörü — geliştirme ortamı, "--" separator gerekli.
|
||||
: new MigratorCommand("dotnet", $"run --project \"{migratorPath}\" -- {extraArgs}", migratorPath);
|
||||
// Kaynak proje klasörü — DLL çalıştırmadan önce PrepareSourceMigratorAsync ile hazırlanır.
|
||||
: new MigratorCommand("dotnet", extraArgs, migratorPath, migratorPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -256,4 +431,5 @@ public static class SetupMigrationRunner
|
|||
}
|
||||
}
|
||||
|
||||
internal sealed record MigratorCommand(string FileName, string Arguments, string WorkingDirectory);
|
||||
/// <param name="ProjectPath">Dolu ise komut kaynak projeden çalışır; <c>Arguments</c> yalnızca migrator argümanlarıdır.</param>
|
||||
internal sealed record MigratorCommand(string FileName, string Arguments, string WorkingDirectory, string? ProjectPath = null);
|
||||
|
|
|
|||
|
|
@ -383,7 +383,13 @@ public class DynamicFormReport : XtraReport
|
|||
CanGrow = true
|
||||
};
|
||||
|
||||
if (TryGetFormattedValue(field, row, out var text))
|
||||
var markup = GetColorMarkup(field, row);
|
||||
if (!string.IsNullOrEmpty(markup))
|
||||
{
|
||||
label.AllowMarkupText = true;
|
||||
label.Text = markup;
|
||||
}
|
||||
else if (TryGetFormattedValue(field, row, out var text))
|
||||
{
|
||||
label.Text = text;
|
||||
label.Font = new DXFont(
|
||||
|
|
@ -402,6 +408,28 @@ public class DynamicFormReport : XtraReport
|
|||
return label;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rengi olan lookup için renkli rozetler, renk kutusu için renk örneği; diğer alanlarda <c>null</c>.
|
||||
/// </summary>
|
||||
private static string GetColorMarkup(FormReportField field, IDictionary<string, object> row)
|
||||
{
|
||||
if (DynamicReportSqlBuilder.HasLookupColor(field.Lookup))
|
||||
{
|
||||
return DynamicReportImageHelper.TryGetRowValue(
|
||||
row,
|
||||
DynamicReportSqlBuilder.GetLookupColorBindingName(field.Field),
|
||||
out var tags)
|
||||
? DynamicReportColorHelper.ToTagsMarkup(tags)
|
||||
: null;
|
||||
}
|
||||
|
||||
return field.Lookup == null &&
|
||||
field.EditorType == PlatformConsts.EditorTypes.dxColorBox &&
|
||||
DynamicReportImageHelper.TryGetRowValue(row, field.BindingName, out var color)
|
||||
? DynamicReportColorHelper.ToSwatchMarkup(color)
|
||||
: null;
|
||||
}
|
||||
|
||||
private static XRRichText CreateHtmlValueControl(
|
||||
FormReportField field,
|
||||
IDictionary<string, object> row,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using DevExpress.Data.Filtering;
|
||||
|
||||
namespace Sozsoft.Reports.PredefinedReports;
|
||||
|
||||
/// <summary>
|
||||
/// Renk kutusu (<c>dxColorBox</c>) kolonlarını ve rengi olan lookup değerlerini DevExpress markup
|
||||
/// metniyle çizer. Hücrenin <c>AllowMarkupText</c> özelliği açık olmalıdır.
|
||||
/// </summary>
|
||||
internal static class DynamicReportColorHelper
|
||||
{
|
||||
// Ayraçlar SQL metnine yazılır ve rapor tanımı XML olarak saklanır; XML 1.0 kontrol
|
||||
// karakterlerini (0x1F/0x1E) kabul etmediği için onların görünür karşılıkları kullanılır.
|
||||
|
||||
/// <summary>Lookup metni ile rengi arasındaki ayraç (SQL'de üretilir).</summary>
|
||||
public const string PairSeparator = "\u241F";
|
||||
|
||||
/// <summary>Çok değerli lookup'ta öğeler arasındaki ayraç (SQL'de üretilir).</summary>
|
||||
public const string ItemSeparator = "\u241E";
|
||||
|
||||
private const string SwatchFunctionName = "SozReportColorSwatch";
|
||||
private const string TagsFunctionName = "SozReportColorTags";
|
||||
private const string Space = " ";
|
||||
|
||||
static DynamicReportColorHelper()
|
||||
{
|
||||
Register(new ColorSwatchFunction());
|
||||
Register(new ColorTagsFunction());
|
||||
}
|
||||
|
||||
public static string CreateSwatchExpression(string valueExpression)
|
||||
{
|
||||
return $"{SwatchFunctionName}({valueExpression})";
|
||||
}
|
||||
|
||||
public static string CreateTagsExpression(string valueExpression)
|
||||
{
|
||||
return $"{TagsFunctionName}({valueExpression})";
|
||||
}
|
||||
|
||||
/// <summary>Renk değerinin yanında küçük bir renk kutusu.</summary>
|
||||
public static string ToSwatchMarkup(object value)
|
||||
{
|
||||
var text = ToText(value);
|
||||
if (!TryParseColor(text, out var color))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
return $"<backcolor={ToHex(color)}>{Space}{Space}{Space}{Space}{Space}</backcolor>{Space}{text}";
|
||||
}
|
||||
|
||||
/// <summary>"Metin␟Renk␞Metin␟Renk" çiftlerini renkli rozetlere çevirir.</summary>
|
||||
public static string ToTagsMarkup(object value)
|
||||
{
|
||||
var text = ToText(value);
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return string.Join(
|
||||
" ",
|
||||
text.Split(ItemSeparator, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(item =>
|
||||
{
|
||||
var parts = item.Split(PairSeparator);
|
||||
var label = parts[0];
|
||||
if (parts.Length < 2 || !TryParseColor(parts[1], out var color))
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
return $"<backcolor={ToHex(color)}><color={ToHex(ReadableTextColor(color))}>{Space}{label}{Space}</color></backcolor>";
|
||||
}));
|
||||
}
|
||||
|
||||
private static bool TryParseColor(string value, out System.Drawing.Color color)
|
||||
{
|
||||
color = System.Drawing.Color.Empty;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
color = System.Drawing.ColorTranslator.FromHtml(value.Trim());
|
||||
return !color.IsEmpty;
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException or FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Açık zeminde beyaz yazı okunmadığı için istemcideki rozetle aynı eşik kullanılır.
|
||||
private static System.Drawing.Color ReadableTextColor(System.Drawing.Color background)
|
||||
{
|
||||
var brightness = (background.R * 299 + background.G * 587 + background.B * 114) / 1000;
|
||||
return brightness > 150
|
||||
? System.Drawing.Color.FromArgb(17, 24, 39)
|
||||
: System.Drawing.Color.White;
|
||||
}
|
||||
|
||||
private static string ToHex(System.Drawing.Color color)
|
||||
{
|
||||
return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
|
||||
}
|
||||
|
||||
private static string ToText(object value)
|
||||
{
|
||||
return value == null || value == DBNull.Value
|
||||
? string.Empty
|
||||
: Convert.ToString(value, CultureInfo.InvariantCulture)?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static void Register(ICustomFunctionOperator function)
|
||||
{
|
||||
if (CriteriaOperator.GetCustomFunction(function.Name) == null)
|
||||
{
|
||||
CriteriaOperator.RegisterCustomFunction(function);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class ColorFunctionBase : ICustomFunctionOperatorBrowsable
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public int MinOperandCount => 1;
|
||||
public int MaxOperandCount => 1;
|
||||
public FunctionCategory Category => FunctionCategory.Text;
|
||||
public string Description => "Returns report markup text for a color value.";
|
||||
|
||||
public abstract object Evaluate(params object[] operands);
|
||||
|
||||
public Type ResultType(params Type[] operands)
|
||||
{
|
||||
return typeof(string);
|
||||
}
|
||||
|
||||
public bool IsValidOperandCount(int count)
|
||||
{
|
||||
return count == 1;
|
||||
}
|
||||
|
||||
public bool IsValidOperandType(int operandIndex, int operandCount, Type type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ColorSwatchFunction : ColorFunctionBase
|
||||
{
|
||||
public override string Name => SwatchFunctionName;
|
||||
|
||||
public override object Evaluate(params object[] operands)
|
||||
{
|
||||
return ToSwatchMarkup(operands.ElementAtOrDefault(0));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ColorTagsFunction : ColorFunctionBase
|
||||
{
|
||||
public override string Name => TagsFunctionName;
|
||||
|
||||
public override object Evaluate(params object[] operands)
|
||||
{
|
||||
return ToTagsMarkup(operands.ElementAtOrDefault(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using DevExpress.Drawing;
|
|||
using DevExpress.XtraPrinting;
|
||||
using DevExpress.XtraReports.UI;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Sozsoft.Platform;
|
||||
using Sozsoft.Platform.Localization;
|
||||
|
||||
namespace Sozsoft.Reports.PredefinedReports;
|
||||
|
|
@ -115,15 +116,18 @@ internal static class DynamicReportLayoutHelper
|
|||
$"[{column.BindingName}]",
|
||||
column.Field,
|
||||
column.EditorType);
|
||||
var markupExpression = CreateColorMarkupExpression(column);
|
||||
cell.AllowMarkupText = markupExpression != null;
|
||||
var displayExpression = markupExpression ?? valueExpression;
|
||||
var textExpression = hasPrefix
|
||||
? $"Concat([{prefixColumnName}], {valueExpression})"
|
||||
: valueExpression;
|
||||
? $"Concat([{prefixColumnName}], {displayExpression})"
|
||||
: displayExpression;
|
||||
|
||||
cell.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", textExpression));
|
||||
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||
"BeforePrint",
|
||||
"Font.Name",
|
||||
DynamicReportImageHelper.CreateFontFamilyExpression(textExpression)));
|
||||
DynamicReportImageHelper.CreateFontFamilyExpression(valueExpression)));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(boldColumnName))
|
||||
{
|
||||
|
|
@ -133,6 +137,23 @@ internal static class DynamicReportLayoutHelper
|
|||
return cell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renkli çizilen kolonun markup ifadesi: rengi olan lookup rozet, renk kutusu renk örneği
|
||||
/// olarak çizilir. Diğer kolonlarda <c>null</c>.
|
||||
/// </summary>
|
||||
public static string CreateColorMarkupExpression(DynamicReportColumn column)
|
||||
{
|
||||
if (DynamicReportSqlBuilder.HasLookupColor(column.Lookup))
|
||||
{
|
||||
return DynamicReportColorHelper.CreateTagsExpression(
|
||||
$"[{DynamicReportSqlBuilder.GetLookupColorBindingName(column.Field)}]");
|
||||
}
|
||||
|
||||
return column.Lookup == null && column.EditorType == PlatformConsts.EditorTypes.dxColorBox
|
||||
? DynamicReportColorHelper.CreateSwatchExpression($"[{column.BindingName}]")
|
||||
: null;
|
||||
}
|
||||
|
||||
public static XRTableCell CreateHtmlDetailCell(DynamicReportColumn column, float width, float height)
|
||||
{
|
||||
var cell = new XRTableCell
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ internal static class DynamicReportSqlBuilder
|
|||
private static readonly Regex LookupParameterRegex = new(
|
||||
@"@\s*param\d+",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
private static readonly Regex LookupColorAliasRegex = new(
|
||||
@"\bAS\s+(?:(?<quoted>\[Color\]|""Color"")|Color\b)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
|
||||
internal interface IReportLookupColumn
|
||||
{
|
||||
|
|
@ -31,6 +34,43 @@ internal static class DynamicReportSqlBuilder
|
|||
return $"{field.FieldName}__LookupText";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renk taşıyan lookup'ın görünen metin + renk çiftleri. Metin kolonu düz kalır (gruplama,
|
||||
/// kriter metni ve kayıt metinleri onu okur); renkli çizim bu kolondan yapılır.
|
||||
/// </summary>
|
||||
internal static string GetLookupColorBindingName(ListFormField field)
|
||||
{
|
||||
return $"{field.FieldName}__LookupColor";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup sorgusu rengi <c>AS [Color]</c> takma adıyla döndürüyorsa kolon adını verir. Sorgu
|
||||
/// rapor kurulurken çalıştırılıp kolonları okunmaz; sözleşme takma addır.
|
||||
/// </summary>
|
||||
internal static bool HasLookupColor(LookupDto lookup)
|
||||
{
|
||||
return GetLookupColorField(lookup, DataSourceTypeEnum.Mssql) != null;
|
||||
}
|
||||
|
||||
internal static string GetLookupColorField(LookupDto lookup, DataSourceTypeEnum dataSourceType)
|
||||
{
|
||||
if (lookup == null || !IsReportQueryableLookup(lookup))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = LookupColorAliasRegex.Match(lookup.LookupQuery);
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tırnaksız takma ad PostgreSQL'de küçük harfe çevrilir.
|
||||
return match.Groups["quoted"].Success || dataSourceType == DataSourceTypeEnum.Mssql
|
||||
? "Color"
|
||||
: "color";
|
||||
}
|
||||
|
||||
internal static bool IsReportQueryableLookup(LookupDto lookup)
|
||||
{
|
||||
return lookup.DataSourceType == UiLookupDataSourceTypeEnum.Query &&
|
||||
|
|
@ -142,11 +182,39 @@ internal static class DynamicReportSqlBuilder
|
|||
var sourceField = GetSourceField(column.Field.FieldName);
|
||||
var resultAlias = QuoteSqlIdentifier(column.BindingName);
|
||||
|
||||
var scalarQuery = dataSourceType == DataSourceTypeEnum.Mssql
|
||||
? $"SELECT TOP 1 {lookupAlias}.{displayExpr} FROM ({lookupSelectQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField}"
|
||||
: $"SELECT {lookupAlias}.{displayExpr} FROM ({lookupSelectQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField} LIMIT 1";
|
||||
string CreateScalarQuery(string selectExpression) => dataSourceType == DataSourceTypeEnum.Mssql
|
||||
? $"SELECT TOP 1 {selectExpression} FROM ({lookupSelectQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField}"
|
||||
: $"SELECT {selectExpression} FROM ({lookupSelectQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField} LIMIT 1";
|
||||
|
||||
return new LookupSelectPart($"({scalarQuery}) AS {resultAlias}", commonTableExpression);
|
||||
var selectSql = $"({CreateScalarQuery($"{lookupAlias}.{displayExpr}")}) AS {resultAlias}";
|
||||
var colorPair = CreateColorPairExpression(column, lookupAlias, displayExpr, dataSourceType);
|
||||
if (colorPair != null)
|
||||
{
|
||||
selectSql += $", ({CreateScalarQuery(colorPair)}) AS {QuoteSqlIdentifier(GetLookupColorBindingName(column.Field))}";
|
||||
}
|
||||
|
||||
return new LookupSelectPart(selectSql, commonTableExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Görünen metin ile rengi <see cref="DynamicReportColorHelper"/>'ın çözdüğü ayraçla birleştirir.
|
||||
/// </summary>
|
||||
private static string CreateColorPairExpression(
|
||||
IReportLookupColumn column,
|
||||
string lookupAlias,
|
||||
string displayExpr,
|
||||
DataSourceTypeEnum dataSourceType)
|
||||
{
|
||||
var colorField = GetLookupColorField(column.Lookup, dataSourceType);
|
||||
if (colorField == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var colorExpr = $"{lookupAlias}.{QuoteSqlIdentifier(colorField)}";
|
||||
return dataSourceType == DataSourceTypeEnum.Mssql
|
||||
? $"CAST({lookupAlias}.{displayExpr} AS NVARCHAR(MAX)) + N'{DynamicReportColorHelper.PairSeparator}' + COALESCE(CAST({colorExpr} AS NVARCHAR(64)), N'')"
|
||||
: $"CAST({lookupAlias}.{displayExpr} AS TEXT) || '{DynamicReportColorHelper.PairSeparator}' || COALESCE(CAST({colorExpr} AS TEXT), '')";
|
||||
}
|
||||
|
||||
private static LookupSelectPart CreateStaticMultiLookupSelectPart(
|
||||
|
|
@ -190,21 +258,31 @@ internal static class DynamicReportSqlBuilder
|
|||
var sourceField = GetSourceField(column.Field.FieldName);
|
||||
var resultAlias = QuoteSqlIdentifier(column.BindingName);
|
||||
|
||||
var scalarQuery = dataSourceType == DataSourceTypeEnum.Mssql
|
||||
string CreateAggregateQuery(string itemExpression, string separator) => dataSourceType == DataSourceTypeEnum.Mssql
|
||||
? $"""
|
||||
SELECT STRING_AGG(CAST({lookupAlias}.{displayExpr} AS NVARCHAR(MAX)), N', ')
|
||||
SELECT STRING_AGG({itemExpression}, N'{separator}')
|
||||
FROM ({lookupSelectQuery}) AS {lookupAlias}
|
||||
INNER JOIN STRING_SPLIT(CAST({sourceField} AS NVARCHAR(MAX)), N'{PlatformConsts.MultiValueDelimiter}') AS SplitValues
|
||||
ON CAST({lookupAlias}.{valueExpr} AS NVARCHAR(4000)) = LTRIM(RTRIM(SplitValues.value))
|
||||
""".ReplaceLineEndings(" ").Trim()
|
||||
: $"""
|
||||
SELECT string_agg(CAST({lookupAlias}.{displayExpr} AS TEXT), ', ')
|
||||
SELECT string_agg({itemExpression}, '{separator}')
|
||||
FROM ({lookupSelectQuery}) AS {lookupAlias}
|
||||
INNER JOIN unnest(string_to_array(CAST({sourceField} AS TEXT), '{PlatformConsts.MultiValueDelimiter}')) AS SplitValues(value)
|
||||
ON CAST({lookupAlias}.{valueExpr} AS TEXT) = btrim(SplitValues.value)
|
||||
""".ReplaceLineEndings(" ").Trim();
|
||||
|
||||
return new LookupSelectPart($"({scalarQuery}) AS {resultAlias}", commonTableExpression);
|
||||
var displayItem = dataSourceType == DataSourceTypeEnum.Mssql
|
||||
? $"CAST({lookupAlias}.{displayExpr} AS NVARCHAR(MAX))"
|
||||
: $"CAST({lookupAlias}.{displayExpr} AS TEXT)";
|
||||
var selectSql = $"({CreateAggregateQuery(displayItem, ", ")}) AS {resultAlias}";
|
||||
var colorPair = CreateColorPairExpression(column, lookupAlias, displayExpr, dataSourceType);
|
||||
if (colorPair != null)
|
||||
{
|
||||
selectSql += $", ({CreateAggregateQuery(colorPair, DynamicReportColorHelper.ItemSeparator)}) AS {QuoteSqlIdentifier(GetLookupColorBindingName(column.Field))}";
|
||||
}
|
||||
|
||||
return new LookupSelectPart(selectSql, commonTableExpression);
|
||||
}
|
||||
|
||||
private static (string CommonTableExpression, string SelectQuery) SplitCommonTableExpression(string query)
|
||||
|
|
|
|||
20
claude.md
20
claude.md
|
|
@ -114,6 +114,7 @@ These sections encode expectations the user will **not** repeat in every prompt.
|
|||
| Bir modülün nasıl çalıştığı, analizi ve kararları | `configs/seeds/docs/modules/{Modül}.md` (dizin: `README.md` §10.5) |
|
||||
| Yetki kodları | `PlatformConsts.AppCodes` (backend) + `constants/permission.constant.ts` (UI) |
|
||||
| Seed yolları | `SeedPathResolver` |
|
||||
| SQL Server → PostgreSQL çeviri kuralları | `.github/instructions/lowcode.instructions.md` §8.3.1 |
|
||||
|
||||
Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → bu dosya → `README.md`.
|
||||
|
||||
|
|
@ -196,6 +197,20 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
|||
menüde nerede / hangi ekranın sekmesi olduğu, ana alanlar, otomatik hesap ve geçişler, ilişkili
|
||||
ekranlar (`RichText` biçimi). MCP `deploy_wizard` 3 satır / 150 karakterin altını reddeder.
|
||||
Ayrıntı: `lowcode.instructions.md` §4.4.2.
|
||||
16. **İki veritabanı sağlayıcısı** → uygulama SQL Server ile olduğu gibi PostgreSQL ile de çalışır
|
||||
(`SettingsConsts.DefaultDatabaseProvider`). Veritabanına dokunan her değişiklik ikisi için birlikte
|
||||
düşünülür ve yazılır; kullanıcı bunu her istekte hatırlatmaz:
|
||||
- `configs/seeds/sql/{object,execute,host/…}/X.sql` yazılan/değişen her dosyanın
|
||||
`configs/seeds/postgres/…/X.sql` eşi aynı işte yazılır/güncellenir (aynı ad, aynı parametre sırası,
|
||||
şema PG'de `public`). Biri olmadan diğeri commit edilmez; "PG sonra" yoktur.
|
||||
- EF modeli SQL Server sözdizimiyle yazılır (kolon tipi, index filtresi, check, varsayılan); PG
|
||||
karşılığı `PostgreSqlModelSyntax` üzerinden `PlatformDbContext.ConfigureProviderSpecificModel`'de
|
||||
üretilir. Çeviriye girmeyen yeni bir sözdizimi eklenirse çeviri de eklenir.
|
||||
- C#'ta üretilen SQL (Dapper, `QueryManager`, seeder komutları, ekran `*Command`/`LookupQuery`
|
||||
metinleri) ya iki sağlayıcıda da çalışan tırnaklı/ANSI biçimde yazılır ya da `DataSourceType`'a göre
|
||||
dallanır.
|
||||
- SQL değişikliği doğrulanırken PG eşi de bir PostgreSQL veritabanında çalıştırılır (iki kez —
|
||||
idempotent). Ayrıntı ve dönüşüm tablosu: `lowcode.instructions.md` §8.3.1.
|
||||
|
||||
### Cross-cutting: touching one thing means touching these too
|
||||
|
||||
|
|
@ -334,7 +349,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
|||
yetkisini aşan bir yetenek (ham SQL gibi) `McpToolNames.RequiredPermissions`'a uygulamadaki
|
||||
karşılık gelen yetkiyle eklenir. `create_table` bu yüzden `.Write`'a ek olarak `App.SqlQueryManager`
|
||||
ister; tablo scriptini tasarımcının `generateCreateTableSql` çıktısıyla aynı biçimde üretir
|
||||
(`McpTableCreator`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir.
|
||||
(`McpTableCreator`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir. Tasarımcı
|
||||
(`SaveTableScriptAsync`) ve `create_table` T-SQL dosyanın PostgreSQL eşini `PostgreSqlTableScript` ile
|
||||
üretip `postgres/object/` altına yazar; script biçimine yeni bir deyim eklenirse çevirisi de eklenir,
|
||||
yoksa PG eşi yazılmaz (log uyarısı). Düşürülen nesnenin dosyası iki klasörden de silinir.
|
||||
- **Çalışan (persona) ekranları ve profil** → çalışanın/yöneticinin kendi listeleri İK menüsüne
|
||||
konmaz: menüsüz `App.Hr.My*`/`Team*` wizard + Intranet panosu widget'ı. Profil sayfasının İzinlerim /
|
||||
Puantajım / Performansım sekmeleri persona ekranlarını salt okunur gösteren çekirdek bileşenlerdir
|
||||
|
|
|
|||
|
|
@ -69780,20 +69780,6 @@
|
|||
"Key": "App.Mcp.Error.TableAlreadyExists",
|
||||
"Value": "\"{0}\" tablosu zaten var. Var olan tabloları SQL Query Manager tablo tasarımcısından değiştirin."
|
||||
},
|
||||
{
|
||||
"Id": "a8300ba4-adcf-5a61-87a9-ea5a5821918f",
|
||||
"CultureName": "en",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.Mcp.Error.TableProviderNotSupported",
|
||||
"Value": "Creating tables over MCP is supported only on SQL Server data sources (current: {0})."
|
||||
},
|
||||
{
|
||||
"Id": "d95bd46b-1cb1-5ed6-b26a-f5b2c633c41e",
|
||||
"CultureName": "tr",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.Mcp.Error.TableProviderNotSupported",
|
||||
"Value": "MCP üzerinden tablo oluşturma yalnızca SQL Server veri kaynaklarında desteklenir (mevcut: {0})."
|
||||
},
|
||||
{
|
||||
"Id": "d88ec341-a3cd-505f-b14a-2c6ce3f82de9",
|
||||
"CultureName": "en",
|
||||
|
|
|
|||
|
|
@ -1,170 +1,88 @@
|
|||
CREATE OR REPLACE FUNCTION dbo."Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns"
|
||||
-- SQL Server surumu kayit sonrasinda eklenen satiri da secer; PostgreSQL proseduru satir
|
||||
-- donduremez ve FinalSeeder yalnizca CALL ile cagirir, bu yuzden o son SELECT yoktur.
|
||||
CREATE OR REPLACE PROCEDURE public."Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns"
|
||||
(
|
||||
p_tenant_name VARCHAR(128) DEFAULT 'Sozsoft',
|
||||
p_definition_culture_name VARCHAR(10) DEFAULT 'en',
|
||||
p_ui_culture_name VARCHAR(10) DEFAULT 'tr',
|
||||
p_list_form_code VARCHAR(64) DEFAULT NULL,
|
||||
p_only_base_definitions BOOLEAN DEFAULT TRUE,
|
||||
p_resource_name VARCHAR(64) DEFAULT NULL,
|
||||
p_max_forms INTEGER DEFAULT 500,
|
||||
p_deactivate_previous BOOLEAN DEFAULT TRUE,
|
||||
p_note VARCHAR(4000) DEFAULT NULL
|
||||
)
|
||||
RETURNS TABLE
|
||||
(
|
||||
"TenantName" VARCHAR,
|
||||
"DictionaryType" VARCHAR,
|
||||
"DefinitionCultureName" VARCHAR,
|
||||
"UiCultureName" VARCHAR,
|
||||
"ListFormCode" VARCHAR,
|
||||
"OnlyBaseDefinitions" BOOLEAN,
|
||||
"ResourceName" VARCHAR,
|
||||
"MaxForms" INTEGER,
|
||||
"CacheKey" TEXT,
|
||||
"FormCount" INTEGER,
|
||||
"FieldCount" INTEGER,
|
||||
"JsonLength" INTEGER,
|
||||
"IsActive" BOOLEAN,
|
||||
"RefreshedAt" TIMESTAMP,
|
||||
"SourceProcedure" VARCHAR,
|
||||
"Note" VARCHAR
|
||||
p_TenantName varchar(128) DEFAULT 'Sozsoft',
|
||||
p_DefinitionCultureName varchar(10) DEFAULT 'en',
|
||||
p_UiCultureName varchar(10) DEFAULT 'tr',
|
||||
p_ListFormCode varchar(64) DEFAULT NULL,
|
||||
p_OnlyBaseDefinitions boolean DEFAULT TRUE,
|
||||
p_ResourceName varchar(64) DEFAULT NULL,
|
||||
p_MaxForms integer DEFAULT 500,
|
||||
p_DeactivatePrevious boolean DEFAULT TRUE,
|
||||
p_Note varchar(4000) DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_json_data JSONB;
|
||||
v_cache_key TEXT;
|
||||
v_form_count INTEGER := 0;
|
||||
v_field_count INTEGER := 0;
|
||||
v_json_length INTEGER := 0;
|
||||
v_JsonData text;
|
||||
v_Json jsonb;
|
||||
v_CacheKey varchar(512);
|
||||
v_FormCount integer := 0;
|
||||
v_FieldCount integer := 0;
|
||||
v_JsonLength integer;
|
||||
BEGIN
|
||||
|
||||
/*
|
||||
SQL Server:
|
||||
EXEC dbo.Sas_H_ListFormFieldsDictionaryJson
|
||||
|
||||
PostgreSQL tarafında bunun JSON / JSONB döndüren bir FUNCTION
|
||||
olduğu varsayılmıştır.
|
||||
*/
|
||||
SELECT dbo."Sas_H_ListFormFieldsDictionaryJson"
|
||||
(
|
||||
p_definition_culture_name,
|
||||
p_ui_culture_name,
|
||||
p_list_form_code,
|
||||
p_only_base_definitions,
|
||||
p_resource_name
|
||||
)
|
||||
INTO v_json_data;
|
||||
|
||||
|
||||
/*
|
||||
JSON boş kontrolü
|
||||
*/
|
||||
IF v_json_data IS NULL THEN
|
||||
RAISE EXCEPTION
|
||||
'JSON veri sözlüğü boş döndü. dbo.Sas_H_ListFormFieldsDictionaryJson çıktısını kontrol edin.'
|
||||
USING ERRCODE = 'P0001';
|
||||
IF to_regprocedure('public."Sas_H_ListFormFieldsDictionaryJson"(varchar, varchar, varchar, boolean, varchar)') IS NULL THEN
|
||||
RAISE EXCEPTION 'public.Sas_H_ListFormFieldsDictionaryJson bulunamadı. Önce mevcut JSON üreten SP kurulmalıdır.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51000';
|
||||
END IF;
|
||||
|
||||
SELECT r."ListFormDataDictionaryJson"
|
||||
INTO v_JsonData
|
||||
FROM public."Sas_H_ListFormFieldsDictionaryJson"(
|
||||
p_DefinitionCultureName,
|
||||
p_UiCultureName,
|
||||
p_ListFormCode,
|
||||
p_OnlyBaseDefinitions,
|
||||
p_ResourceName) r
|
||||
WHERE NULLIF(BTRIM(r."ListFormDataDictionaryJson"), '') IS NOT NULL
|
||||
LIMIT 1;
|
||||
|
||||
/*
|
||||
CacheKey oluşturulması
|
||||
*/
|
||||
v_cache_key :=
|
||||
'ListFormJson|'
|
||||
|| COALESCE(p_tenant_name, '')
|
||||
|| '|'
|
||||
|| COALESCE(p_definition_culture_name, '')
|
||||
|| '|'
|
||||
|| COALESCE(p_ui_culture_name, '')
|
||||
|| '|'
|
||||
|| COALESCE(p_list_form_code, '')
|
||||
|| '|'
|
||||
|| CASE
|
||||
WHEN COALESCE(p_only_base_definitions, TRUE)
|
||||
THEN '1'
|
||||
ELSE '0'
|
||||
END
|
||||
|| '|'
|
||||
|| COALESCE(p_resource_name, '')
|
||||
|| '|'
|
||||
|| COALESCE(p_max_forms, 500)::TEXT;
|
||||
|
||||
|
||||
/*
|
||||
Form sayısı
|
||||
|
||||
JSON:
|
||||
{
|
||||
"listForms": [...]
|
||||
}
|
||||
*/
|
||||
IF jsonb_typeof(v_json_data -> 'listForms') = 'array' THEN
|
||||
|
||||
v_form_count :=
|
||||
jsonb_array_length(v_json_data -> 'listForms');
|
||||
|
||||
IF v_JsonData IS NULL THEN
|
||||
RAISE EXCEPTION 'JSON veri sözlüğü boş döndü. public.Sas_H_ListFormFieldsDictionaryJson çıktısını kontrol edin.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51001';
|
||||
END IF;
|
||||
|
||||
IF NOT (v_JsonData IS JSON OBJECT OR v_JsonData IS JSON ARRAY) THEN
|
||||
RAISE EXCEPTION 'Üretilen JsonData geçerli JSON değil.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51002';
|
||||
END IF;
|
||||
|
||||
/*
|
||||
Toplam Field sayısı
|
||||
v_Json := v_JsonData::jsonb;
|
||||
|
||||
Her listForms elemanının:
|
||||
{
|
||||
"fields": [...]
|
||||
}
|
||||
v_CacheKey := concat(
|
||||
'ListFormJson|',
|
||||
COALESCE(p_TenantName, ''), '|',
|
||||
COALESCE(p_DefinitionCultureName, ''), '|',
|
||||
COALESCE(p_UiCultureName, ''), '|',
|
||||
COALESCE(p_ListFormCode, ''), '|',
|
||||
CASE WHEN COALESCE(p_OnlyBaseDefinitions, TRUE) THEN '1' ELSE '0' END, '|',
|
||||
COALESCE(p_ResourceName, ''), '|',
|
||||
COALESCE(p_MaxForms, 500)::text
|
||||
);
|
||||
|
||||
alanındaki kayıtlar sayılır.
|
||||
*/
|
||||
SELECT COUNT(*)::INTEGER
|
||||
INTO v_field_count
|
||||
FROM jsonb_array_elements(
|
||||
COALESCE(
|
||||
v_json_data -> 'listForms',
|
||||
'[]'::JSONB
|
||||
)
|
||||
) AS lf
|
||||
CROSS JOIN LATERAL jsonb_array_elements(
|
||||
CASE
|
||||
WHEN jsonb_typeof(lf -> 'fields') = 'array'
|
||||
THEN lf -> 'fields'
|
||||
ELSE '[]'::JSONB
|
||||
END
|
||||
) AS ff;
|
||||
v_JsonLength := length(v_JsonData);
|
||||
|
||||
IF jsonb_typeof(v_Json -> 'listForms') = 'array' THEN
|
||||
v_FormCount := jsonb_array_length(v_Json -> 'listForms');
|
||||
|
||||
/*
|
||||
JSON karakter uzunluğu
|
||||
*/
|
||||
v_json_length := LENGTH(v_json_data::TEXT);
|
||||
SELECT COUNT(1)
|
||||
INTO v_FieldCount
|
||||
FROM jsonb_array_elements(v_Json -> 'listForms') lf
|
||||
CROSS JOIN LATERAL jsonb_array_elements(
|
||||
CASE WHEN jsonb_typeof(lf -> 'fields') = 'array' THEN lf -> 'fields' ELSE '[]'::jsonb END
|
||||
) ff;
|
||||
END IF;
|
||||
|
||||
|
||||
/*
|
||||
PostgreSQL function çağrısı kendi transaction'ı içerisinde
|
||||
çalıştığı için SQL Server'daki BEGIN TRAN / COMMIT burada
|
||||
ayrıca kullanılmaz.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Önceki aktif kaydı pasif yap
|
||||
*/
|
||||
IF p_deactivate_previous THEN
|
||||
|
||||
UPDATE dbo."Sas_H_DbTableColumn"
|
||||
IF p_DeactivatePrevious THEN
|
||||
UPDATE public."Sas_H_DbTableColumn"
|
||||
SET "IsActive" = FALSE
|
||||
WHERE "DictionaryType" = 'ListFormJson'
|
||||
AND "CacheKey" = v_cache_key
|
||||
AND "CacheKey" = v_CacheKey
|
||||
AND COALESCE("IsActive", FALSE) = TRUE;
|
||||
|
||||
END IF;
|
||||
|
||||
|
||||
/*
|
||||
Yeni JSON kaydını ekle
|
||||
*/
|
||||
INSERT INTO dbo."Sas_H_DbTableColumn"
|
||||
INSERT INTO public."Sas_H_DbTableColumn"
|
||||
(
|
||||
"TenantName",
|
||||
"DictionaryType",
|
||||
|
|
@ -186,54 +104,23 @@ BEGIN
|
|||
)
|
||||
VALUES
|
||||
(
|
||||
COALESCE(p_tenant_name, 'Sozsoft'),
|
||||
COALESCE(p_TenantName, 'Sozsoft'),
|
||||
'ListFormJson',
|
||||
COALESCE(p_definition_culture_name, 'en'),
|
||||
COALESCE(p_ui_culture_name, 'tr'),
|
||||
p_list_form_code,
|
||||
COALESCE(p_only_base_definitions, TRUE),
|
||||
p_resource_name,
|
||||
COALESCE(p_max_forms, 500),
|
||||
v_cache_key,
|
||||
v_json_data,
|
||||
v_form_count,
|
||||
v_field_count,
|
||||
v_json_length,
|
||||
COALESCE(p_DefinitionCultureName, 'en'),
|
||||
COALESCE(p_UiCultureName, 'tr'),
|
||||
p_ListFormCode,
|
||||
COALESCE(p_OnlyBaseDefinitions, TRUE),
|
||||
p_ResourceName,
|
||||
COALESCE(p_MaxForms, 500),
|
||||
v_CacheKey,
|
||||
v_JsonData,
|
||||
v_FormCount,
|
||||
v_FieldCount,
|
||||
v_JsonLength,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
'dbo.Sas_H_ListFormFieldsDictionaryJson',
|
||||
p_note
|
||||
LOCALTIMESTAMP,
|
||||
'public.Sas_H_ListFormFieldsDictionaryJson',
|
||||
p_Note
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
Eklenen aktif kaydı döndür
|
||||
*/
|
||||
RETURN QUERY
|
||||
|
||||
SELECT
|
||||
T."TenantName",
|
||||
T."DictionaryType",
|
||||
T."DefinitionCultureName",
|
||||
T."UiCultureName",
|
||||
T."ListFormCode",
|
||||
T."OnlyBaseDefinitions",
|
||||
T."ResourceName",
|
||||
T."MaxForms",
|
||||
T."CacheKey",
|
||||
T."FormCount",
|
||||
T."FieldCount",
|
||||
T."JsonLength",
|
||||
T."IsActive",
|
||||
T."RefreshedAt",
|
||||
T."SourceProcedure",
|
||||
T."Note"
|
||||
FROM dbo."Sas_H_DbTableColumn" T
|
||||
WHERE T."DictionaryType" = 'ListFormJson'
|
||||
AND T."CacheKey" = v_cache_key
|
||||
AND T."IsActive" = TRUE
|
||||
ORDER BY T."RefreshedAt" DESC
|
||||
LIMIT 1;
|
||||
|
||||
END;
|
||||
$$;
|
||||
$$;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
DO $$
|
||||
-- HangFire tablolari Hangfire.PostgreSql depolamasindan gelir ("hangfire" semasi, kucuk harfli ad
|
||||
-- ve kolonlar). Sema HttpApi.Host ilk acildiginda kurulur; DbMigrator kurmadigi icin ilk migrate'te
|
||||
-- tablolar yoksa view atlanir ve sonraki migrate'te olusturulur.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('hangfire.job') IS NULL
|
||||
OR to_regclass('hangfire.jobparameter') IS NULL
|
||||
|
|
@ -11,46 +14,61 @@ BEGIN
|
|||
CREATE OR REPLACE VIEW public."Sas_H_BackgroundWorker_JobFlow" AS
|
||||
SELECT
|
||||
j.id AS "Id",
|
||||
|
||||
-- Kiraci kapsamli isin adi WorkerType:Name#TenantId biciminde kurulur; ekranda is adi sade
|
||||
-- kalsin, kiraci ayri kolonda gorunsun diye ek burada ayrilir.
|
||||
CASE
|
||||
WHEN position('#' in COALESCE(p."RecurringJobId", '')) > 0
|
||||
THEN split_part(p."RecurringJobId", '#', 1)
|
||||
WHEN strpos(COALESCE(p."RecurringJobId", ''), '#') > 0
|
||||
THEN left(p."RecurringJobId", strpos(p."RecurringJobId", '#') - 1)
|
||||
ELSE COALESCE(p."RecurringJobId", 'OneTimeJob')
|
||||
END AS "JobName",
|
||||
|
||||
t."Name" AS "Tenant",
|
||||
|
||||
COALESCE(prc."ProcessingAt", enq."EnqueuedAt", j.createdat) AS "ExecutionDate",
|
||||
|
||||
CASE
|
||||
WHEN suc."DurationMs" IS NOT NULL THEN suc."DurationMs"
|
||||
WHEN suc."DurationMs" IS NOT NULL
|
||||
THEN suc."DurationMs"
|
||||
|
||||
WHEN prc."ProcessingAt" IS NOT NULL AND suc."SucceededAt" IS NOT NULL
|
||||
THEN (EXTRACT(EPOCH FROM (suc."SucceededAt" - prc."ProcessingAt")) * 1000)::bigint
|
||||
|
||||
WHEN prc."ProcessingAt" IS NOT NULL AND fld."FailedAt" IS NOT NULL
|
||||
THEN (EXTRACT(EPOCH FROM (fld."FailedAt" - prc."ProcessingAt")) * 1000)::bigint
|
||||
|
||||
ELSE NULL
|
||||
END AS "Duration",
|
||||
|
||||
j.statename AS "Status",
|
||||
|
||||
CASE
|
||||
WHEN j.statename = 'Failed' THEN COALESCE(
|
||||
fld."ExceptionMessage",
|
||||
fld."FailedReason",
|
||||
fld."ExceptionType",
|
||||
'Unknown error'
|
||||
)
|
||||
WHEN j.statename = 'Failed'
|
||||
THEN COALESCE(
|
||||
fld."ExceptionMessage",
|
||||
fld."FailedReason",
|
||||
fld."ExceptionType",
|
||||
'Unknown error'
|
||||
)
|
||||
ELSE NULL
|
||||
END AS "FailureReason",
|
||||
fld."ExceptionType",
|
||||
fld."ExceptionDetails"
|
||||
|
||||
fld."ExceptionType" AS "ExceptionType",
|
||||
fld."ExceptionDetails" AS "ExceptionDetails"
|
||||
|
||||
FROM hangfire.job j
|
||||
|
||||
LEFT JOIN LATERAL
|
||||
(
|
||||
SELECT
|
||||
MAX(CASE
|
||||
WHEN jp.name = 'RecurringJobId'
|
||||
THEN REPLACE(jp.value, '"', '')
|
||||
END) AS "RecurringJobId"
|
||||
WHEN jp.name = 'RecurringJobId'
|
||||
THEN REPLACE(jp.value, '"', '')
|
||||
END) AS "RecurringJobId"
|
||||
FROM hangfire.jobparameter jp
|
||||
WHERE jp.jobid = j.id
|
||||
) p ON TRUE
|
||||
|
||||
LEFT JOIN LATERAL
|
||||
(
|
||||
SELECT
|
||||
|
|
@ -61,6 +79,7 @@ LEFT JOIN LATERAL
|
|||
ORDER BY s.createdat DESC
|
||||
LIMIT 1
|
||||
) enq ON TRUE
|
||||
|
||||
LEFT JOIN LATERAL
|
||||
(
|
||||
SELECT
|
||||
|
|
@ -71,17 +90,22 @@ LEFT JOIN LATERAL
|
|||
ORDER BY s.createdat DESC
|
||||
LIMIT 1
|
||||
) prc ON TRUE
|
||||
|
||||
LEFT JOIN LATERAL
|
||||
(
|
||||
SELECT
|
||||
s.createdat AS "SucceededAt",
|
||||
NULLIF(s.data::jsonb ->> 'PerformanceDuration', '')::bigint AS "DurationMs"
|
||||
CASE
|
||||
WHEN BTRIM(s.data::jsonb ->> 'PerformanceDuration') ~ '^[+-]?[0-9]{1,18}$'
|
||||
THEN BTRIM(s.data::jsonb ->> 'PerformanceDuration')::bigint
|
||||
END AS "DurationMs"
|
||||
FROM hangfire.state s
|
||||
WHERE s.jobid = j.id
|
||||
AND s.name = 'Succeeded'
|
||||
ORDER BY s.createdat DESC
|
||||
LIMIT 1
|
||||
) suc ON TRUE
|
||||
|
||||
LEFT JOIN LATERAL
|
||||
(
|
||||
SELECT
|
||||
|
|
@ -96,17 +120,22 @@ LEFT JOIN LATERAL
|
|||
ORDER BY s.createdat DESC
|
||||
LIMIT 1
|
||||
) fld ON TRUE
|
||||
|
||||
LEFT JOIN LATERAL
|
||||
(
|
||||
SELECT CASE
|
||||
WHEN length(split_part(p."RecurringJobId", '#', 2)) = 36
|
||||
AND split_part(p."RecurringJobId", '#', 2) ~ '^[0-9a-fA-F-]+$'
|
||||
THEN split_part(p."RecurringJobId", '#', 2)::uuid
|
||||
END AS "TenantId"
|
||||
SELECT
|
||||
CASE
|
||||
WHEN strpos(COALESCE(p."RecurringJobId", ''), '#') > 0
|
||||
AND substring(p."RecurringJobId" FROM strpos(p."RecurringJobId", '#') + 1 FOR 36)
|
||||
~ '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'
|
||||
THEN substring(p."RecurringJobId" FROM strpos(p."RecurringJobId", '#') + 1 FOR 36)::uuid
|
||||
END AS "TenantId"
|
||||
) tn ON TRUE
|
||||
|
||||
LEFT JOIN public."AbpTenants" t
|
||||
ON t."Id" = tn."TenantId"
|
||||
WHERE COALESCE(j.invocationdata::jsonb ->> 'Method', '') <> 'DoWorkAsync'
|
||||
|
||||
WHERE j.invocationdata::jsonb ->> 'Method' <> 'DoWorkAsync'
|
||||
$view$;
|
||||
END;
|
||||
$$;
|
||||
|
|
|
|||
59
configs/seeds/postgres/object/Adm_T_Contact.sql
Normal file
59
configs/seeds/postgres/object/Adm_T_Contact.sql
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Adm_T_Contact"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Logo" text NULL,
|
||||
"Name" varchar(256) NOT NULL,
|
||||
"CompanyType" varchar(16) NOT NULL DEFAULT 'Company',
|
||||
"Email" varchar(256) NULL,
|
||||
"Phone" varchar(32) NULL,
|
||||
"Website" varchar(256) NULL,
|
||||
"Address1" varchar(256) NULL,
|
||||
"Address2" varchar(256) NULL,
|
||||
"Country" varchar(128) NULL,
|
||||
"City" varchar(128) NULL,
|
||||
"District" varchar(128) NULL,
|
||||
"Township" varchar(128) NULL,
|
||||
"PostalCode" varchar(16) NULL,
|
||||
"TaxNumber" varchar(32) NULL,
|
||||
"TaxOffice" varchar(128) NULL,
|
||||
"JobPosition" varchar(128) NULL,
|
||||
"CultureName" varchar(16) NULL,
|
||||
"Tags" varchar(512) NULL,
|
||||
"SalesUserId" uuid NULL,
|
||||
"SalesCurrency" varchar(16) NULL,
|
||||
"SalesPaymentTermId" uuid NULL,
|
||||
"SalesPaymentMethodId" uuid NULL,
|
||||
"DeliveryMethodId" uuid NULL,
|
||||
"SalesIncotermId" uuid NULL,
|
||||
"SalesIncotermLocation" varchar(128) NULL,
|
||||
"RfqGrouping" varchar(16) NOT NULL DEFAULT 'OnOrder',
|
||||
"BuyerUserId" uuid NULL,
|
||||
"PurchasePaymentTermId" uuid NULL,
|
||||
"PurchasePaymentMethodId" uuid NULL,
|
||||
"ReceiptReminder" boolean NOT NULL DEFAULT FALSE,
|
||||
"ReminderDaysBefore" integer NULL DEFAULT 1,
|
||||
"SupplierCurrency" varchar(16) NULL,
|
||||
"PurchaseIncotermId" uuid NULL,
|
||||
"PurchaseIncotermLocation" varchar(128) NULL,
|
||||
"FinancialStatus" varchar(16) NULL,
|
||||
"Reference" varchar(64) NULL,
|
||||
"Barcode" varchar(64) NULL,
|
||||
"SectorId" uuid NULL,
|
||||
"ReceivableAccount" varchar(64) NULL,
|
||||
"PayableAccount" varchar(64) NULL,
|
||||
"InvoiceAutoApprove" varchar(16) NULL DEFAULT 'AfterThree',
|
||||
"PaymentQrType" varchar(16) NULL,
|
||||
"Latitude" double precision NULL,
|
||||
"Longitude" double precision NULL,
|
||||
"Notes" text NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Adm_T_Contact" PRIMARY KEY ("Id")
|
||||
);
|
||||
19
configs/seeds/postgres/object/Adm_T_ContactBankAccount.sql
Normal file
19
configs/seeds/postgres/object/Adm_T_ContactBankAccount.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Adm_T_ContactBankAccount"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"ContactId" uuid NOT NULL,
|
||||
"BankName" varchar(128) NULL,
|
||||
"Iban" varchar(34) NOT NULL,
|
||||
"AccountHolder" varchar(256) NULL,
|
||||
"Currency" varchar(16) NULL,
|
||||
"IsDefault" boolean NOT NULL DEFAULT FALSE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Adm_T_ContactBankAccount" PRIMARY KEY ("Id")
|
||||
);
|
||||
21
configs/seeds/postgres/object/Adm_T_ContactPerson.sql
Normal file
21
configs/seeds/postgres/object/Adm_T_ContactPerson.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Adm_T_ContactPerson"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"ContactId" uuid NOT NULL,
|
||||
"Photo" text NULL,
|
||||
"Name" varchar(256) NOT NULL,
|
||||
"JobTitle" varchar(128) NULL,
|
||||
"Email" varchar(256) NULL,
|
||||
"Phone" varchar(32) NULL,
|
||||
"Mobile" varchar(32) NULL,
|
||||
"Notes" text NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Adm_T_ContactPerson" PRIMARY KEY ("Id")
|
||||
);
|
||||
16
configs/seeds/postgres/object/Adm_T_ContactTag.sql
Normal file
16
configs/seeds/postgres/object/Adm_T_ContactTag.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Adm_T_ContactTag"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"Color" varchar(16) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Adm_T_ContactTag" PRIMARY KEY ("Id")
|
||||
);
|
||||
25
configs/seeds/postgres/object/Hr_F_HrSetting.sql
Normal file
25
configs/seeds/postgres/object/Hr_F_HrSetting.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
CREATE OR REPLACE FUNCTION public."Hr_F_HrSetting"
|
||||
(
|
||||
p_TenantId uuid,
|
||||
p_Name varchar(128)
|
||||
)
|
||||
RETURNS varchar(2048)
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Ayar ekranının çözüm sırası (T|G|D): kiracı değeri, genel değer, tanımdaki varsayılan.
|
||||
-- ProviderKey Guid metnidir; SQL Server karşılaştırması büyük/küçük harf duyarsız olduğundan lower().
|
||||
RETURN COALESCE(
|
||||
(SELECT s."Value" FROM "AbpSettings" s
|
||||
WHERE s."Name" = p_Name AND s."ProviderName" = 'T' AND p_TenantId IS NOT NULL
|
||||
AND lower(s."ProviderKey") = p_TenantId::text
|
||||
LIMIT 1),
|
||||
(SELECT s."Value" FROM "AbpSettings" s
|
||||
WHERE s."Name" = p_Name AND s."ProviderName" = 'G'
|
||||
LIMIT 1),
|
||||
(SELECT d."DefaultValue" FROM "Sas_H_SettingDefinition" d
|
||||
WHERE d."Code" = p_Name AND d."IsDeleted" = FALSE
|
||||
LIMIT 1));
|
||||
END;
|
||||
$$;
|
||||
37
configs/seeds/postgres/object/Hr_F_LeaveNetDays.sql
Normal file
37
configs/seeds/postgres/object/Hr_F_LeaveNetDays.sql
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
CREATE OR REPLACE FUNCTION public."Hr_F_LeaveNetDays"
|
||||
(
|
||||
p_TenantId uuid,
|
||||
p_EmployeeId uuid,
|
||||
p_StartDate date,
|
||||
p_EndDate date
|
||||
)
|
||||
RETURNS numeric(9, 1)
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_Total numeric(9, 1);
|
||||
v_Day date := p_StartDate;
|
||||
BEGIN
|
||||
-- İzin talebinin düştüğü gün sayısı: çalışma günleri (Hr_F_LeaveWorkDays) eksi çalışanın
|
||||
-- rapor (istirahat) kapsamındaki günleri. İzin sırasında alınan raporun günleri izinden sayılmaz;
|
||||
-- puantajda da o günler Raporlu'dur, bakiye ile puantaj aynı günü iki kez kullanmaz.
|
||||
IF p_StartDate IS NULL OR p_EndDate IS NULL OR p_EndDate < p_StartDate THEN
|
||||
RETURN 0;
|
||||
END IF;
|
||||
|
||||
v_Total := public."Hr_F_LeaveWorkDays"(p_TenantId, p_StartDate, p_EndDate);
|
||||
|
||||
WHILE v_Day <= p_EndDate AND v_Total > 0 LOOP
|
||||
IF EXISTS (SELECT 1 FROM "Adm_T_UserSickLeave" s
|
||||
WHERE s."UserId" = p_EmployeeId AND s."IsDeleted" = FALSE
|
||||
AND v_Day BETWEEN s."StartDate"::date AND s."EndDate"::date) THEN
|
||||
v_Total := v_Total - public."Hr_F_LeaveWorkDays"(p_TenantId, v_Day, v_Day);
|
||||
END IF;
|
||||
|
||||
v_Day := v_Day + 1;
|
||||
END LOOP;
|
||||
|
||||
RETURN CASE WHEN v_Total < 0 THEN 0 ELSE v_Total END;
|
||||
END;
|
||||
$$;
|
||||
76
configs/seeds/postgres/object/Hr_F_LeaveWorkDays.sql
Normal file
76
configs/seeds/postgres/object/Hr_F_LeaveWorkDays.sql
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
CREATE OR REPLACE FUNCTION public."Hr_F_LeaveWorkDays"
|
||||
(
|
||||
p_TenantId uuid,
|
||||
p_StartDate date,
|
||||
p_EndDate date
|
||||
)
|
||||
RETURNS numeric(9, 1)
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
v_Work boolean[];
|
||||
v_Total numeric(9, 1) := 0;
|
||||
v_Day date := p_StartDate;
|
||||
v_Value numeric(3, 1);
|
||||
v_Calendar numeric(3, 1);
|
||||
BEGIN
|
||||
-- İzin günü = ayarlarda çalışma günü işaretli olup Çalışma Takvimi'nde tatil olmayan gün.
|
||||
-- Yarım gün (arife) 0,5 sayılır. Yıllık tekrarlı satır başlangıç yılından itibaren her yıl
|
||||
-- aynı gün/ay aralığına düşer (29 Şubat artık olmayan yılda 28 Şubat'a kayar).
|
||||
IF p_StartDate IS NULL OR p_EndDate IS NULL OR p_EndDate < p_StartDate THEN
|
||||
RETURN 0;
|
||||
END IF;
|
||||
|
||||
-- Gün sırası Pazartesi = 1 (ISODOW). Ayar tanımı yoksa Pzt–Cum.
|
||||
SELECT array_agg(
|
||||
CASE
|
||||
WHEN s.v IS NULL THEN d.DayIndex <= 5
|
||||
WHEN lower(s.v) = 'true' THEN TRUE
|
||||
ELSE FALSE
|
||||
END
|
||||
ORDER BY d.DayIndex)
|
||||
INTO v_Work
|
||||
FROM (VALUES
|
||||
(1, 'App.HrSettings.Leave.WorkMonday'),
|
||||
(2, 'App.HrSettings.Leave.WorkTuesday'),
|
||||
(3, 'App.HrSettings.Leave.WorkWednesday'),
|
||||
(4, 'App.HrSettings.Leave.WorkThursday'),
|
||||
(5, 'App.HrSettings.Leave.WorkFriday'),
|
||||
(6, 'App.HrSettings.Leave.WorkSaturday'),
|
||||
(7, 'App.HrSettings.Leave.WorkSunday')) d (DayIndex, SettingName)
|
||||
CROSS JOIN LATERAL (SELECT public."Hr_F_HrSetting"(p_TenantId, d.SettingName) AS v) s;
|
||||
|
||||
WHILE v_Day <= p_EndDate LOOP
|
||||
v_Value := 0;
|
||||
|
||||
IF v_Work[EXTRACT(ISODOW FROM v_Day)::int] THEN
|
||||
v_Value := 1;
|
||||
|
||||
SELECT CASE WHEN MAX(CASE WHEN c."DayType" = 'Holiday' THEN 1 ELSE 0 END) = 1 THEN 0 ELSE 0.5 END
|
||||
INTO v_Calendar
|
||||
FROM "Hr_T_WorkCalendar" c
|
||||
WHERE c."IsDeleted" = FALSE AND c."IsActive" = TRUE
|
||||
AND c."TenantId" IS NOT DISTINCT FROM p_TenantId
|
||||
AND (
|
||||
((c."RecurrenceRule" IS NULL OR c."RecurrenceRule" = '') AND v_Day BETWEEN c."StartDate" AND c."EndDate")
|
||||
OR (c."RecurrenceRule" = 'FREQ=YEARLY'
|
||||
AND EXTRACT(YEAR FROM v_Day) >= EXTRACT(YEAR FROM c."StartDate")
|
||||
AND v_Day BETWEEN (c."StartDate" + make_interval(years => (EXTRACT(YEAR FROM v_Day) - EXTRACT(YEAR FROM c."StartDate"))::int))::date
|
||||
AND (c."EndDate" + make_interval(years => (EXTRACT(YEAR FROM v_Day) - EXTRACT(YEAR FROM c."StartDate"))::int))::date)
|
||||
)
|
||||
HAVING COUNT(*) > 0;
|
||||
|
||||
-- Takvimde satır yoksa gün tam sayılır (SELECT INTO satırsızken değişkeni NULL yapar).
|
||||
IF FOUND THEN
|
||||
v_Value := v_Calendar;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
v_Total := v_Total + v_Value;
|
||||
v_Day := v_Day + 1;
|
||||
END LOOP;
|
||||
|
||||
RETURN v_Total;
|
||||
END;
|
||||
$$;
|
||||
26
configs/seeds/postgres/object/Hr_F_TimesheetPeriodClosed.sql
Normal file
26
configs/seeds/postgres/object/Hr_F_TimesheetPeriodClosed.sql
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
CREATE OR REPLACE FUNCTION public."Hr_F_TimesheetPeriodClosed"
|
||||
(
|
||||
p_TenantId uuid,
|
||||
p_StartDate date,
|
||||
p_EndDate date
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Aralığın değdiği aylardan biri kapalı puantaj dönemiyse TRUE. Puantajı değiştiren kayıtlar
|
||||
-- (izin, rapor, puantaj günü) kapalı aya yazılamaz; dönemi İK açar, açılış ayı yeniden hesaplar.
|
||||
IF p_StartDate IS NULL OR p_EndDate IS NULL THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
|
||||
RETURN EXISTS (
|
||||
SELECT 1 FROM "Hr_T_TimesheetPeriod" p
|
||||
WHERE p."IsDeleted" = FALSE AND p."Status" = 'Closed'
|
||||
AND p."TenantId" IS NOT DISTINCT FROM p_TenantId
|
||||
AND p."PeriodYear" * 100 + p."PeriodMonth"
|
||||
BETWEEN EXTRACT(YEAR FROM p_StartDate)::int * 100 + EXTRACT(MONTH FROM p_StartDate)::int
|
||||
AND EXTRACT(YEAR FROM p_EndDate)::int * 100 + EXTRACT(MONTH FROM p_EndDate)::int);
|
||||
END;
|
||||
$$;
|
||||
28
configs/seeds/postgres/object/Hr_F_WorkCalendarDayType.sql
Normal file
28
configs/seeds/postgres/object/Hr_F_WorkCalendarDayType.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
CREATE OR REPLACE FUNCTION public."Hr_F_WorkCalendarDayType"
|
||||
(
|
||||
p_TenantId uuid,
|
||||
p_Day date
|
||||
)
|
||||
RETURNS varchar(16)
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Çalışma Takvimi'nde güne düşen tür: Holiday (tam gün tatil) yarım günü (HalfDay) ezer; yoksa NULL.
|
||||
-- Yıllık tekrarlı satır eşlemesi Hr_F_LeaveWorkDays ile aynıdır.
|
||||
RETURN (
|
||||
SELECT CASE WHEN MAX(CASE WHEN c."DayType" = 'Holiday' THEN 1 ELSE 0 END) = 1 THEN 'Holiday' ELSE 'HalfDay' END
|
||||
FROM "Hr_T_WorkCalendar" c
|
||||
WHERE c."IsDeleted" = FALSE AND c."IsActive" = TRUE
|
||||
AND c."TenantId" IS NOT DISTINCT FROM p_TenantId
|
||||
AND (
|
||||
((c."RecurrenceRule" IS NULL OR c."RecurrenceRule" = '') AND p_Day BETWEEN c."StartDate"::date AND c."EndDate"::date)
|
||||
OR (c."RecurrenceRule" = 'FREQ=YEARLY'
|
||||
AND EXTRACT(YEAR FROM p_Day) >= EXTRACT(YEAR FROM c."StartDate")
|
||||
AND p_Day BETWEEN (c."StartDate" + make_interval(years => (EXTRACT(YEAR FROM p_Day) - EXTRACT(YEAR FROM c."StartDate"))::int))::date
|
||||
AND (c."EndDate" + make_interval(years => (EXTRACT(YEAR FROM p_Day) - EXTRACT(YEAR FROM c."StartDate"))::int))::date)
|
||||
)
|
||||
HAVING COUNT(*) > 0
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
125
configs/seeds/postgres/object/Hr_S_AttendanceImport.sql
Normal file
125
configs/seeds/postgres/object/Hr_S_AttendanceImport.sql
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
-- Custom Endpoint sonucu (Received/Inserted/Skipped/Errors) okuduğu için prosedür değil, satır dönen fonksiyondur.
|
||||
CREATE OR REPLACE FUNCTION public."Hr_S_AttendanceImport"(
|
||||
p_TenantId varchar(64),
|
||||
p_UserId varchar(64),
|
||||
p_Items text
|
||||
)
|
||||
RETURNS TABLE ("Received" integer, "Inserted" integer, "Skipped" integer, "Errors" integer)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_Tenant uuid;
|
||||
v_Actor uuid;
|
||||
v_Json jsonb;
|
||||
v_Item jsonb;
|
||||
v_EventText text;
|
||||
v_EventTime timestamp;
|
||||
v_Received integer;
|
||||
v_Inserted integer;
|
||||
v_Errors integer;
|
||||
BEGIN
|
||||
-- PDKS ucu (Custom Endpoint POST /hr/attendance/): gövdedeki Items JSON dizisini hareket olarak
|
||||
-- yazar. Her öğe: CardNo (kullanıcı kartındaki PDKS Kart No), RegistrationNo (Sicil No) ya da UserName;
|
||||
-- EventTime (yerel saat, ISO 8601), Direction (In/Out/Auto), DeviceCode. Kart/sicil eşlemesini
|
||||
-- Hr_S_AttendanceProcess yapar. Aynı kişi ve aynı zamanla daha önce gelmiş okutma atlanır; kapalı
|
||||
-- döneme düşen ve kartı tanımsız okutma hatalı işaretlenir, gönderimi durdurmaz.
|
||||
BEGIN
|
||||
v_Tenant := p_TenantId::uuid;
|
||||
EXCEPTION WHEN invalid_text_representation THEN
|
||||
v_Tenant := NULL;
|
||||
END;
|
||||
BEGIN
|
||||
v_Actor := p_UserId::uuid;
|
||||
EXCEPTION WHEN invalid_text_representation THEN
|
||||
v_Actor := NULL;
|
||||
END;
|
||||
|
||||
-- ISJSON yalnız dizi ve nesneyi geçerli sayar; nesne tek öğe olarak okunur (OPENJSON ... WITH gibi).
|
||||
IF p_Items IS NOT NULL THEN
|
||||
BEGIN
|
||||
v_Json := p_Items::jsonb;
|
||||
EXCEPTION WHEN others THEN
|
||||
v_Json := NULL;
|
||||
END;
|
||||
END IF;
|
||||
IF v_Json IS NULL OR jsonb_typeof(v_Json) NOT IN ('array', 'object') THEN
|
||||
RAISE EXCEPTION 'Items geçerli bir JSON dizisi olmalıdır.' USING ERRCODE = 'P0001', DETAIL = '51210';
|
||||
END IF;
|
||||
IF jsonb_typeof(v_Json) = 'object' THEN
|
||||
v_Json := jsonb_build_array(v_Json);
|
||||
END IF;
|
||||
|
||||
DROP TABLE IF EXISTS ai_item;
|
||||
CREATE TEMP TABLE ai_item
|
||||
(
|
||||
CardNo varchar(64) NULL, UserName varchar(256) NULL, EventTime timestamp NULL,
|
||||
Direction varchar(8) NULL, DeviceCode varchar(64) NULL, EmployeeId uuid NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
DROP TABLE IF EXISTS ai_inserted;
|
||||
CREATE TEMP TABLE ai_inserted (Id uuid) ON COMMIT DROP;
|
||||
|
||||
FOR v_Item IN SELECT e FROM jsonb_array_elements(v_Json) e LOOP
|
||||
-- Dizideki skaler öğe OPENJSON ... WITH'te bütün alanları boş bir satırdır.
|
||||
IF jsonb_typeof(v_Item) <> 'object' THEN
|
||||
v_Item := '{}'::jsonb;
|
||||
END IF;
|
||||
|
||||
v_EventText := v_Item ->> 'EventTime';
|
||||
v_EventTime := NULL;
|
||||
IF v_EventText IS NOT NULL THEN
|
||||
BEGIN
|
||||
v_EventTime := v_EventText::timestamp;
|
||||
EXCEPTION WHEN others THEN
|
||||
v_EventTime := NULL;
|
||||
END;
|
||||
END IF;
|
||||
|
||||
INSERT INTO ai_item (CardNo, UserName, EventTime, Direction, DeviceCode)
|
||||
VALUES (
|
||||
COALESCE(NULLIF(btrim(left(v_Item ->> 'CardNo', 64)), ''), NULLIF(btrim(left(v_Item ->> 'RegistrationNo', 64)), '')),
|
||||
NULLIF(btrim(left(v_Item ->> 'UserName', 256)), ''),
|
||||
v_EventTime,
|
||||
CASE WHEN lower(v_Item ->> 'Direction') IN ('in', 'out') THEN left(v_Item ->> 'Direction', 8) ELSE 'Auto' END,
|
||||
left(v_Item ->> 'DeviceCode', 64));
|
||||
END LOOP;
|
||||
|
||||
v_Received := (SELECT COUNT(*) FROM ai_item)::integer;
|
||||
|
||||
DELETE FROM ai_item WHERE EventTime IS NULL OR (CardNo IS NULL AND UserName IS NULL);
|
||||
|
||||
UPDATE ai_item i
|
||||
SET EmployeeId = u."Id"
|
||||
FROM public."AbpUsers" u
|
||||
WHERE lower(u."UserName") = lower(i.UserName) AND u."IsDeleted" = FALSE
|
||||
AND (u."TenantId" = v_Tenant OR (u."TenantId" IS NULL AND v_Tenant IS NULL))
|
||||
AND i.UserName IS NOT NULL;
|
||||
|
||||
WITH ins AS (
|
||||
INSERT INTO public."Hr_T_AttendanceLog"
|
||||
("Id", "TenantId", "EmployeeId", "CardNo", "EventTime", "Direction", "Source", "DeviceCode", "IsProcessed", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), v_Tenant, n.EmployeeId, n.CardNo, n.EventTime, n.Direction, 'Pdks', n.DeviceCode, FALSE,
|
||||
(NOW() AT TIME ZONE 'UTC'), v_Actor, FALSE
|
||||
FROM (
|
||||
SELECT DISTINCT a.EmployeeId, a.CardNo, a.EventTime, a.Direction, a.DeviceCode FROM ai_item a
|
||||
) n
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM public."Hr_T_AttendanceLog" l
|
||||
WHERE l."IsDeleted" = FALSE AND l."EventTime" = n.EventTime
|
||||
AND (l."TenantId" = v_Tenant OR (l."TenantId" IS NULL AND v_Tenant IS NULL))
|
||||
AND ((n.EmployeeId IS NOT NULL AND l."EmployeeId" = n.EmployeeId)
|
||||
OR (n.EmployeeId IS NULL AND lower(l."CardNo") = lower(n.CardNo))))
|
||||
RETURNING "Id"
|
||||
)
|
||||
INSERT INTO ai_inserted (Id)
|
||||
SELECT ins."Id" FROM ins;
|
||||
|
||||
CALL public."Hr_S_AttendanceProcess"(FALSE);
|
||||
|
||||
v_Inserted := (SELECT COUNT(*) FROM ai_inserted)::integer;
|
||||
v_Errors := (SELECT COUNT(*) FROM public."Hr_T_AttendanceLog" l INNER JOIN ai_inserted i ON i.Id = l."Id"
|
||||
WHERE l."ProcessError" IS NOT NULL)::integer;
|
||||
|
||||
RETURN QUERY SELECT v_Received, v_Inserted, v_Received - v_Inserted, v_Errors;
|
||||
END;
|
||||
$$;
|
||||
97
configs/seeds/postgres/object/Hr_S_AttendanceProcess.sql
Normal file
97
configs/seeds/postgres/object/Hr_S_AttendanceProcess.sql
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_AttendanceProcess"(
|
||||
p_FailOnClosed boolean DEFAULT TRUE
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_Key record;
|
||||
BEGIN
|
||||
-- İşlenmemiş hareketleri (ekran, Excel import, PDKS ucu; silinenler dahil) işler: çalışanı kullanıcı
|
||||
-- kartındaki PDKS Kart No'dan (yoksa Sicil No'dan) bulur, hareketin iş gününü vardiyaya göre belirler ve etkilenen günlerin puantajını
|
||||
-- yeniden hesaplar. Ekrandan gelen değişiklik kapalı döneme düşerse kayıt geri alınır
|
||||
-- (p_FailOnClosed = TRUE); PDKS ve gece işinde satır hatalı işaretlenir.
|
||||
-- Kart/sicil eşleşmesi SQL Server harmanlaması gibi büyük/küçük harf duyarsızdır.
|
||||
DROP TABLE IF EXISTS ap_log;
|
||||
CREATE TEMP TABLE ap_log
|
||||
(
|
||||
Id uuid PRIMARY KEY, TenantId uuid NULL, EmployeeId uuid NULL,
|
||||
CardNo varchar(64) NULL, EventTime timestamp, IsDeleted boolean, OldWorkDate date NULL,
|
||||
NewWorkDate date NULL, Error varchar(32) NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
INSERT INTO ap_log (Id, TenantId, EmployeeId, CardNo, EventTime, IsDeleted, OldWorkDate)
|
||||
SELECT "Id", "TenantId", "EmployeeId", "CardNo", "EventTime", "IsDeleted", "WorkDate"
|
||||
FROM public."Hr_T_AttendanceLog"
|
||||
WHERE "IsProcessed" = FALSE;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM ap_log) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
UPDATE ap_log a
|
||||
SET EmployeeId = u.Id
|
||||
FROM (
|
||||
SELECT a2.Id AS LogId, (
|
||||
SELECT x."Id"
|
||||
FROM public."AbpUsers" x
|
||||
WHERE (lower(x."PdksCardNo") = lower(a2.CardNo) OR lower(x."RegistrationNo") = lower(a2.CardNo)) AND x."IsDeleted" = FALSE
|
||||
AND (x."TenantId" = a2.TenantId OR (x."TenantId" IS NULL AND a2.TenantId IS NULL))
|
||||
ORDER BY CASE WHEN lower(x."PdksCardNo") = lower(a2.CardNo) THEN 0 ELSE 1 END, x."IsActive" DESC NULLS LAST
|
||||
LIMIT 1) AS Id
|
||||
FROM ap_log a2
|
||||
WHERE a2.EmployeeId IS NULL AND a2.CardNo IS NOT NULL AND a2.CardNo <> ''
|
||||
) u
|
||||
WHERE u.LogId = a.Id AND u.Id IS NOT NULL;
|
||||
|
||||
UPDATE ap_log SET Error = 'UnknownCard' WHERE EmployeeId IS NULL AND IsDeleted = FALSE;
|
||||
|
||||
UPDATE ap_log a
|
||||
SET NewWorkDate = (a.EventTime - s."DayOffset" * INTERVAL '1 minute')::date
|
||||
FROM public."Hr_V_AttendanceShift" s
|
||||
WHERE s."EmployeeId" = a.EmployeeId
|
||||
AND a.IsDeleted = FALSE AND a.Error IS NULL;
|
||||
|
||||
-- Kapalı dönem: hareketin eski ya da yeni günü kapalı aya düşüyorsa.
|
||||
UPDATE ap_log a
|
||||
SET Error = 'ClosedPeriod'
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM public."Hr_T_TimesheetPeriod" p
|
||||
WHERE p."IsDeleted" = FALSE AND p."Status" = 'Closed'
|
||||
AND (p."TenantId" = a.TenantId OR (p."TenantId" IS NULL AND a.TenantId IS NULL))
|
||||
AND ((p."PeriodYear" = EXTRACT(YEAR FROM a.OldWorkDate)::integer AND p."PeriodMonth" = EXTRACT(MONTH FROM a.OldWorkDate)::integer)
|
||||
OR (p."PeriodYear" = EXTRACT(YEAR FROM a.NewWorkDate)::integer AND p."PeriodMonth" = EXTRACT(MONTH FROM a.NewWorkDate)::integer)));
|
||||
|
||||
IF p_FailOnClosed = TRUE AND EXISTS (SELECT 1 FROM ap_log WHERE Error = 'ClosedPeriod') THEN
|
||||
RAISE EXCEPTION 'Kapalı puantaj dönemine ait hareket eklenemez, değiştirilemez ya da silinemez. Dönemi İnsan Kaynakları açabilir.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51200';
|
||||
END IF;
|
||||
|
||||
UPDATE public."Hr_T_AttendanceLog" l
|
||||
SET "EmployeeId" = a.EmployeeId,
|
||||
"WorkDate" = CASE WHEN a.Error = 'ClosedPeriod' THEN l."WorkDate" ELSE a.NewWorkDate END,
|
||||
"ProcessError" = a.Error,
|
||||
"Source" = COALESCE(l."Source", 'Import'),
|
||||
"IsProcessed" = TRUE
|
||||
FROM ap_log a
|
||||
WHERE a.Id = l."Id";
|
||||
|
||||
-- Etkilenen günler: hareketin eski günü (taşındıysa ya da silindiyse) ve yeni günü.
|
||||
DROP TABLE IF EXISTS ap_keys;
|
||||
CREATE TEMP TABLE ap_keys (TenantId uuid NULL, EmployeeId uuid, FromDate date, ToDate date) ON COMMIT DROP;
|
||||
|
||||
INSERT INTO ap_keys (TenantId, EmployeeId, FromDate, ToDate)
|
||||
SELECT k.TenantId, k.EmployeeId, MIN(k.D), MAX(k.D)
|
||||
FROM (
|
||||
SELECT TenantId, EmployeeId, OldWorkDate AS D FROM ap_log
|
||||
WHERE EmployeeId IS NOT NULL AND OldWorkDate IS NOT NULL AND COALESCE(Error, '') <> 'ClosedPeriod'
|
||||
UNION ALL
|
||||
SELECT TenantId, EmployeeId, NewWorkDate FROM ap_log
|
||||
WHERE EmployeeId IS NOT NULL AND NewWorkDate IS NOT NULL AND Error IS NULL
|
||||
) k
|
||||
GROUP BY k.TenantId, k.EmployeeId;
|
||||
|
||||
FOR v_Key IN SELECT TenantId, EmployeeId, FromDate, ToDate FROM ap_keys LOOP
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_Key.TenantId, v_Key.EmployeeId, v_Key.FromDate, v_Key.ToDate, NULL);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
17
configs/seeds/postgres/object/Hr_S_DevelopmentPlanSync.sql
Normal file
17
configs/seeds/postgres/object/Hr_S_DevelopmentPlanSync.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_DevelopmentPlanSync"(
|
||||
p_PlanId uuid
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Eğitim aksiyonu, çalışan o eğitime "Katıldı" olarak işlendiyse kendiliğinden tamamlanır;
|
||||
-- tamamlanan aksiyonun ilerlemesi her zaman %100'dür.
|
||||
UPDATE "Hr_T_DevelopmentPlan" dp
|
||||
SET "Status" = 'Completed', "Progress" = 100
|
||||
WHERE dp."Id" = p_PlanId AND dp."IsDeleted" = FALSE AND dp."Status" <> 'Cancelled'
|
||||
AND (dp."Status" = 'Completed' OR EXISTS (
|
||||
SELECT 1 FROM "Hr_T_TrainingParticipant" tp
|
||||
WHERE tp."TrainingId" = dp."TrainingId" AND tp."UserId" = dp."EmployeeId"
|
||||
AND tp."AttendanceStatus" = 'Attended' AND tp."IsDeleted" = FALSE));
|
||||
END
|
||||
$$;
|
||||
26
configs/seeds/postgres/object/Hr_S_LeaveDaysRefresh.sql
Normal file
26
configs/seeds/postgres/object/Hr_S_LeaveDaysRefresh.sql
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_LeaveDaysRefresh"(
|
||||
p_TenantId uuid,
|
||||
p_EmployeeId uuid
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Rapor eklenince, tarihi değişince ya da silinince çalışanın diğer izin taleplerinin gün sayısı
|
||||
-- yeniden hesaplanır (Hr_F_LeaveNetDays): rapora denk gelen yıllık izin günü bakiyeye geri döner.
|
||||
-- Rapor türündeki talep kendi günlerini sayar; onun gün sayısına dokunulmaz.
|
||||
UPDATE public."Hr_T_LeaveRequest" AS r
|
||||
SET "Days" = s."NewDays"
|
||||
FROM (
|
||||
SELECT x."Id",
|
||||
CASE WHEN x."IsHalfDay" = TRUE AND n."Days" > 0.5 THEN 0.5 ELSE n."Days" END AS "NewDays"
|
||||
FROM public."Hr_T_LeaveRequest" x
|
||||
INNER JOIN public."Hr_T_LeaveType" t ON t."Id" = x."LeaveTypeId"
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT public."Hr_F_LeaveNetDays"(p_TenantId, x."EmployeeId", x."StartDate"::date, x."EndDate"::date) AS "Days"
|
||||
) n
|
||||
WHERE x."EmployeeId" = p_EmployeeId AND x."IsDeleted" = FALSE AND t."Code" <> 'Sick'
|
||||
) s
|
||||
WHERE r."Id" = s."Id"
|
||||
AND r."Days" <> s."NewDays";
|
||||
END
|
||||
$$;
|
||||
44
configs/seeds/postgres/object/Hr_S_LeaveNotify.sql
Normal file
44
configs/seeds/postgres/object/Hr_S_LeaveNotify.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_LeaveNotify"(
|
||||
p_RequestId uuid,
|
||||
p_RecipientId uuid,
|
||||
p_TypeName varchar(64),
|
||||
p_SubjectKey varchar(128),
|
||||
p_MessageKey varchar(128),
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Alıcı talepten gelir (müdür, İK onaycısı ya da çalışan); tipin Custom kuralları yalnızca
|
||||
-- kanalı belirler. Metin TR dil anahtarından okunur, yer tutucular talepten dolar.
|
||||
IF p_RequestId IS NULL OR p_RecipientId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
INSERT INTO public."Adm_T_Notification" ("Id", "TenantId", "NotificationRuleId", "NotificationChannel", "NotificationTypeId",
|
||||
"Identifier", "UserId", "Message", "Subject", "IsSent", "IsRead", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), lr."TenantId", r."Id", r."Channel", t."Id",
|
||||
CASE WHEN r."Channel" = 'Mail' THEN u."Email" ELSE u."Id"::text END,
|
||||
u."Id",
|
||||
left(replace(replace(replace(replace(replace(replace(COALESCE(msg."Value", p_MessageKey),
|
||||
'[Employee]', COALESCE(lr."EmployeeName", '')),
|
||||
'[LeaveType]', COALESCE(lt."Name", '')),
|
||||
'[StartDate]', to_char(lr."StartDate", 'DD.MM.YYYY')),
|
||||
'[EndDate]', to_char(lr."EndDate", 'DD.MM.YYYY')),
|
||||
'[Days]', rtrim(rtrim(to_char(round(lr."Days", 1), 'FM999999999990.0'), '0'), '.')),
|
||||
'[Status]', COALESCE(lr."ApprovalStatus", '')), 2048),
|
||||
left(COALESCE(sub."Value", p_SubjectKey), 256),
|
||||
(r."Channel" = 'UiActivity'), FALSE, (NOW() AT TIME ZONE 'UTC'), p_ActorId, FALSE
|
||||
FROM public."Hr_T_LeaveRequest" lr
|
||||
INNER JOIN public."Hr_T_LeaveType" lt ON lt."Id" = lr."LeaveTypeId"
|
||||
INNER JOIN public."AbpUsers" u ON u."Id" = p_RecipientId AND u."IsActive" = TRUE
|
||||
INNER JOIN public."Adm_T_NotificationType" t ON t."Name" = p_TypeName AND t."IsDeleted" = FALSE
|
||||
AND (t."TenantId" = lr."TenantId" OR (t."TenantId" IS NULL AND lr."TenantId" IS NULL))
|
||||
INNER JOIN public."Adm_T_NotificationRule" r ON r."NotificationTypeId" = t."Id" AND r."IsActive" = TRUE
|
||||
AND r."IsCustomized" = FALSE AND r."IsDeleted" = FALSE AND r."RecipientType" = 'Custom' AND r."RecipientId" IS NULL
|
||||
LEFT JOIN public."Sas_H_LanguageText" msg ON msg."ResourceName" = 'Platform' AND msg."Key" = p_MessageKey AND msg."CultureName" = 'tr'
|
||||
LEFT JOIN public."Sas_H_LanguageText" sub ON sub."ResourceName" = 'Platform' AND sub."Key" = p_SubjectKey AND sub."CultureName" = 'tr'
|
||||
WHERE lr."Id" = p_RequestId
|
||||
AND (r."Channel" <> 'Mail' OR u."Email" IS NOT NULL);
|
||||
END
|
||||
$$;
|
||||
393
configs/seeds/postgres/object/Hr_S_LeaveRequestAfterSave.sql
Normal file
393
configs/seeds/postgres/object/Hr_S_LeaveRequestAfterSave.sql
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_LeaveRequestAfterSave"(
|
||||
p_RequestId uuid,
|
||||
p_ActorId uuid,
|
||||
p_Actor varchar(16)
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_StatusStart constant varchar(50) := 'Onaya Gönderildi';
|
||||
v_StatusManager constant varchar(50) := 'Müdür Onayı';
|
||||
v_StatusHr constant varchar(50) := 'İK Onayı';
|
||||
v_StatusApproved constant varchar(50) := 'Onaylandı';
|
||||
v_StatusRejected constant varchar(50) := 'Reddedildi';
|
||||
|
||||
v_TenantId uuid;
|
||||
v_EmployeeId uuid;
|
||||
v_LeaveTypeId uuid;
|
||||
v_StartDate timestamp;
|
||||
v_EndDate timestamp;
|
||||
v_IsHalfDay boolean;
|
||||
v_HalfDayPart varchar(16);
|
||||
v_Document text;
|
||||
v_SickLeaveId uuid;
|
||||
v_Status varchar(50);
|
||||
v_LastStatus varchar(50);
|
||||
v_Description varchar(1024);
|
||||
v_IsDeleted boolean;
|
||||
|
||||
v_EmployeeName varchar(256);
|
||||
v_DepartmentId uuid;
|
||||
v_JobPositionId uuid;
|
||||
v_Gender varchar(16);
|
||||
v_HireDate date;
|
||||
v_TerminationDate date;
|
||||
|
||||
v_Positions uuid[];
|
||||
v_Managers uuid[] := '{}';
|
||||
v_Level integer := 0;
|
||||
v_ManagerIds varchar(1024);
|
||||
v_ManagerUserNames varchar(1024);
|
||||
v_RequiresManager integer;
|
||||
v_HrApprover varchar(1024);
|
||||
|
||||
v_TypeCode varchar(32);
|
||||
v_TypeGender varchar(16);
|
||||
v_MaxDaysPerYear integer;
|
||||
v_Deducts boolean;
|
||||
v_RequiresDocument boolean;
|
||||
v_AllowHalfDay boolean;
|
||||
v_ExceedAction varchar(16);
|
||||
v_WorkDays numeric(9, 1);
|
||||
v_Days numeric(9, 1);
|
||||
v_Warning varchar(512);
|
||||
v_Remaining numeric(9, 1);
|
||||
v_OtherPending numeric(9, 1);
|
||||
v_UsedThisYear numeric(9, 1);
|
||||
|
||||
v_Recipients uuid[];
|
||||
v_Recipient uuid;
|
||||
v_Type varchar(64);
|
||||
v_Subject varchar(128);
|
||||
v_Body varchar(128);
|
||||
BEGIN
|
||||
-- İzin talebinin her kaydından (ekleme, güncelleme, Grid Workflow kararı, silme) sonra çalışır:
|
||||
-- çalışan bilgisi, talep no, müdür, gün sayısı tamamlanır; kurallar doğrulanır (hata kaydı
|
||||
-- geri alır); onaylı rapor türü Sağlık Kartı istirahatiyle eşlenir; durum değiştiyse bildirim
|
||||
-- gider; puantaj yeniden hesaplanır.
|
||||
SELECT "TenantId", "EmployeeId", "LeaveTypeId", "StartDate", "EndDate", "IsHalfDay", "HalfDayPart",
|
||||
"DocumentContent", "SickLeaveId", "ApprovalStatus", "LastNotifiedStatus", "Description", "IsDeleted"
|
||||
INTO v_TenantId, v_EmployeeId, v_LeaveTypeId, v_StartDate, v_EndDate, v_IsHalfDay, v_HalfDayPart,
|
||||
v_Document, v_SickLeaveId, v_Status, v_LastStatus, v_Description, v_IsDeleted
|
||||
FROM public."Hr_T_LeaveRequest"
|
||||
WHERE "Id" = p_RequestId;
|
||||
|
||||
IF v_EmployeeId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Puantajı kapanmış aya düşen talep eklenemez, değiştirilemez, silinemez (eski tarihleri
|
||||
-- Hr_S_LeaveRequestGuard denetler).
|
||||
IF public."Hr_F_TimesheetPeriodClosed"(v_TenantId, v_StartDate::date, v_EndDate::date) THEN
|
||||
RAISE EXCEPTION 'İzin tarihleri kapalı bir puantaj dönemine düşüyor. Önce İnsan Kaynakları dönemi açmalıdır.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51118';
|
||||
END IF;
|
||||
|
||||
IF v_IsDeleted = TRUE THEN
|
||||
-- Silinen talebin istirahati de kalkar; rapora denk gelen izin günleri ve puantaj yenilenir.
|
||||
IF v_SickLeaveId IS NOT NULL THEN
|
||||
UPDATE public."Adm_T_UserSickLeave"
|
||||
SET "IsDeleted" = TRUE, "DeletionTime" = (NOW() AT TIME ZONE 'UTC'), "DeleterId" = p_ActorId
|
||||
WHERE "Id" = v_SickLeaveId AND "IsDeleted" = FALSE;
|
||||
|
||||
CALL public."Hr_S_LeaveDaysRefresh"(v_TenantId, v_EmployeeId);
|
||||
END IF;
|
||||
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_TenantId, v_EmployeeId, v_StartDate::date, v_EndDate::date, p_ActorId);
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Çalışan ve departman müdürü
|
||||
---------------------------------------------------------------------------------------------
|
||||
SELECT CONCAT("Name", ' ', "Surname"), "DepartmentId", "JobPositionId", "Gender",
|
||||
"HireDate"::date, "TerminationDate"::date
|
||||
INTO v_EmployeeName, v_DepartmentId, v_JobPositionId, v_Gender, v_HireDate, v_TerminationDate
|
||||
FROM public."AbpUsers"
|
||||
WHERE "Id" = v_EmployeeId;
|
||||
|
||||
-- Müdür = çalışanın departmanındaki en üst pozisyon(lar)ın sahibi (üst pozisyonu başka
|
||||
-- departmanda olan ya da üstü olmayan pozisyon). Çalışan bu pozisyondaysa ya da pozisyon boşsa
|
||||
-- bir üst pozisyona çıkılır.
|
||||
v_Positions := ARRAY(
|
||||
SELECT p."Id"
|
||||
FROM public."Hr_T_JobPosition" p
|
||||
LEFT JOIN public."Hr_T_JobPosition" parent ON parent."Id" = p."ParentId"
|
||||
WHERE p."IsDeleted" = FALSE AND p."DepartmentId" = v_DepartmentId
|
||||
AND (p."TenantId" = v_TenantId OR (p."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
AND (p."ParentId" IS NULL OR parent."DepartmentId" <> p."DepartmentId"));
|
||||
|
||||
IF v_JobPositionId = ANY (v_Positions) THEN
|
||||
v_Positions := ARRAY(
|
||||
SELECT "ParentId" FROM public."Hr_T_JobPosition"
|
||||
WHERE "Id" = v_JobPositionId AND "ParentId" IS NOT NULL);
|
||||
END IF;
|
||||
|
||||
WHILE v_Level < 10 AND cardinality(v_Positions) > 0 LOOP
|
||||
v_Managers := ARRAY(
|
||||
SELECT u."Id"
|
||||
FROM public."AbpUsers" u
|
||||
WHERE u."IsDeleted" = FALSE AND u."IsActive" = TRUE AND u."Id" <> v_EmployeeId
|
||||
AND (u."TenantId" = v_TenantId OR (u."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
AND u."JobPositionId" = ANY (v_Positions));
|
||||
|
||||
EXIT WHEN cardinality(v_Managers) > 0;
|
||||
|
||||
v_Positions := ARRAY(
|
||||
SELECT DISTINCT p."ParentId" FROM public."Hr_T_JobPosition" p
|
||||
WHERE p."Id" = ANY (v_Positions) AND p."ParentId" IS NOT NULL);
|
||||
v_Level := v_Level + 1;
|
||||
END LOOP;
|
||||
|
||||
-- Kimlikler ve adlar aynı sırada birleşir; Guard ve persona süzgeci ManagerIds içinde kimlik arar.
|
||||
SELECT string_agg(u."Id"::text, '|' ORDER BY u."Id"), string_agg(u."UserName", '|' ORDER BY u."Id")
|
||||
INTO v_ManagerIds, v_ManagerUserNames
|
||||
FROM public."AbpUsers" u
|
||||
WHERE u."Id" = ANY (v_Managers);
|
||||
|
||||
-- Pozisyonu en üstte olan (üst pozisyonu olmayan) çalışanın onaylayacak müdürü yoktur; müdür adımı
|
||||
-- atlanır ve talep İK onayına gider. Akış bu kolona bakan karşılaştırma düğümüyle dallanır.
|
||||
v_RequiresManager :=
|
||||
CASE WHEN v_ManagerIds IS NULL AND EXISTS (SELECT 1 FROM public."Hr_T_JobPosition"
|
||||
WHERE "Id" = v_JobPositionId AND "ParentId" IS NULL AND "IsDeleted" = FALSE)
|
||||
THEN 0 ELSE 1 END;
|
||||
|
||||
IF v_ManagerIds IS NULL AND v_RequiresManager = 1 AND (v_Status IS NULL OR v_Status IN (v_StatusStart, v_StatusManager)) THEN
|
||||
RAISE EXCEPTION 'Çalışanın departman müdürü bulunamadı. Kullanıcı kartında departman ve pozisyon tanımlı olmalı, departmanın müdür pozisyonunda aktif bir kullanıcı bulunmalıdır.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51110';
|
||||
END IF;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Yöneticinin yapabileceği geçişler: müdür onayından İK onayına ya da redde. Başka bir
|
||||
-- geçiş (ör. doğrudan Onaylandı) önceki duruma geri alınır.
|
||||
---------------------------------------------------------------------------------------------
|
||||
IF p_Actor = 'Manager' AND COALESCE(v_Status, '') <> COALESCE(v_LastStatus, '')
|
||||
AND NOT (v_LastStatus = v_StatusManager AND v_Status IN (v_StatusHr, v_StatusRejected)) THEN
|
||||
v_Status := v_LastStatus;
|
||||
UPDATE public."Hr_T_LeaveRequest" SET "ApprovalStatus" = v_LastStatus WHERE "Id" = p_RequestId;
|
||||
END IF;
|
||||
|
||||
-- Çalışanın açtığı talep doğrudan müdür onayına düşer (persona ekranında akış başlatılmaz);
|
||||
-- müdürü olmayan en üst pozisyonda İK onayına.
|
||||
IF v_Status IS NULL AND p_Actor = 'Employee' THEN
|
||||
v_Status := CASE WHEN v_RequiresManager = 1 THEN v_StatusManager ELSE v_StatusHr END;
|
||||
END IF;
|
||||
|
||||
-- Adıma girerken onaycı kayıttan çözülür: müdür adımında departman müdürü, İK adımında İK
|
||||
-- ekranının akışındaki İK düğümü (tek kaynak; diğer ekranların düğümü yalnız geçişi yapar).
|
||||
-- Adım içindeki kayıtta (karar anı) motorun yazdığı karar vereni ezmemek için dokunulmaz.
|
||||
SELECT "Approver" INTO v_HrApprover
|
||||
FROM public."Sas_H_ListFormWorkflow"
|
||||
WHERE "ListFormCode" = 'App.Hr.LeaveRequest' AND "Kind" = 'Approval' AND "Title" = v_StatusHr
|
||||
LIMIT 1;
|
||||
|
||||
UPDATE public."Hr_T_LeaveRequest" AS lr
|
||||
SET "EmployeeName" = v_EmployeeName,
|
||||
"DepartmentId" = v_DepartmentId,
|
||||
"JobPositionId" = v_JobPositionId,
|
||||
"ManagerIds" = COALESCE(v_ManagerIds, lr."ManagerIds"),
|
||||
"ManagerUserNames" = COALESCE(v_ManagerUserNames, lr."ManagerUserNames"),
|
||||
"RequiresManagerApproval" = v_RequiresManager,
|
||||
"ApprovalStatus" = v_Status,
|
||||
"ApprovalUser" = CASE
|
||||
WHEN v_Status = COALESCE(v_LastStatus, '') THEN lr."ApprovalUser"
|
||||
WHEN v_Status = v_StatusManager THEN COALESCE(v_ManagerUserNames, lr."ApprovalUser")
|
||||
WHEN v_Status = v_StatusHr AND v_HrApprover IS NOT NULL THEN v_HrApprover
|
||||
ELSE lr."ApprovalUser"
|
||||
END,
|
||||
"HalfDayPart" = CASE WHEN v_IsHalfDay = TRUE THEN COALESCE(v_HalfDayPart, 'Morning') ELSE NULL END,
|
||||
"RequestNo" = CASE
|
||||
WHEN lr."RequestNo" IS NULL OR rtrim(lr."RequestNo") = '' THEN
|
||||
'IZN-' || EXTRACT(YEAR FROM lr."CreationTime")::int::text || '-' || right('00000' || (
|
||||
SELECT COALESCE(MAX(CASE WHEN right(x."RequestNo", 5) ~ '^[0-9]+$' THEN right(x."RequestNo", 5)::int END), 0) + 1
|
||||
FROM public."Hr_T_LeaveRequest" x
|
||||
WHERE (x."TenantId" = v_TenantId OR (x."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
AND x."RequestNo" LIKE 'IZN-' || EXTRACT(YEAR FROM x."CreationTime")::int::text || '-%')::text, 5)
|
||||
ELSE lr."RequestNo"
|
||||
END
|
||||
WHERE lr."Id" = p_RequestId;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Gün sayısı ve kurallar (sonuçlanmış talepte yeniden doğrulanmaz)
|
||||
---------------------------------------------------------------------------------------------
|
||||
SELECT "Code", "Gender", "MaxDaysPerYear", "DeductsFromAnnualLeave", "RequiresDocument",
|
||||
"AllowHalfDay", "BalanceExceedAction"
|
||||
INTO v_TypeCode, v_TypeGender, v_MaxDaysPerYear, v_Deducts, v_RequiresDocument,
|
||||
v_AllowHalfDay, v_ExceedAction
|
||||
FROM public."Hr_T_LeaveType"
|
||||
WHERE "Id" = v_LeaveTypeId;
|
||||
|
||||
-- Rapor türü kendi çalışma günlerini sayar; diğer türlerde rapora denk gelen gün izinden düşmez.
|
||||
v_WorkDays := public."Hr_F_LeaveWorkDays"(v_TenantId, v_StartDate::date, v_EndDate::date);
|
||||
v_Days := CASE WHEN v_TypeCode = 'Sick' THEN v_WorkDays
|
||||
ELSE public."Hr_F_LeaveNetDays"(v_TenantId, v_EmployeeId, v_StartDate::date, v_EndDate::date) END;
|
||||
IF v_IsHalfDay = TRUE THEN
|
||||
v_Days := CASE WHEN v_Days > 0.5 THEN 0.5 ELSE v_Days END;
|
||||
END IF;
|
||||
|
||||
UPDATE public."Hr_T_LeaveRequest" SET "Days" = v_Days WHERE "Id" = p_RequestId;
|
||||
|
||||
IF v_Status IS NULL OR v_Status IN (v_StatusStart, v_StatusManager, v_StatusHr) THEN
|
||||
IF v_TypeGender IS NOT NULL AND lower(v_TypeGender) <> 'all' AND lower(COALESCE(v_Gender, '')) <> lower(v_TypeGender) THEN
|
||||
RAISE EXCEPTION 'Bu izin türü çalışanın cinsiyetine uygun değil.' USING ERRCODE = 'P0001', DETAIL = '51111';
|
||||
END IF;
|
||||
|
||||
IF v_IsHalfDay = TRUE AND v_AllowHalfDay = FALSE THEN
|
||||
RAISE EXCEPTION 'Bu izin türü yarım gün kullanılamaz.' USING ERRCODE = 'P0001', DETAIL = '51112';
|
||||
END IF;
|
||||
|
||||
IF v_IsHalfDay = TRUE AND v_StartDate::date <> v_EndDate::date THEN
|
||||
RAISE EXCEPTION 'Yarım gün izinde başlangıç ve bitiş aynı gün olmalıdır.' USING ERRCODE = 'P0001', DETAIL = '51113';
|
||||
END IF;
|
||||
|
||||
IF v_RequiresDocument = TRUE AND (v_Document IS NULL OR rtrim(v_Document) = '') THEN
|
||||
RAISE EXCEPTION 'Bu izin türü için belge (rapor vb.) eklenmelidir.' USING ERRCODE = 'P0001', DETAIL = '51114';
|
||||
END IF;
|
||||
|
||||
IF v_WorkDays <= 0 THEN
|
||||
RAISE EXCEPTION 'Seçilen tarih aralığında çalışma günü yok.' USING ERRCODE = 'P0001', DETAIL = '51115';
|
||||
END IF;
|
||||
|
||||
IF (v_HireDate IS NOT NULL AND v_StartDate::date < v_HireDate)
|
||||
OR (v_TerminationDate IS NOT NULL AND v_EndDate::date > v_TerminationDate) THEN
|
||||
RAISE EXCEPTION 'İzin tarihleri çalışanın işe giriş ve işten çıkış tarihleri arasında olmalıdır.' USING ERRCODE = 'P0001', DETAIL = '51119';
|
||||
END IF;
|
||||
|
||||
-- Çakışma: reddedilmemiş başka bir talep ya da talebe bağlı olmayan bir istirahat aynı
|
||||
-- güne düşüyorsa. Aynı günün iki farklı yarısı birlikte alınabilir.
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public."Hr_T_LeaveRequest" o
|
||||
WHERE o."EmployeeId" = v_EmployeeId AND o."Id" <> p_RequestId AND o."IsDeleted" = FALSE
|
||||
AND (o."ApprovalStatus" IS NULL OR o."ApprovalStatus" <> v_StatusRejected)
|
||||
AND o."StartDate"::date <= v_EndDate::date
|
||||
AND o."EndDate"::date >= v_StartDate::date
|
||||
AND NOT (o."IsHalfDay" = TRUE AND v_IsHalfDay = TRUE
|
||||
AND COALESCE(o."HalfDayPart", 'Morning') <> COALESCE(v_HalfDayPart, 'Morning')))
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM public."Adm_T_UserSickLeave" s
|
||||
WHERE s."UserId" = v_EmployeeId AND s."IsDeleted" = FALSE
|
||||
AND (v_SickLeaveId IS NULL OR s."Id" <> v_SickLeaveId)
|
||||
AND NOT EXISTS (SELECT 1 FROM public."Hr_T_LeaveRequest" l WHERE l."SickLeaveId" = s."Id" AND l."IsDeleted" = FALSE)
|
||||
AND s."StartDate"::date <= v_EndDate::date
|
||||
AND s."EndDate"::date >= v_StartDate::date) THEN
|
||||
RAISE EXCEPTION 'Bu tarihlerle çakışan başka bir izin ya da rapor kaydı var.' USING ERRCODE = 'P0001', DETAIL = '51116';
|
||||
END IF;
|
||||
|
||||
v_Warning := NULL;
|
||||
|
||||
IF v_Deducts = TRUE THEN
|
||||
v_Remaining := COALESCE((SELECT "RemainingDays" FROM public."Hr_V_LeaveSummary" WHERE "Id" = v_EmployeeId), 0);
|
||||
v_OtherPending := COALESCE((
|
||||
SELECT SUM(o."Days") FROM public."Hr_T_LeaveRequest" o
|
||||
INNER JOIN public."Hr_T_LeaveType" ot ON ot."Id" = o."LeaveTypeId"
|
||||
WHERE o."EmployeeId" = v_EmployeeId AND o."Id" <> p_RequestId AND o."IsDeleted" = FALSE
|
||||
AND ot."DeductsFromAnnualLeave" = TRUE
|
||||
AND (o."ApprovalStatus" IS NULL OR o."ApprovalStatus" IN (v_StatusStart, v_StatusManager, v_StatusHr))), 0);
|
||||
|
||||
IF v_Days > v_Remaining - v_OtherPending THEN
|
||||
v_Warning := CONCAT('Yıllık izin bakiyesi yetersiz: kullanılabilir ',
|
||||
rtrim(rtrim(to_char(round(v_Remaining - v_OtherPending, 1), 'FM999999999990.0'), '0'), '.'),
|
||||
' gün, talep ', rtrim(rtrim(to_char(round(v_Days, 1), 'FM999999999990.0'), '0'), '.'), ' gün.');
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF v_Warning IS NULL AND v_MaxDaysPerYear IS NOT NULL THEN
|
||||
v_UsedThisYear := COALESCE((
|
||||
SELECT SUM(o."Days") FROM public."Hr_T_LeaveRequest" o
|
||||
WHERE o."EmployeeId" = v_EmployeeId AND o."Id" <> p_RequestId AND o."IsDeleted" = FALSE
|
||||
AND o."LeaveTypeId" = v_LeaveTypeId
|
||||
AND EXTRACT(YEAR FROM o."StartDate") = EXTRACT(YEAR FROM v_StartDate)
|
||||
AND (o."ApprovalStatus" IS NULL OR o."ApprovalStatus" <> v_StatusRejected)), 0);
|
||||
|
||||
IF v_UsedThisYear + v_Days > v_MaxDaysPerYear THEN
|
||||
v_Warning := CONCAT('Bu izin türünün yıllık sınırı ', v_MaxDaysPerYear, ' gün; bu yıl kullanılan/bekleyen ',
|
||||
rtrim(rtrim(to_char(round(v_UsedThisYear, 1), 'FM999999999990.0'), '0'), '.'),
|
||||
' gün, talep ', rtrim(rtrim(to_char(round(v_Days, 1), 'FM999999999990.0'), '0'), '.'), ' gün.');
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF v_Warning IS NOT NULL AND COALESCE(v_ExceedAction, 'Block') = 'Block' THEN
|
||||
RAISE EXCEPTION '%', v_Warning USING ERRCODE = 'P0001', DETAIL = '51117';
|
||||
END IF;
|
||||
|
||||
UPDATE public."Hr_T_LeaveRequest" SET "BalanceWarning" = v_Warning WHERE "Id" = p_RequestId;
|
||||
END IF;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Rapor türü = Sağlık Kartı › İstirahatler satırı (tek kayıt). İstirahat onayla oluşur: onay
|
||||
-- bekleyen rapor talebi sağlık kartında ve puantajda görünmez, izin günlerini düşürmez.
|
||||
---------------------------------------------------------------------------------------------
|
||||
IF v_TypeCode = 'Sick' AND v_Status = v_StatusApproved THEN
|
||||
IF v_SickLeaveId IS NULL OR NOT EXISTS (SELECT 1 FROM public."Adm_T_UserSickLeave" WHERE "Id" = v_SickLeaveId) THEN
|
||||
v_SickLeaveId := gen_random_uuid();
|
||||
INSERT INTO public."Adm_T_UserSickLeave" ("Id", "TenantId", "UserId", "StartDate", "EndDate", "Days", "SickLeaveType",
|
||||
"Institution", "Clinic", "SickLeaveReason", "CreationTime", "CreatorId", "IsDeleted")
|
||||
VALUES (v_SickLeaveId, v_TenantId, v_EmployeeId, v_StartDate::date, v_EndDate::date,
|
||||
(v_EndDate::date - v_StartDate::date) + 1, 'Disease', NULL, NULL,
|
||||
left(v_Description, 512), (NOW() AT TIME ZONE 'UTC'), p_ActorId, FALSE);
|
||||
|
||||
UPDATE public."Hr_T_LeaveRequest" SET "SickLeaveId" = v_SickLeaveId WHERE "Id" = p_RequestId;
|
||||
ELSE
|
||||
UPDATE public."Adm_T_UserSickLeave"
|
||||
SET "StartDate" = v_StartDate::date,
|
||||
"EndDate" = v_EndDate::date,
|
||||
"Days" = (v_EndDate::date - v_StartDate::date) + 1,
|
||||
"IsDeleted" = FALSE, "DeletionTime" = NULL, "DeleterId" = NULL,
|
||||
"LastModificationTime" = (NOW() AT TIME ZONE 'UTC'), "LastModifierId" = p_ActorId
|
||||
WHERE "Id" = v_SickLeaveId;
|
||||
END IF;
|
||||
ELSIF v_SickLeaveId IS NOT NULL THEN
|
||||
-- Reddedilen, onayı beklemeye dönen ya da türü rapordan başka bir türe çevrilen talebin istirahati kalkar.
|
||||
UPDATE public."Adm_T_UserSickLeave"
|
||||
SET "IsDeleted" = TRUE, "DeletionTime" = (NOW() AT TIME ZONE 'UTC'), "DeleterId" = p_ActorId
|
||||
WHERE "Id" = v_SickLeaveId AND "IsDeleted" = FALSE;
|
||||
|
||||
IF v_TypeCode <> 'Sick' THEN
|
||||
UPDATE public."Hr_T_LeaveRequest" SET "SickLeaveId" = NULL WHERE "Id" = p_RequestId;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Bildirim: yalnızca bildirim taşıyan durumlara geçişte, bir kez
|
||||
---------------------------------------------------------------------------------------------
|
||||
IF v_Status IN (v_StatusManager, v_StatusHr, v_StatusApproved, v_StatusRejected)
|
||||
AND COALESCE(v_LastStatus, '') <> v_Status THEN
|
||||
IF v_Status = v_StatusManager THEN
|
||||
v_Recipients := v_Managers;
|
||||
ELSIF v_Status = v_StatusHr THEN
|
||||
-- Onaycı metni akış tasarımcısında elle yazılır; SQL Server'daki gibi harf duyarsız eşlenir.
|
||||
v_Recipients := ARRAY(
|
||||
SELECT DISTINCT u."Id"
|
||||
FROM string_to_table(COALESCE(v_HrApprover, ''), '|') AS a(value)
|
||||
INNER JOIN public."AbpUsers" u ON (lower(u."UserName") = lower(btrim(a.value, ' '))
|
||||
OR lower(u."Email") = lower(btrim(a.value, ' ')))
|
||||
AND u."IsDeleted" = FALSE
|
||||
AND (u."TenantId" = v_TenantId OR (u."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
WHERE btrim(a.value, ' ') <> '');
|
||||
ELSE
|
||||
v_Recipients := ARRAY[v_EmployeeId];
|
||||
END IF;
|
||||
|
||||
v_Type := CASE WHEN v_Status IN (v_StatusManager, v_StatusHr) THEN 'IzinTalebiOnayBekliyor' ELSE 'IzinTalebiSonuclandi' END;
|
||||
v_Subject := CASE WHEN v_Status IN (v_StatusManager, v_StatusHr) THEN 'App.Hr.Leave.Notification.Approval.Subject' ELSE 'App.Hr.Leave.Notification.Result.Subject' END;
|
||||
v_Body := CASE WHEN v_Status IN (v_StatusManager, v_StatusHr) THEN 'App.Hr.Leave.Notification.Approval.Message' ELSE 'App.Hr.Leave.Notification.Result.Message' END;
|
||||
|
||||
FOREACH v_Recipient IN ARRAY v_Recipients LOOP
|
||||
CALL public."Hr_S_LeaveNotify"(p_RequestId, v_Recipient, v_Type, v_Subject, v_Body, p_ActorId);
|
||||
END LOOP;
|
||||
|
||||
UPDATE public."Hr_T_LeaveRequest" SET "LastNotifiedStatus" = v_Status WHERE "Id" = p_RequestId;
|
||||
END IF;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Rapor, çalışanın diğer izinlerinin gün sayısını değiştirir. Puantaj: talebin günleri yeniden
|
||||
-- hesaplanır (onaylı izin İzinli, rapor Raporlu gün olur); tarihi değişen talebin artık
|
||||
-- karşılıksız kalan eski günlerini hesap kendisi aralığa katar. İleri tarih yazılmaz.
|
||||
---------------------------------------------------------------------------------------------
|
||||
IF v_TypeCode = 'Sick' OR v_SickLeaveId IS NOT NULL THEN
|
||||
CALL public."Hr_S_LeaveDaysRefresh"(v_TenantId, v_EmployeeId);
|
||||
END IF;
|
||||
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_TenantId, v_EmployeeId, v_StartDate::date, v_EndDate::date, p_ActorId);
|
||||
END
|
||||
$$;
|
||||
72
configs/seeds/postgres/object/Hr_S_LeaveRequestGuard.sql
Normal file
72
configs/seeds/postgres/object/Hr_S_LeaveRequestGuard.sql
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_LeaveRequestGuard"(
|
||||
p_RequestId uuid,
|
||||
p_ActorId uuid,
|
||||
p_Actor varchar(16),
|
||||
p_Operation varchar(16)
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_EmployeeId uuid;
|
||||
v_Status varchar(50);
|
||||
v_ManagerIds varchar(1024);
|
||||
v_TenantId uuid;
|
||||
v_StartDate date;
|
||||
v_EndDate date;
|
||||
BEGIN
|
||||
-- Persona ekranları satırı DefaultFilter ile süzer; kayıt uçları satırı yalnızca Id ile
|
||||
-- güncellediği için sahiplik ve aşama kuralı yazma tarafında burada uygulanır. Kayıttan önce
|
||||
-- çalıştığı için talebin eski tarihleri de burada kapalı puantaj dönemine karşı denetlenir.
|
||||
IF p_RequestId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
SELECT "EmployeeId", "ApprovalStatus", "ManagerIds", "TenantId", "StartDate"::date, "EndDate"::date
|
||||
INTO v_EmployeeId, v_Status, v_ManagerIds, v_TenantId, v_StartDate, v_EndDate
|
||||
FROM public."Hr_T_LeaveRequest"
|
||||
WHERE "Id" = p_RequestId AND "IsDeleted" = FALSE;
|
||||
|
||||
IF v_EmployeeId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF public."Hr_F_TimesheetPeriodClosed"(v_TenantId, v_StartDate, v_EndDate) THEN
|
||||
RAISE EXCEPTION 'İzin tarihleri kapalı bir puantaj dönemine düşüyor. Önce İnsan Kaynakları dönemi açmalıdır.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51118';
|
||||
END IF;
|
||||
|
||||
IF p_Actor = 'Hr' THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF p_Actor = 'Employee' THEN
|
||||
IF v_EmployeeId <> p_ActorId THEN
|
||||
RAISE EXCEPTION 'Bu izin talebi size ait değil.' USING ERRCODE = 'P0001', DETAIL = '51100';
|
||||
END IF;
|
||||
|
||||
-- Karar verilmiş talep çalışan tarafından değiştirilemez; iptal için İK'ya başvurulur.
|
||||
IF v_Status IS NOT NULL AND v_Status NOT IN ('Onaya Gönderildi', 'Müdür Onayı') THEN
|
||||
RAISE EXCEPTION 'Onay sürecinde ilerlemiş ya da sonuçlanmış izin talebi değiştirilemez veya silinemez. İptal için İnsan Kaynakları ile görüşün.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51101';
|
||||
END IF;
|
||||
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF p_Actor = 'Manager' THEN
|
||||
IF p_Operation = 'Delete' THEN
|
||||
RAISE EXCEPTION 'Yönetici izin talebini silemez.' USING ERRCODE = 'P0001', DETAIL = '51102';
|
||||
END IF;
|
||||
|
||||
-- ManagerIds SQL Server döneminde büyük harfli GUID metni taşıyabilir; karşılaştırma harf duyarsız.
|
||||
IF p_ActorId IS NULL
|
||||
OR strpos('|' || lower(COALESCE(v_ManagerIds, '')) || '|', '|' || lower(p_ActorId::text) || '|') = 0 THEN
|
||||
RAISE EXCEPTION 'Bu izin talebinin onaycısı değilsiniz.' USING ERRCODE = 'P0001', DETAIL = '51103';
|
||||
END IF;
|
||||
|
||||
IF v_Status IS NULL OR v_Status <> 'Müdür Onayı' THEN
|
||||
RAISE EXCEPTION 'İzin talebi müdür onayında beklemiyor.' USING ERRCODE = 'P0001', DETAIL = '51104';
|
||||
END IF;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
106
configs/seeds/postgres/object/Hr_S_LeaveSickLeaveSync.sql
Normal file
106
configs/seeds/postgres/object/Hr_S_LeaveSickLeaveSync.sql
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_LeaveSickLeaveSync"(
|
||||
p_SickLeaveId uuid,
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_TenantId uuid;
|
||||
v_UserId uuid;
|
||||
v_StartDate date;
|
||||
v_EndDate date;
|
||||
v_Reason varchar(1024);
|
||||
v_IsDeleted boolean;
|
||||
v_RequestId uuid;
|
||||
v_OldStart date;
|
||||
v_OldEnd date;
|
||||
v_Days numeric(9, 1);
|
||||
v_LeaveTypeId uuid;
|
||||
v_ActorName varchar(256);
|
||||
v_Year varchar(4);
|
||||
BEGIN
|
||||
-- Sağlık Kartı › İstirahatler'den (ya da kullanıcı detayındaki istirahat listesinden) girilen
|
||||
-- rapor, İzin Yönetimi'nde onaylanmış "Sick" türünde talep olarak görünür (tek kayıt). Talep
|
||||
-- tarafı istirahati kendisi yazar; bu prosedür yalnızca istirahat ekranından gelen değişikliği taşır.
|
||||
IF p_SickLeaveId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
SELECT "TenantId", "UserId", "StartDate"::date, "EndDate"::date, "SickLeaveReason", "IsDeleted"
|
||||
INTO v_TenantId, v_UserId, v_StartDate, v_EndDate, v_Reason, v_IsDeleted
|
||||
FROM public."Adm_T_UserSickLeave"
|
||||
WHERE "Id" = p_SickLeaveId;
|
||||
|
||||
IF v_UserId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Eşlenen talep istirahatin önceki tarihlerini taşır; kapalı aya düşen eski ya da yeni gün varsa
|
||||
-- kayıt geri alınır (puantaj ve bordro kapanmış aydır).
|
||||
SELECT "Id", "StartDate"::date, "EndDate"::date
|
||||
INTO v_RequestId, v_OldStart, v_OldEnd
|
||||
FROM public."Hr_T_LeaveRequest"
|
||||
WHERE "SickLeaveId" = p_SickLeaveId
|
||||
ORDER BY "IsDeleted", "CreationTime"
|
||||
LIMIT 1;
|
||||
|
||||
IF public."Hr_F_TimesheetPeriodClosed"(v_TenantId, v_StartDate, v_EndDate)
|
||||
OR public."Hr_F_TimesheetPeriodClosed"(v_TenantId, v_OldStart, v_OldEnd) THEN
|
||||
RAISE EXCEPTION 'Rapor kapalı bir puantaj dönemine düşüyor. Önce İnsan Kaynakları dönemi açmalıdır.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51120';
|
||||
END IF;
|
||||
|
||||
IF v_IsDeleted = TRUE THEN
|
||||
UPDATE public."Hr_T_LeaveRequest"
|
||||
SET "IsDeleted" = TRUE, "DeletionTime" = (NOW() AT TIME ZONE 'UTC'), "DeleterId" = p_ActorId
|
||||
WHERE "SickLeaveId" = p_SickLeaveId AND "IsDeleted" = FALSE;
|
||||
ELSE
|
||||
v_Days := public."Hr_F_LeaveWorkDays"(v_TenantId, v_StartDate, v_EndDate);
|
||||
|
||||
IF v_RequestId IS NOT NULL THEN
|
||||
UPDATE public."Hr_T_LeaveRequest"
|
||||
SET "StartDate" = v_StartDate, "EndDate" = v_EndDate, "Days" = v_Days,
|
||||
"IsDeleted" = FALSE, "DeletionTime" = NULL, "DeleterId" = NULL,
|
||||
"LastModificationTime" = (NOW() AT TIME ZONE 'UTC'), "LastModifierId" = p_ActorId
|
||||
WHERE "Id" = v_RequestId;
|
||||
ELSE
|
||||
-- Kiracıda rapor türü tanımlı değilse eşlenecek talep yoktur.
|
||||
SELECT "Id" INTO v_LeaveTypeId
|
||||
FROM public."Hr_T_LeaveType"
|
||||
WHERE "Code" = 'Sick' AND "IsDeleted" = FALSE
|
||||
AND ("TenantId" = v_TenantId OR ("TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
LIMIT 1;
|
||||
|
||||
IF v_LeaveTypeId IS NOT NULL THEN
|
||||
v_ActorName := (SELECT "UserName" FROM public."AbpUsers" WHERE "Id" = p_ActorId);
|
||||
v_Year := EXTRACT(YEAR FROM (NOW() AT TIME ZONE 'UTC'))::int::text;
|
||||
|
||||
-- İstirahat sağlık birimince girilmiş bir belgedir; onay akışına girmeden onaylı yazılır.
|
||||
INSERT INTO public."Hr_T_LeaveRequest"
|
||||
("Id", "TenantId", "RequestNo", "EmployeeId", "EmployeeName", "DepartmentId", "JobPositionId", "LeaveTypeId",
|
||||
"StartDate", "EndDate", "IsHalfDay", "Days", "Description", "SickLeaveId",
|
||||
"ApprovalStatus", "ApprovalUser", "ApprovalDate", "LastNotifiedStatus", "IsActive", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), v_TenantId,
|
||||
'IZN-' || v_Year || '-' || right('00000' || (
|
||||
SELECT COALESCE(MAX(CASE WHEN right(x."RequestNo", 5) ~ '^[0-9]+$' THEN right(x."RequestNo", 5)::int END), 0) + 1
|
||||
FROM public."Hr_T_LeaveRequest" x
|
||||
WHERE (x."TenantId" = v_TenantId OR (x."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
AND x."RequestNo" LIKE 'IZN-' || v_Year || '-%')::text, 5),
|
||||
u."Id", CONCAT(u."Name", ' ', u."Surname"), u."DepartmentId", u."JobPositionId", v_LeaveTypeId,
|
||||
v_StartDate, v_EndDate, FALSE, v_Days, left(v_Reason, 1024), p_SickLeaveId,
|
||||
'Onaylandı', v_ActorName, (NOW() AT TIME ZONE 'UTC'), 'Onaylandı', TRUE, (NOW() AT TIME ZONE 'UTC'), p_ActorId, FALSE
|
||||
FROM public."AbpUsers" u
|
||||
WHERE u."Id" = v_UserId;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Rapora denk gelen izin günleri bakiyeye döner; puantajda raporlu günler (ve tarihi değiştiyse
|
||||
-- eski günler) yeniden hesaplanır.
|
||||
CALL public."Hr_S_LeaveDaysRefresh"(v_TenantId, v_UserId);
|
||||
|
||||
IF v_OldStart IS NOT NULL AND v_OldStart < v_StartDate THEN v_StartDate := v_OldStart; END IF;
|
||||
IF v_OldEnd IS NOT NULL AND v_OldEnd > v_EndDate THEN v_EndDate := v_OldEnd; END IF;
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_TenantId, v_UserId, v_StartDate, v_EndDate, p_ActorId);
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_PerformanceFeedbackOpen"(
|
||||
p_FeedbackId uuid,
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_ReviewId uuid;
|
||||
v_ReviewerId uuid;
|
||||
BEGIN
|
||||
SELECT "ReviewId", "ReviewerId" INTO v_ReviewId, v_ReviewerId
|
||||
FROM "Hr_T_PerformanceFeedback"
|
||||
WHERE "Id" = p_FeedbackId AND "IsDeleted" = FALSE;
|
||||
|
||||
IF v_ReviewId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Değerlendirici, değerlendirmenin yetkinlikleri üzerinden puan verir; satırlar burada açılır.
|
||||
INSERT INTO "Hr_T_PerformanceFeedbackScore" ("Id", "TenantId", "FeedbackId", "SkillId", "IsActive", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), c."TenantId", p_FeedbackId, c."SkillId", TRUE, NOW() AT TIME ZONE 'UTC', p_ActorId, FALSE
|
||||
FROM "Hr_T_PerformanceCompetency" c
|
||||
WHERE c."ReviewId" = v_ReviewId AND c."IsDeleted" = FALSE
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Hr_T_PerformanceFeedbackScore" s
|
||||
WHERE s."FeedbackId" = p_FeedbackId AND s."SkillId" = c."SkillId" AND s."IsDeleted" = FALSE);
|
||||
|
||||
CALL public."Hr_S_PerformanceNotify"(v_ReviewId, v_ReviewerId, 'Performans360Talebi',
|
||||
'App.Hr.Performance.Notification.Feedback.Subject', 'App.Hr.Performance.Notification.Feedback.Message', p_ActorId);
|
||||
END
|
||||
$$;
|
||||
66
configs/seeds/postgres/object/Hr_S_PerformanceGuard.sql
Normal file
66
configs/seeds/postgres/object/Hr_S_PerformanceGuard.sql
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_PerformanceGuard"(
|
||||
p_ReviewId uuid,
|
||||
p_UserId uuid,
|
||||
p_Actor varchar(16),
|
||||
p_Scope varchar(16),
|
||||
p_FeedbackId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_Status varchar(16);
|
||||
v_EmployeeId uuid;
|
||||
v_ManagerId uuid;
|
||||
v_PeriodStatus varchar(16);
|
||||
v_Allowed boolean;
|
||||
BEGIN
|
||||
-- Kişisel ekranlar (Değerlendirmelerim, Ekibim, 360 Taleplerim) satırı listede süzer; bu kontrol
|
||||
-- aynı kuralı yazma tarafında uygular. Kayıt uçları satırı yalnızca Id ile güncellediği için
|
||||
-- başka birinin kaydına doğrudan yapılan istek burada reddedilir.
|
||||
IF p_Actor = 'Hr' THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
SELECT rv."Status", rv."EmployeeId", rv."ManagerId", p."Status"
|
||||
INTO v_Status, v_EmployeeId, v_ManagerId, v_PeriodStatus
|
||||
FROM "Hr_T_PerformanceReview" rv
|
||||
INNER JOIN "Hr_T_PerformancePeriod" p ON p."Id" = rv."PeriodId"
|
||||
WHERE rv."Id" = p_ReviewId AND rv."IsDeleted" = FALSE;
|
||||
|
||||
IF v_Status IS NULL THEN
|
||||
RAISE EXCEPTION 'Değerlendirme kaydı bulunamadı.' USING ERRCODE = 'P0001', DETAIL = '51000';
|
||||
END IF;
|
||||
|
||||
IF v_PeriodStatus = 'Closed' THEN
|
||||
RAISE EXCEPTION 'Dönem kapatıldığı için değerlendirme değiştirilemez.' USING ERRCODE = 'P0001', DETAIL = '51001';
|
||||
END IF;
|
||||
|
||||
IF p_Actor = 'Employee' AND (p_UserId IS NULL OR p_UserId <> v_EmployeeId) THEN
|
||||
RAISE EXCEPTION 'Yalnızca kendi değerlendirmenizi değiştirebilirsiniz.' USING ERRCODE = 'P0001', DETAIL = '51002';
|
||||
END IF;
|
||||
|
||||
IF p_Actor = 'Manager' AND (p_UserId IS NULL OR v_ManagerId IS NULL OR p_UserId <> v_ManagerId) THEN
|
||||
RAISE EXCEPTION 'Yalnızca yöneticisi olduğunuz değerlendirmeleri değiştirebilirsiniz.' USING ERRCODE = 'P0001', DETAIL = '51003';
|
||||
END IF;
|
||||
|
||||
IF p_Actor = 'Reviewer' AND NOT EXISTS (
|
||||
SELECT 1 FROM "Hr_T_PerformanceFeedback" f
|
||||
WHERE f."Id" = p_FeedbackId AND f."ReviewId" = p_ReviewId AND f."ReviewerId" = p_UserId AND f."IsDeleted" = FALSE) THEN
|
||||
RAISE EXCEPTION 'Yalnızca size atanan 360 değerlendirmesini doldurabilirsiniz.' USING ERRCODE = 'P0001', DETAIL = '51004';
|
||||
END IF;
|
||||
|
||||
-- Durum matrisi: hangi rol hangi aşamada neyi değiştirebilir.
|
||||
v_Allowed := CASE
|
||||
WHEN p_Scope = 'DevelopmentPlan' THEN TRUE
|
||||
WHEN p_Actor = 'Employee' AND p_Scope = 'Review' THEN v_Status IN ('GoalSetting', 'SelfReview')
|
||||
WHEN p_Actor = 'Employee' AND p_Scope IN ('Goal', 'Competency') THEN v_Status IN ('GoalSetting', 'SelfReview')
|
||||
WHEN p_Actor = 'Manager' AND p_Scope IN ('Review', 'Goal', 'Competency') THEN v_Status IN ('GoalSetting', 'SelfReview', 'ManagerReview')
|
||||
WHEN p_Actor = 'Reviewer' THEN v_Status IN ('GoalSetting', 'SelfReview', 'ManagerReview')
|
||||
ELSE FALSE
|
||||
END;
|
||||
|
||||
IF NOT v_Allowed THEN
|
||||
RAISE EXCEPTION 'Değerlendirme bu aşamada bu işlem için açık değil.' USING ERRCODE = 'P0001', DETAIL = '51005';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
42
configs/seeds/postgres/object/Hr_S_PerformanceNotify.sql
Normal file
42
configs/seeds/postgres/object/Hr_S_PerformanceNotify.sql
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_PerformanceNotify"(
|
||||
p_ReviewId uuid,
|
||||
p_RecipientId uuid,
|
||||
p_TypeName varchar(64),
|
||||
p_SubjectKey varchar(128),
|
||||
p_MessageKey varchar(128),
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Alıcı kayıttan gelir (çalışan, yönetici ya da 360 değerlendiricisi); tipin Custom kuralları
|
||||
-- yalnızca kanalı belirler. Metin TR dil anahtarından okunur, yer tutucular kayıttan dolar.
|
||||
IF p_ReviewId IS NULL OR p_RecipientId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
INSERT INTO "Adm_T_Notification" ("Id", "TenantId", "NotificationRuleId", "NotificationChannel", "NotificationTypeId", "Identifier", "UserId", "Message", "Subject", "IsSent", "IsRead", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), rv."TenantId", r."Id", r."Channel", t."Id",
|
||||
CASE WHEN r."Channel" = 'Mail' THEN u."Email" ELSE u."Id"::text END,
|
||||
u."Id",
|
||||
LEFT(REPLACE(REPLACE(REPLACE(COALESCE(msg."Value", p_MessageKey),
|
||||
'[Employee]', COALESCE(e."Name" || ' ' || e."Surname", '')),
|
||||
'[Period]', COALESCE(p."Name", '')),
|
||||
'[Status]', COALESCE(st."Value", rv."Status")), 2048),
|
||||
LEFT(COALESCE(sub."Value", p_SubjectKey), 256),
|
||||
r."Channel" = 'UiActivity', FALSE, NOW() AT TIME ZONE 'UTC', p_ActorId, FALSE
|
||||
FROM "Hr_T_PerformanceReview" rv
|
||||
INNER JOIN "Hr_T_PerformancePeriod" p ON p."Id" = rv."PeriodId"
|
||||
INNER JOIN "AbpUsers" e ON e."Id" = rv."EmployeeId"
|
||||
INNER JOIN "AbpUsers" u ON u."Id" = p_RecipientId AND u."IsActive" = TRUE
|
||||
INNER JOIN "Adm_T_NotificationType" t ON t."Name" = p_TypeName AND t."IsDeleted" = FALSE
|
||||
AND (t."TenantId" = rv."TenantId" OR (t."TenantId" IS NULL AND rv."TenantId" IS NULL))
|
||||
INNER JOIN "Adm_T_NotificationRule" r ON r."NotificationTypeId" = t."Id" AND r."IsActive" = TRUE AND r."IsCustomized" = FALSE
|
||||
AND r."IsDeleted" = FALSE AND r."RecipientType" = 'Custom' AND r."RecipientId" IS NULL
|
||||
LEFT JOIN "Sas_H_LanguageText" msg ON msg."ResourceName" = 'Platform' AND msg."Key" = p_MessageKey AND msg."CultureName" = 'tr'
|
||||
LEFT JOIN "Sas_H_LanguageText" sub ON sub."ResourceName" = 'Platform' AND sub."Key" = p_SubjectKey AND sub."CultureName" = 'tr'
|
||||
LEFT JOIN "Sas_H_LanguageText" st ON st."ResourceName" = 'Platform' AND st."Key" = 'App.Hr.PerformanceReview.Status.' || rv."Status" AND st."CultureName" = 'tr'
|
||||
WHERE rv."Id" = p_ReviewId
|
||||
AND (r."Channel" <> 'Mail' OR u."Email" IS NOT NULL);
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_PerformancePeriodActivate"(
|
||||
p_PeriodId uuid,
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_TenantId uuid;
|
||||
v_ReviewIds uuid[];
|
||||
v_EmployeeIds uuid[];
|
||||
v_i integer;
|
||||
BEGIN
|
||||
SELECT "TenantId" INTO v_TenantId
|
||||
FROM "Hr_T_PerformancePeriod"
|
||||
WHERE "Id" = p_PeriodId AND "IsDeleted" = FALSE AND "Status" = 'Active';
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Dönem aktif olduğunda kiracının aktif her kullanıcısına bir değerlendirme açılır; var olana
|
||||
-- dokunulmaz, böylece dönem yeniden kaydedildiğinde yalnızca yeni katılan çalışanlar eklenir.
|
||||
WITH created AS (
|
||||
INSERT INTO "Hr_T_PerformanceReview" ("Id", "TenantId", "PeriodId", "EmployeeId", "EmployeeName", "DepartmentId", "JobPositionId", "Status", "LastStatus", "IsActive", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), v_TenantId, p_PeriodId, u."Id", LEFT(LTRIM(COALESCE(u."Name", '') || ' ' || COALESCE(u."Surname", '')), 128), u."DepartmentId", u."JobPositionId", 'GoalSetting', 'GoalSetting', TRUE, NOW() AT TIME ZONE 'UTC', p_ActorId, FALSE
|
||||
FROM "AbpUsers" u
|
||||
WHERE u."IsDeleted" = FALSE AND u."IsActive" = TRUE
|
||||
AND (u."TenantId" = v_TenantId OR (u."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Hr_T_PerformanceReview" rv
|
||||
WHERE rv."PeriodId" = p_PeriodId AND rv."EmployeeId" = u."Id" AND rv."IsDeleted" = FALSE)
|
||||
RETURNING "Id", "EmployeeId"
|
||||
)
|
||||
SELECT array_agg("Id"), array_agg("EmployeeId") INTO v_ReviewIds, v_EmployeeIds
|
||||
FROM created;
|
||||
|
||||
-- Dönemin yetkinlik seti eksik olan her değerlendirmeye kopyalanır (sonradan eklenen yetkinlik dahil).
|
||||
INSERT INTO "Hr_T_PerformanceCompetency" ("Id", "TenantId", "ReviewId", "SkillId", "ExpectedLevelId", "IsActive", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), rv."TenantId", rv."Id", ps."SkillId", ps."ExpectedLevelId", TRUE, NOW() AT TIME ZONE 'UTC', p_ActorId, FALSE
|
||||
FROM "Hr_T_PerformanceReview" rv
|
||||
INNER JOIN "Hr_T_PerformancePeriodSkill" ps ON ps."PeriodId" = rv."PeriodId" AND ps."IsDeleted" = FALSE AND ps."IsActive" = TRUE
|
||||
WHERE rv."PeriodId" = p_PeriodId AND rv."IsDeleted" = FALSE AND rv."Status" <> 'Completed'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Hr_T_PerformanceCompetency" c
|
||||
WHERE c."ReviewId" = rv."Id" AND c."SkillId" = ps."SkillId" AND c."IsDeleted" = FALSE);
|
||||
|
||||
IF v_ReviewIds IS NOT NULL THEN
|
||||
FOR v_i IN 1 .. array_length(v_ReviewIds, 1) LOOP
|
||||
CALL public."Hr_S_PerformanceNotify"(v_ReviewIds[v_i], v_EmployeeIds[v_i], 'PerformansDurumDegisti',
|
||||
'App.Hr.Performance.Notification.Status.Subject', 'App.Hr.Performance.Notification.Status.Message', p_ActorId);
|
||||
END LOOP;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_PerformanceReviewAfterSave"(
|
||||
p_ReviewId uuid,
|
||||
p_UserId uuid,
|
||||
p_Actor varchar(16)
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_Status varchar(16);
|
||||
v_Last varchar(16);
|
||||
v_EmployeeId uuid;
|
||||
v_ManagerId uuid;
|
||||
v_TenantId uuid;
|
||||
v_Valid boolean;
|
||||
BEGIN
|
||||
SELECT "Status", COALESCE("LastStatus", 'GoalSetting'), "EmployeeId", "ManagerId", "TenantId"
|
||||
INTO v_Status, v_Last, v_EmployeeId, v_ManagerId, v_TenantId
|
||||
FROM "Hr_T_PerformanceReview"
|
||||
WHERE "Id" = p_ReviewId AND "IsDeleted" = FALSE;
|
||||
|
||||
IF v_Status IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Elle açılan değerlendirmede organizasyon bilgisi çalışandan, yetkinlikler dönemin setinden gelir.
|
||||
-- Ad, pano kartının başlığı olduğu için kayıtta metin olarak tutulur.
|
||||
UPDATE "Hr_T_PerformanceReview" rv
|
||||
SET "EmployeeName" = LEFT(LTRIM(COALESCE(u."Name", '') || ' ' || COALESCE(u."Surname", '')), 128),
|
||||
"DepartmentId" = COALESCE(rv."DepartmentId", u."DepartmentId"),
|
||||
"JobPositionId" = COALESCE(rv."JobPositionId", u."JobPositionId")
|
||||
FROM "AbpUsers" u
|
||||
WHERE u."Id" = rv."EmployeeId" AND rv."Id" = p_ReviewId;
|
||||
|
||||
INSERT INTO "Hr_T_PerformanceCompetency" ("Id", "TenantId", "ReviewId", "SkillId", "ExpectedLevelId", "IsActive", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), rv."TenantId", rv."Id", ps."SkillId", ps."ExpectedLevelId", TRUE, NOW() AT TIME ZONE 'UTC', p_UserId, FALSE
|
||||
FROM "Hr_T_PerformanceReview" rv
|
||||
INNER JOIN "Hr_T_PerformancePeriodSkill" ps ON ps."PeriodId" = rv."PeriodId" AND ps."IsDeleted" = FALSE AND ps."IsActive" = TRUE
|
||||
WHERE rv."Id" = p_ReviewId AND rv."Status" <> 'Completed'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Hr_T_PerformanceCompetency" c
|
||||
WHERE c."ReviewId" = rv."Id" AND c."SkillId" = ps."SkillId" AND c."IsDeleted" = FALSE);
|
||||
|
||||
IF v_Status <> v_Last THEN
|
||||
-- İK her geçişi yapar; çalışan öz değerlendirmeyi gönderir; yönetici hedefleri onaylar,
|
||||
-- değerlendirmesini kalibrasyona gönderir ya da kaydı bir önceki aşamaya iade eder.
|
||||
v_Valid := CASE
|
||||
WHEN p_Actor = 'Hr' THEN TRUE
|
||||
WHEN p_Actor = 'Employee' AND v_Last = 'SelfReview' AND v_Status = 'ManagerReview' THEN TRUE
|
||||
WHEN p_Actor = 'Manager' AND v_Last = 'GoalSetting' AND v_Status = 'SelfReview' THEN TRUE
|
||||
WHEN p_Actor = 'Manager' AND v_Last = 'SelfReview' AND v_Status = 'GoalSetting' THEN TRUE
|
||||
WHEN p_Actor = 'Manager' AND v_Last = 'ManagerReview' AND v_Status IN ('SelfReview', 'Calibration') THEN TRUE
|
||||
ELSE FALSE
|
||||
END;
|
||||
|
||||
-- Hedef belirleme ancak ağırlıkları toplamı 100 olan en az bir hedefle kapanır.
|
||||
IF v_Valid AND v_Last = 'GoalSetting' AND v_Status <> 'GoalSetting' AND NOT EXISTS (
|
||||
SELECT 1 FROM "Hr_T_PerformanceGoal" g
|
||||
WHERE g."ReviewId" = p_ReviewId AND g."IsDeleted" = FALSE
|
||||
HAVING SUM(g."Weight") = 100) THEN
|
||||
v_Valid := FALSE;
|
||||
END IF;
|
||||
|
||||
IF NOT v_Valid THEN
|
||||
UPDATE "Hr_T_PerformanceReview" SET "Status" = v_Last WHERE "Id" = p_ReviewId;
|
||||
v_Status := v_Last;
|
||||
ELSE
|
||||
UPDATE "Hr_T_PerformanceReview"
|
||||
SET "LastStatus" = v_Status,
|
||||
"SelfSubmittedDate" = CASE WHEN v_Status = 'ManagerReview' THEN NOW() AT TIME ZONE 'UTC' ELSE "SelfSubmittedDate" END,
|
||||
"ManagerSubmittedDate" = CASE WHEN v_Status = 'Calibration' THEN NOW() AT TIME ZONE 'UTC' ELSE "ManagerSubmittedDate" END,
|
||||
"CompletedDate" = CASE WHEN v_Status = 'Completed' THEN NOW() AT TIME ZONE 'UTC' ELSE NULL END
|
||||
WHERE "Id" = p_ReviewId;
|
||||
|
||||
IF v_Status IN ('GoalSetting', 'SelfReview', 'Completed') THEN
|
||||
CALL public."Hr_S_PerformanceNotify"(p_ReviewId, v_EmployeeId, 'PerformansDurumDegisti',
|
||||
'App.Hr.Performance.Notification.Status.Subject', 'App.Hr.Performance.Notification.Status.Message', p_UserId);
|
||||
END IF;
|
||||
|
||||
IF v_Status IN ('ManagerReview', 'Completed') THEN
|
||||
CALL public."Hr_S_PerformanceNotify"(p_ReviewId, v_ManagerId, 'PerformansDurumDegisti',
|
||||
'App.Hr.Performance.Notification.Status.Subject', 'App.Hr.Performance.Notification.Status.Message', p_UserId);
|
||||
END IF;
|
||||
|
||||
-- Tamamlanan değerlendirmenin yönetici seviyeleri çalışanın yetkinlik profiline yazılır
|
||||
-- (Progress 0–100 → UserSkill.Level 1–5).
|
||||
IF v_Status = 'Completed' THEN
|
||||
UPDATE "Adm_T_UserSkill" us
|
||||
SET "Level" = x.lvl, "Note" = LEFT('Performans: ' || p."Name", 512),
|
||||
"LastModificationTime" = NOW() AT TIME ZONE 'UTC', "LastModifierId" = p_UserId
|
||||
FROM "Hr_T_PerformanceCompetency" c
|
||||
INNER JOIN "Hr_T_SkillLevel" l ON l."Id" = c."ManagerLevelId"
|
||||
INNER JOIN "Hr_T_PerformanceReview" rv ON rv."Id" = c."ReviewId"
|
||||
INNER JOIN "Hr_T_PerformancePeriod" p ON p."Id" = rv."PeriodId"
|
||||
CROSS JOIN LATERAL (SELECT CASE WHEN l."Progress" <= 20 THEN 1 WHEN l."Progress" >= 100 THEN 5 ELSE CAST(CEILING(l."Progress" / 20.0) AS integer) END AS lvl) x
|
||||
WHERE c."SkillId" = us."SkillId" AND c."ReviewId" = p_ReviewId AND c."IsDeleted" = FALSE
|
||||
AND us."UserId" = v_EmployeeId AND us."IsDeleted" = FALSE
|
||||
AND (us."TenantId" = v_TenantId OR (us."TenantId" IS NULL AND v_TenantId IS NULL));
|
||||
|
||||
INSERT INTO "Adm_T_UserSkill" ("Id", "TenantId", "UserId", "SkillId", "Level", "Note", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), v_TenantId, v_EmployeeId, c."SkillId", x.lvl, LEFT('Performans: ' || p."Name", 512), NOW() AT TIME ZONE 'UTC', p_UserId, FALSE
|
||||
FROM "Hr_T_PerformanceCompetency" c
|
||||
INNER JOIN "Hr_T_SkillLevel" l ON l."Id" = c."ManagerLevelId"
|
||||
INNER JOIN "Hr_T_PerformanceReview" rv ON rv."Id" = c."ReviewId"
|
||||
INNER JOIN "Hr_T_PerformancePeriod" p ON p."Id" = rv."PeriodId"
|
||||
CROSS JOIN LATERAL (SELECT CASE WHEN l."Progress" <= 20 THEN 1 WHEN l."Progress" >= 100 THEN 5 ELSE CAST(CEILING(l."Progress" / 20.0) AS integer) END AS lvl) x
|
||||
WHERE c."ReviewId" = p_ReviewId AND c."IsDeleted" = FALSE
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "Adm_T_UserSkill" us
|
||||
WHERE us."UserId" = v_EmployeeId AND us."SkillId" = c."SkillId" AND us."IsDeleted" = FALSE
|
||||
AND (us."TenantId" = v_TenantId OR (us."TenantId" IS NULL AND v_TenantId IS NULL)));
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
CALL public."Hr_S_PerformanceReviewRecalc"(p_ReviewId);
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_PerformanceReviewRecalc"(
|
||||
p_ReviewId uuid
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF p_ReviewId IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Gerçekleşme %: Artan hedefte Gerçekleşen/Hedef, Azalan hedefte Hedef/Gerçekleşen; 0–150'ye kırpılır.
|
||||
-- Azalan hedefte sıfır gerçekleşme hedefin tamamen tutturulduğu anlamına gelir, tavan verilir.
|
||||
UPDATE "Hr_T_PerformanceGoal" g
|
||||
SET "AchievementRate" = (
|
||||
SELECT CASE
|
||||
WHEN x.rate IS NULL THEN NULL
|
||||
WHEN x.rate > 150 THEN 150
|
||||
WHEN x.rate < 0 THEN 0
|
||||
ELSE x.rate
|
||||
END
|
||||
FROM (SELECT CAST(CASE
|
||||
WHEN g."ActualValue" IS NULL THEN NULL
|
||||
WHEN g."Direction" = 'Decreasing' AND g."ActualValue" <= 0 THEN 150
|
||||
WHEN g."Direction" = 'Decreasing' THEN g."TargetValue" / g."ActualValue" * 100
|
||||
ELSE g."ActualValue" / g."TargetValue" * 100
|
||||
END AS numeric(18, 2)) AS rate) x)
|
||||
WHERE g."ReviewId" = p_ReviewId AND g."IsDeleted" = FALSE;
|
||||
|
||||
-- Yetkinlik puanı yöneticinin seçtiği seviyenin Progress değeridir.
|
||||
UPDATE "Hr_T_PerformanceCompetency" c
|
||||
SET "Score" = (
|
||||
SELECT l."Progress"
|
||||
FROM "Hr_T_SkillLevel" l
|
||||
WHERE l."Id" = c."ManagerLevelId" AND l."IsDeleted" = FALSE)
|
||||
WHERE c."ReviewId" = p_ReviewId AND c."IsDeleted" = FALSE;
|
||||
|
||||
UPDATE "Hr_T_PerformanceFeedback" f
|
||||
SET "Score" = (
|
||||
SELECT CAST(AVG(CAST(l."Progress" AS numeric(9, 2))) AS numeric(6, 2))
|
||||
FROM "Hr_T_PerformanceFeedbackScore" s
|
||||
INNER JOIN "Hr_T_SkillLevel" l ON l."Id" = s."LevelId" AND l."IsDeleted" = FALSE
|
||||
WHERE s."FeedbackId" = f."Id" AND s."IsDeleted" = FALSE)
|
||||
WHERE f."ReviewId" = p_ReviewId AND f."IsDeleted" = FALSE;
|
||||
|
||||
UPDATE "Hr_T_PerformanceReview" rv
|
||||
SET "GoalScore" = s.goal_score,
|
||||
"CompetencyScore" = s.competency_score,
|
||||
"Feedback360Score" = s.feedback_score,
|
||||
"OverallScore" = CASE
|
||||
WHEN s.goal_score IS NULL AND s.competency_score IS NULL THEN NULL
|
||||
ELSE CAST((COALESCE(s.goal_score, 0) * s.goal_weight + COALESCE(s.competency_score, 0) * s.competency_weight) / 100 AS numeric(6, 2))
|
||||
END
|
||||
FROM (
|
||||
SELECT r."Id",
|
||||
p."GoalWeight" AS goal_weight,
|
||||
p."CompetencyWeight" AS competency_weight,
|
||||
(SELECT CASE WHEN COUNT(*) = 0 THEN NULL
|
||||
ELSE CAST(SUM(COALESCE(g."AchievementRate", 0) * g."Weight") / 100 AS numeric(6, 2)) END
|
||||
FROM "Hr_T_PerformanceGoal" g
|
||||
WHERE g."ReviewId" = r."Id" AND g."IsDeleted" = FALSE) AS goal_score,
|
||||
(SELECT CAST(AVG(c."Score") AS numeric(6, 2))
|
||||
FROM "Hr_T_PerformanceCompetency" c
|
||||
WHERE c."ReviewId" = r."Id" AND c."IsDeleted" = FALSE AND c."Score" IS NOT NULL) AS competency_score,
|
||||
(SELECT CAST(AVG(f."Score") AS numeric(6, 2))
|
||||
FROM "Hr_T_PerformanceFeedback" f
|
||||
WHERE f."ReviewId" = r."Id" AND f."IsDeleted" = FALSE AND f."Status" = 'Submitted' AND f."Score" IS NOT NULL) AS feedback_score
|
||||
FROM "Hr_T_PerformanceReview" r
|
||||
INNER JOIN "Hr_T_PerformancePeriod" p ON p."Id" = r."PeriodId"
|
||||
WHERE r."Id" = p_ReviewId
|
||||
) s
|
||||
WHERE rv."Id" = s."Id";
|
||||
|
||||
-- Derece kalibre puandan, yoksa genel puandan; aralık alt sınırı dahil, üst sınırı hariçtir.
|
||||
UPDATE "Hr_T_PerformanceReview" rv
|
||||
SET "RatingId" = (
|
||||
SELECT rt."Id"
|
||||
FROM "Hr_T_PerformanceRating" rt
|
||||
WHERE rt."IsDeleted" = FALSE AND rt."IsActive" = TRUE
|
||||
AND (rt."TenantId" = rv."TenantId" OR (rt."TenantId" IS NULL AND rv."TenantId" IS NULL))
|
||||
AND COALESCE(rv."CalibratedScore", rv."OverallScore") >= rt."MinScore"
|
||||
AND COALESCE(rv."CalibratedScore", rv."OverallScore") < rt."MaxScore"
|
||||
ORDER BY rt."Level" DESC
|
||||
LIMIT 1)
|
||||
WHERE rv."Id" = p_ReviewId;
|
||||
END
|
||||
$$;
|
||||
461
configs/seeds/postgres/object/Hr_S_TimesheetCalculate.sql
Normal file
461
configs/seeds/postgres/object/Hr_S_TimesheetCalculate.sql
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_TimesheetCalculate"(
|
||||
p_TenantId uuid,
|
||||
p_EmployeeId uuid DEFAULT NULL,
|
||||
p_From date DEFAULT NULL,
|
||||
p_To date DEFAULT NULL,
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_From date := p_From;
|
||||
v_To date := p_To;
|
||||
v_Today date := LOCALTIMESTAMP::date;
|
||||
v_OrphanFrom date;
|
||||
v_OrphanTo date;
|
||||
v_Last date;
|
||||
v_LimitText text;
|
||||
v_Limit numeric(9, 2);
|
||||
v_WeeklyLimit integer;
|
||||
v_Now timestamp;
|
||||
BEGIN
|
||||
-- Günlük puantajı hareketlerden, izin/rapor/eğitim kayıtlarından ve Çalışma Takvimi'nden kurar.
|
||||
-- p_EmployeeId boşsa kiracının bütün çalışanları. Kapalı dönemin günleri yazılmaz (yalnız haftalık
|
||||
-- birikime girer); elle düzeltilmiş gün (IsManual) girdilerini korur, yalnız süreleri yeniden hesaplanır.
|
||||
-- İzin/rapor kaydını yazan prosedürler de bunu çağırdığı için hata fırlatmaz.
|
||||
-- Devam takibi çalışana göre iki türlüdür: PDKS Kart No'su olan ve okutması gelmeye başlamış çalışanda
|
||||
-- okutmasız iş günü devamsızdır; diğer (elle takip edilen) çalışanda kayıtsız iş günü vardiya saatleriyle "geldi" sayılır
|
||||
-- (IsAssumed), İK yalnız gelinmeyen günü işaretler.
|
||||
-- Süre farkları T-SQL DATEDIFF(minute) gibi dakika sınırı sayısıdır (saniyeler kesilerek alınır).
|
||||
IF v_From IS NULL OR v_To IS NULL OR v_To < v_From THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Tek çalışanlık hesapta, kaynağı artık olmayan izin/rapor/eğitim günü (talebin tarihi değişti,
|
||||
-- rapor ya da katılım silindi) aralığa katılır; böylece eski günler aynı işlemde düzelir.
|
||||
IF p_EmployeeId IS NOT NULL THEN
|
||||
SELECT MIN(t."WorkDate"), MAX(t."WorkDate")
|
||||
INTO v_OrphanFrom, v_OrphanTo
|
||||
FROM public."Hr_T_Timesheet" t
|
||||
WHERE t."EmployeeId" = p_EmployeeId AND t."IsDeleted" = FALSE AND t."IsManual" = FALSE
|
||||
AND t."DayType" IN ('Leave', 'Sick', 'Training')
|
||||
AND (t."WorkDate" < v_From OR t."WorkDate" > v_To)
|
||||
AND NOT EXISTS (SELECT 1 FROM public."Adm_T_UserSickLeave" s
|
||||
WHERE s."UserId" = t."EmployeeId" AND s."IsDeleted" = FALSE
|
||||
AND t."WorkDate" BETWEEN s."StartDate"::date AND s."EndDate"::date)
|
||||
AND NOT EXISTS (SELECT 1 FROM public."Hr_T_LeaveRequest" q
|
||||
WHERE q."EmployeeId" = t."EmployeeId" AND q."IsDeleted" = FALSE AND q."ApprovalStatus" = 'Onaylandı'
|
||||
AND t."WorkDate" BETWEEN q."StartDate"::date AND q."EndDate"::date)
|
||||
AND NOT EXISTS (SELECT 1 FROM public."Hr_T_TrainingParticipant" p
|
||||
INNER JOIN public."Hr_T_Training" g ON g."Id" = p."TrainingId" AND g."IsDeleted" = FALSE
|
||||
WHERE p."UserId" = t."EmployeeId" AND p."IsDeleted" = FALSE AND p."AttendanceStatus" = 'Attended'
|
||||
AND t."WorkDate" BETWEEN g."StartDate" AND g."EndDate");
|
||||
|
||||
IF v_OrphanFrom < v_From THEN v_From := v_OrphanFrom; END IF;
|
||||
IF v_OrphanTo > v_To THEN v_To := v_OrphanTo; END IF;
|
||||
END IF;
|
||||
|
||||
-- Fazla mesai haftalık birikimle hesaplandığı için aralık tam haftalara (Pzt–Paz) genişler.
|
||||
-- 1900-01-01 Pazartesidir; gün sırası oturum ayarlarından bağımsızdır.
|
||||
v_From := v_From - ((v_From - DATE '1900-01-01') % 7);
|
||||
v_To := v_To + (6 - ((v_To - DATE '1900-01-01') % 7));
|
||||
v_Last := CASE WHEN v_To > v_Today THEN v_Today ELSE v_To END;
|
||||
IF v_Last < v_From THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
v_LimitText := btrim(replace(public."Hr_F_HrSetting"(p_TenantId, 'App.HrSettings.Attendance.WeeklyLimitHours'), ',', '.'));
|
||||
IF v_LimitText ~ '^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)$' THEN
|
||||
BEGIN
|
||||
v_Limit := v_LimitText::numeric(9, 2);
|
||||
EXCEPTION WHEN numeric_value_out_of_range THEN
|
||||
v_Limit := NULL;
|
||||
END;
|
||||
END IF;
|
||||
v_WeeklyLimit := trunc(60 * COALESCE(v_Limit, 45))::integer;
|
||||
|
||||
-- Hafta tatili İzin Yönetimi'nin çalışma günü ayarlarıdır (tek kaynak); tanım yoksa Pzt–Cum.
|
||||
DROP TABLE IF EXISTS tc_work;
|
||||
CREATE TEMP TABLE tc_work (DayIndex integer PRIMARY KEY, IsWork boolean) ON COMMIT DROP;
|
||||
INSERT INTO tc_work (DayIndex, IsWork)
|
||||
SELECT d.DayIndex,
|
||||
CASE
|
||||
WHEN s.Value IS NULL THEN d.DayIndex < 5
|
||||
WHEN lower(s.Value) = 'true' THEN TRUE
|
||||
ELSE FALSE
|
||||
END
|
||||
FROM (VALUES
|
||||
(0, 'App.HrSettings.Leave.WorkMonday'),
|
||||
(1, 'App.HrSettings.Leave.WorkTuesday'),
|
||||
(2, 'App.HrSettings.Leave.WorkWednesday'),
|
||||
(3, 'App.HrSettings.Leave.WorkThursday'),
|
||||
(4, 'App.HrSettings.Leave.WorkFriday'),
|
||||
(5, 'App.HrSettings.Leave.WorkSaturday'),
|
||||
(6, 'App.HrSettings.Leave.WorkSunday')) d (DayIndex, SettingName)
|
||||
CROSS JOIN LATERAL (SELECT public."Hr_F_HrSetting"(p_TenantId, d.SettingName)::text AS Value) s;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Çalışanlar: aralıkta istihdam edilen aktif kullanıcılar + hareketi ya da puantajı olanlar
|
||||
---------------------------------------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS tc_emp;
|
||||
CREATE TEMP TABLE tc_emp
|
||||
(
|
||||
EmployeeId uuid PRIMARY KEY, HireDate date NULL, TerminationDate date NULL,
|
||||
ShiftId uuid NULL, StartMin integer, EndMin integer, BreakMin integer, LateTol integer, EarlyTol integer,
|
||||
CardSince date NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
-- Kartlı takip: kullanıcı kartında PDKS Kart No varsa ilk PDKS/aktarım okutmasının gününden başlar;
|
||||
-- öncesi (kart yeni verildiyse) ve elle girilen hareketler elle takibi değiştirmez.
|
||||
INSERT INTO tc_emp (EmployeeId, HireDate, TerminationDate, ShiftId, StartMin, EndMin, BreakMin, LateTol, EarlyTol, CardSince)
|
||||
SELECT u."Id", u."HireDate"::date, u."TerminationDate"::date,
|
||||
s."ShiftId", s."StartMin", s."EndMin", s."BreakMin", s."LateTol", s."EarlyTol",
|
||||
CASE WHEN NULLIF(btrim(u."PdksCardNo"), '') IS NOT NULL THEN
|
||||
(SELECT MIN(l."WorkDate") FROM public."Hr_T_AttendanceLog" l
|
||||
WHERE l."EmployeeId" = u."Id" AND l."IsDeleted" = FALSE AND l."ProcessError" IS NULL AND l."Source" <> 'Manual')
|
||||
END
|
||||
FROM public."AbpUsers" u
|
||||
INNER JOIN public."Hr_V_AttendanceShift" s ON s."EmployeeId" = u."Id"
|
||||
WHERE u."IsDeleted" = FALSE
|
||||
AND (u."TenantId" = p_TenantId OR (u."TenantId" IS NULL AND p_TenantId IS NULL))
|
||||
AND (p_EmployeeId IS NULL OR u."Id" = p_EmployeeId)
|
||||
AND (
|
||||
(u."IsActive" = TRUE AND u."HireDate" IS NOT NULL AND u."HireDate"::date <= v_Last
|
||||
AND (u."TerminationDate" IS NULL OR u."TerminationDate"::date >= v_From))
|
||||
OR EXISTS (SELECT 1 FROM public."Hr_T_AttendanceLog" l
|
||||
WHERE l."EmployeeId" = u."Id" AND l."IsDeleted" = FALSE AND l."WorkDate" BETWEEN v_From AND v_Last)
|
||||
OR EXISTS (SELECT 1 FROM public."Hr_T_Timesheet" x
|
||||
WHERE x."EmployeeId" = u."Id" AND x."IsDeleted" = FALSE AND x."WorkDate" BETWEEN v_From AND v_Last)
|
||||
);
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM tc_emp) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Günler: hafta tatili, takvim türü, kapalı dönem
|
||||
---------------------------------------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS tc_day;
|
||||
CREATE TEMP TABLE tc_day (WorkDate date PRIMARY KEY, IsWorkDay boolean, CalendarType varchar(16) NULL, IsClosed boolean) ON COMMIT DROP;
|
||||
|
||||
INSERT INTO tc_day (WorkDate, IsWorkDay, CalendarType, IsClosed)
|
||||
SELECT d.WorkDate,
|
||||
COALESCE(w.IsWork, FALSE),
|
||||
public."Hr_F_WorkCalendarDayType"(p_TenantId, d.WorkDate),
|
||||
EXISTS (
|
||||
SELECT 1 FROM public."Hr_T_TimesheetPeriod" p
|
||||
WHERE p."IsDeleted" = FALSE AND p."Status" = 'Closed'
|
||||
AND (p."TenantId" = p_TenantId OR (p."TenantId" IS NULL AND p_TenantId IS NULL))
|
||||
AND p."PeriodYear" = EXTRACT(YEAR FROM d.WorkDate)::integer
|
||||
AND p."PeriodMonth" = EXTRACT(MONTH FROM d.WorkDate)::integer)
|
||||
FROM (SELECT g::date AS WorkDate FROM generate_series(v_From, v_Last, INTERVAL '1 day') g) d
|
||||
LEFT JOIN tc_work w ON w.DayIndex = (d.WorkDate - DATE '1900-01-01') % 7;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Çalışan × gün satırları
|
||||
---------------------------------------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS tc_row;
|
||||
CREATE TEMP TABLE tc_row
|
||||
(
|
||||
EmployeeId uuid NOT NULL, WorkDate date NOT NULL,
|
||||
ShiftId uuid NULL, StartMin integer, EndMin integer, ShiftBreak integer, LateTol integer, EarlyTol integer,
|
||||
IsEmployed boolean NOT NULL DEFAULT TRUE, HasCard boolean NOT NULL DEFAULT FALSE, IsAssumed boolean NOT NULL DEFAULT FALSE,
|
||||
IsWorkDay boolean, CalendarType varchar(16) NULL, IsClosed boolean,
|
||||
ExistingId uuid NULL, IsManual boolean NOT NULL DEFAULT FALSE, HalfDayLeave boolean NOT NULL DEFAULT FALSE,
|
||||
LeaveTypeId uuid NULL, TrainingId uuid NULL,
|
||||
DayType varchar(16) NULL, FirstIn timestamp NULL, LastOut timestamp NULL, HasEvents boolean NOT NULL DEFAULT FALSE,
|
||||
ScanBreak integer NOT NULL DEFAULT 0, BreakMinutes integer NOT NULL DEFAULT 0, NetMinutes integer NOT NULL DEFAULT 0,
|
||||
LateMinutes integer NOT NULL DEFAULT 0, EarlyLeaveMinutes integer NOT NULL DEFAULT 0, OvertimeMinutes integer NOT NULL DEFAULT 0,
|
||||
IsIncomplete boolean NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY (EmployeeId, WorkDate)
|
||||
) ON COMMIT DROP;
|
||||
|
||||
INSERT INTO tc_row (EmployeeId, WorkDate, ShiftId, StartMin, EndMin, ShiftBreak, LateTol, EarlyTol,
|
||||
IsEmployed, HasCard, IsWorkDay, CalendarType, IsClosed)
|
||||
SELECT e.EmployeeId, d.WorkDate, e.ShiftId, e.StartMin, e.EndMin, e.BreakMin, e.LateTol, e.EarlyTol,
|
||||
CASE WHEN e.HireDate IS NOT NULL AND e.HireDate <= d.WorkDate
|
||||
AND (e.TerminationDate IS NULL OR e.TerminationDate >= d.WorkDate) THEN TRUE ELSE FALSE END,
|
||||
CASE WHEN e.CardSince <= d.WorkDate THEN TRUE ELSE FALSE END, d.IsWorkDay, d.CalendarType, d.IsClosed
|
||||
FROM tc_emp e
|
||||
CROSS JOIN tc_day d;
|
||||
|
||||
UPDATE tc_row r
|
||||
SET ExistingId = t."Id", IsManual = t."IsManual"
|
||||
FROM (
|
||||
SELECT DISTINCT ON (x."EmployeeId", x."WorkDate") x."EmployeeId", x."WorkDate", x."Id", x."IsManual"
|
||||
FROM public."Hr_T_Timesheet" x
|
||||
WHERE x."IsDeleted" = FALSE AND x."WorkDate" BETWEEN v_From AND v_Last
|
||||
AND (x."TenantId" = p_TenantId OR (x."TenantId" IS NULL AND p_TenantId IS NULL))
|
||||
AND x."EmployeeId" IN (SELECT EmployeeId FROM tc_emp)
|
||||
ORDER BY x."EmployeeId", x."WorkDate", x."IsManual" DESC NULLS LAST, x."CreationTime" NULLS FIRST
|
||||
) t
|
||||
WHERE t."EmployeeId" = r.EmployeeId AND t."WorkDate" = r.WorkDate;
|
||||
|
||||
-- Kapalı dönemin günü olduğu gibi kalır; elle düzeltilmiş günün girdileri korunur.
|
||||
UPDATE tc_row r
|
||||
SET DayType = t."DayType", LeaveTypeId = t."LeaveTypeId", TrainingId = t."TrainingId", FirstIn = t."FirstIn", LastOut = t."LastOut",
|
||||
BreakMinutes = t."BreakMinutes",
|
||||
NetMinutes = CASE WHEN r.IsClosed = TRUE THEN t."NetMinutes" ELSE 0 END,
|
||||
LateMinutes = CASE WHEN r.IsClosed = TRUE THEN t."LateMinutes" ELSE 0 END,
|
||||
EarlyLeaveMinutes = CASE WHEN r.IsClosed = TRUE THEN t."EarlyLeaveMinutes" ELSE 0 END,
|
||||
OvertimeMinutes = CASE WHEN r.IsClosed = TRUE THEN ROUND(t."OvertimeHours" * 60, 0)::integer ELSE 0 END,
|
||||
IsIncomplete = t."IsIncomplete",
|
||||
IsAssumed = CASE WHEN r.IsClosed = TRUE THEN t."IsAssumed" ELSE FALSE END
|
||||
FROM public."Hr_T_Timesheet" t
|
||||
WHERE t."Id" = r.ExistingId
|
||||
AND (r.IsClosed = TRUE OR r.IsManual = TRUE);
|
||||
|
||||
-- Elle devamsız/izinli/raporlu işaretlenen günde saat tutulmaz.
|
||||
UPDATE tc_row
|
||||
SET FirstIn = NULL, LastOut = NULL, BreakMinutes = 0
|
||||
WHERE IsManual = TRUE AND IsClosed = FALSE AND DayType IN ('Absent', 'Leave', 'Sick');
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Hareketler: ilk giriş, son çıkış, okutulan mola. Yönü "Auto" olan okutma sıraya göre
|
||||
-- giriş/çıkış sayılır (tek sıra giriş, çift sıra çıkış).
|
||||
-- Yön büyük/küçük harf duyarsız karşılaştırılır (SQL Server harmanlamasıyla aynı sonuç).
|
||||
---------------------------------------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS tc_event;
|
||||
CREATE TEMP TABLE tc_event (EmployeeId uuid, WorkDate date, EventTime timestamp, Dir varchar(8),
|
||||
NextTime timestamp NULL, NextDir varchar(8) NULL) ON COMMIT DROP;
|
||||
|
||||
WITH ev AS (
|
||||
SELECT l."EmployeeId" AS EmployeeId, l."WorkDate" AS WorkDate, l."EventTime" AS EventTime,
|
||||
MIN(lower(l."Direction")) AS Direction
|
||||
FROM public."Hr_T_AttendanceLog" l
|
||||
WHERE l."IsDeleted" = FALSE AND l."ProcessError" IS NULL AND l."WorkDate" BETWEEN v_From AND v_Last
|
||||
AND (l."TenantId" = p_TenantId OR (l."TenantId" IS NULL AND p_TenantId IS NULL))
|
||||
AND l."EmployeeId" IN (SELECT EmployeeId FROM tc_emp)
|
||||
GROUP BY l."EmployeeId", l."WorkDate", l."EventTime"
|
||||
), numbered AS (
|
||||
SELECT ev.*, ROW_NUMBER() OVER (PARTITION BY ev.EmployeeId, ev.WorkDate ORDER BY ev.EventTime) AS Rn
|
||||
FROM ev
|
||||
), dir AS (
|
||||
SELECT n.EmployeeId, n.WorkDate, n.EventTime,
|
||||
CASE WHEN n.Direction = 'in' THEN 'In'
|
||||
WHEN n.Direction = 'out' THEN 'Out'
|
||||
WHEN n.Rn % 2 = 1 THEN 'In' ELSE 'Out' END AS Dir
|
||||
FROM numbered n
|
||||
)
|
||||
INSERT INTO tc_event (EmployeeId, WorkDate, EventTime, Dir, NextTime, NextDir)
|
||||
SELECT dir.EmployeeId, dir.WorkDate, dir.EventTime, dir.Dir,
|
||||
LEAD(dir.EventTime) OVER (PARTITION BY dir.EmployeeId, dir.WorkDate ORDER BY dir.EventTime),
|
||||
LEAD(dir.Dir) OVER (PARTITION BY dir.EmployeeId, dir.WorkDate ORDER BY dir.EventTime)
|
||||
FROM dir;
|
||||
|
||||
UPDATE tc_row r
|
||||
SET HasEvents = TRUE,
|
||||
FirstIn = COALESCE(s.FirstIn, s.FirstAny),
|
||||
LastOut = CASE WHEN s.LastOut > COALESCE(s.FirstIn, s.FirstAny) THEN s.LastOut END
|
||||
FROM (
|
||||
SELECT e.EmployeeId, e.WorkDate,
|
||||
MIN(CASE WHEN e.Dir = 'In' THEN e.EventTime END) AS FirstIn,
|
||||
MIN(e.EventTime) AS FirstAny,
|
||||
MAX(CASE WHEN e.Dir = 'Out' THEN e.EventTime END) AS LastOut
|
||||
FROM tc_event e
|
||||
GROUP BY e.EmployeeId, e.WorkDate
|
||||
) s
|
||||
WHERE s.EmployeeId = r.EmployeeId AND s.WorkDate = r.WorkDate
|
||||
AND r.IsClosed = FALSE AND r.IsManual = FALSE;
|
||||
|
||||
-- Okutulan mola: ilk giriş ile son çıkış arasındaki çıkış → giriş aralıkları.
|
||||
UPDATE tc_row r
|
||||
SET ScanBreak = COALESCE((
|
||||
SELECT SUM((EXTRACT(EPOCH FROM date_trunc('minute', e.NextTime) - date_trunc('minute', e.EventTime)) / 60)::integer)
|
||||
FROM tc_event e
|
||||
WHERE e.EmployeeId = r.EmployeeId AND e.WorkDate = r.WorkDate
|
||||
AND e.Dir = 'Out' AND e.NextDir = 'In'
|
||||
AND e.EventTime >= r.FirstIn AND e.NextTime <= r.LastOut), 0)
|
||||
WHERE r.IsClosed = FALSE AND r.IsManual = FALSE AND r.FirstIn IS NOT NULL AND r.LastOut IS NOT NULL;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Gün tipi (öncelik sırasıyla):
|
||||
-- Raporlu: takvim günü; SGK istirahati hafta sonunu da kapsar.
|
||||
-- Ücretsiz izin (IsPaid = 0; doğum izni dahil): takvim günü; hafta tatili ve genel tatil ücreti doğmaz.
|
||||
-- Resmî tatil, Hafta tatili: ücretli izne denk gelen tatil izinden sayılmaz (4857 md. 56);
|
||||
-- İzin Yönetimi'nin gün sayısıyla aynı kural.
|
||||
-- Ücretli izin > okutmalı Çalışma > Eğitim (katılımcı "Katıldı") > Çalışma / Devamsız.
|
||||
-- Okutması olmayan iş günü: kartlı çalışanda devamsız (bugün henüz değil), kartsız çalışanda
|
||||
-- (bugün dahil) vardiya saatleriyle çalışılmış sayılır.
|
||||
---------------------------------------------------------------------------------------------
|
||||
UPDATE tc_row r
|
||||
SET HalfDayLeave = CASE WHEN x.LvIsHalfDay = TRUE THEN TRUE ELSE FALSE END,
|
||||
LeaveTypeId = x.LvLeaveTypeId,
|
||||
TrainingId = x.TrTrainingId,
|
||||
DayType = CASE
|
||||
WHEN x.SkId IS NOT NULL THEN 'Sick'
|
||||
WHEN x.LvId IS NOT NULL AND x.LvIsPaid = FALSE AND (x.LvIsHalfDay = FALSE OR r.FirstIn IS NULL) THEN 'Leave'
|
||||
WHEN r.CalendarType = 'Holiday' THEN 'Holiday'
|
||||
WHEN r.IsWorkDay = FALSE THEN 'WeeklyOff'
|
||||
WHEN x.LvId IS NOT NULL AND (x.LvIsHalfDay = FALSE OR r.FirstIn IS NULL) THEN 'Leave'
|
||||
WHEN r.FirstIn IS NOT NULL THEN 'Work'
|
||||
WHEN x.TrTrainingId IS NOT NULL THEN 'Training'
|
||||
WHEN r.HasCard = FALSE THEN 'Work'
|
||||
WHEN r.WorkDate < v_Today THEN 'Absent'
|
||||
END
|
||||
FROM (
|
||||
SELECT r2.EmployeeId, r2.WorkDate, sk.Id AS SkId,
|
||||
lv.Id AS LvId, lv.IsHalfDay AS LvIsHalfDay, lv.LeaveTypeId AS LvLeaveTypeId, lv.IsPaid AS LvIsPaid,
|
||||
tr.TrainingId AS TrTrainingId
|
||||
FROM tc_row r2
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT s."Id" AS Id
|
||||
FROM public."Adm_T_UserSickLeave" s
|
||||
WHERE s."UserId" = r2.EmployeeId AND s."IsDeleted" = FALSE
|
||||
AND r2.WorkDate BETWEEN s."StartDate"::date AND s."EndDate"::date
|
||||
LIMIT 1
|
||||
) sk ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT q."Id" AS Id, q."IsHalfDay" AS IsHalfDay, q."LeaveTypeId" AS LeaveTypeId, lt."IsPaid" AS IsPaid
|
||||
FROM public."Hr_T_LeaveRequest" q
|
||||
INNER JOIN public."Hr_T_LeaveType" lt ON lt."Id" = q."LeaveTypeId"
|
||||
WHERE q."EmployeeId" = r2.EmployeeId AND q."IsDeleted" = FALSE AND q."ApprovalStatus" = 'Onaylandı'
|
||||
AND r2.WorkDate BETWEEN q."StartDate"::date AND q."EndDate"::date
|
||||
ORDER BY q."IsHalfDay" NULLS FIRST
|
||||
LIMIT 1
|
||||
) lv ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT p."TrainingId" AS TrainingId
|
||||
FROM public."Hr_T_TrainingParticipant" p
|
||||
INNER JOIN public."Hr_T_Training" g ON g."Id" = p."TrainingId" AND g."IsDeleted" = FALSE
|
||||
WHERE p."UserId" = r2.EmployeeId AND p."IsDeleted" = FALSE AND p."AttendanceStatus" = 'Attended'
|
||||
AND r2.WorkDate BETWEEN g."StartDate" AND g."EndDate"
|
||||
ORDER BY g."StartDate" NULLS FIRST
|
||||
LIMIT 1
|
||||
) tr ON TRUE
|
||||
WHERE r2.IsClosed = FALSE AND r2.IsManual = FALSE
|
||||
) x
|
||||
WHERE x.EmployeeId = r.EmployeeId AND x.WorkDate = r.WorkDate;
|
||||
|
||||
-- İstihdam dışındaki hareketsiz gün yazılmaz (işe girişten önce, çıkıştan sonra).
|
||||
UPDATE tc_row
|
||||
SET DayType = NULL
|
||||
WHERE IsClosed = FALSE AND IsManual = FALSE AND IsEmployed = FALSE AND HasEvents = FALSE;
|
||||
|
||||
-- Elle takip edilen çalışanın kayıtsız iş günü ve okutmasız eğitim günü: vardiya saatleri
|
||||
-- (arifede yarım gün). Eğitim günü haftalık birikime çalışma gibi girer.
|
||||
UPDATE tc_row
|
||||
SET IsAssumed = TRUE,
|
||||
FirstIn = WorkDate + StartMin * INTERVAL '1 minute',
|
||||
LastOut = WorkDate + (StartMin + CASE WHEN CalendarType = 'HalfDay'
|
||||
THEN ((EndMin - StartMin + 1440) % 1440) / 2
|
||||
ELSE (EndMin - StartMin + 1440) % 1440 END) * INTERVAL '1 minute'
|
||||
WHERE IsClosed = FALSE AND IsManual = FALSE AND DayType IN ('Work', 'Training') AND HasEvents = FALSE;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Süreler: mola = MAX(vardiya molası, okutulan mola); net = son çıkış − ilk giriş − mola.
|
||||
-- Geç kalma / erken çıkış toleransı aşılırsa dakikanın tamamı yazılır. Yarım gün (arife)
|
||||
-- vardiya süresinin yarısında biter; yarım gün izinli günde geç/erken yazılmaz.
|
||||
---------------------------------------------------------------------------------------------
|
||||
UPDATE tc_row r
|
||||
SET BreakMinutes = CASE
|
||||
WHEN m.Gross <= 0 THEN 0
|
||||
WHEN r.IsManual = TRUE THEN CASE WHEN r.BreakMinutes > m.Gross THEN m.Gross ELSE r.BreakMinutes END
|
||||
WHEN (CASE WHEN r.ScanBreak > r.ShiftBreak THEN r.ScanBreak ELSE r.ShiftBreak END) > m.Gross THEN m.Gross
|
||||
ELSE CASE WHEN r.ScanBreak > r.ShiftBreak THEN r.ScanBreak ELSE r.ShiftBreak END
|
||||
END,
|
||||
IsIncomplete = CASE WHEN r.FirstIn IS NOT NULL AND r.LastOut IS NULL THEN TRUE ELSE FALSE END,
|
||||
LateMinutes = CASE
|
||||
WHEN r.DayType = 'Work' AND r.HalfDayLeave = FALSE AND r.FirstIn IS NOT NULL AND m.LateDiff > r.LateTol
|
||||
THEN m.LateDiff
|
||||
ELSE 0 END,
|
||||
EarlyLeaveMinutes = CASE
|
||||
WHEN r.DayType = 'Work' AND r.HalfDayLeave = FALSE AND r.LastOut IS NOT NULL AND m.EarlyDiff > r.EarlyTol
|
||||
THEN m.EarlyDiff
|
||||
ELSE 0 END
|
||||
FROM (
|
||||
SELECT r2.EmployeeId, r2.WorkDate,
|
||||
CASE WHEN r2.FirstIn IS NOT NULL AND r2.LastOut IS NOT NULL
|
||||
THEN (EXTRACT(EPOCH FROM date_trunc('minute', r2.LastOut) - date_trunc('minute', r2.FirstIn)) / 60)::integer
|
||||
ELSE 0 END AS Gross,
|
||||
(EXTRACT(EPOCH FROM date_trunc('minute', r2.FirstIn)
|
||||
- (r2.WorkDate + r2.StartMin * INTERVAL '1 minute')) / 60)::integer AS LateDiff,
|
||||
(EXTRACT(EPOCH FROM (r2.WorkDate + (r2.StartMin + CASE WHEN r2.CalendarType = 'HalfDay'
|
||||
THEN ((r2.EndMin - r2.StartMin + 1440) % 1440) / 2
|
||||
ELSE (r2.EndMin - r2.StartMin + 1440) % 1440 END) * INTERVAL '1 minute')
|
||||
- date_trunc('minute', r2.LastOut)) / 60)::integer AS EarlyDiff
|
||||
FROM tc_row r2
|
||||
WHERE r2.IsClosed = FALSE AND r2.DayType IS NOT NULL
|
||||
) m
|
||||
WHERE m.EmployeeId = r.EmployeeId AND m.WorkDate = r.WorkDate;
|
||||
|
||||
UPDATE tc_row
|
||||
SET NetMinutes = CASE
|
||||
WHEN FirstIn IS NOT NULL AND LastOut IS NOT NULL
|
||||
AND (EXTRACT(EPOCH FROM date_trunc('minute', LastOut) - date_trunc('minute', FirstIn)) / 60)::integer > BreakMinutes
|
||||
THEN (EXTRACT(EPOCH FROM date_trunc('minute', LastOut) - date_trunc('minute', FirstIn)) / 60)::integer - BreakMinutes
|
||||
ELSE 0 END
|
||||
WHERE IsClosed = FALSE AND DayType IS NOT NULL;
|
||||
|
||||
-- Negatif çıkmasın: tolerans içindeki erken giriş geç kalma değildir.
|
||||
UPDATE tc_row SET LateMinutes = 0 WHERE LateMinutes < 0;
|
||||
UPDATE tc_row SET EarlyLeaveMinutes = 0 WHERE EarlyLeaveMinutes < 0;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Fazla mesai (4857 md. 41): hafta tatili ve resmî tatilde çalışılan sürenin tamamı; diğer
|
||||
-- günlerde haftanın kümülatif net süresi haftalık sınırı aştığı günden itibaren aşan kısım.
|
||||
---------------------------------------------------------------------------------------------
|
||||
UPDATE tc_row r
|
||||
SET OvertimeMinutes = CASE
|
||||
WHEN r.DayType IN ('WeeklyOff', 'Holiday') THEN r.NetMinutes
|
||||
ELSE (CASE WHEN w.Cumulative > v_WeeklyLimit THEN w.Cumulative - v_WeeklyLimit ELSE 0 END)
|
||||
- (CASE WHEN w.Cumulative - r.NetMinutes > v_WeeklyLimit THEN w.Cumulative - r.NetMinutes - v_WeeklyLimit ELSE 0 END)
|
||||
END
|
||||
FROM (
|
||||
SELECT r2.EmployeeId, r2.WorkDate,
|
||||
SUM(CASE WHEN r2.DayType IN ('WeeklyOff', 'Holiday') THEN 0 ELSE r2.NetMinutes END)
|
||||
OVER (PARTITION BY r2.EmployeeId, (r2.WorkDate - DATE '1900-01-01') / 7
|
||||
ORDER BY r2.WorkDate ROWS UNBOUNDED PRECEDING) AS Cumulative
|
||||
FROM tc_row r2
|
||||
WHERE r2.DayType IS NOT NULL
|
||||
) w
|
||||
WHERE w.EmployeeId = r.EmployeeId AND w.WorkDate = r.WorkDate
|
||||
AND r.IsClosed = FALSE;
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
-- Yazma
|
||||
---------------------------------------------------------------------------------------------
|
||||
v_Now := (NOW() AT TIME ZONE 'UTC');
|
||||
|
||||
UPDATE public."Hr_T_Timesheet" t
|
||||
SET "ShiftId" = r.ShiftId,
|
||||
"DayType" = r.DayType,
|
||||
"LeaveTypeId" = r.LeaveTypeId,
|
||||
"TrainingId" = r.TrainingId,
|
||||
"FirstIn" = r.FirstIn,
|
||||
"LastOut" = r.LastOut,
|
||||
"BreakMinutes" = r.BreakMinutes,
|
||||
"NetMinutes" = r.NetMinutes,
|
||||
"NetHours" = CAST(r.NetMinutes / 60.0 AS numeric(6, 2)),
|
||||
"LateMinutes" = r.LateMinutes,
|
||||
"EarlyLeaveMinutes" = r.EarlyLeaveMinutes,
|
||||
"OvertimeHours" = CAST(r.OvertimeMinutes / 60.0 AS numeric(6, 2)),
|
||||
"IsIncomplete" = r.IsIncomplete,
|
||||
"IsAssumed" = r.IsAssumed,
|
||||
"CalculatedAt" = v_Now,
|
||||
"LastModificationTime" = v_Now,
|
||||
"LastModifierId" = COALESCE(p_ActorId, t."LastModifierId")
|
||||
FROM tc_row r
|
||||
WHERE r.ExistingId = t."Id" AND r.IsClosed = FALSE AND r.DayType IS NOT NULL;
|
||||
|
||||
-- Artık karşılığı olmayan otomatik gün (ör. geriye dönük işten çıkış) kalkar.
|
||||
UPDATE public."Hr_T_Timesheet" t
|
||||
SET "IsDeleted" = TRUE, "DeletionTime" = v_Now, "DeleterId" = p_ActorId
|
||||
FROM tc_row r
|
||||
WHERE r.ExistingId = t."Id" AND r.IsClosed = FALSE AND r.IsManual = FALSE AND r.DayType IS NULL;
|
||||
|
||||
-- PeriodKey SQL Server'daki CONVERT(nvarchar(36), uniqueidentifier) ile aynı (büyük harf) yazılır.
|
||||
INSERT INTO public."Hr_T_Timesheet"
|
||||
("Id", "TenantId", "EmployeeId", "WorkDate", "PeriodYear", "PeriodMonth", "PeriodKey", "ShiftId", "DayType", "LeaveTypeId", "TrainingId",
|
||||
"FirstIn", "LastOut", "BreakMinutes", "NetMinutes", "NetHours", "LateMinutes", "EarlyLeaveMinutes", "OvertimeHours",
|
||||
"IsManual", "IsIncomplete", "IsAssumed", "CalculatedAt", "CreationTime", "CreatorId", "IsDeleted")
|
||||
SELECT gen_random_uuid(), p_TenantId, r.EmployeeId, r.WorkDate,
|
||||
EXTRACT(YEAR FROM r.WorkDate)::integer, EXTRACT(MONTH FROM r.WorkDate)::integer,
|
||||
upper(r.EmployeeId::text) || '-' || (EXTRACT(YEAR FROM r.WorkDate)::integer * 100 + EXTRACT(MONTH FROM r.WorkDate)::integer)::text,
|
||||
r.ShiftId, r.DayType, r.LeaveTypeId, r.TrainingId, r.FirstIn, r.LastOut, r.BreakMinutes, r.NetMinutes,
|
||||
CAST(r.NetMinutes / 60.0 AS numeric(6, 2)), r.LateMinutes, r.EarlyLeaveMinutes,
|
||||
CAST(r.OvertimeMinutes / 60.0 AS numeric(6, 2)), FALSE, r.IsIncomplete, r.IsAssumed, v_Now, v_Now, p_ActorId, FALSE
|
||||
FROM tc_row r
|
||||
WHERE r.IsClosed = FALSE AND r.ExistingId IS NULL AND r.DayType IS NOT NULL;
|
||||
END;
|
||||
$$;
|
||||
70
configs/seeds/postgres/object/Hr_S_TimesheetNightly.sql
Normal file
70
configs/seeds/postgres/object/Hr_S_TimesheetNightly.sql
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
-- Zamanlanmış işin BeforeSp'si PostgreSQL'de "SELECT * FROM public.<ad>()" ile çağrıldığı için
|
||||
-- prosedür değil, void dönen fonksiyondur.
|
||||
CREATE OR REPLACE FUNCTION public."Hr_S_TimesheetNightly"()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_Today date := LOCALTIMESTAMP::date;
|
||||
v_Yesterday date := LOCALTIMESTAMP::date - 1;
|
||||
v_MonthStart date;
|
||||
v_PrevStart date;
|
||||
v_TenantId uuid;
|
||||
v_PeriodId uuid;
|
||||
v_From date;
|
||||
BEGIN
|
||||
-- Zamanlanmış iş (Timesheet Nightly, her gece 02:00). Hareketi olmayan günleri (devamsız, hafta
|
||||
-- tatili, resmî tatil, izinli, raporlu) kurar ve geriye dönük onaylanan izin/rapor, takvim ya da
|
||||
-- vardiya değişikliğini yansıtır: dünün ayı ve kapanmamışsa bir önceki ay, işlenmemiş ve kartı sonradan
|
||||
-- tanımlanan hareketler.
|
||||
-- İş host ve her kiracı için çağrılır, prosedür parametre almaz; bu yüzden veritabanındaki bütün
|
||||
-- kiracıları işler ve aynı gün işlenmiş kiracıyı (dönemin NightlyAt değeri) atlar.
|
||||
v_MonthStart := make_date(EXTRACT(YEAR FROM v_Yesterday)::integer, EXTRACT(MONTH FROM v_Yesterday)::integer, 1);
|
||||
v_PrevStart := (v_MonthStart - INTERVAL '1 month')::date;
|
||||
|
||||
-- Kart numarası kullanıcı kartına sonradan girilen okutmalar yeniden işlenir.
|
||||
UPDATE public."Hr_T_AttendanceLog" l
|
||||
SET "IsProcessed" = FALSE, "ProcessError" = NULL
|
||||
WHERE l."IsDeleted" = FALSE AND l."ProcessError" = 'UnknownCard'
|
||||
AND EXISTS (SELECT 1 FROM public."AbpUsers" u
|
||||
WHERE (lower(u."PdksCardNo") = lower(l."CardNo") OR lower(u."RegistrationNo") = lower(l."CardNo")) AND u."IsDeleted" = FALSE
|
||||
AND (u."TenantId" = l."TenantId" OR (u."TenantId" IS NULL AND l."TenantId" IS NULL)));
|
||||
|
||||
CALL public."Hr_S_AttendanceProcess"(FALSE);
|
||||
|
||||
FOR v_TenantId IN
|
||||
SELECT DISTINCT "TenantId" FROM public."AbpUsers" WHERE "IsDeleted" = FALSE AND "HireDate" IS NOT NULL
|
||||
LOOP
|
||||
v_PeriodId := (SELECT "Id" FROM public."Hr_T_TimesheetPeriod"
|
||||
WHERE "IsDeleted" = FALSE
|
||||
AND "PeriodYear" = EXTRACT(YEAR FROM v_Yesterday)::integer
|
||||
AND "PeriodMonth" = EXTRACT(MONTH FROM v_Yesterday)::integer
|
||||
AND ("TenantId" = v_TenantId OR ("TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
LIMIT 1);
|
||||
|
||||
-- Ay dönemi gece işinde kendiliğinden açılır.
|
||||
IF v_PeriodId IS NULL THEN
|
||||
v_PeriodId := gen_random_uuid();
|
||||
INSERT INTO public."Hr_T_TimesheetPeriod" ("Id", "TenantId", "PeriodYear", "PeriodMonth", "Status", "CreationTime", "IsDeleted")
|
||||
VALUES (v_PeriodId, v_TenantId, EXTRACT(YEAR FROM v_Yesterday)::integer, EXTRACT(MONTH FROM v_Yesterday)::integer,
|
||||
'Open', (NOW() AT TIME ZONE 'UTC'), FALSE);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM public."Hr_T_TimesheetPeriod" WHERE "Id" = v_PeriodId AND "NightlyAt" >= v_Today::timestamp) THEN
|
||||
v_From := CASE WHEN EXISTS (
|
||||
SELECT 1 FROM public."Hr_T_TimesheetPeriod"
|
||||
WHERE "IsDeleted" = FALSE AND "Status" = 'Closed'
|
||||
AND "PeriodYear" = EXTRACT(YEAR FROM v_PrevStart)::integer
|
||||
AND "PeriodMonth" = EXTRACT(MONTH FROM v_PrevStart)::integer
|
||||
AND ("TenantId" = v_TenantId OR ("TenantId" IS NULL AND v_TenantId IS NULL)))
|
||||
THEN v_MonthStart ELSE v_PrevStart END;
|
||||
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_TenantId, NULL, v_From, v_Yesterday, NULL);
|
||||
|
||||
UPDATE public."Hr_T_TimesheetPeriod"
|
||||
SET "NightlyAt" = LOCALTIMESTAMP, "CalculatedAt" = (NOW() AT TIME ZONE 'UTC')
|
||||
WHERE "Id" = v_PeriodId;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_TimesheetPeriodAfterSave"(
|
||||
p_PeriodId uuid,
|
||||
p_ActorId uuid
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_TenantId uuid;
|
||||
v_Year integer;
|
||||
v_Month integer;
|
||||
v_Status varchar(16);
|
||||
v_ClosedAt timestamp;
|
||||
v_From date;
|
||||
v_To date;
|
||||
BEGIN
|
||||
-- Puantaj Dönemleri kaydı: açık dönemin her kaydı ayı bütün çalışanlar için yeniden hesaplar;
|
||||
-- kapatma son kez hesaplayıp ayı kilitler. Kapalı ayın hareketleri ve günleri değiştirilemez.
|
||||
SELECT "TenantId", "PeriodYear", "PeriodMonth", "Status", "ClosedAt"
|
||||
INTO v_TenantId, v_Year, v_Month, v_Status, v_ClosedAt
|
||||
FROM public."Hr_T_TimesheetPeriod"
|
||||
WHERE "Id" = p_PeriodId AND "IsDeleted" = FALSE;
|
||||
|
||||
IF v_Year IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF v_Month NOT BETWEEN 1 AND 12 OR v_Year NOT BETWEEN 2000 AND 2100 THEN
|
||||
RAISE EXCEPTION 'Dönem yılı 2000–2100, ayı 1–12 arasında olmalıdır.' USING ERRCODE = 'P0001', DETAIL = '51220';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM public."Hr_T_TimesheetPeriod"
|
||||
WHERE "Id" <> p_PeriodId AND "IsDeleted" = FALSE AND "PeriodYear" = v_Year AND "PeriodMonth" = v_Month
|
||||
AND ("TenantId" = v_TenantId OR ("TenantId" IS NULL AND v_TenantId IS NULL))) THEN
|
||||
RAISE EXCEPTION 'Bu yıl ve ay için puantaj dönemi zaten var.' USING ERRCODE = 'P0001', DETAIL = '51221';
|
||||
END IF;
|
||||
|
||||
v_From := make_date(v_Year, v_Month, 1);
|
||||
v_To := (v_From + INTERVAL '1 month - 1 day')::date;
|
||||
|
||||
IF v_Status = 'Closed' AND v_ClosedAt IS NOT NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Kapalı aya izin yazılamadığı için ayda onay bekleyen talep varken dönem kapanmaz; aksi hâlde
|
||||
-- sonradan onaylanan izin puantaja hiç yansımazdı.
|
||||
IF v_Status = 'Closed' AND EXISTS (
|
||||
SELECT 1 FROM public."Hr_T_LeaveRequest" q
|
||||
WHERE q."IsDeleted" = FALSE
|
||||
AND (q."TenantId" = v_TenantId OR (q."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
AND (q."ApprovalStatus" IS NULL OR q."ApprovalStatus" IN ('Onaya Gönderildi', 'Müdür Onayı', 'İK Onayı'))
|
||||
AND q."StartDate"::date <= v_To AND q."EndDate"::date >= v_From) THEN
|
||||
RAISE EXCEPTION 'Bu ayda onay bekleyen izin talebi var. Talepler sonuçlanmadan dönem kapatılamaz.'
|
||||
USING ERRCODE = 'P0001', DETAIL = '51223';
|
||||
END IF;
|
||||
|
||||
-- Kapatma anında da son hesap yapılır; hesap kapalı ayı yazmadığı için önce açık sayılır.
|
||||
UPDATE public."Hr_T_TimesheetPeriod" SET "Status" = 'Open' WHERE "Id" = p_PeriodId;
|
||||
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_TenantId, NULL, v_From, v_To, p_ActorId);
|
||||
|
||||
UPDATE public."Hr_T_TimesheetPeriod"
|
||||
SET "Status" = COALESCE(v_Status, 'Open'),
|
||||
"CalculatedAt" = (NOW() AT TIME ZONE 'UTC'),
|
||||
"ClosedAt" = CASE WHEN v_Status = 'Closed' THEN (NOW() AT TIME ZONE 'UTC') END,
|
||||
"ClosedById" = CASE WHEN v_Status = 'Closed' THEN p_ActorId END
|
||||
WHERE "Id" = p_PeriodId;
|
||||
END;
|
||||
$$;
|
||||
41
configs/seeds/postgres/object/Hr_S_TrainingSync.sql
Normal file
41
configs/seeds/postgres/object/Hr_S_TrainingSync.sql
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_TrainingSync"(
|
||||
p_TrainingId uuid,
|
||||
p_UserId uuid DEFAULT NULL,
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_TenantId uuid;
|
||||
v_StartDate date;
|
||||
v_EndDate date;
|
||||
v_Participant uuid;
|
||||
v_PlanId uuid;
|
||||
BEGIN
|
||||
-- Eğitim ya da katılımcı kaydından sonra çalışır (p_UserId boşsa eğitimin bütün katılımcıları):
|
||||
-- "Katıldı" işaretli katılımcının eğitim günleri puantajda Eğitim olur (devamsız yazılmaz), bağlı
|
||||
-- gelişim aksiyonu tamamlanır. Silinen katılımcı/eğitim ve değişen tarih için eski günleri
|
||||
-- puantaj hesabı kendisi düzeltir.
|
||||
SELECT "TenantId", "StartDate", "EndDate" INTO v_TenantId, v_StartDate, v_EndDate
|
||||
FROM "Hr_T_Training"
|
||||
WHERE "Id" = p_TrainingId;
|
||||
|
||||
IF v_StartDate IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
FOR v_Participant IN
|
||||
SELECT DISTINCT "UserId" FROM "Hr_T_TrainingParticipant"
|
||||
WHERE "TrainingId" = p_TrainingId AND (p_UserId IS NULL OR "UserId" = p_UserId)
|
||||
LOOP
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_TenantId, v_Participant, v_StartDate, v_EndDate, p_ActorId);
|
||||
END LOOP;
|
||||
|
||||
FOR v_PlanId IN
|
||||
SELECT "Id" FROM "Hr_T_DevelopmentPlan"
|
||||
WHERE "TrainingId" = p_TrainingId AND "IsDeleted" = FALSE AND (p_UserId IS NULL OR "EmployeeId" = p_UserId)
|
||||
LOOP
|
||||
CALL public."Hr_S_DevelopmentPlanSync"(v_PlanId);
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
55
configs/seeds/postgres/object/Hr_S_WorkCalendarAfterSave.sql
Normal file
55
configs/seeds/postgres/object/Hr_S_WorkCalendarAfterSave.sql
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
CREATE OR REPLACE PROCEDURE public."Hr_S_WorkCalendarAfterSave"(
|
||||
p_CalendarId uuid,
|
||||
p_ActorId uuid DEFAULT NULL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_TenantId uuid;
|
||||
v_StartDate date;
|
||||
v_EndDate date;
|
||||
v_Recurrence varchar(256);
|
||||
v_Shift integer;
|
||||
BEGIN
|
||||
-- Çalışma Takvimi satırı eklenince, değişince ya da silinince tatile denk gelen izin taleplerinin
|
||||
-- gün sayısı ve puantaj yeniden hesaplanır; aksi hâlde sonradan girilen bayram izinden düşmeye,
|
||||
-- puantajda çalışma günü görünmeye devam ederdi. Yıllık tekrarlı satırın bu yılki ve gelecek
|
||||
-- yılki karşılığı işlenir; kapalı puantaj ayı yazılmaz.
|
||||
SELECT "TenantId", "StartDate"::date, "EndDate"::date, "RecurrenceRule"
|
||||
INTO v_TenantId, v_StartDate, v_EndDate, v_Recurrence
|
||||
FROM public."Hr_T_WorkCalendar"
|
||||
WHERE "Id" = p_CalendarId;
|
||||
|
||||
IF v_StartDate IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF v_Recurrence = 'FREQ=YEARLY' THEN
|
||||
v_Shift := EXTRACT(YEAR FROM LOCALTIMESTAMP)::integer - EXTRACT(YEAR FROM v_StartDate)::integer;
|
||||
IF v_Shift > 0 THEN
|
||||
v_StartDate := (v_StartDate + make_interval(years => v_Shift))::date;
|
||||
v_EndDate := (v_EndDate + make_interval(years => v_Shift + 1))::date;
|
||||
ELSE
|
||||
v_EndDate := (v_EndDate + INTERVAL '1 year')::date;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
UPDATE public."Hr_T_LeaveRequest" r
|
||||
SET "Days" = CASE WHEN r."IsHalfDay" = TRUE AND n.Days > 0.5 THEN 0.5 ELSE n.Days END
|
||||
FROM (
|
||||
SELECT q."Id",
|
||||
CASE WHEN t."Code" = 'Sick'
|
||||
THEN public."Hr_F_LeaveWorkDays"(q."TenantId", q."StartDate"::date, q."EndDate"::date)
|
||||
ELSE public."Hr_F_LeaveNetDays"(q."TenantId", q."EmployeeId", q."StartDate"::date, q."EndDate"::date)
|
||||
END AS Days
|
||||
FROM public."Hr_T_LeaveRequest" q
|
||||
INNER JOIN public."Hr_T_LeaveType" t ON t."Id" = q."LeaveTypeId"
|
||||
WHERE q."IsDeleted" = FALSE
|
||||
AND (q."TenantId" = v_TenantId OR (q."TenantId" IS NULL AND v_TenantId IS NULL))
|
||||
AND q."StartDate"::date <= v_EndDate AND q."EndDate"::date >= v_StartDate
|
||||
) n
|
||||
WHERE n."Id" = r."Id";
|
||||
|
||||
CALL public."Hr_S_TimesheetCalculate"(v_TenantId, NULL, v_StartDate, v_EndDate, p_ActorId);
|
||||
END;
|
||||
$$;
|
||||
27
configs/seeds/postgres/object/Hr_T_AttendanceLog.sql
Normal file
27
configs/seeds/postgres/object/Hr_T_AttendanceLog.sql
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_AttendanceLog"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"EmployeeId" uuid NULL,
|
||||
"CardNo" varchar(64) NULL,
|
||||
"EventTime" timestamp NOT NULL,
|
||||
"Direction" varchar(8) NOT NULL DEFAULT 'Auto',
|
||||
"Source" varchar(16) NULL,
|
||||
"DeviceCode" varchar(64) NULL,
|
||||
"Note" varchar(512) NULL,
|
||||
"WorkDate" date NULL,
|
||||
"IsProcessed" boolean NOT NULL DEFAULT FALSE,
|
||||
"ProcessError" varchar(32) NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_AttendanceLog" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_AttendanceLog_Employee" ON public."Hr_T_AttendanceLog" ("EmployeeId", "WorkDate") INCLUDE ("EventTime", "Direction", "IsDeleted", "ProcessError");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_AttendanceLog_Pending" ON public."Hr_T_AttendanceLog" ("IsProcessed") WHERE "IsProcessed" = FALSE;
|
||||
20
configs/seeds/postgres/object/Hr_T_BenefitType.sql
Normal file
20
configs/seeds/postgres/object/Hr_T_BenefitType.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_BenefitType"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Code" varchar(32) NOT NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"DefaultAmount" numeric(18, 2) NULL,
|
||||
"CurrencyId" varchar(64) NULL,
|
||||
"IsTaxable" boolean NOT NULL DEFAULT FALSE,
|
||||
"IsSgkSubject" boolean NOT NULL DEFAULT FALSE,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_BenefitType" PRIMARY KEY ("Id")
|
||||
);
|
||||
53
configs/seeds/postgres/object/Hr_T_Candidate.sql
Normal file
53
configs/seeds/postgres/object/Hr_T_Candidate.sql
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Candidate"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"FirstName" varchar(128) NOT NULL,
|
||||
"LastName" varchar(128) NOT NULL,
|
||||
"Email" varchar(256) NOT NULL,
|
||||
"PhoneNumber" varchar(32) NULL,
|
||||
"Gender" varchar(16) NULL,
|
||||
"BirthDate" date NULL,
|
||||
"Country" varchar(64) NULL,
|
||||
"City" varchar(128) NULL,
|
||||
"Address" varchar(512) NULL,
|
||||
"EducationLevel" varchar(32) NULL,
|
||||
"School" varchar(256) NULL,
|
||||
"Major" varchar(256) NULL,
|
||||
"GraduationYear" integer NULL,
|
||||
"ExperienceYears" numeric(4, 1) NULL,
|
||||
"CurrentCompany" varchar(256) NULL,
|
||||
"CurrentTitle" varchar(256) NULL,
|
||||
"ExpectedSalary" numeric(18, 2) NULL,
|
||||
"CurrencyId" varchar(64) NULL,
|
||||
"SourceId" uuid NULL,
|
||||
"LinkedInUrl" varchar(512) NULL,
|
||||
"SkillIds" varchar(2048) NULL,
|
||||
"Photo" text NULL,
|
||||
"CvDocument" text NULL,
|
||||
"Notes" text NULL,
|
||||
"KvkkConsent" boolean NOT NULL,
|
||||
"KvkkConsentDate" timestamp NOT NULL,
|
||||
"KvkkConsentMethod" varchar(32) NOT NULL DEFAULT 'Written',
|
||||
"KvkkConsentDocument" text NULL,
|
||||
"UserId" uuid NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Candidate" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_Candidate_KvkkConsent') THEN
|
||||
ALTER TABLE public."Hr_T_Candidate" ADD CONSTRAINT "CK_Hr_T_Candidate_KvkkConsent" CHECK ("KvkkConsent" = TRUE);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_Candidate_Email" ON public."Hr_T_Candidate" ("TenantId", "Email") WHERE "IsDeleted" = FALSE;
|
||||
16
configs/seeds/postgres/object/Hr_T_CandidateSource.sql
Normal file
16
configs/seeds/postgres/object/Hr_T_CandidateSource.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_CandidateSource"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"Description" varchar(512) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_CandidateSource" PRIMARY KEY ("Id")
|
||||
);
|
||||
15
configs/seeds/postgres/object/Hr_T_Certificate.sql
Normal file
15
configs/seeds/postgres/object/Hr_T_Certificate.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Certificate"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(200) NOT NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Certificate" PRIMARY KEY ("Id")
|
||||
);
|
||||
64
configs/seeds/postgres/object/Hr_T_DevelopmentPlan.sql
Normal file
64
configs/seeds/postgres/object/Hr_T_DevelopmentPlan.sql
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DevelopmentPlan"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"EmployeeId" uuid NOT NULL,
|
||||
"ReviewId" uuid NULL,
|
||||
"SkillId" uuid NULL,
|
||||
"Title" varchar(256) NOT NULL,
|
||||
"ActionType" varchar(16) NOT NULL DEFAULT 'Training',
|
||||
"TrainingId" uuid NULL,
|
||||
"CertificateId" uuid NULL,
|
||||
"MentorId" uuid NULL,
|
||||
"StartDate" date NULL,
|
||||
"DueDate" date NULL,
|
||||
"Status" varchar(16) NOT NULL DEFAULT 'Planned',
|
||||
"Progress" integer NOT NULL DEFAULT 0,
|
||||
"Description" varchar(1024) NULL,
|
||||
"EmployeeNote" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DevelopmentPlan" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_DevelopmentPlan_ActionType') THEN
|
||||
ALTER TABLE public."Hr_T_DevelopmentPlan" ADD CONSTRAINT "CK_Hr_T_DevelopmentPlan_ActionType" CHECK ("ActionType" IN ('Training', 'Mentoring', 'Project', 'Reading', 'Certificate', 'Other'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_DevelopmentPlan_Status') THEN
|
||||
ALTER TABLE public."Hr_T_DevelopmentPlan" ADD CONSTRAINT "CK_Hr_T_DevelopmentPlan_Status" CHECK ("Status" IN ('Planned', 'InProgress', 'Completed', 'Cancelled'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_DevelopmentPlan_Progress') THEN
|
||||
ALTER TABLE public."Hr_T_DevelopmentPlan" ADD CONSTRAINT "CK_Hr_T_DevelopmentPlan_Progress" CHECK ("Progress" BETWEEN 0 AND 100);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_DevelopmentPlan_Dates') THEN
|
||||
ALTER TABLE public."Hr_T_DevelopmentPlan" ADD CONSTRAINT "CK_Hr_T_DevelopmentPlan_Dates" CHECK ("DueDate" IS NULL OR "StartDate" IS NULL OR "DueDate" >= "StartDate");
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_DevelopmentPlan_Employee" ON public."Hr_T_DevelopmentPlan" ("EmployeeId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_DevelopmentPlan_Review" ON public."Hr_T_DevelopmentPlan" ("ReviewId");
|
||||
25
configs/seeds/postgres/object/Hr_T_DisciplinaryCase.sql
Normal file
25
configs/seeds/postgres/object/Hr_T_DisciplinaryCase.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DisciplinaryCase"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"CaseNo" varchar(32) NULL,
|
||||
"UserId" uuid NOT NULL,
|
||||
"DepartmentId" uuid NULL,
|
||||
"JobPositionId" uuid NULL,
|
||||
"IncidentDate" date NOT NULL,
|
||||
"IncidentTime" timestamp NULL,
|
||||
"IncidentPlace" varchar(256) NULL,
|
||||
"Subject" varchar(256) NOT NULL,
|
||||
"IncidentDescription" text NOT NULL,
|
||||
"WitnessUserIds" text NULL,
|
||||
"ReportedById" uuid NULL,
|
||||
"Status" varchar(32) NOT NULL DEFAULT 'Open',
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DisciplinaryCase" PRIMARY KEY ("Id")
|
||||
);
|
||||
21
configs/seeds/postgres/object/Hr_T_DisciplinaryDecision.sql
Normal file
21
configs/seeds/postgres/object/Hr_T_DisciplinaryDecision.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DisciplinaryDecision"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"CaseId" uuid NOT NULL,
|
||||
"DecisionDate" date NOT NULL,
|
||||
"PenaltyTypeId" uuid NOT NULL,
|
||||
"EffectiveDate" date NULL,
|
||||
"NotificationDate" date NULL,
|
||||
"DeductionAmount" numeric(18, 4) NULL,
|
||||
"DecisionText" text NULL,
|
||||
"Status" varchar(32) NOT NULL DEFAULT 'Draft',
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DisciplinaryDecision" PRIMARY KEY ("Id")
|
||||
);
|
||||
19
configs/seeds/postgres/object/Hr_T_DisciplinaryDefense.sql
Normal file
19
configs/seeds/postgres/object/Hr_T_DisciplinaryDefense.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DisciplinaryDefense"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"CaseId" uuid NOT NULL,
|
||||
"RequestDate" date NOT NULL,
|
||||
"DueDate" date NOT NULL,
|
||||
"ResponseDate" date NULL,
|
||||
"DefenseText" text NULL,
|
||||
"Status" varchar(32) NOT NULL DEFAULT 'Requested',
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DisciplinaryDefense" PRIMARY KEY ("Id")
|
||||
);
|
||||
18
configs/seeds/postgres/object/Hr_T_DisciplinaryFile.sql
Normal file
18
configs/seeds/postgres/object/Hr_T_DisciplinaryFile.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DisciplinaryFile"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"CaseId" uuid NOT NULL,
|
||||
"DocumentTypeId" uuid NULL,
|
||||
"FileDate" date NOT NULL,
|
||||
"FileName" varchar(256) NOT NULL,
|
||||
"FileContent" text NOT NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DisciplinaryFile" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DisciplinaryPenaltyType"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"Severity" integer NOT NULL DEFAULT 1,
|
||||
"Description" varchar(512) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DisciplinaryPenaltyType" PRIMARY KEY ("Id")
|
||||
);
|
||||
20
configs/seeds/postgres/object/Hr_T_Doctor.sql
Normal file
20
configs/seeds/postgres/object/Hr_T_Doctor.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Doctor"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"FullName" varchar(200) NOT NULL,
|
||||
"DiplomaNo" varchar(50) NULL,
|
||||
"DiplomaDate" date NULL,
|
||||
"OhsCertificateNo" varchar(50) NULL,
|
||||
"OhsCertificateDate" date NULL,
|
||||
"IdentityNo" varchar(11) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Doctor" PRIMARY KEY ("Id")
|
||||
);
|
||||
17
configs/seeds/postgres/object/Hr_T_DocumentType.sql
Normal file
17
configs/seeds/postgres/object/Hr_T_DocumentType.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DocumentType"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"GroupName" varchar(50) NOT NULL,
|
||||
"Name" varchar(200) NOT NULL,
|
||||
"IsRequired" boolean NOT NULL DEFAULT FALSE,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DocumentType" PRIMARY KEY ("Id")
|
||||
);
|
||||
20
configs/seeds/postgres/object/Hr_T_Driver.sql
Normal file
20
configs/seeds/postgres/object/Hr_T_Driver.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Driver"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"UserId" uuid NOT NULL,
|
||||
"Vehicle1Id" uuid NULL,
|
||||
"Vehicle2Id" uuid NULL,
|
||||
"Vehicle3Id" uuid NULL,
|
||||
"Vehicle4Id" uuid NULL,
|
||||
"Vehicle5Id" uuid NULL,
|
||||
"TotalKm" integer NOT NULL DEFAULT 0,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Driver" PRIMARY KEY ("Id")
|
||||
);
|
||||
19
configs/seeds/postgres/object/Hr_T_DriverAccident.sql
Normal file
19
configs/seeds/postgres/object/Hr_T_DriverAccident.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DriverAccident"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"DriverId" uuid NOT NULL,
|
||||
"AccidentDate" date NOT NULL,
|
||||
"Description" varchar(512) NULL,
|
||||
"VehicleDocumentId" uuid NULL,
|
||||
"FileName" varchar(256) NULL,
|
||||
"FileContent" text NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DriverAccident" PRIMARY KEY ("Id")
|
||||
);
|
||||
17
configs/seeds/postgres/object/Hr_T_DriverDocument.sql
Normal file
17
configs/seeds/postgres/object/Hr_T_DriverDocument.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DriverDocument"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"DriverId" uuid NOT NULL,
|
||||
"VehicleDocumentId" uuid NOT NULL,
|
||||
"FileName" varchar(256) NOT NULL,
|
||||
"FileContent" text NOT NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DriverDocument" PRIMARY KEY ("Id")
|
||||
);
|
||||
20
configs/seeds/postgres/object/Hr_T_DriverPenalty.sql
Normal file
20
configs/seeds/postgres/object/Hr_T_DriverPenalty.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_DriverPenalty"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"DriverId" uuid NOT NULL,
|
||||
"PenaltyDate" date NOT NULL,
|
||||
"Amount" numeric(18, 4) NULL,
|
||||
"Description" varchar(512) NULL,
|
||||
"VehicleDocumentId" uuid NULL,
|
||||
"FileName" varchar(256) NULL,
|
||||
"FileContent" text NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_DriverPenalty" PRIMARY KEY ("Id")
|
||||
);
|
||||
17
configs/seeds/postgres/object/Hr_T_EvaluationCriterion.sql
Normal file
17
configs/seeds/postgres/object/Hr_T_EvaluationCriterion.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_EvaluationCriterion"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"Weight" numeric(5, 2) NOT NULL DEFAULT 1,
|
||||
"Description" varchar(512) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_EvaluationCriterion" PRIMARY KEY ("Id")
|
||||
);
|
||||
21
configs/seeds/postgres/object/Hr_T_EventLog.sql
Normal file
21
configs/seeds/postgres/object/Hr_T_EventLog.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_EventLog"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"BranchId" uuid NULL,
|
||||
"LogSource" varchar(32) NOT NULL,
|
||||
"UserId" uuid NOT NULL,
|
||||
"EventDate" date NOT NULL,
|
||||
"EventTime" varchar(5) NOT NULL,
|
||||
"Importance" integer NOT NULL DEFAULT 1,
|
||||
"ShiftId" uuid NULL,
|
||||
"Description" text NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_EventLog" PRIMARY KEY ("Id")
|
||||
);
|
||||
21
configs/seeds/postgres/object/Hr_T_ExpenseType.sql
Normal file
21
configs/seeds/postgres/object/Hr_T_ExpenseType.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_ExpenseType"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Code" varchar(32) NOT NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"DefaultAmount" numeric(18, 2) NULL,
|
||||
"CurrencyId" varchar(64) NULL,
|
||||
"IsTaxable" boolean NOT NULL DEFAULT FALSE,
|
||||
"IsSgkSubject" boolean NOT NULL DEFAULT FALSE,
|
||||
"RequiresReceipt" boolean NOT NULL DEFAULT FALSE,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_ExpenseType" PRIMARY KEY ("Id")
|
||||
);
|
||||
15
configs/seeds/postgres/object/Hr_T_Hospital.sql
Normal file
15
configs/seeds/postgres/object/Hr_T_Hospital.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Hospital"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(250) NOT NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Hospital" PRIMARY KEY ("Id")
|
||||
);
|
||||
15
configs/seeds/postgres/object/Hr_T_HospitalDepartment.sql
Normal file
15
configs/seeds/postgres/object/Hr_T_HospitalDepartment.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_HospitalDepartment"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(200) NOT NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_HospitalDepartment" PRIMARY KEY ("Id")
|
||||
);
|
||||
26
configs/seeds/postgres/object/Hr_T_Interview.sql
Normal file
26
configs/seeds/postgres/object/Hr_T_Interview.sql
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Interview"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"ApplicationId" uuid NOT NULL,
|
||||
"Title" varchar(256) NULL,
|
||||
"InterviewType" varchar(32) NOT NULL DEFAULT 'FaceToFace',
|
||||
"StartDate" timestamp NOT NULL,
|
||||
"EndDate" timestamp NOT NULL,
|
||||
"Location" varchar(256) NULL,
|
||||
"MeetingUrl" varchar(512) NULL,
|
||||
"InterviewerUserId" uuid NOT NULL,
|
||||
"Status" varchar(32) NOT NULL DEFAULT 'Planned',
|
||||
"Recommendation" varchar(32) NULL,
|
||||
"OverallScore" numeric(5, 2) NULL,
|
||||
"Notes" text NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Interview" PRIMARY KEY ("Id")
|
||||
);
|
||||
28
configs/seeds/postgres/object/Hr_T_InterviewScore.sql
Normal file
28
configs/seeds/postgres/object/Hr_T_InterviewScore.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_InterviewScore"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"InterviewId" uuid NOT NULL,
|
||||
"CriterionId" uuid NOT NULL,
|
||||
"Score" integer NOT NULL,
|
||||
"Comment" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_InterviewScore" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_InterviewScore_Score') THEN
|
||||
ALTER TABLE public."Hr_T_InterviewScore" ADD CONSTRAINT "CK_Hr_T_InterviewScore_Score" CHECK ("Score" BETWEEN 1 AND 5);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_InterviewScore_Criterion" ON public."Hr_T_InterviewScore" ("InterviewId", "CriterionId") WHERE "IsDeleted" = FALSE;
|
||||
29
configs/seeds/postgres/object/Hr_T_JobApplication.sql
Normal file
29
configs/seeds/postgres/object/Hr_T_JobApplication.sql
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_JobApplication"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"CandidateId" uuid NOT NULL,
|
||||
"JobPostingId" uuid NOT NULL,
|
||||
"CandidateName" varchar(256) NULL,
|
||||
"PostingTitle" varchar(256) NULL,
|
||||
"ApplicationDate" date NOT NULL,
|
||||
"SourceId" uuid NULL,
|
||||
"OwnerUserId" uuid NULL,
|
||||
"Stage" varchar(32) NOT NULL DEFAULT 'Applied',
|
||||
"RejectionReasonId" uuid NULL,
|
||||
"RejectionNote" varchar(1024) NULL,
|
||||
"AverageScore" numeric(5, 2) NULL,
|
||||
"SortOrder" integer NOT NULL DEFAULT 0,
|
||||
"Notes" text NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_JobApplication" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_JobApplication_CandidatePosting" ON public."Hr_T_JobApplication" ("TenantId", "CandidateId", "JobPostingId") WHERE "IsDeleted" = FALSE;
|
||||
43
configs/seeds/postgres/object/Hr_T_JobOffer.sql
Normal file
43
configs/seeds/postgres/object/Hr_T_JobOffer.sql
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_JobOffer"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"OfferNo" varchar(32) NULL,
|
||||
"ApplicationId" uuid NOT NULL,
|
||||
"CandidateName" varchar(256) NULL,
|
||||
"OfferDate" date NOT NULL,
|
||||
"ValidUntil" date NULL,
|
||||
"BranchId" uuid NOT NULL,
|
||||
"DepartmentId" uuid NOT NULL,
|
||||
"JobPositionId" uuid NOT NULL,
|
||||
"EmploymentType" varchar(32) NOT NULL DEFAULT 'FullTime',
|
||||
"WorkModel" varchar(32) NOT NULL DEFAULT 'Office',
|
||||
"StartDate" date NULL,
|
||||
"GrossSalary" numeric(18, 2) NOT NULL,
|
||||
"CurrencyId" varchar(64) NULL,
|
||||
"BenefitCodes" varchar(1024) NULL,
|
||||
"Status" varchar(32) NOT NULL DEFAULT 'Draft',
|
||||
"ResponseDate" date NULL,
|
||||
"Notes" text NULL,
|
||||
"ApprovalUser" varchar(256) NULL,
|
||||
"ApprovalStatus" varchar(128) NULL,
|
||||
"ApprovalDate" timestamp NULL,
|
||||
"ApprovalDescription" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_JobOffer" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_JobOffer_GrossSalary') THEN
|
||||
ALTER TABLE public."Hr_T_JobOffer" ADD CONSTRAINT "CK_Hr_T_JobOffer_GrossSalary" CHECK ("GrossSalary" >= 0);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
30
configs/seeds/postgres/object/Hr_T_JobPosting.sql
Normal file
30
configs/seeds/postgres/object/Hr_T_JobPosting.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_JobPosting"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"PostingNo" varchar(32) NULL,
|
||||
"StaffRequestId" uuid NULL,
|
||||
"Title" varchar(256) NOT NULL,
|
||||
"BranchId" uuid NOT NULL,
|
||||
"DepartmentId" uuid NOT NULL,
|
||||
"JobPositionId" uuid NOT NULL,
|
||||
"Headcount" integer NOT NULL DEFAULT 1,
|
||||
"EmploymentType" varchar(32) NOT NULL DEFAULT 'FullTime',
|
||||
"WorkModel" varchar(32) NOT NULL DEFAULT 'Office',
|
||||
"Location" varchar(256) NULL,
|
||||
"PublishDate" date NULL,
|
||||
"ClosingDate" date NULL,
|
||||
"SourceIds" varchar(1024) NULL,
|
||||
"Description" text NULL,
|
||||
"Requirements" text NULL,
|
||||
"Status" varchar(32) NOT NULL DEFAULT 'Draft',
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_JobPosting" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_LeaveBalanceAdjustment"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"EmployeeId" uuid NOT NULL,
|
||||
"AdjustmentDate" date NOT NULL,
|
||||
"Days" numeric(5, 1) NOT NULL,
|
||||
"Reason" varchar(512) NOT NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_LeaveBalanceAdjustment" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_LeaveBalanceAdjustment_Days') THEN
|
||||
ALTER TABLE public."Hr_T_LeaveBalanceAdjustment" ADD CONSTRAINT "CK_Hr_T_LeaveBalanceAdjustment_Days" CHECK ("Days" <> 0);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_LeaveBalanceAdjustment_Employee" ON public."Hr_T_LeaveBalanceAdjustment" ("TenantId", "EmployeeId") WHERE "IsDeleted" = FALSE;
|
||||
58
configs/seeds/postgres/object/Hr_T_LeaveRequest.sql
Normal file
58
configs/seeds/postgres/object/Hr_T_LeaveRequest.sql
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_LeaveRequest"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"RequestNo" varchar(32) NULL,
|
||||
"EmployeeId" uuid NOT NULL,
|
||||
"EmployeeName" varchar(256) NULL,
|
||||
"DepartmentId" uuid NULL,
|
||||
"JobPositionId" uuid NULL,
|
||||
"LeaveTypeId" uuid NOT NULL,
|
||||
"StartDate" timestamp NOT NULL,
|
||||
"EndDate" timestamp NOT NULL,
|
||||
"IsHalfDay" boolean NOT NULL DEFAULT FALSE,
|
||||
"HalfDayPart" varchar(16) NULL,
|
||||
"Days" numeric(5, 1) NOT NULL DEFAULT 0,
|
||||
"Description" varchar(1024) NULL,
|
||||
"DocumentFileName" varchar(256) NULL,
|
||||
"DocumentContent" text NULL,
|
||||
"BalanceWarning" varchar(512) NULL,
|
||||
"SickLeaveId" uuid NULL,
|
||||
"ManagerIds" varchar(1024) NULL,
|
||||
"ManagerUserNames" varchar(1024) NULL,
|
||||
"RequiresManagerApproval" integer NOT NULL DEFAULT 1,
|
||||
"ApprovalStatus" varchar(50) NULL,
|
||||
"ApprovalUser" varchar(1024) NULL,
|
||||
"ApprovalDate" timestamp NULL,
|
||||
"ApprovalDescription" varchar(512) NULL,
|
||||
"LastNotifiedStatus" varchar(50) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_LeaveRequest" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_LeaveRequest_Dates') THEN
|
||||
ALTER TABLE public."Hr_T_LeaveRequest" ADD CONSTRAINT "CK_Hr_T_LeaveRequest_Dates" CHECK ("EndDate" >= "StartDate");
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_LeaveRequest_HalfDayPart') THEN
|
||||
ALTER TABLE public."Hr_T_LeaveRequest" ADD CONSTRAINT "CK_Hr_T_LeaveRequest_HalfDayPart" CHECK ("HalfDayPart" IS NULL OR "HalfDayPart" IN ('Morning', 'Afternoon'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_LeaveRequest_Employee" ON public."Hr_T_LeaveRequest" ("TenantId", "EmployeeId", "StartDate") WHERE "IsDeleted" = FALSE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_LeaveRequest_SickLeave" ON public."Hr_T_LeaveRequest" ("SickLeaveId") WHERE "SickLeaveId" IS NOT NULL;
|
||||
31
configs/seeds/postgres/object/Hr_T_LeaveType.sql
Normal file
31
configs/seeds/postgres/object/Hr_T_LeaveType.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_LeaveType"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Code" varchar(32) NOT NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"Gender" varchar(16) NOT NULL DEFAULT 'All',
|
||||
"MaxDaysPerYear" integer NULL,
|
||||
"IsPaid" boolean NOT NULL DEFAULT TRUE,
|
||||
"DeductsFromAnnualLeave" boolean NOT NULL DEFAULT FALSE,
|
||||
"RequiresDocument" boolean NOT NULL DEFAULT FALSE,
|
||||
"AllowHalfDay" boolean NOT NULL DEFAULT FALSE,
|
||||
"BalanceExceedAction" varchar(16) NOT NULL DEFAULT 'Block',
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_LeaveType" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_LeaveType_BalanceExceedAction') THEN
|
||||
ALTER TABLE public."Hr_T_LeaveType" ADD CONSTRAINT "CK_Hr_T_LeaveType_BalanceExceedAction" CHECK ("BalanceExceedAction" IN ('Block', 'Warn'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
24
configs/seeds/postgres/object/Hr_T_PerformanceCompetency.sql
Normal file
24
configs/seeds/postgres/object/Hr_T_PerformanceCompetency.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformanceCompetency"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"ReviewId" uuid NOT NULL,
|
||||
"SkillId" uuid NOT NULL,
|
||||
"ExpectedLevelId" uuid NULL,
|
||||
"SelfLevelId" uuid NULL,
|
||||
"ManagerLevelId" uuid NULL,
|
||||
"Score" numeric(6, 2) NULL,
|
||||
"SelfComment" varchar(1024) NULL,
|
||||
"ManagerComment" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformanceCompetency" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_PerformanceCompetency_Skill" ON public."Hr_T_PerformanceCompetency" ("ReviewId", "SkillId") WHERE "IsDeleted" = FALSE;
|
||||
41
configs/seeds/postgres/object/Hr_T_PerformanceFeedback.sql
Normal file
41
configs/seeds/postgres/object/Hr_T_PerformanceFeedback.sql
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformanceFeedback"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"ReviewId" uuid NOT NULL,
|
||||
"ReviewerId" uuid NOT NULL,
|
||||
"Relation" varchar(16) NOT NULL DEFAULT 'Peer',
|
||||
"Status" varchar(16) NOT NULL DEFAULT 'Pending',
|
||||
"Score" numeric(6, 2) NULL,
|
||||
"Comment" varchar(2048) NULL,
|
||||
"SubmittedDate" timestamp NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformanceFeedback" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceFeedback_Relation') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceFeedback" ADD CONSTRAINT "CK_Hr_T_PerformanceFeedback_Relation" CHECK ("Relation" IN ('Peer', 'Subordinate', 'Superior', 'InternalCustomer'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceFeedback_Status') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceFeedback" ADD CONSTRAINT "CK_Hr_T_PerformanceFeedback_Status" CHECK ("Status" IN ('Pending', 'Submitted'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_PerformanceFeedback_Reviewer" ON public."Hr_T_PerformanceFeedback" ("ReviewId", "ReviewerId") WHERE "IsDeleted" = FALSE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_PerformanceFeedback_ReviewerId" ON public."Hr_T_PerformanceFeedback" ("ReviewerId");
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformanceFeedbackScore"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"FeedbackId" uuid NOT NULL,
|
||||
"SkillId" uuid NOT NULL,
|
||||
"LevelId" uuid NULL,
|
||||
"Comment" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformanceFeedbackScore" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_PerformanceFeedbackScore_Skill" ON public."Hr_T_PerformanceFeedbackScore" ("FeedbackId", "SkillId") WHERE "IsDeleted" = FALSE;
|
||||
60
configs/seeds/postgres/object/Hr_T_PerformanceGoal.sql
Normal file
60
configs/seeds/postgres/object/Hr_T_PerformanceGoal.sql
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformanceGoal"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"ReviewId" uuid NOT NULL,
|
||||
"GoalType" varchar(8) NOT NULL DEFAULT 'KPI',
|
||||
"Title" varchar(256) NOT NULL,
|
||||
"Description" varchar(1024) NULL,
|
||||
"Direction" varchar(16) NOT NULL DEFAULT 'Increasing',
|
||||
"Unit" varchar(32) NULL,
|
||||
"TargetValue" numeric(18, 2) NOT NULL,
|
||||
"ActualValue" numeric(18, 2) NULL,
|
||||
"Weight" numeric(5, 2) NOT NULL,
|
||||
"AchievementRate" numeric(6, 2) NULL,
|
||||
"SelfComment" varchar(1024) NULL,
|
||||
"ManagerComment" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformanceGoal" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceGoal_GoalType') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceGoal" ADD CONSTRAINT "CK_Hr_T_PerformanceGoal_GoalType" CHECK ("GoalType" IN ('KPI', 'OKR'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceGoal_Direction') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceGoal" ADD CONSTRAINT "CK_Hr_T_PerformanceGoal_Direction" CHECK ("Direction" IN ('Increasing', 'Decreasing'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceGoal_Weight') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceGoal" ADD CONSTRAINT "CK_Hr_T_PerformanceGoal_Weight" CHECK ("Weight" > 0 AND "Weight" <= 100);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceGoal_TargetValue') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceGoal" ADD CONSTRAINT "CK_Hr_T_PerformanceGoal_TargetValue" CHECK ("TargetValue" > 0);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_PerformanceGoal_Review" ON public."Hr_T_PerformanceGoal" ("ReviewId");
|
||||
48
configs/seeds/postgres/object/Hr_T_PerformancePeriod.sql
Normal file
48
configs/seeds/postgres/object/Hr_T_PerformancePeriod.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformancePeriod"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"StartDate" date NOT NULL,
|
||||
"EndDate" date NOT NULL,
|
||||
"SelfReviewDueDate" date NULL,
|
||||
"ManagerReviewDueDate" date NULL,
|
||||
"GoalWeight" numeric(5, 2) NOT NULL DEFAULT 60,
|
||||
"CompetencyWeight" numeric(5, 2) NOT NULL DEFAULT 40,
|
||||
"Is360Enabled" boolean NOT NULL DEFAULT FALSE,
|
||||
"Status" varchar(16) NOT NULL DEFAULT 'Planned',
|
||||
"Description" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformancePeriod" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformancePeriod_Dates') THEN
|
||||
ALTER TABLE public."Hr_T_PerformancePeriod" ADD CONSTRAINT "CK_Hr_T_PerformancePeriod_Dates" CHECK ("EndDate" >= "StartDate");
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformancePeriod_Weights') THEN
|
||||
ALTER TABLE public."Hr_T_PerformancePeriod" ADD CONSTRAINT "CK_Hr_T_PerformancePeriod_Weights" CHECK ("GoalWeight" >= 0 AND "CompetencyWeight" >= 0 AND "GoalWeight" + "CompetencyWeight" = 100);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformancePeriod_Status') THEN
|
||||
ALTER TABLE public."Hr_T_PerformancePeriod" ADD CONSTRAINT "CK_Hr_T_PerformancePeriod_Status" CHECK ("Status" IN ('Planned', 'Active', 'Calibration', 'Closed'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformancePeriodSkill"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"PeriodId" uuid NOT NULL,
|
||||
"SkillId" uuid NOT NULL,
|
||||
"ExpectedLevelId" uuid NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformancePeriodSkill" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_PerformancePeriodSkill_Skill" ON public."Hr_T_PerformancePeriodSkill" ("PeriodId", "SkillId") WHERE "IsDeleted" = FALSE;
|
||||
39
configs/seeds/postgres/object/Hr_T_PerformanceRating.sql
Normal file
39
configs/seeds/postgres/object/Hr_T_PerformanceRating.sql
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformanceRating"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"Level" integer NOT NULL,
|
||||
"MinScore" numeric(6, 2) NOT NULL,
|
||||
"MaxScore" numeric(6, 2) NOT NULL,
|
||||
"Color" varchar(16) NULL,
|
||||
"TargetDistribution" numeric(5, 2) NULL,
|
||||
"Description" varchar(512) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformanceRating" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceRating_Range') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceRating" ADD CONSTRAINT "CK_Hr_T_PerformanceRating_Range" CHECK ("MinScore" < "MaxScore");
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceRating_TargetDistribution') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceRating" ADD CONSTRAINT "CK_Hr_T_PerformanceRating_TargetDistribution" CHECK ("TargetDistribution" IS NULL OR "TargetDistribution" BETWEEN 0 AND 100);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_PerformanceRating_Level" ON public."Hr_T_PerformanceRating" ("TenantId", "Level") WHERE "IsDeleted" = FALSE;
|
||||
54
configs/seeds/postgres/object/Hr_T_PerformanceReview.sql
Normal file
54
configs/seeds/postgres/object/Hr_T_PerformanceReview.sql
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_PerformanceReview"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"PeriodId" uuid NOT NULL,
|
||||
"EmployeeId" uuid NOT NULL,
|
||||
"EmployeeName" varchar(128) NULL,
|
||||
"ManagerId" uuid NULL,
|
||||
"DepartmentId" uuid NULL,
|
||||
"JobPositionId" uuid NULL,
|
||||
"Status" varchar(16) NOT NULL DEFAULT 'GoalSetting',
|
||||
"LastStatus" varchar(16) NULL,
|
||||
"GoalScore" numeric(6, 2) NULL,
|
||||
"CompetencyScore" numeric(6, 2) NULL,
|
||||
"Feedback360Score" numeric(6, 2) NULL,
|
||||
"OverallScore" numeric(6, 2) NULL,
|
||||
"CalibratedScore" numeric(6, 2) NULL,
|
||||
"RatingId" uuid NULL,
|
||||
"SelfComment" varchar(2048) NULL,
|
||||
"ManagerComment" varchar(2048) NULL,
|
||||
"CalibrationNote" varchar(1024) NULL,
|
||||
"SelfSubmittedDate" timestamp NULL,
|
||||
"ManagerSubmittedDate" timestamp NULL,
|
||||
"CompletedDate" timestamp NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_PerformanceReview" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceReview_Status') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceReview" ADD CONSTRAINT "CK_Hr_T_PerformanceReview_Status" CHECK ("Status" IN ('GoalSetting', 'SelfReview', 'ManagerReview', 'Calibration', 'Completed'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CK_Hr_T_PerformanceReview_CalibratedScore') THEN
|
||||
ALTER TABLE public."Hr_T_PerformanceReview" ADD CONSTRAINT "CK_Hr_T_PerformanceReview_CalibratedScore" CHECK ("CalibratedScore" IS NULL OR "CalibratedScore" BETWEEN 0 AND 150);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "UX_Hr_T_PerformanceReview_Employee" ON public."Hr_T_PerformanceReview" ("PeriodId", "EmployeeId") WHERE "IsDeleted" = FALSE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_PerformanceReview_Manager" ON public."Hr_T_PerformanceReview" ("ManagerId");
|
||||
17
configs/seeds/postgres/object/Hr_T_RejectionReason.sql
Normal file
17
configs/seeds/postgres/object/Hr_T_RejectionReason.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_RejectionReason"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(128) NOT NULL,
|
||||
"RejectedBy" varchar(32) NOT NULL DEFAULT 'Company',
|
||||
"Description" varchar(512) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_RejectionReason" PRIMARY KEY ("Id")
|
||||
);
|
||||
22
configs/seeds/postgres/object/Hr_T_Shift.sql
Normal file
22
configs/seeds/postgres/object/Hr_T_Shift.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Shift"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"Name" varchar(64) NOT NULL,
|
||||
"StartTime" varchar(5) NULL,
|
||||
"EndTime" varchar(5) NULL,
|
||||
"BreakMinutes" integer NULL,
|
||||
"WorkingHours" numeric(5, 2) NULL,
|
||||
"IsNightShift" boolean NOT NULL DEFAULT FALSE,
|
||||
"LateToleranceMinutes" integer NULL,
|
||||
"EarlyLeaveToleranceMinutes" integer NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Shift" PRIMARY KEY ("Id")
|
||||
);
|
||||
34
configs/seeds/postgres/object/Hr_T_StaffRequest.sql
Normal file
34
configs/seeds/postgres/object/Hr_T_StaffRequest.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_StaffRequest"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"RequestNo" varchar(32) NULL,
|
||||
"RequestDate" date NOT NULL,
|
||||
"BranchId" uuid NOT NULL,
|
||||
"DepartmentId" uuid NOT NULL,
|
||||
"JobPositionId" uuid NOT NULL,
|
||||
"Headcount" integer NOT NULL DEFAULT 1,
|
||||
"EmploymentType" varchar(32) NOT NULL DEFAULT 'FullTime',
|
||||
"Reason" varchar(32) NOT NULL DEFAULT 'NewPosition',
|
||||
"ReplacedUserId" uuid NULL,
|
||||
"ExpectedStartDate" date NULL,
|
||||
"MinSalary" numeric(18, 2) NULL,
|
||||
"MaxSalary" numeric(18, 2) NULL,
|
||||
"CurrencyId" varchar(64) NULL,
|
||||
"Qualifications" text NULL,
|
||||
"Description" text NULL,
|
||||
"RequestedById" uuid NULL,
|
||||
"ApprovalUser" varchar(256) NULL,
|
||||
"ApprovalStatus" varchar(128) NULL,
|
||||
"ApprovalDate" timestamp NULL,
|
||||
"ApprovalDescription" varchar(1024) NULL,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_StaffRequest" PRIMARY KEY ("Id")
|
||||
);
|
||||
39
configs/seeds/postgres/object/Hr_T_Timesheet.sql
Normal file
39
configs/seeds/postgres/object/Hr_T_Timesheet.sql
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Timesheet"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"EmployeeId" uuid NOT NULL,
|
||||
"WorkDate" date NOT NULL,
|
||||
"PeriodYear" integer NOT NULL,
|
||||
"PeriodMonth" integer NOT NULL,
|
||||
"PeriodKey" varchar(48) NOT NULL,
|
||||
"ShiftId" uuid NULL,
|
||||
"DayType" varchar(16) NOT NULL DEFAULT 'Work',
|
||||
"LeaveTypeId" uuid NULL,
|
||||
"TrainingId" uuid NULL,
|
||||
"FirstIn" timestamp NULL,
|
||||
"LastOut" timestamp NULL,
|
||||
"BreakMinutes" integer NOT NULL DEFAULT 0,
|
||||
"NetMinutes" integer NOT NULL DEFAULT 0,
|
||||
"NetHours" numeric(6, 2) NOT NULL DEFAULT 0,
|
||||
"LateMinutes" integer NOT NULL DEFAULT 0,
|
||||
"EarlyLeaveMinutes" integer NOT NULL DEFAULT 0,
|
||||
"OvertimeHours" numeric(6, 2) NOT NULL DEFAULT 0,
|
||||
"IsManual" boolean NOT NULL DEFAULT FALSE,
|
||||
"IsIncomplete" boolean NOT NULL DEFAULT FALSE,
|
||||
"IsAssumed" boolean NOT NULL DEFAULT FALSE,
|
||||
"Note" varchar(512) NULL,
|
||||
"CalculatedAt" timestamp NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Timesheet" PRIMARY KEY ("Id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_Timesheet_EmployeeDate" ON public."Hr_T_Timesheet" ("EmployeeId", "WorkDate") INCLUDE ("IsDeleted", "IsManual");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IX_Hr_T_Timesheet_Period" ON public."Hr_T_Timesheet" ("TenantId", "PeriodYear", "PeriodMonth") INCLUDE ("EmployeeId", "DayType", "IsDeleted");
|
||||
21
configs/seeds/postgres/object/Hr_T_TimesheetPeriod.sql
Normal file
21
configs/seeds/postgres/object/Hr_T_TimesheetPeriod.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_TimesheetPeriod"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"PeriodYear" integer NOT NULL,
|
||||
"PeriodMonth" integer NOT NULL,
|
||||
"Status" varchar(16) NOT NULL DEFAULT 'Open',
|
||||
"CalculatedAt" timestamp NULL,
|
||||
"ClosedAt" timestamp NULL,
|
||||
"ClosedById" uuid NULL,
|
||||
"NightlyAt" timestamp NULL,
|
||||
"Note" varchar(512) NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_TimesheetPeriod" PRIMARY KEY ("Id")
|
||||
);
|
||||
35
configs/seeds/postgres/object/Hr_T_Training.sql
Normal file
35
configs/seeds/postgres/object/Hr_T_Training.sql
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_Training"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"TrainingTypeId" uuid NOT NULL,
|
||||
"TrainingSubjectId" uuid NOT NULL,
|
||||
"EntryDate" date NOT NULL,
|
||||
"BranchId" uuid NOT NULL,
|
||||
"StartDate" date NOT NULL,
|
||||
"EndDate" date NOT NULL,
|
||||
"StartTime" timestamp NULL,
|
||||
"EndTime" timestamp NULL,
|
||||
"TotalHours" numeric(18, 4) NULL,
|
||||
"TrainerName" varchar(200) NULL,
|
||||
"TrainingPlace" text NULL,
|
||||
"TrainingInstitution" text NULL,
|
||||
"Description" text NULL,
|
||||
"Status" varchar(16) NOT NULL DEFAULT 'Open',
|
||||
"SkillIds" text NULL,
|
||||
"CostPlace" numeric(18, 4) NULL DEFAULT 0,
|
||||
"CostTrainer" numeric(18, 4) NULL DEFAULT 0,
|
||||
"CostAccommodation" numeric(18, 4) NULL DEFAULT 0,
|
||||
"CostTransport" numeric(18, 4) NULL DEFAULT 0,
|
||||
"CostCatering" numeric(18, 4) NULL DEFAULT 0,
|
||||
"CostOther" numeric(18, 4) NULL DEFAULT 0,
|
||||
"CostTotal" numeric(18, 4) NULL DEFAULT 0,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_Training" PRIMARY KEY ("Id")
|
||||
);
|
||||
18
configs/seeds/postgres/object/Hr_T_TrainingFile.sql
Normal file
18
configs/seeds/postgres/object/Hr_T_TrainingFile.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_TrainingFile"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"TrainingId" uuid NOT NULL,
|
||||
"DocumentTypeId" uuid NULL,
|
||||
"FileDate" date NOT NULL,
|
||||
"FileName" varchar(256) NOT NULL,
|
||||
"FileContent" text NOT NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_TrainingFile" PRIMARY KEY ("Id")
|
||||
);
|
||||
23
configs/seeds/postgres/object/Hr_T_TrainingParticipant.sql
Normal file
23
configs/seeds/postgres/object/Hr_T_TrainingParticipant.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_TrainingParticipant"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"TrainingId" uuid NOT NULL,
|
||||
"UserId" uuid NOT NULL,
|
||||
"HireDate" date NULL,
|
||||
"DepartmentId" uuid NULL,
|
||||
"JobPositionId" uuid NULL,
|
||||
"AttendanceStatus" varchar(16) NOT NULL DEFAULT 'Attended',
|
||||
"EvaluationScore" numeric(18, 4) NULL,
|
||||
"ExamScore" numeric(18, 4) NULL,
|
||||
"LeaveDate" date NULL,
|
||||
"Note" text NULL,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_TrainingParticipant" PRIMARY KEY ("Id")
|
||||
);
|
||||
17
configs/seeds/postgres/object/Hr_T_TrainingSubject.sql
Normal file
17
configs/seeds/postgres/object/Hr_T_TrainingSubject.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE IF NOT EXISTS public."Hr_T_TrainingSubject"
|
||||
(
|
||||
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
"TenantId" uuid NULL,
|
||||
"TrainingTypeId" uuid NOT NULL,
|
||||
"Name" varchar(256) NOT NULL,
|
||||
"IsEvaluated" boolean NOT NULL DEFAULT TRUE,
|
||||
"IsActive" boolean NOT NULL DEFAULT TRUE,
|
||||
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
||||
"CreatorId" uuid NULL,
|
||||
"LastModificationTime" timestamp NULL,
|
||||
"LastModifierId" uuid NULL,
|
||||
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
|
||||
"DeletionTime" timestamp NULL,
|
||||
"DeleterId" uuid NULL,
|
||||
CONSTRAINT "PK_Hr_T_TrainingSubject" PRIMARY KEY ("Id")
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue