json2ubl-ts
v5.0.0
Published
JSON verilerinden resmi UBL-TR 2.1 XML belgeleri oluşturan kapsamlı TypeScript kütüphanesi
Maintainers
Readme
json2ubl-ts
JSON verilerinden resmi UBL-TR 2.1 XML belgeleri oluşturan kapsamlı TypeScript kütüphanesi.
GİB e-Fatura Paketi Schematron 20260701 ve UBL-TR Kod Listeleri v1.43 kurallarını hedefler (yürürlük 14.09.2026). İki katmanlı API sunar:
- SimpleInvoiceBuilder — Basit JSON gir, SDK tüm hesaplamaları otomatik yapsın
- InvoiceBuilder — Tam kontrol — tüm alanları kendin belirle
Üretilen belge tipleri
| Belge | Giriş katmanı | UBL kökü |
|---|---|---|
| e-Fatura / e-Arşiv Faturası | SimpleInvoiceBuilder · InvoiceBuilder · InvoiceSession | Invoice |
| e-İrsaliye | DespatchBuilder · DespatchSession | DespatchAdvice |
| e-SMM — Serbest Meslek Makbuzu (v5.0.0) | SimpleSelfEmployedVoucherBuilder · SelfEmployedVoucherSession | Invoice |
| e-MM — Müstahsil Makbuzu (v5.0.0) | SimpleProducerReceiptBuilder · ProducerReceiptSession | CreditNote |
Her belge tipi aynı üç katmanı taşır: Simple* (girdi eşleme) → hesaplayıcı →
seri hâle getirici; ve her biri kendi *Session sınıfıyla reaktif olarak da
sürülebilir.
Özellikler
- Otomatik Hesaplama: KDV, ÖTV (matrah artırıcı), Damga V. (matrah azaltıcı), tevkifat, iskonto
- Otomatik Tip/Profil Tespiti: Satır vergi durumlarına göre fatura tipi ve profilini tespit eder
- Dinamik Konfigürasyon: Runtime'da DB'den config güncellemesi — restart gerektirmez
- Reaktif Session (InvoiceSession): EventEmitter tabanlı canlı hesaplama ve UI state derivation
- 12 Fatura Profili: TEMELFATURA, TICARIFATURA, IHRACAT, YOLCUBERABERFATURA, OZELFATURA, KAMU, HKS, ENERJI, ILAC_TIBBICIHAZ, YATIRIMTESVIK, IDIS, EARSIVFATURA
- 20+ Fatura Tipi: SATIS, IADE, TEVKIFAT, ISTISNA, OZELMATRAH, IHRACKAYITLI, SGK, KOMISYONCU, KONAKLAMAVERGISI, TEKNOLOJIDESTEK, YTB* vb.
- 3 İrsaliye Profili: TEMELIRSALIYE, HKSIRSALIYE, IDISIRSALIYE
- e-SMM + e-MM (v5.0.0): Serbest meslek makbuzu ve müstahsil makbuzu, faturayla ORTAK gövde üzerinden → §9 · §10
- HKS masraf düzlemi (v5.0.0): Komisyoncu faturasının belge düzeyi masraf kalemleri (
documentAllowanceCharges, 18 kapalı kod) → §5 - 3 Katmanlı Validasyon: Ortak + Tip-bazlı + Profil-bazlı + Çapraz matris
- 53 Schematron Kuralı: Tam uyumluluk (UBL-TR Common + Main Schematron)
- Yazıyla Tutar Notu (v3.0.0): Her faturaya
YAZIYLA:#ÜÇ BİN İKİ YÜZ KIRK ÜÇ TÜRK LIRASI ELLİ ALTI KURUŞ#notu otomatik ve koşulsuz eklenir (biçim sahadan ölçüldü) → §7.1 - CJS + ESM + DTS çıktı
Kurulum
yarn add json2ubl-ts1. SimpleInvoiceBuilder — Basit Kullanım
En az veriyle fatura oluştur, SDK hesaplamaları ve XML'i otomatik üretsin:
import { SimpleInvoiceBuilder } from 'json2ubl-ts';
const builder = new SimpleInvoiceBuilder();
const { xml } = builder.build({
sender: {
taxNumber: '1234567890',
name: 'Demo Yazılım AŞ',
taxOffice: 'Büyük Mükellefler VD',
address: 'Levent Mah. No:42',
district: 'Beşiktaş',
city: 'İstanbul',
},
customer: {
taxNumber: '9876543210',
name: 'Alıcı Ticaret AŞ',
taxOffice: 'Ankara VD',
address: 'Kızılay',
district: 'Çankaya',
city: 'Ankara',
},
lines: [
{ name: 'Yazılım Lisansı', quantity: 1, price: 10000, kdvPercent: 20 },
{ name: 'Teknik Destek', quantity: 12, price: 500, unitCode: 'Ay', kdvPercent: 20 },
],
});
// SDK otomatik olarak:
// - KDV hesaplar (10000 × 20% = 2000 + 6000 × 20% = 1200)
// - VKN/TCKN ayrımı yapar (10 hane → VKN)
// - Tip tespit eder (SATIS)
// - Profil tespit eder (TICARIFATURA)
// - UUID üretir
// - Signature oluşturur
// - Tam UBL-TR XML çıktısı verirTevkifat Faturası
const { xml } = builder.build({
sender: { /* ... */ },
customer: { /* ... */ },
lines: [
{
name: 'İnşaat Taahhüt İşi',
quantity: 1,
price: 200000,
kdvPercent: 20,
withholdingTaxCode: '601', // Yapım işleri tevkifatı %40
},
],
});
// Otomatik: tip=TEVKIFAT, KDV=40000, Tevkifat=16000, Ödenecek=224000İade Faturası
const { xml } = builder.build({
sender: { /* ... */ },
customer: { /* ... */ },
type: 'IADE',
billingReference: {
id: 'ABC2025000000001',
issueDate: '2025-01-15',
},
lines: [
{ name: 'Ürün İade', quantity: 1, price: 10000, kdvPercent: 20 },
],
});
// Otomatik: profil=TEMELFATURA, BillingReference.DocumentTypeCode='IADE'Çoklu iade referansı (4.5.5+) — bir iade faturası birden çok asıl faturayı
referanslayabilir. GİB buna izin verir: XSD cac:BillingReference
maxOccurs="unbounded", şematron IADEInvioceCheck sayı değil ORAN denetler
(her referans 16 haneli ve DocumentTypeCode='IADE' olmalıdır).
const { xml } = builder.build({
type: 'IADE',
billingReferences: [
{ id: 'ABC2025000000001', issueDate: '2025-01-15' },
{ id: 'XYZ2025000000009', issueDate: '2025-02-03' },
],
// ...
});
// Çıktı: iki ayrı <cac:BillingReference> düğümüTekil billingReference alanı geriye uyum için KORUNUR (@deprecated): çoğul
alan verilmediğinde tek elemanlı liste gibi işlenir ve birebir aynı XML çıkar.
Yol adresleme mevcut çoğul alanların desenini izler:
SessionPaths.billingReferencesId(0) → 'billingReferences[0].id'.
İstisna / İhraç Kayıtlı / Özel Matrah
// İstisna
builder.build({
/* ... */
kdvExemptionCode: '301', // Mal ihracatı
lines: [{ name: 'İhraç Malı', quantity: 100, price: 500, kdvPercent: 0 }],
});
// İhraç Kayıtlı
builder.build({
/* ... */
kdvExemptionCode: '701',
lines: [{ name: 'Tekstil', quantity: 500, price: 200, kdvPercent: 0 }],
});
// Özel Matrah
builder.build({
/* ... */
type: 'OZELMATRAH',
kdvExemptionCode: '801',
ozelMatrah: { percent: 20, taxable: 50000, amount: 10000 },
lines: [{ name: '2. El Araç', quantity: 1, price: 250000, kdvPercent: 0 }],
});Dövizli Fatura
builder.build({
/* ... */
currencyCode: 'USD',
exchangeRate: 32.50,
lines: [{ name: 'Software License', quantity: 1, price: 5000, kdvPercent: 20 }],
});2. InvoiceSession — Reaktif Frontend Entegrasyonu (AR-10, v2.1.0+)
Frontend'de canlı veri girişi sırasında path-based update + field-level events + line-level FieldVisibility + validator pipeline sağlar:
import {
InvoiceSession,
SessionPaths,
// v2.2.4+ — public type re-export'ları (Library Öneri #5)
type Suggestion,
type SuggestionRule,
type SuggestionSeverity,
type PathErrorPayload,
type PathErrorCode,
type LineFieldVisibility,
// v2.2.5+ — UBL TR-Identifier scheme literal union (Library Öneri #7)
type PartyIdentificationSchemeId,
PARTY_IDENTIFICATION_SCHEME_IDS,
} from 'json2ubl-ts';
const session = new InvoiceSession();
// ── Path-based update (TEK MUTATE GATEWAY, AR-10) ──
session.update(SessionPaths.senderTaxNumber, '1234567890');
session.update(SessionPaths.senderName, 'Firma A.Ş.');
session.update(SessionPaths.customerTaxNumber, '9876543210');
session.addLine({ name: 'Ürün A', quantity: 10, price: 100, kdvPercent: 20 });
session.update(SessionPaths.lineKdvPercent(0), 18); // tek alan, line-level
// Tip değiştir → profil auto-resolve (IADE → TEMELFATURA otomatik)
session.update(SessionPaths.type, 'IADE');
// Liability değiştir → profil auto-resolve (earchive → EARSIVFATURA otomatik)
session.update(SessionPaths.liability, 'earchive');
// Çift indeks (line.taxes[ti])
session.update(SessionPaths.lineTaxCode(0, 0), '0071');
session.update(SessionPaths.lineTaxPercent(0, 0), 25);
// Party identifications (v2.2.1+) — IDIS / KAMU / HKS profilleri için kritik
session.update(SessionPaths.senderIdentificationSchemeId(0), 'MERSISNO');
session.update(SessionPaths.senderIdentificationValue(0), '0123456789012345');
session.update(SessionPaths.buyerCustomerIdentificationSchemeId(0), 'MUSTERINO'); // KAMU B-83
session.update(SessionPaths.buyerCustomerIdentificationValue(0), 'MUS-9876');
// Additional documents attachment (v2.2.6+, Library Öneri #9) — file upload pattern
session.update(SessionPaths.additionalDocumentId(0), 'DOC-001');
session.update(SessionPaths.additionalDocumentAttachmentFilename(0), 'fatura.pdf');
session.update(SessionPaths.additionalDocumentAttachmentMimeCode(0), 'application/pdf');
session.update(SessionPaths.additionalDocumentAttachmentData(0), '<base64>'); // FileReader sonucu
session.update(SessionPaths.additionalDocumentAttachmentEncodingCode(0), 'Base64');
// Composite alanı temizleme (v2.2.1+) — v1.x setX(undefined) karşılığı
session.unset('billingReference'); // tüm composite kaldırılır
session.unset('paymentMeans');
session.unset('liability'); // session-level state temizlenir
session.unset('despatchReferences'); // array composite (v2.2.3+)
session.unset('additionalDocuments'); // array composite (v2.2.3+)
// Identifications splice/replace API (v2.2.3+) — KAMU MUSTERINO / IDIS SEVKIYATNO
// gibi ekle-sil akışında index kaydırma için kritik (path-based update yapamaz).
session.setIdentifications('sender', [
{ schemeId: 'MERSISNO', value: '0123456789012345' },
{ schemeId: 'KUNYENO', value: 'K-001' },
]);
session.removeIdentification('customer', 0); // index 0'ı sil, sonrakileri kaydır
session.setIdentifications('sender', undefined); // tüm dizi silinir
// XML üret
const xml = session.buildXml();Event Dinleme (React/Vue/Svelte state güncellemeleri)
// 1. Field-level events (granüler, ilk emit)
session.on('field-changed', ({ path, value, previousValue, requestedValue, forcedReason }) => {
// path: 'sender.taxNumber' | 'lines[0].kdvPercent' vb.
// requestedValue + forcedReason: D-12 force durumunda dolar (örn. isExport=true tip ISTISNA force)
});
// 1b. Public tipli event listener'lar (v2.2.4+ — Suggestion / PathErrorPayload import'a açık)
session.on('suggestion', (suggestions: Suggestion[]) => {
// her öneri: { ruleId, path, value, reason, severity }
showSuggestionPanel(suggestions);
});
session.on('path-error', (err: PathErrorPayload) => {
// err.code: PathErrorCode literal union (INVALID_PATH | UNKNOWN_PATH | ...)
showError(err.code, err.reason);
});
session.on('field-activated', ({ path, reason }) => {
// path: 'fields.showWithholdingTaxSelector' (visibility false → true)
ui.show(path);
});
session.on('field-deactivated', ({ path, reason }) => ui.hide(path));
session.on('line-field-changed', ({ lineIndex, path, field, value }) => {
// line-level update'ler için ek granüler event
});
// 2. Path validation reddi (D-Seçenek B)
session.on('path-error', ({ code, path, reason, requestedValue }) => {
// code: 'INVALID_PATH' | 'READ_ONLY_PATH' | 'UNKNOWN_PATH' | 'INDEX_OUT_OF_BOUNDS'
// | 'PROFILE_EXPORT_MISMATCH' | 'PROFILE_LIABILITY_MISMATCH' | 'LIABILITY_LOCKED_BY_EXPORT'
showError(code, reason);
});
// 3. UI state snapshot
session.on('ui-state-changed', (state) => {
// state.fields: doc-level FieldVisibility (18 boolean)
// state.lineFields[i]: line-level LineFieldVisibility (10 boolean per line)
// state.allowedProfiles, state.allowedTypes, state.availableExemptions
setUIState(state);
});
// 4. Validation
session.on('warnings', (warnings: ValidationWarning[]) => showWarnings(warnings));
session.on('validation-error', (errors: ValidationError[]) => debugLog(errors)); // raw stream
// 5. Snapshot events (geriye uyumlu)
session.on('type-changed', ({ type, profile }) => {});
session.on('profile-changed', ({ profile, type }) => {});
session.on('liability-changed', ({ liability }) => {});
session.on('calculated', (calc) => {});
session.on('changed', (input) => {});
// 6. Runtime exception (calculate throw vb.) — path-related için 'path-error' kullan
session.on('error', (err: Error) => {});Session API Referansı (v2.1.0)
class InvoiceSession extends EventEmitter {
// Constructor
constructor(options?: {
initialInput?: Partial<SimpleInvoiceInput>;
isExport?: boolean; // readonly, M10 kontratı
liability?: 'einvoice' | 'earchive';
autoCalculate?: boolean;
allowReducedKdvRate?: boolean; // M4 / B-78.1 opt-in
});
// Getter'lar
get input(): SimpleInvoiceInput;
get calculation(): CalculatedDocument | null;
get uiState(): InvoiceUIState; // .fields + .lineFields[]
get fields(): FieldVisibility;
get warnings(): ValidationWarning[];
get liability(): CustomerLiability | undefined;
get isExport(): boolean;
// ✨ TEK MUTATE GATEWAY (AR-10) ✨
update<P extends keyof SessionPathMap>(path: P, value: SessionPathMap[P]): void;
// Composite/scope reset (v2.2.1+) — v1.x setX(undefined) semantik karşılığı
unset(scope: UnsetScope): void;
// UnsetScope = 'billingReference' | 'paymentMeans' | 'ozelMatrah' | 'sgk'
// | 'invoicePeriod' | 'buyerCustomer' | 'taxRepresentativeParty'
// | 'eArchiveInfo' | 'onlineSale' | 'orderReference' | 'liability'
// | 'despatchReferences' | 'additionalDocuments' // v2.2.3+
// Identifications splice/replace API (v2.2.3+) — Library Öneri #4
removeIdentification(party: IdentificationParty, index: number): void;
setIdentifications(party: IdentificationParty,
identifications: SimplePartyIdentification[] | undefined): void;
// IdentificationParty = 'sender' | 'customer' | 'buyerCustomer'
// Line CRUD (path-based değil — array operations)
addLine(line: SimpleLineInput): void;
updateLine(index: number, updates: Partial<SimpleLineInput>): void;
removeLine(index: number): void;
setLines(lines: SimpleLineInput[]): void;
// Sorgulama (Mimsoft form dropdown'ları için kritik)
getAllowedProfiles(type?: string): string[];
getAllowedTypes(profile?: string): string[];
getAvailableExemptions(): ExemptionDefinition[];
getAvailableBillingDocumentTypeCodes(): { code: string; label: string; forced: boolean }[];
// Çıktı
calculate(): CalculatedDocument | null;
validate(): ValidationWarning[];
toInvoiceInput(): InvoiceInput; // D-3 reference equality cache
buildXml(options?: { validationLevel?: 'none' | 'basic' | 'strict' }): string;
}Path-based Update — Geçiş (v2.0.0 → v2.1.0)
| v2.0.0 (kaldırıldı) | v2.1.0 (yeni) |
|---|---|
| setSender(party) | update(SessionPaths.senderTaxNumber, x) + ... |
| setType('TEVKIFAT') | update(SessionPaths.type, 'TEVKIFAT') |
| setProfile('IHRACAT') | constructor { isExport: true } + getAllowedProfiles() |
| setLiability('earchive') | update(SessionPaths.liability, 'earchive') |
| setCurrency('USD', 32) | update(SessionPaths.currencyCode, 'USD') + update(SessionPaths.exchangeRate, 32) |
| setKdvExemptionCode('351') | update(SessionPaths.kdvExemptionCode, '351') |
| setBillingReference(ref) | update(SessionPaths.billingReferenceId, ref.id) + ... |
| setBillingReference(undefined) | unset('billingReference') (v2.2.1+) |
| setPaymentMeans(pm) | update(SessionPaths.paymentMeansMeansCode, '1') + ... |
| setPaymentMeans(undefined) | unset('paymentMeans') (v2.2.1+) |
| setDespatchReferences(undefined) | unset('despatchReferences') (v2.2.3+) |
| setAdditionalDocuments(undefined) | unset('additionalDocuments') (v2.2.3+) |
| setSenderIdentifications(ids) | setIdentifications('sender', ids) (v2.2.3+) |
| removeSenderIdentification(i) | removeIdentification('sender', i) (v2.2.3+) |
| setInput(full) / patchInput(patch) | constructor { initialInput } veya path-based update sequence |
Migration disiplini: Mimsoft form akışı tek mutate noktasından (update) geçer; SessionPaths map TypeScript autocomplete + compile-time tip kontrolü sağlar. Composite alanları temizlemek için unset(scope) (v2.2.1+).
LineFieldVisibility (Line-level UI, AR-10)
interface LineFieldVisibility {
showKdvExemptionCodeSelector: boolean; // line.kdvPercent === 0 + non-self-exemption
showWithholdingTaxSelector: boolean; // WITHHOLDING_ALLOWED_TYPES (TEVKIFAT/YTBTEVKIFAT/IADE/YTBIADE/SGK/SARJ/SARJANLIK)
showWithholdingPercentInput: boolean; // withholdingTaxCode === '650' (dinamik)
showLineDelivery: boolean; // IHRACAT veya IHRACKAYITLI
showCommodityClassification: boolean; // IHRACKAYITLI + line.kdvExemptionCode === '702'
showAlicidibsatirkod: boolean; // IHRACKAYITLI + 702
showAdditionalItemIdentifications: boolean; // TEKNOLOJIDESTEK / ILACTIBBI / IDIS
showItemClassificationCode: boolean; // YATIRIMTESVIK harcama tipi
showProductTraceId: boolean; // YATIRIMTESVIK + itemClassificationCode === '01'
showSerialId: boolean; // YATIRIMTESVIK Kod 01 makine
}
// Erişim:
session.uiState.lineFields[lineIndex].showCommodityClassification // booleanLiability ve isExport Davranışı (M10 kontratı)
isExport: constructor-only, readonly.update('isExport', x)→path-error(READ_ONLY_PATH).isExport=truesession'da:- profil otomatik
IHRACAT, tip otomatikISTISNA(M2 identity). update('type', 'SATIS')→field-changedpayload{ value: 'ISTISNA', requestedValue: 'SATIS', forcedReason: 'isExport=true' }(D-12).update('liability', x)→path-error(LIABILITY_LOCKED_BY_EXPORT), state değişmez.
- profil otomatik
liabilitypath-based:update(SessionPaths.liability, 'einvoice' | 'earchive' | undefined). Profile auto-resolve (örn.earchive+TICARIFATURA→EARSIVFATURA).- Profile constraint:
update('profile', 'TICARIFATURA')liability='earchive'ise →path-error(PROFILE_LIABILITY_MISMATCH).
React Hook Örneği (Mimsoft)
function useInvoiceSession(initialInput?: Partial<SimpleInvoiceInput>) {
const session = useMemo(() => new InvoiceSession({ initialInput }), []);
const [uiState, setUIState] = useState(session.uiState);
useEffect(() => {
session.on('ui-state-changed', setUIState);
return () => session.off('ui-state-changed', setUIState);
}, [session]);
return { session, uiState };
}
// Form bileşeni
const { session, uiState } = useInvoiceSession();
<input onChange={e => session.update(SessionPaths.senderTaxNumber, e.target.value)} />
{uiState.fields.showWithholdingTaxSelector && <WithholdingDropdown />}
{uiState.lineFields[0]?.showCommodityClassification && <GtipInput />}2.X SuggestionEngine — Advisory Öneriler (AR-10 Faz 2, v2.2.0+)
Faz 2 ile birlikte session, validator-error'lardan ayrı bir advisory kanal sunar. Validator "bu olmadan XML üretilmez" derken, suggestion "bu varsayılanı seçmek istemez misin?" der. Aynı path için ikisi paralel emit edilebilir; UI iki mesajı yan yana gösterir (kırmızı hata + mavi öneri).
suggestion event:
session.on('suggestion', (suggestions) => {
// suggestions: Suggestion[] — yeni veya değişmiş öneriler (batch payload)
for (const s of suggestions) {
showAdvisoryHint({
path: s.path, // 'lines[0].kdvExemptionCode'
value: s.value, // '351'
reason: s.reason, // Türkçe tooltip
severity: s.severity, // 'recommended' | 'optional'
ruleId: s.ruleId, // 'kdv/zero-suggest-351'
label: s.displayLabel, // '351 — KDV İstisna' (opsiyonel)
});
}
});Diff semantics: Boş diff (added=0 && changed=0) → emit YOK. Aynı state ardışık validate() çağrıları suggestion event'i tekrarlamaz. Primary key ${ruleId}::${path} — value/reason/severity değişimi changed olarak emit edilir.
Kural kapsamı (v2.2.0): 23 kural — KDV (7), Tevkifat (5), IHRACKAYITLI (3), YATIRIMTESVIK (4), Delivery (3), Misc (2). Domain bazlı src/calculator/suggestion-rules/ altında.
Apply pattern:
session.on('suggestion', (suggestions) => {
// Kullanıcı "Apply" tıklarsa
for (const s of suggestions) {
if (userApproved(s)) {
session.update(s.path as any, s.value);
}
}
});Performance: 100 satır × 23 kural senaryosunda suggestion engine ~0.01ms (15ms threshold × 1000 altı). Toplam pipeline ~0.14ms. Detay: audit/sprint-08i-implementation-log.md.
3. ConfigManager — Dinamik Konfigürasyon
Vergi, tevkifat, istisna, birim ve para birimi tanımları statik embed edilmiştir. Uygulama çalışırken DB'den güncel verilerle override edilebilir, restart gerekmez:
import { configManager } from 'json2ubl-ts';
// ── 1. Başlangıçta DB verisiyle initialize ──
const dbTaxes = await db.query('SELECT * FROM taxes');
const dbWithholdings = await db.query('SELECT * FROM withholding_taxes');
const dbExemptions = await db.query('SELECT * FROM exemptions');
configManager.initialize({
taxes: dbTaxes,
withholdingTaxes: dbWithholdings,
exemptions: dbExemptions,
});
// ── 2. Runtime güncelleme (DB trigger sonrası) ──
// Örn: Yeni tevkifat kodu eklendi
configManager.updateWithholdingTaxes(newWithholdings);
// ── 3. Config değişiklik event'i dinle ──
configManager.on('config:taxes-updated', (taxes) => {
console.log(`Vergi tanımları güncellendi: ${taxes.length} kayıt`);
});
configManager.on('config:all-updated', () => {
// Tüm session'ları yeniden hesapla
activeSessions.forEach(s => s.calculate());
});
// ── 4. Snapshot al (frontend'e gönder) ──
const snap = configManager.snapshot();
// { version: 3, taxes: [...], withholdingTaxes: [...], exemptions: [...], units: [...], currencies: [...] }
// ── 5. Fabrika ayarlarına dön ──
configManager.reset();ConfigManager API
class ConfigManager extends EventEmitter {
// Initialization
initialize(options: ConfigInitOptions): void;
reset(): void;
get isInitialized(): boolean;
get version(): number;
// Runtime Update
updateTaxes(taxes: TaxDefinition[]): void;
updateWithholdingTaxes(wt: WithholdingTaxDefinition[]): void;
updateExemptions(ex: ExemptionDefinition[]): void;
updateUnits(units: UnitDefinition[]): void;
updateCurrencies(currencies: CurrencyDefinition[]): void;
updateAll(options: ConfigInitOptions): void;
// Okuma
getTax(code: string): TaxDefinition | undefined;
getWithholdingTax(code: string): WithholdingTaxDefinition | undefined;
getExemption(code: string): ExemptionDefinition | undefined;
resolveUnitCode(input: string): string;
getCurrency(code: string): CurrencyDefinition | undefined;
// Listeler
get taxes(): ReadonlyArray<TaxDefinition>;
get withholdingTaxes(): ReadonlyArray<WithholdingTaxDefinition>;
get exemptions(): ReadonlyArray<ExemptionDefinition>;
get units(): ReadonlyArray<UnitDefinition>;
get currencies(): ReadonlyArray<CurrencyDefinition>;
// Snapshot
snapshot(): ConfigInitOptions & { version: number };
// Events
on(event: 'config:initialized', handler: () => void): this;
on(event: 'config:taxes-updated', handler: (taxes: TaxDefinition[]) => void): this;
on(event: 'config:all-updated', handler: () => void): this;
}4. InvoiceBuilder — Tam Kontrol (Düşük Seviye)
Tüm alanları kendin belirle, SDK sadece XML serialize etsin:
import { InvoiceBuilder, InvoiceProfileId, InvoiceTypeCode } from 'json2ubl-ts';
const builder = new InvoiceBuilder({ validationLevel: 'strict' });
const xml = builder.build({
id: 'ABC2024000000001',
uuid: '12345678-1234-1234-1234-123456789012',
profileId: InvoiceProfileId.TEMELFATURA,
invoiceTypeCode: InvoiceTypeCode.SATIS,
issueDate: '2024-01-15',
currencyCode: 'TRY',
supplier: {
vknTckn: '1234567890',
taxIdType: 'VKN',
name: 'Satıcı Firma A.Ş.',
cityName: 'İstanbul',
country: 'Türkiye',
taxOffice: 'Beyoğlu VD',
},
customer: {
vknTckn: '12345678901',
taxIdType: 'TCKN',
firstName: 'Ahmet',
familyName: 'Yılmaz',
cityName: 'Ankara',
country: 'Türkiye',
},
taxTotals: [{
taxAmount: 2000,
taxSubtotals: [{
taxableAmount: 10000,
taxAmount: 2000,
percent: 20,
taxTypeCode: '0015',
taxTypeName: 'KDV',
}],
}],
legalMonetaryTotal: {
lineExtensionAmount: 10000,
taxExclusiveAmount: 10000,
taxInclusiveAmount: 12000,
payableAmount: 12000,
},
lines: [{
id: '1',
invoicedQuantity: 1,
unitCode: 'C62',
lineExtensionAmount: 10000,
taxTotal: {
taxAmount: 2000,
taxSubtotals: [{ taxableAmount: 10000, taxAmount: 2000, percent: 20, taxTypeCode: '0015' }],
},
item: { name: 'Yazılım Lisansı' },
price: { priceAmount: 10000 },
}],
});5. Profil × Tip Uyumluluk Matrisi
Bu tablo bir SEÇİM listesidir — "GİB'in tanıdığı tipler" listesi değil.
PROFILE_TYPE_MATRIX(src/ubltr/vocabulary/constants.ts) tek kaynaktır; üretim kapısıvalidateCrossMatrix. Bir tipin burada olmaması, o tipin OKUNAMAYACAĞI anlamına gelmez (bkz.TEVKIFATIADEnotu).
| Profil | İzin Verilen Tipler | |--------|-------------------| | TEMELFATURA | SATIS, IADE, TEVKIFAT, ISTISNA, OZELMATRAH, IHRACKAYITLI, SGK, KOMISYONCU, KONAKLAMAVERGISI | | TICARIFATURA | SATIS, TEVKIFAT, ISTISNA, OZELMATRAH, IHRACKAYITLI, SGK, KOMISYONCU, KONAKLAMAVERGISI | | EARSIVFATURA | Tüm standart + TEKNOLOJIDESTEK + YTBSATIS/YTBIADE/YTBISTISNA/YTBTEVKIFAT + HKSSATIS/HKSKOMISYONCU | | IHRACAT | ISTISNA | | YOLCUBERABERFATURA | ISTISNA | | OZELFATURA | ISTISNA | | KAMU | SATIS, IADE, TEVKIFAT, ISTISNA, OZELMATRAH, IHRACKAYITLI, SGK, KOMISYONCU, KONAKLAMAVERGISI | | HKS | SATIS, ISTISNA, TEVKIFAT, KOMISYONCU | | ILAC_TIBBICIHAZ | SATIS, ISTISNA, TEVKIFAT, IADE, IHRACKAYITLI | | YATIRIMTESVIK | SATIS, ISTISNA, IADE, TEVKIFAT | | ENERJI | SARJ, SARJANLIK | | IDIS | SATIS, ISTISNA, IADE, TEVKIFAT, IHRACKAYITLI |
HKS'in iki düzlemi (v4.4.0)
Hal faturası iki ayrı düzlemde kesilir ve tip adı düzleme göre değişir:
| Düzlem | ProfileID | InvoiceTypeCode |
|---|---|---|
| e-Fatura | HKS | SATIS / KOMISYONCU (+ ISTISNA, TEVKIFAT) |
| e-Arşiv | EARSIVFATURA | HKSSATIS / HKSKOMISYONCU |
Şematron bu ayrımı kısıtlamaz (HKS için tek kural HKSInvioceCheck: her
kalemde 19 karakterli KUNYENO); ayrım GİB'in kapıyı açma biçimidir. Beş
kombinasyon da canlı şematronla doğrulandı.
HKS masraf düzlemi (v5.0.0)
Komisyoncu faturasının tanımlayıcı özelliği belge düzeyi masraf kalemleridir
(komisyon, navlun, hammaliye, rüsum, borsa payı…). documentAllowanceCharges
alanı her biri için bir cac:AllowanceCharge bloğu üretir:
const { xml } = new SimpleInvoiceBuilder({ prettyPrint: true }).build({
id: 'HKS2026000000001',
uuid: 'aaaaaaaa-0001-4000-8001-000000000001',
datetime: '2026-04-23T13:00:00',
profile: 'HKS',
type: 'KOMISYONCU',
sender: { /* … */ },
customer: { /* … */ },
lines: [{
name: 'Domates', quantity: 500, price: 6, unitCode: 'KGM', kdvPercent: 10,
additionalItemIdentifications: [{ schemeId: 'KUNYENO', value: 'KUN-2026-042-DOM001' }],
}],
documentAllowanceCharges: [
{ chargeIndicator: false, reason: 'HKSKOMISYON', percent: 8 }, // tabandan türetilir → 240,00
{ chargeIndicator: false, reason: 'HKSTICBORSASI', amount: 4.5 }, // tutar OTORİTE
],
});
// LineExtension 3.000,00 · ChargeTotal 244,50 · Payable 3.055,50reasonHKS düzleminde kapalı kümedir —HKS_CHARGE_CODES(18 kod:HKSKOMISYON,HKSKOMISYONKDV,HKSNAVLUN,HKSRUSUM,HKSTICBORSASI,HKSGVTEVKIFAT,HKSBAGKURTEVKIFAT,HKSMILLISAVUNMAFON,HKSDIGERMASRAFLARve KDV ikizleri). Diğer profillerde serbest metindir. Ekran etiketleri:HKS_CHARGE_LABELS.amountotoritedir; verilmezsepercentile belgeLineExtensionAmountından türetilir.baseAmountverilmedikçe XML'ecbc:BaseAmountyazılmaz (GİB'in HKS örneğinde masraf kalemleri taban taşımaz).- 🔴
chargeIndicatorbu düzlemde toplam YÖNÜNÜ BELİRLEMEZ. GİB'in kendi örneğinde blokların tamamıfalseolduğu hâlde toplamChargeTotalAmounta yazılır ve ödenecek tutardan düşülür — yukarıdaki örnekte 3.000 + 300 KDV − 244,50 = 3.055,50.
TEVKIFATIADE / YTBTEVKIFATIADE — üretimde sunulmaz, gelen belgede tanınır
v4.4.0'da her iki tip de PROFILE_TYPE_MATRIX'ten çıkarıldı: şematronda ayırt
edici kuralları yok (hep IADE/YTBIADE ile aynı VEYA-grubunda) ve
GeneralWithholdingTaxTotalCheck gereği tevkifat toplamı taşıyamazlar — yani
IADE'nin adı farklı kopyasından ibaretler. Enum değerleri, IADE grubu
davranışı ve billingReference zorunluluğu KORUNDU: başka bir entegratörden
bu tiplerle gelen fatura okunabilir/gösterilebilir kalır.
Tevkifatlı iadenin doğru yapısı: tip IADE + kalemde tevkifat kodu.
Senaryo-Bazlı Zorunluluklar
| Senaryo | Zorunlu Alanlar |
|---------|----------------|
| IADE | billingReference (fatura ref.); profil seçilmemişse otomatik TEMELFATURA. İzinli profiller: TEMELFATURA, EARSIVFATURA, ILAC_TIBBICIHAZ, YATIRIMTESVIK, IDIS, KAMU (v4.0.0) |
| TEVKIFAT | En az 1 satırda withholdingTaxCode |
| ISTISNA | kdvExemptionCode (201-350) |
| IHRACKAYITLI | kdvExemptionCode (701-703) |
| OZELMATRAH | kdvExemptionCode (801-812) + ozelMatrah |
| SGK | sgk bilgisi + invoicePeriod |
| IHRACAT | buyerCustomer + satırlarda delivery (INCOTERMS, GTİP) |
| YOLCUBERABERFATURA | buyerCustomer + taxRepresentativeParty |
| KAMU | paymentMeans + IBAN (TR + 24 karakter) |
| SARJ (v4.0.0) | invoicePeriod (4 alan: startDate/startTime/endDate/endTime, tarih ≥ 2005-01-01) + additionalDocuments içinde schemeId: 'ESURaporID' GUID + IssueDate + alıcıda tam 1 PLAKA kimliği |
| SARJANLIK (v4.0.0) | invoicePeriod (4 alan) + alıcıda tam 1 PLAKA kimliği + her satırda serialId |
| e-İrsaliye (v4.0.0) | shipment.licensePlates — en az 1 geçerli plaka zorunlu; TR formatı ^(0[1-9]\|[1-7][0-9]\|8[01])[A-Z]+[0-9]+$, yabancı ^[A-Z0-9_-]+$ |
| Döviz | currencyCode + exchangeRate |
6. Hesaplama Motoru Detayları
Vergi Hesaplama Algoritması
1. brütTutar = birimFiyat × miktar
2. iskonto = brütTutar × (iskontoProsent / 100)
3. netTutar = brütTutar - iskonto
4. Her ek vergi (ÖTV, Damga V. vb.) için:
- ekVergi = netTutar × (vergiOranı / 100)
- baseStat=true, baseCalculate=true → KDV matrahı += ekVergi (matrah artırıcı: ÖTV)
- baseStat=true, baseCalculate=false → KDV matrahı -= ekVergi (matrah azaltıcı: Damga V.)
- baseStat=false, baseCalculate=false → taxForCalculate *= -1 (negatif: Gelir V. Stopajı)
5. KDV = modifiye_matrah × (kdvOranı / 100)
6. Tevkifat = kdvTutarı × (tevkifatOranı / 100)
7. Ödenecek = netTutar + taxForCalculate_toplam - tevkifatTip Otomatik Tespiti
1. Satırda withholdingTaxCode varsa → TEVKIFAT
2. Kullanıcı type override verdiyse → override
3. kdvExemptionCode verilmişse → documentType'a göre (ISTISNA/IHRACKAYITLI/OZELMATRAH)
4. KDV %0 satır varsa → ISTISNA veya SATIS (karışık)
5. Varsayılan → SATISProfil Otomatik Tespiti
1. Kullanıcı profile override verdiyse → override
2. eArchiveInfo veya onlineSale varsa → EARSIVFATURA
3. buyerCustomer varsa → IHRACAT
4. IADE tipi → TEMELFATURA
5. SGK tipi → TEMELFATURA
6. Varsayılan → TICARIFATURA7. Embed Konfigürasyon Verileri
SDK aşağıdaki verileri statik olarak embed eder. ConfigManager ile runtime'da güncellenebilir:
| Config | Kayıt Sayısı | Açıklama |
|--------|-------------|----------|
| taxes | 25 | Vergi tanımları (KDV, ÖTV 1-4, Damga V., ÖİV, BSMV, vb.) |
| withholdingTaxes | 52 | Tevkifat kodları (601-627 kısmi, 801-825 tam) |
| exemptions | 86 | İstisna/muafiyet kodları (201-350 istisna, 701-703 ihraç kayıtlı, 801-812 özel matrah) |
| units | 75 | Birim kodları (Adet/C62, Litre/LTR, Kilogram/KGM, vb.) |
| currencies | 30 | Para birimleri (TRY, USD, EUR, GBP, vb.) |
7.1. Yazıyla Tutar Notu (v3.0.0)
⚠️ DAVRANIŞ DEĞİŞİKLİĞİ. v3.0.0'dan itibaren her faturaya, notların İLKİ olarak bir "yazıyla tutar" notu eklenir. Opsiyon yoktur, kapatılamaz. Byte-bazlı XML karşılaştırması yapıyorsanız beklenen çıktılarınızı yeniden üretin. İrsaliye (
DespatchAdvice) etkilenmez.
<cbc:InvoiceTypeCode>SATIS</cbc:InvoiceTypeCode>
<cbc:Note>YAZIYLA:#ÜÇ BİN İKİ YÜZ KIRK ÜÇ TÜRK LIRASI ELLİ ALTI KURUŞ#</cbc:Note> <!-- otomatik, ilk sırada -->
<cbc:Note>Sicil No: 0606</cbc:Note> <!-- sizin notlarınız -->
<cbc:DocumentCurrencyCode>TRY</cbc:DocumentCurrencyCode>Biçim uydurulmadı: 88 gerçek fatura notu bayt düzeyinde incelenerek çıkarıldı ve 88'inin
tamamı PayableAmounttan yeniden hesaplanarak doğrulandı — kelime/birim farkı sıfır.
Kaynak cac:LegalMonetaryTotal/cbc:PayableAmounttır ve not, cbc:PayableAmountın yazdığı
string'in birebir aynı yuvarlamasından türetilir — not ile belgedeki tutar ayrışamaz.
| Durum | Çıktı |
|---|---|
| 3243,56 TRY | YAZIYLA:#ÜÇ BİN İKİ YÜZ KIRK ÜÇ TÜRK LIRASI ELLİ ALTI KURUŞ# |
| 1,05 TRY | YAZIYLA:#BİR TÜRK LIRASI BEŞ KURUŞ# — kesir de yazıyla |
| 660000,00 TRY | YAZIYLA:#ALTI YÜZ ALTMIŞ BİN TÜRK LIRASI# — kuruş sıfırsa kesir hiç yazılmaz |
| 0,00 TRY | YAZIYLA:#SIFIR TÜRK LIRASI# |
| -182,20 TRY | YAZIYLA:#EKSİ YÜZ SEKSEN İKİ TÜRK LIRASI YİRMİ KURUŞ# — işaret yutulmaz |
| 10000,00 USD | YAZIYLA:#ON BİN AMERIKAN DOLARI# |
| 2,50 CHF (tablo dışı) | YAZIYLA:#İKİ CHF ELLİ KURUŞ# — ISO kodu olduğu gibi |
🔴 Kuruş sıfırken kapanış #inden önce boşluk YOKTUR ve not tek satırdır.
Birim adları — ÖLÇÜLDÜ / seçildi
| Kod | Büyük birim | Küçük birim |
|---|---|---|
| TRY | TÜRK LIRASI — ÖLÇÜLDÜ (86 kayıt) | KURUŞ — ÖLÇÜLDÜ (33 kayıt) |
| USD | AMERIKAN DOLARI — ÖLÇÜLDÜ (1 kayıt) | SENT — seçildi |
| EUR | AVRO — ÖLÇÜLDÜ (1 kayıt), EURO değil | SENT — seçildi |
| GBP | İNGİLİZ STERLİNİ — seçildi | PENİ — seçildi |
🔴 TÜRK LIRASI ve AMERIKAN DOLARI noktasız I ile yazılır. Türkçe yazım kuralına göre
LİRASI/AMERİKAN doğru olurdu; saha standardı böyle değil ve amaç alanı birebir eşlemektir —
bu bilinçli, düzeltmeyin. Seçilen adlar (GBP) ise doğru Türkçe yazımla yazılır.
Tablo tek satırla genişletilebilir: AMOUNT_IN_WORDS_UNITS
(src/ubltr/vocabulary/amount-in-words-config.ts).
notes dizinizde elle yazılmış bir yazıyla-notu (YAZIYLA: ile başlayan) varsa
serileştirmede atılır — belgede çelişen iki not bulunmasın diye. Girdi nesneniz değişmez.
Sayı okuma saf ve ayrı bir modüldedir (lira ve kuruş tarafı buradan geçer):
import { numberToTurkishWords, formatAmountInWordsNote } from 'json2ubl-ts';
numberToTurkishWords(1000); // 'BİN' (❌ 'BİR BİN')
numberToTurkishWords(1_000_000); // 'BİR MİLYON' (burada BİR yazılır)
numberToTurkishWords(11_000); // 'ON BİR BİN'
numberToTurkishWords(56); // 'ELLİ ALTI' (kuruş tarafı da aynı modül)
formatAmountInWordsNote(3243.56, 'TRY');
// 'YAZIYLA:#ÜÇ BİN İKİ YÜZ KIRK ÜÇ TÜRK LIRASI ELLİ ALTI KURUŞ#'7.2. İmza İskeleti — includeUblExtensions (v4.1.0)
Kütüphane imza üretmez. Belgenin imzalanması entegratörün ya da sunucu tarafı bir
imzalayıcının işidir. Ancak imzayı kendiniz atıyorsanız, XAdES'in yerleştirileceği
ext:UBLExtensions kancasına ihtiyacınız olur.
const builder = new SimpleInvoiceBuilder({
validationLevel: 'strict',
includeUblExtensions: true, // varsayılan: false
});true iken kök elemanın ilk çocuğu olarak boş iskelet yazılır:
<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionContent/>
</ext:UBLExtension>
</ext:UBLExtensions>Neden gerekli: GİB UBL-Invoice-2.1.xsd kök sequence'ında ext:UBLExtensions ilk
elemandır. İskelet yokken XSD doğrulaması şu hatayla düşer:
"UBLVersionID" elementi bu konumda geçersiz. Bu noktada beklenen: UBLExtensions.Varsayılan neden false: yerleşik tüketicilerin çoğunda zarfı ve imzayı entegratör
ekler; koşulsuz emit onların çıktısını değiştirirdi. Bayrak InvoiceBuilder,
DespatchBuilder ve SimpleInvoiceBuilder üzerinde çalışır.
⚠️ İskelet tek başına XSD'yi geçirmez.
ExtensionContentboş olamaz ve GİB şeması ayrıcacac:Signaturebekler — ikisini de imzalayıcı ekler. İmzasız çıktı tüm Schematron iş kurallarından temiz geçer; kalan XSD eksiği yalnızca imzadır.
8. Sorumluluk Matrisi
Kütüphane hangi karardan sorumlu, hangisinden değil. Tüketici kodunun bilmesi gereken non-obvious davranışlar.
| Karar | Kapsam | Dosya / Kaynak |
|-------|--------|-----------------|
| M1 | PROFILE_TYPE_MATRIX tek truth source; profil+tip kombinasyonları whitelist | src/calculator/invoice-rules.ts |
| M2 | IHRACAT/YOLCU/OZELFATURA yalnızca ISTISNA tipi kabul eder | src/calculator/invoice-rules.ts |
| M3 (B-95) | 650 dinamik stopaj — kullanıcı withholdingTaxPercent: 0-100 girer; UR-2 65000+percent combo XML'de | src/calculator/withholding-config.ts · examples/10 |
| M4 (B-96) | 555 Demirbaş KDV — BuilderOptions.allowReducedKdvRate: true opt-in flag zorunlu; default false reject eder | src/types/builder-options.ts · examples/30 |
| M5 | TAX_EXEMPTION_MATRIX — istisna kodu × fatura tipi whitelist/forbidden + requiresZeroKdvLine | src/documents/invoice/validators/cross-check-matrix.ts |
| M6 | Parent-child conditional required — parent opsiyonel, parent varsa child zorunlu | src/documents/invoice/validators/*.ts |
| M7 | Exemption-config → cross-check matrisi otomatik türetilir | src/calculator/exemption-config.ts |
| M8 | CustomizationID: Fatura TR1.2, e-İrsaliye TR1.2.1 — kütüphane builder'da sabitler | src/ubl/namespaces.ts |
| M9 (B-102) | Calculator tam float, yuvarlama yalnızca XML yazım anında toFixed(2) XSD-yuvarlamalı alanlarda | src/calculator/*.ts · src/serializers/*.ts |
| M10 (B-102) | setLiability() isExport=true iken no-op (error yerine) | src/calculator/invoice-rules.ts |
| M11 (Sprint 8c, B-NEW-11) | Self-exemption tipleri (ISTISNA, IHRACKAYITLI, OZELMATRAH + IHRACAT, YOLCUBERABERFATURA, OZELFATURA, YATIRIMTESVIK profilleri) kendi istisna kodlarını taşır; dışındaki tiplerde KDV=0 kalem için kullanıcıdan 351 manuel zorunlu (calculator otomatik atamaz) | src/ubltr/vocabulary/self-exemption-types.ts · src/validators/manual-exemption-validator.ts |
| M12 (Sprint 8d) | Phantom KDV (Vazgeçilen KDV Tutarı) — YATIRIMTESVIK+ISTISNA ve EARSIVFATURA+YTBISTISNA'da satır KDV matematiği TaxSubtotal içinde taşınır fakat LegalMonetaryTotal + parent TaxTotal'a dahil edilmez; CalculationSequenceNumeric=-1 otomatik. Her satırda 0 < kdvPercent ≤ 100 + exemption code (308 Makine/01, 339 İnşaat/02) zorunlu. Kaynak: GİB Yatırım Teşvik Teknik Kılavuzu v1.1 (Aralık 2025) | src/calculator/phantom-kdv-rules.ts · src/validators/phantom-kdv-validator.ts |
| M13 (v3.0.0) | Yazıyla tutar notu — YAZIYLA:#...# HER faturaya, notların İLKİ olarak, KOŞULSUZ eklenir (opsiyon yok). Biçim 88 gerçek fatura notundan ÖLÇÜLDÜ. Kaynak PayableAmount; tam sayı VE kesir yazıyla, kuruş sıfırsa kesir hiç yazılmaz. Tüketicinin elle yazdığı yazıyla-notları serileştirmede elenir. İrsaliye etkilenmez | src/utils/amount-in-words.ts · src/utils/turkish-number-words.ts · src/ubltr/vocabulary/amount-in-words-config.ts · §7.1 |
| M14 (v4.0.0) | GİB Schematron 20260701 uyumu — Enerji/Şarj 4 zorunluluk, e-İrsaliye plaka zorunluluğu + format, 308/339 YATIRIMTESVIK kapsam kısıtı, İDİS SE-/ES-, IADE+KAMU, kod 233. Yeni kurallar koşulsuz uygulanır (issueDate koşullu geçiş dönemi YOK). Enerji kuralları basic seviyede de çalışır. Yürürlük 14.09.2026 | src/documents/invoice/validators/enerji-validator.ts · src/validators/despatch-validators.ts · src/validators/yatirim-tesvik-validator.ts · audit/sprint-09-plan.md |
| AR-1 | cbcTag → cbcRequiredTag + cbcOptionalTag split | src/ubl/primitives/xml-helpers.ts |
| AR-2 | driverPerson → driverPersons[] array (çoklu sürücü desteği) | src/types/despatch-input.ts · examples/34 |
| AR-3..5 | PROFILE_TYPE_MATRIX helper API; map/matrix export edilmez | src/calculator/invoice-rules.ts |
| AR-6 | Eski dead PaymentMeansCode set kaldırıldı | src/calculator/payment-means-config.ts |
| AR-7 | Satır-seviyesi kdvExemptionCode alanı kaldırıldı (belge seviyesi tek kaynak) | src/types/invoice-input.ts |
| AR-8 | Outstanding/Oversupply input alanları kaldırıldı | src/types/*.ts |
| AR-9 (Sprint 8c, isim konuldu) | Reactive InvoiceSession (vision) — kullanıcı girişi akış tabanlı validator feedback. Mevcut src/invoice-session.ts snapshot validator rolü korunur; reaktif katman v2.1.0'da yeni modül olarak eklenecek | audit/reactive-session-design-notes.md |
| AR-10 (Sprint 8h, v2.1.0) | Reactive InvoiceSession (concrete realization) — AR-9 vision'ın somut implementation'ı: path-based update(path, value) API + SessionPaths map + field-level events (fieldChanged/fieldActivated/fieldDeactivated/lineFieldChanged) + line-level FieldVisibility + validator pipeline (5 validator) + B-78 parametre köprüsü + structured pathError event. 19 eski setter kaldırıldı (BREAKING). liability update(SessionPaths.liability, x) ile yönetilir; isExport constructor-locked readonly. M10 kontratı korunur. | src/calculator/invoice-session.ts · src/calculator/session-paths.generated.ts · audit/sprint-08h-plan.md · audit/sprint-08h-implementation-log.md |
| B-07 | IHRACKAYITLI + 702 için GTİP (12 hane) + AlıcıDİBKod (11 hane) zorunlu; simple-input'ta SimpleLineDeliveryInput.alicidibsatirkod alanı (B-NEW-12 / Sprint 8c). 4.5.1: izinli ikinci kod saticidibsatirkod (SATICIDIBSATIRKOD schemeID) — zorunlu DEĞİL, uzunluk şartı YOK | src/documents/invoice/validators/profile-validators.ts · examples/07 |
| B-08 | YATIRIMTESVIK: ytbNo (6 hane) + Kod 01 Makine için productTraceId+serialId+brand+model; IADE grubunda muaf | src/documents/invoice/validators/profile-validators.ts · examples/12 |
| B-83 | KAMU: buyerCustomer + paymentMeans + TR IBAN zorunlu; additionalIdentifiers (MUSTERINO) | examples/15 |
| B-104 | Despatch DriverPerson.nationalityId 11-hane TCKN zorunlu (ISO kodu reddedilir) | src/validators/despatch-validators.ts |
| B-NEW-13 (Sprint 8c) | YOLCUBERABERFATURA: SimpleBuyerCustomerInput.nationalityId + passportId + belge seviyesi SimpleInvoiceInput.taxRepresentativeParty zorunlu | src/calculator/simple-types.ts · examples/20 |
Kütüphane SORUMLULUĞUNDA OLMAYAN
- Dijital imza —
ext:UBLExtensionsyapısı kütüphane tarafından üretilmez. Bu imzalayıcı servisin (GİB veya özel entegrasyon) sorumluluğudur (ACIK-SORULAR §3). - Stopaj modeli XML pattern seçimi — negatif
TaxAmountmı yoksa ayrıAllowanceChargemı: kütüphane XSD uyumlu pozitif stopaj subtotal üretir; GİB reddederse tüketici sorumluluğu. - Prod schematron simülasyonu —
validationLevel: 'strict'statik kurallar. GİB schematron + production quirks ayrı. - e-Arşiv RAPORU (
EArsiv.xsd) — kütüphane yalnız EARSIVFATURA UBL faturası üretir; günlük/aylık rapor XML'i üretmez.EArsiv.xsdv1.1_8 değişiklikleri (aliciType→xs:choice,esuRaporID1..n) bu yüzden kapsam dışıdır. - Kullanıcı hesabı / HR-XML —
erreceiptalias'ı,UserOptionCode171-174,AuthorizedWorkScopekuralları özel entegratör operasyonuna aittir. - e-MM / e-SMM'in TAŞINMASI — belge XML'i v5.0.0'dan beri üretilir (§10 · §9), ancak SMS doğrulama, GİB'e gönderim ve e-Arşiv raporlaması kütüphane dışıdır.
- e-Arşiv zorunluluk eşiği (509 GT §IV.2.4.3; 1/1/2026'dan itibaren tutara bakılmaksızın) — "hangi belgeyi keseyim" kararı tüketiciye aittir; kütüphane verilen profili üretir.
Ayrıntı: audit/FIX-PLANI-v3.md.
9. e-SMM — Serbest Meslek Makbuzu (v5.0.0)
Serbest meslek erbabının düzenlediği makbuz. UBL kökü Invoice; künye tek
değerlidir (ProfileID = EARSIVBELGE, InvoiceTypeCode = SERBESTMESLEKMAKBUZU)
ve faturadaki profil × tip matrisinin karşılığı yoktur.
import { SimpleSelfEmployedVoucherBuilder } from 'json2ubl-ts';
const { xml } = new SimpleSelfEmployedVoucherBuilder({ prettyPrint: true }).build({
documentNumber: 'SMM2026000000001',
datetime: '2026-09-16T14:30:00',
sendMethod: 'ELEKTRONIK', // 'ELEKTRONIK' | 'KAGIT'
supplier: { // serbest meslek erbabı — TCKN → cac:Person
taxNumber: '11111111110', name: 'Ayşe Yılmaz',
address: 'Barbaros Mah. 1. Sk. No:2', district: 'Ataşehir', city: 'İstanbul',
taxOffice: 'Kozyatağı',
},
customer: { // hizmeti alan
taxNumber: '1234567890', name: 'Örnek Danışmanlık A.Ş.',
address: 'Cumhuriyet Mah. 5. Cd. No:8', district: 'Şişli', city: 'İstanbul',
taxOffice: 'Mecidiyeköy',
},
incomeTaxWithholdingPercent: 20, // 0003 gelir vergisi stopajı
lines: [{ name: 'Danışmanlık hizmeti', quantity: 1, price: 10_000, kdvPercent: 20 }],
});
// Payable 10.000,00 (10.000 brüt + 2.000 KDV − 2.000 stopaj)- Toplam zinciri: brüt → stopaj (
0003) → KDV → KDV tevkifatı (9015). Vergi kümesi tip düzeyinde{0003, 0015, 9015}e kapalıdır. uuidverilmezse üretilir; determinizm gerekiyorsa açıkça geçin.- Reaktif kullanım:
SelfEmployedVoucherSession— faturanınInvoiceSessionıyla aynı sözleşme (update(), path tabanlı olaylar,SelfEmployedVoucherSessionPaths).
10. e-MM — Müstahsil Makbuzu (v5.0.0)
Müstahsilden (çiftçi) mal alan tüccarın düzenlediği makbuz. UBL kökü
**CreditNote**tır — faturadan en büyük yapısal fark budur. Künye yine tek
değerlidir (ProfileID = EARSIVBELGE, CreditNoteTypeCode = MUSTAHSILMAKBUZ).
import { SimpleProducerReceiptBuilder } from 'json2ubl-ts';
const { xml } = new SimpleProducerReceiptBuilder({ prettyPrint: true }).build({
documentNumber: 'MMB2026000000001',
datetime: '2026-09-16T14:30:00',
sendMethod: 'ELEKTRONIK',
issuer: { // belgeyi DÜZENLEYEN tüccar/alıcı
taxNumber: '1234567890', name: 'Örnek Sebze Meyve Tic. A.Ş.',
address: 'Hal Cd. No:1', district: 'Kocasinan', city: 'Kayseri',
taxOffice: 'Kocasinan',
},
producer: { // MÜSTAHSİL — malı satan çiftçi
taxNumber: '11111111110', name: 'Ahmet Çiftçi',
address: 'Köy Mah. 3. Sk. No:5', district: 'Melikgazi', city: 'Kayseri',
},
incomeTaxWithholdingPercent: 2, // 0003; kalem bazında ezilebilir
lines: [{ name: 'Domates', quantity: 1000, price: 10, unitCode: 'Kilogram' }],
});
// Payable 9.800,00 (10.000 brüt − 200 stopaj)- 🔴 TARAF ROLLERİ TERSTİR. Belgeyi düzenleyen
issuer, UBL'decac:AccountingSupplierPartyyuvasına gider; ekonomik sezgi tersini söyler (malı SATAN müstahsildir). Prod belgelerde 27/27 böyle ölçülmüştür. - ÇIKIŞ KDV'Sİ YOKTUR — müstahsil KDV mükellefi değildir; ödenecek tutar
brütten kesinti düşülerek bulunur (
TaxInclusiveAmount = brüt − kesinti). - Dört kesinti ekseni: gelir vergisi stopajı (
0003), Bağ-Kur (8001), SGK primi, Mera Fonu (9040). - Reaktif kullanım:
ProducerReceiptSession+ProducerReceiptSessionPaths.
Kapsam sınırı — kütüphane yalnız belge XML'ini üretir. SMS doğrulama, GİB'e gönderim ve e-Arşiv raporlaması kütüphane dışıdır (§8).
Geliştirme
yarn install # Bağımlılıkları kur
yarn typecheck # TypeScript kontrol
yarn test # Testleri çalıştır
yarn build # Derleme (CJS + ESM + DTS)
yarn examples # 38 örnek senaryoyu çalıştır (Sprint 8b)
npx tsx examples/run-all.ts yatirimtesvik # Slug filtreli
npx tsx examples/NN-slug/run.ts # Tek senaryoLisans
MIT
