## Make Policy
Sigorta şirketinin, `PolicyRequested` aşamasındaki bir iş (job) için ürettiği poliçeyi platforma kaydeder. İstek başarılı olduğunda `PolicyMade` state geçişi tetiklenir; poliçe, prim ve vade planı kaydedilir, poliçe/bilgilendirme PDF'leri işe eklenir ve ilgili taraflara bildirim e-postası gönderilir.
---
### Uç Nokta (Endpoint)
`POST {{baseUrl}}/api/offline/jobs/:jobId/policies`
---
### Kullanım
Bu servis, platformdan gelen poliçeleşme talebinin sigorta şirketi tarafındaki karşılığıdır. Önerilen akış:
1. `Authentication Login` servisinden token alınır.
2. `GET /api/offline/jobs` ile `PolicyRequested` aşamasındaki işler listelenir.
3. `GET /api/offline/jobs/:jobId/request-details` ile işin `ProductId`, talep edilen ödeme türü ve taksit sayısı okunur.
4. Poliçe sigorta şirketi sisteminde üretilir.
5. Bu servis çağrılarak poliçe numarası, prim tutarları ve PDF'ler platforma iletilir.
6. `201 Created` dönerse iş `PolicyMade` aşamasına geçmiştir; sonraki adım zeyil akışıdır (`EndorsementRequested`).
> 💡 `grossPremium`, `netPremium` ve `commission` değerleri, daha önce teklifte sunulan prim ile **birebir aynı** olmalıdır; aksi hâlde `JobState.PremiumMismatch` alınır. (Acente bazında prim doğrulaması kapatılmışsa bu kontrol atlanır.)
> 💡 `premium` bloğu yalnızca platformda kayıtlı prim/vade planının **yerine geçmesi** isteniyorsa gönderilir. Gönderilmezse teklif aşamasında kaydedilen prim ve taksit planı kullanılır, taksit tarihleri poliçe başlangıç tarihinden itibaren aylık olarak yeniden üretilir.
> ⚠️ İş `PolicyRequested` aşamasında değilse state machine geçişe izin vermez ve istek `500 Internal Server Error` / `JobState.UnknownBusinessRule` ile sonuçlanır.
---
### Kimlik Doğrulama
Bu uç nokta geçerli bir JWT erişim token'ı gerektirir. Token, `Authentication Login` servisinden alınır ve `Authorization` başlığında gönderilir.
| Başlık | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `Authorization` | string | ✅ | `Bearer <token>` biçiminde erişim token'ı. |
| `Content-Type` | string | ✅ | `application/json` olmalıdır. |
Token'ın `InsuranceCompanyAPIUser`, `InsuranceCompanyAPIAdmin` veya `Supervizor` rollerinden en az birini ve `Create` yetkisini taşıması gerekir. `InsuranceCompanyAPIUser` yalnızca kendi üzerine atanmış işlerde işlem yapabilir; `InsuranceCompanyAPIAdmin` ve `Supervizor` şirket kapsamındaki tüm işlerde işlem yapabilir.
---
### İstek Parametreleri (Path Variables)
| Parametre | Tür | Zorunlu | Min | Max | Açıklama |
| --- | --- | --- | --- | --- | --- |
| `jobId` | integer | ✅ | 1 | 2147483647 | Poliçesi kaydedilecek işin benzersiz kimliği. |
> ⚠️ `jobId` yalnızca yoldan alınır. Gövdede `jobId` göndermek etkisizdir; sunucu bu alanı her zaman yol parametresiyle ezer.
---
### İstek Gövdesi
| Parametre | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `productId` | integer | ✅ | Poliçenin üretildiği ürünün kimliği. Sıfırdan büyük olmalıdır. İşin `request-details` yanıtındaki ürün kimliğiyle eşleşmelidir. |
| `policyNumber` | string | ✅ | Sigorta şirketinin verdiği poliçe numarası. |
| `renewalNo` | integer | ✅ | Poliçenin yenileme numarası. Sıfır veya daha büyük olmalıdır. |
| `policyStartDate` | string (date-time) | ✅ | Poliçe başlangıç tarihi. |
| `policyEndDate` | string (date-time) | ✅ | Poliçe bitiş tarihi. `policyStartDate` değerinden büyük olmalıdır. |
| `processingDate` | string (date-time) | ✅ | Poliçe tanzim tarihi. |
| `grossPremium` | number | ✅ | Poliçe brüt primi. Sıfırdan büyük olmalıdır. |
| `netPremium` | number | ✅ | Poliçe net primi. Sıfırdan büyük olmalıdır. |
| `commission` | number | ✅ | Poliçe komisyon tutarı. Sıfır veya daha büyük olmalıdır. |
| `currencyType` | integer | ✅ | Poliçe para birimi (`CurrencyType`). Geçerli değerler için [bknz.](https://docs.insurgateway.com/offline-constants#currency-type) |
| `exchangeRate` | number | ✅ | Poliçe tanzim anındaki döviz kuru. `currencyType` = TL ise **tam olarak `1`**, diğer para birimlerinde **1'den büyük** olmalıdır. |
| `isTransferCompleted` | boolean | — | Poliçenin transfer akışıyla kaydedilip kaydedilmediğini belirtir. Varsayılan `false`. |
| `message` | string | — | Poliçeye iliştirilen serbest not. En fazla 500 karakter. |
| `premium` | object | — | Prim ve vade planı bilgisi. Gönderilirse platformda kayıtlı prim yerine bu bilgi kullanılır. Bkz. `premium` nesnesi. |
| `policyPdf` | object | — | Poliçe PDF dokümanı. Bkz. `PolicyFileRequest` nesnesi. |
| `informationPdf` | object | — | Bilgilendirme formu PDF dokümanı. Bkz. `PolicyFileRequest` nesnesi. |
| `resultKeySets` | array | — | Sigorta şirketinin poliçeyle birlikte döndürdüğü sonuç alanları. Bkz. `resultKeySets` nesnesi. |
**`premium` nesnesi** (gönderilirse aşağıdaki alanların doğrulaması uygulanır):
| Parametre | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `paymentOptionType` | integer | ✅ | Ödeme seçeneği türü (`PaymentType`). Geçerli değerler için [bknz.](https://docs.insurgateway.com/offline-constants#payment-type) Platformun talep ettiği ödeme türüyle eşleşmelidir. |
| `currencyType` | integer | ✅ | Prim para birimi (`CurrencyType`). Geçerli değerler için [bknz.](https://docs.insurgateway.com/offline-constants#currency-type) |
| `installmentNumber` | integer | ✅ | Taksit sayısı. Sıfırdan büyük olmalı ve platformun talep ettiği taksit sayısıyla eşleşmelidir. |
| `grossPremium` | number | ✅ | Brüt prim. Sıfırdan büyük olmalıdır. Vade planındaki `paymentAmount` toplamına eşit olmalıdır. |
| `netPremium` | number | ✅ | Net prim. Sıfırdan büyük olmalıdır. |
| `commission` | number | ✅ | Komisyon tutarı. Sıfır veya daha büyük olmalı ve vade planındaki komisyon toplamından küçük olmamalıdır. |
| `premiumInstallmentDetails` | array | ⚠️ Koşullu | Vade planı. `installmentNumber` 1'den büyükse zorunludur ve boş olamaz. |
| `isInstallmentsEqual` | boolean | — | Taksitlerin eşit olup olmadığını belirtir. Varsayılan `false`. |
| `firstInstallmentRate` | integer | — | İlk taksit oranı. Gönderilirse 1–100 aralığında olmalıdır. |
| `exchangeRate` | number | — | Prim için döviz kuru. |
| `installmentExplanation` | string | — | Vade planı açıklaması. |
| `grossPremiumTL` | number | — | Kullanılmaz; gönderilse de yok sayılır. TL karşılıkları `grossPremium × exchangeRate` ile sunucuda hesaplanır. |
| `commissionTL` | number | — | Kullanılmaz; gönderilse de yok sayılır. |
**`premiumInstallmentDetails` nesnesi:**
| Parametre | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `installmentNumber` | integer | ✅ | Taksit planının toplam taksit sayısı. Sıfırdan büyük olmalıdır. |
| `installmentNo` | integer | ✅ | Taksitin sıra numarası. Sıfırdan büyük olmalıdır. |
| `paymentAmount` | number | ✅ | Taksit tutarı. Sıfırdan büyük olmalıdır. |
| `commission` | number | ✅ | Taksite ait komisyon tutarı. Sıfır veya daha büyük olmalıdır. |
| `paymentDate` | string (date-time) | ✅ | Taksitin ödeme tarihi. |
**`PolicyFileRequest` nesnesi** (`policyPdf` ve `informationPdf`):
| Parametre | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `fileName` | string | ✅ | Dosya adı. `.pdf` uzantısıyla bitmelidir. |
| `base64Content` | string | ✅ | Base64 kodlanmış dosya içeriği. Çözüldüğünde `%PDF` imzasıyla başlayan geçerli bir PDF olmalıdır. |
| `contentType` | string | — | Yok sayılır; sunucu içerik türünü her zaman `application/pdf` olarak atar. |
> ⚠️ Sunucu, kaydedilen dosyanın adını `{jobId}_{policyNumber}_{renewalNo}_{DokümanTipi}_{rastgele}` biçiminde yeniden üretir. Gönderdiğiniz `fileName` yalnızca uzantı için kullanılır.
**`resultKeySets` nesnesi:**
| Parametre | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| `id` | integer | — | Alanın (key) benzersiz kimliği. |
| `keyName` | string | — | Alanın adı. |
| `keyValue` | array (string) | — | Alana ait değer listesi. |
**Örnek İstek:**
```json
{
"productId": 41,
"policyNumber": "1002340019",
"renewalNo": 0,
"policyStartDate": "2026-08-15T00:00:00",
"policyEndDate": "2027-08-15T00:00:00",
"processingDate": "2026-08-10T00:00:00",
"grossPremium": 12500.75,
"netPremium": 10500.00,
"commission": 1250.00,
"currencyType": 1,
"exchangeRate": 1,
"isTransferCompleted": false,
"message": "Poliçe tanzim edilmiştir.",
"premium": {
"paymentOptionType": 1,
"currencyType": 1,
"isInstallmentsEqual": true,
"installmentNumber": 2,
"firstInstallmentRate": 50,
"grossPremium": 12500.75,
"netPremium": 10500.00,
"commission": 1250.00,
"exchangeRate": 1,
"installmentExplanation": "2 eşit taksit",
"premiumInstallmentDetails": [
{
"installmentNumber": 2,
"installmentNo": 1,
"paymentAmount": 6250.38,
"commission": 625.00,
"paymentDate": "2026-08-15T00:00:00"
},
{
"installmentNumber": 2,
"installmentNo": 2,
"paymentAmount": 6250.37,
"commission": 625.00,
"paymentDate": "2026-09-15T00:00:00"
}
]
},
"policyPdf": {
"fileName": "policy.pdf",
"base64Content": "JVBERi0xLjQKJeLjz9M..."
},
"informationPdf": {
"fileName": "information.pdf",
"base64Content": "JVBERi0xLjQKJeLjz9M..."
},
"resultKeySets": [
{ "id": 1, "keyName": "PolicyNumber", "keyValue": ["1002340019"] }
]
}
```
---
### İstek Gövdesi Şeması (JSON Schema)
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "CreatePolicyRequest",
"type": "object",
"required": [
"productId",
"policyNumber",
"renewalNo",
"policyStartDate",
"policyEndDate",
"processingDate",
"grossPremium",
"netPremium",
"commission",
"currencyType",
"exchangeRate"
],
"properties": {
"productId": {
"type": "integer",
"format": "int32",
"exclusiveMinimum": 0,
"maximum": 2147483647
},
"policyNumber": { "type": "string", "minLength": 1 },
"renewalNo": {
"type": "integer",
"format": "int32",
"minimum": 0,
"maximum": 2147483647
},
"policyStartDate": { "type": "string", "format": "date-time" },
"policyEndDate": {
"type": "string",
"format": "date-time",
"$comment": "policyStartDate değerinden büyük olmalıdır."
},
"processingDate": { "type": "string", "format": "date-time" },
"grossPremium": { "type": "number", "format": "decimal", "exclusiveMinimum": 0 },
"netPremium": { "type": "number", "format": "decimal", "exclusiveMinimum": 0 },
"commission": { "type": "number", "format": "decimal", "minimum": 0 },
"currencyType": {
"type": "integer",
"format": "int32",
"minimum": 1,
"maximum": 7,
"$comment": "CurrencyType — https://docs.insurgateway.com/offline-constants#currency-type"
},
"exchangeRate": { "type": "number", "format": "decimal", "exclusiveMinimum": 0 },
"isTransferCompleted": { "type": "boolean", "default": false },
"message": { "type": ["string", "null"], "maxLength": 500 },
"premium": { "$ref": "#/$defs/PremiumEntryRequest" },
"policyPdf": { "$ref": "#/$defs/PolicyFileRequest" },
"informationPdf": { "$ref": "#/$defs/PolicyFileRequest" },
"resultKeySets": {
"type": "array",
"items": { "$ref": "#/$defs/KeySetRequest" }
}
},
"allOf": [
{
"if": { "properties": { "currencyType": { "const": 1 } }, "required": ["currencyType"] },
"then": { "properties": { "exchangeRate": { "const": 1 } } },
"else": { "properties": { "exchangeRate": { "exclusiveMinimum": 1 } } }
}
],
"$defs": {
"PremiumEntryRequest": {
"type": "object",
"required": [
"paymentOptionType",
"currencyType",
"installmentNumber",
"grossPremium",
"netPremium",
"commission"
],
"properties": {
"paymentOptionType": {
"type": "integer",
"format": "int32",
"minimum": 1,
"maximum": 3,
"$comment": "PaymentType — https://docs.insurgateway.com/offline-constants#payment-type"
},
"currencyType": {
"type": "integer",
"format": "int32",
"minimum": 1,
"maximum": 7,
"$comment": "CurrencyType — https://docs.insurgateway.com/offline-constants#currency-type"
},
"isInstallmentsEqual": { "type": "boolean", "default": false },
"firstInstallmentRate": {
"type": ["integer", "null"],
"format": "int16",
"minimum": 1,
"maximum": 100
},
"installmentNumber": {
"type": "integer",
"format": "int16",
"exclusiveMinimum": 0,
"maximum": 32767
},
"grossPremium": { "type": "number", "format": "decimal", "exclusiveMinimum": 0 },
"netPremium": { "type": "number", "format": "decimal", "exclusiveMinimum": 0 },
"commission": { "type": "number", "format": "decimal", "minimum": 0 },
"grossPremiumTL": {
"type": ["number", "null"],
"format": "decimal",
"$comment": "Sunucu tarafında kullanılmaz."
},
"commissionTL": {
"type": ["number", "null"],
"format": "decimal",
"$comment": "Sunucu tarafında kullanılmaz."
},
"exchangeRate": { "type": ["number", "null"], "format": "decimal" },
"installmentExplanation": { "type": ["string", "null"] },
"premiumInstallmentDetails": {
"type": "array",
"items": { "$ref": "#/$defs/PremiumInstallmentDetailRequest" }
}
},
"allOf": [
{
"if": {
"properties": { "installmentNumber": { "exclusiveMinimum": 1 } },
"required": ["installmentNumber"]
},
"then": {
"required": ["premiumInstallmentDetails"],
"properties": { "premiumInstallmentDetails": { "minItems": 1 } }
}
}
]
},
"PremiumInstallmentDetailRequest": {
"type": "object",
"required": ["installmentNumber", "installmentNo", "paymentAmount", "commission", "paymentDate"],
"properties": {
"installmentNumber": {
"type": "integer",
"format": "int16",
"exclusiveMinimum": 0,
"maximum": 32767
},
"installmentNo": {
"type": "integer",
"format": "int16",
"exclusiveMinimum": 0,
"maximum": 32767
},
"paymentAmount": { "type": "number", "format": "decimal", "exclusiveMinimum": 0 },
"commission": { "type": "number", "format": "decimal", "minimum": 0 },
"paymentDate": { "type": "string", "format": "date-time" }
}
},
"PolicyFileRequest": {
"type": "object",
"required": ["fileName", "base64Content"],
"properties": {
"fileName": {
"type": "string",
"minLength": 5,
"pattern": "(?i)\\.pdf$"
},
"base64Content": {
"type": "string",
"minLength": 1,
"contentEncoding": "base64",
"contentMediaType": "application/pdf",
"$comment": "Çözüldüğünde %PDF imzasıyla başlamalıdır."
},
"contentType": {
"type": ["string", "null"],
"$comment": "Yok sayılır; sunucu application/pdf atar."
}
}
},
"KeySetRequest": {
"type": "object",
"properties": {
"id": { "type": "integer", "format": "int32" },
"keyName": { "type": ["string", "null"] },
"keyValue": { "type": ["array", "null"], "items": { "type": "string" } }
}
}
}
}
```
> 💡 Şemada `additionalProperties: false` bilinçli olarak yoktur: API bilinmeyen alanları yok sayar, isteği reddetmez.
---
### Başarılı Yanıt (201 Created)
Poliçe başarıyla kaydedildiğinde HTTP `201 Created` döner. **Yanıt gövdesi yoktur.**
Bu noktada sunucu tarafında gerçekleşenler:
| Tablo / Sistem | İşlem |
| --- | --- |
| `Policies` | Poliçe kaydı eklenir (`ProducedChannel = Offline`). |
| `Premiums` / `PremiumInstallmentDetails` | Prim ve vade planı yeniden yazılır. |
| `Jobs` | `CurrentStage = PolicyMade` olarak güncellenir. |
| `JobActions` | `PolicyMade` tipinde yeni aksiyon eklenir. |
| `OperationWatch` | `Success = true`, `Complete = true` işaretlenir. |
| Platform | Poliçe, prim ve aksiyon bilgisi platform servisine yazılır. |
| Doküman / E-posta | PDF'ler arka plan işiyle kaydedilir, bildirim e-postası gönderilir. |
---
### Hatalı Yanıt (400 Bad Request)
Hatalar `ApiResponse` sarmalayıcısıyla döner:
| Alan | Tür | Açıklama |
| --- | --- | --- |
| `Status` | boolean | Hata durumunda her zaman `false`. |
| `Code` | string | Hata kodu. Alan bazlı doğrulama hatalarında `Validation.General`, diğerlerinde ilgili iş kuralı kodu. |
| `Message` | string | Hata açıklaması. |
| `Response` | array \| null | Doğrulama hatalarında `{ Field, Message }` listesi; diğer hatalarda `null`. |
**Alan doğrulama hatası:**
```json
{
"Status": false,
"Code": "Validation.General",
"Message": "One or more validation errors occurred",
"Response": [
{ "Field": "PolicyNumber", "Message": "PolicyNumber is required." },
{ "Field": "ExchangeRate", "Message": "ExchangeRate must be 1 for TL." }
]
}
```
**İş kuralı hataları:**
| `Code` | Ne zaman oluşur |
| --- | --- |
| `JobState.PremiumMismatch` | Poliçe brüt/net/komisyon tutarı, teklifte sunulan prim ile aynı değil. |
| `JobState.InstallmentTotalMismatch` | `premiumInstallmentDetails` toplamı brüt prime eşit değil ya da komisyon toplamı komisyon primini aşıyor. |
| `JobState.PaymentOptionMismatch` | Gönderilen `paymentOptionType`, platformun talep ettiği ödeme türüyle eşleşmiyor. |
| `JobState.InstallmentNumberMismatch` | Gönderilen `installmentNumber`, teklifte sunulan taksit seçenekleri arasında yok. |
```json
{
"Status": false,
"Code": "JobState.PremiumMismatch",
"Message": "Premium values do not match between policy and proposal: Poliçe ve Teklif'te alınan bilgilerde brüt prim farkı oluşmuştur. Teklif ve Poliçe brüt priminin doğru olduğundan eminiz olunuz. Teklif Brüt Prim: 12.400,00 Poliçe Brüt Prim: 12.500,75",
"Response": null
}
```
---
### Hatalı Yanıt (401 Unauthorized)
Token gönderilmediğinde, geçersiz olduğunda veya süresi dolduğunda HTTP `401 Unauthorized` döner. **Yanıt gövdesi boştur.**
> 💡 Token'ın süresi dolduysa yanıtta `Token-Expired: true` başlığı bulunur. Bu durumda `POST /api/authentication/refreshtoken` ile yeni token alınmalıdır.
---
### Hatalı Yanıt (403 Forbidden)
| `Code` | Ne zaman oluşur |
| --- | --- |
| `OfflineUsers.InsuranceCompanyNotFound` | Token'da geçerli bir sigorta şirketi ataması yok. |
| `OfflineUsers.UserIdentityNotResolved` | Token'dan kullanıcı kimliği çözümlenemedi. |
| `OfflineJobs.JobInsuranceCompanyDenied` | İş, token'daki sigorta şirketi kapsamının dışında. |
| `OfflineJobs.JobOwnershipDenied` | `InsuranceCompanyAPIUser` rolündeki kullanıcı, kendisine ait olmayan bir işte işlem yapmaya çalıştı. |
```json
{
"Status": false,
"Code": "OfflineJobs.JobOwnershipDenied",
"Message": "You do not have permission to access this job.",
"Response": null
}
```
---
### Hatalı Yanıt (404 Not Found)
| `Code` | Ne zaman oluşur |
| --- | --- |
| `OfflineJobs.JobNotFound` | Verilen `jobId` ile eşleşen iş kaydı yok. |
| `JobState.OperationWatchNotFound` | Teklif ve ürün için poliçe operasyon kaydı bulunamadı. |
| `JobState.PremiumNotFound` | Teklifte, talep edilen taksit ve ödeme türüne ait prim kaydı bulunamadı (ve istekte `premium` gönderilmedi). |
| `JobState.PlatformUserNotFound` | Aksiyonu karşılayacak platform kullanıcısı bulunamadı. |
```json
{
"Status": false,
"Code": "OfflineJobs.JobNotFound",
"Message": "No job found for the requested JobId: 999999.",
"Response": null
}
```
---
### Hatalı Yanıt (409 Conflict)
| `Code` | Ne zaman oluşur |
| --- | --- |
| `JobState.PolicyMadeByOtherCompany` | Teklif başka bir sigorta şirketi tarafından poliçeleştirilmiş. |
| `JobState.PolicyAlreadyExists` | Aynı poliçe/zeyil/yenileme numarasıyla kayıt zaten var ya da ürün için poliçe bilgisi mevcut. |
```json
{
"Status": false,
"Code": "JobState.PolicyAlreadyExists",
"Message": "A policy already exists for job 12345.",
"Response": null
}
```
---
### Hatalı Yanıt (500 Internal Server Error)
| `Code` | Ne zaman oluşur |
| --- | --- |
| `JobState.PlatformServiceFailure` | Poliçe, prim ve taksit bilgileri platform servisine yazılamadı. |
| `JobState.UnknownBusinessRule` | Eşlenmemiş bir iş kuralı ihlali oluştu. İş `PolicyRequested` aşamasında değilken bu servis çağrıldığında da bu kod döner. |
| `JobState.TransitionError` | State geçişi sırasında beklenmeyen bir hata oluştu. |
```json
{
"Status": false,
"Code": "JobState.UnknownBusinessRule",
"Message": "An unknown business rule error occurred: 'Poliçe Yapıldı' aşamasından 'Poliçe Yapıldı' aşamasına geçiş yapılamaz. Bu aşamadan yalnızca şu aşamalara geçiş yapılabilir: Mesaj, Zeyil Talebi.",
"Response": null
}
```
Authentication
AuthorizationBearer
Bearer authentication of the form Bearer <token>, where token is your auth token.
Path parameters
jobIdintegerRequired
Poliçesi kaydedilecek işin benzersiz kimliği.
Request
This endpoint expects an object.