diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index 1d8e4d4..b1024f4 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -2128,3 +2128,9 @@ CI iş akışı da Chromium’u kurup bu koşucuyu her push ve pull request’te Container işinin başında production Compose tanımı `AUTH_SECRET` placeholder ile `docker compose config --quiet` üzerinden doğrulanıyor; eksik zorunlu değişken veya bozuk healthcheck tanımı image build’inden önce yakalanıyor. Doğrulama: giriş HTTP 200, beş şehrin tamamı görünür (her biri 3 eşleşme), ICS içe aktarma ve sekme değişimi sonrası kalıcılık başarılı, hazır taslakta dört çıktı düğmesi etkin, evrak listesi yazdırma penceresi doğru başlıkla açıldı, üç hazırlık özeti çerçevesi açıldı. + +### 2026-09-06 · D03 eski PDF üreticisinin kaldırılması + +Üretim kodunda kullanılmayan ve Türkçe karakterleri WinAnsi nedeniyle bozabilen eski `src/services/pdf.js`/jsPDF yolu kaldırıldı. Teklif belgelerinin üretim yolu artık Türkçe HTML ve tarayıcının yazdırma/PDF akışı; `bidDocs.js` açıklaması da bu durumu yansıtıyor. `jspdf` ve yalnız bu yola ait bağımlılıklar paket kilidinden çıkarıldı. + +Doğrulama: jsPDF ve eski üreticiye kaynak referansı kalmadı; test ve üretim derlemesi başarılı. Gerçek resmi ihale dosyasıyla çok sayfalı PDF görsel kabulü ve beş resmi ihale başvurusu hâlâ açık işlerdir. diff --git a/package-lock.json b/package-lock.json index a522a95..3d0fc08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "canvas-confetti": "^1.9.4", "exceljs": "^4.4.0", - "jspdf": "^4.2.1", "lucide-react": "^0.469.0", "pdfjs-dist": "^6.2.108", "react": "^18.3.1", @@ -1714,6 +1713,72 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", @@ -1878,12 +1943,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/pako": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", - "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", - "license": "MIT" - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -1891,13 +1950,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/raf": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", - "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/react": { "version": "18.3.31", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", @@ -1919,13 +1971,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -2028,16 +2073,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2241,26 +2276,6 @@ "url": "https://www.paypal.me/kirilvatev" } }, - "node_modules/canvg": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", - "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@types/raf": "^3.4.0", - "core-js": "^3.8.3", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.7", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "svg-pathdata": "^6.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -2310,21 +2325,6 @@ "dev": true, "license": "MIT" }, - "node_modules/core-js": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz", - "integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -2356,16 +2356,6 @@ "node": ">= 10" } }, - "node_modules/css-line-break": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", - "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "license": "MIT", - "optional": true, - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2543,16 +2533,6 @@ "csstype": "^3.0.2" } }, - "node_modules/dompurify": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.14.tgz", - "integrity": "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true, - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -2722,17 +2702,6 @@ "node": ">=6.0.0" } }, - "node_modules/fast-png": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", - "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", - "license": "MIT", - "dependencies": { - "@types/pako": "^2.0.3", - "iobuffer": "^5.3.2", - "pako": "^2.1.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2751,12 +2720,6 @@ } } }, - "node_modules/fflate": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", - "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", - "license": "MIT" - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2837,20 +2800,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/html2canvas": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", - "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "license": "MIT", - "optional": true, - "dependencies": { - "css-line-break": "^2.1.0", - "text-segmentation": "^1.0.3" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/idb-keyval": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz", @@ -2909,12 +2858,6 @@ "node": ">=12" } }, - "node_modules/iobuffer": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", - "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", - "license": "MIT" - }, "node_modules/is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", @@ -2969,23 +2912,6 @@ "node": ">=6" } }, - "node_modules/jspdf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz", - "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.6", - "fast-png": "^6.2.0", - "fflate": "^0.8.1" - }, - "optionalDependencies": { - "canvg": "^3.0.11", - "core-js": "^3.6.0", - "dompurify": "^3.3.1", - "html2canvas": "^1.0.0-rc.5" - } - }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -3603,22 +3529,6 @@ "opencollective-postinstall": "index.js" } }, - "node_modules/pako": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", - "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "(MIT AND Zlib)" - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -3640,13 +3550,6 @@ "@napi-rs/canvas": "^1.0.0" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "license": "MIT", - "optional": true - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3766,16 +3669,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "license": "MIT", - "optional": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -3931,16 +3824,6 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, - "node_modules/rgbcolor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", - "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", - "optional": true, - "engines": { - "node": ">= 0.8.15" - } - }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -4067,16 +3950,6 @@ "node": ">=0.10.0" } }, - "node_modules/stackblur-canvas": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", - "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.14" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4086,16 +3959,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/svg-pathdata": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", - "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/tailwindcss": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", @@ -4157,16 +4020,6 @@ "integrity": "sha512-pv4GjmramjdObhDyR1q85Td8X60Puu/lGQn7Kw2id05LLgHhAcWgnz6xSdMCSxBMWjQDmMyDXPTC2aqADdpiow==", "license": "Apache-2.0" }, - "node_modules/text-segmentation": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", - "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "license": "MIT", - "optional": true, - "dependencies": { - "utrie": "^1.0.2" - } - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -4299,16 +4152,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/utrie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "license": "MIT", - "optional": true, - "dependencies": { - "base64-arraybuffer": "^1.0.2" - } - }, "node_modules/uuid": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", diff --git a/package.json b/package.json index e24c1ed..36d1b9a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "dependencies": { "canvas-confetti": "^1.9.4", "exceljs": "^4.4.0", - "jspdf": "^4.2.1", "lucide-react": "^0.469.0", "pdfjs-dist": "^6.2.108", "react": "^18.3.1", diff --git a/src/services/bidDocs.js b/src/services/bidDocs.js index 6cccfc4..81af090 100644 --- a/src/services/bidDocs.js +++ b/src/services/bidDocs.js @@ -3,8 +3,8 @@ import { quantityParts, parseBidPrice, bidRowTotal, validateBidRows, bidTotal, b * İhale dosyası belgeleri — teklif mektubu, birim fiyat teklif cetveli ve * evrak kontrol listesi. * - * NEDEN HTML/YAZDIR: jsPDF'in gömülü fontları WinAnsi; ş/ğ/ı/İ taşımıyor - * (bkz. services/pdf.js, başlıkları ASCII'ye düşürüyor). Resmî teklif + * NEDEN HTML/YAZDIR: Eski jsPDF yolunun gömülü fontları WinAnsi; ş/ğ/ı/İ + * taşımıyordu. Resmî teklif * mektubunda bozuk Türkçe kabul edilemez, o yüzden belgeler tam Türkçe HTML * olarak üretilip tarayıcının yazdır → "PDF olarak kaydet" akışına veriliyor. */ diff --git a/src/services/pdf.js b/src/services/pdf.js deleted file mode 100644 index dc8fc87..0000000 --- a/src/services/pdf.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Gerçek PDF üretimi (jsPDF) — Birim Fiyat Teklif Cetveli. - * İhalenin kalemlerini tabloya döker, girilen birim fiyatlarla toplamı hesaplar, - * firma bilgisi + kaşe/imza alanı ekler ve indirir. - */ -import { jsPDF } from 'jspdf'; - -const money = (n) => - (Number(n) || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); - -export function generateBidSheetPDF(tender, { firma = 'Firma Adı', prices = {}, stamp = true } = {}) { - const doc = new jsPDF({ unit: 'mm', format: 'a4' }); - const W = 210; - const M = 15; - let y = 18; - - doc.setFont('helvetica', 'bold'); - doc.setFontSize(15); - doc.text('BIRIM FIYAT TEKLIF CETVELI', W / 2, y, { align: 'center' }); - y += 8; - - doc.setFontSize(9); - doc.setFont('helvetica', 'normal'); - const ikn = tender.ikn || ''; - const kurum = (tender.kurum || '').slice(0, 90); - doc.text(`Idare: ${kurum}`, M, y); y += 5; - doc.text(`Isin Adi: ${(tender.baslik || '').slice(0, 95)}`, M, y); y += 5; - doc.text(`IKN / DTN: ${ikn} Tarih: ${tender.tarih || ''}`, M, y); y += 5; - doc.text(`Teklif Veren: ${firma}`, M, y); y += 8; - - // Tablo başlığı - const cols = [ - { t: 'No', x: M, w: 12, a: 'left' }, - { t: 'Mal / Hizmet Adi', x: M + 12, w: 88, a: 'left' }, - { t: 'Miktar', x: M + 100, w: 20, a: 'right' }, - { t: 'Birim', x: M + 120, w: 18, a: 'left' }, - { t: 'B.Fiyat', x: M + 138, w: 20, a: 'right' }, - { t: 'Tutar', x: M + 158, w: 22, a: 'right' }, - ]; - doc.setFillColor(30, 58, 138); - doc.setTextColor(255); - doc.setFont('helvetica', 'bold'); - doc.rect(M, y - 4.5, W - 2 * M, 7, 'F'); - cols.forEach((c) => doc.text(c.t, c.a === 'right' ? c.x + c.w - 1 : c.x + 1, y, { align: c.a })); - y += 5; - - doc.setTextColor(20); - doc.setFont('helvetica', 'normal'); - doc.setFontSize(8.5); - const items = tender.items && tender.items.length ? tender.items : [{ id: 'x1', ad: '(Kalem girilmemis)', miktar: '', birim: '' }]; - let total = 0; - items.forEach((it, i) => { - const bf = Number(prices[it.id] ?? it.birimFiyat ?? 0); - const miktar = parseFloat(String(it.miktar).replace(',', '.')) || 0; - const tutar = bf * miktar; - total += tutar; - if (i % 2 === 0) { doc.setFillColor(245, 247, 250); doc.rect(M, y - 4, W - 2 * M, 6, 'F'); } - doc.text(String(i + 1), M + 1, y); - doc.text(String(it.ad || '').slice(0, 60), M + 13, y); - doc.text(String(it.miktar || ''), M + 120 - 1, y, { align: 'right' }); - doc.text(String(it.birim || ''), M + 120 + 1, y); - doc.text(bf ? money(bf) : '—', M + 158 - 1, y, { align: 'right' }); - doc.text(tutar ? money(tutar) : '—', W - M - 1, y, { align: 'right' }); - y += 6; - if (y > 250) { doc.addPage(); y = 20; } - }); - - // Toplam - doc.setFont('helvetica', 'bold'); - doc.setFontSize(10); - doc.setDrawColor(200); - doc.line(M, y - 2, W - M, y - 2); - y += 4; - doc.text('GENEL TOPLAM (KDV Haric):', M + 90, y, { align: 'right' }); - doc.text(`${money(total)} TL`, W - M - 1, y, { align: 'right' }); - y += 16; - - // Kaşe / imza alanı - if (stamp) { - doc.setFont('helvetica', 'normal'); - doc.setFontSize(9); - doc.setDrawColor(150); - doc.roundedRect(W - M - 60, y, 60, 30, 2, 2); - doc.text('Kase ve Imza', W - M - 30, y + 17, { align: 'center' }); - doc.setTextColor(120); - doc.setFontSize(7.5); - doc.text(firma, W - M - 30, y + 23, { align: 'center' }); - } - - doc.setTextColor(150); - doc.setFontSize(7); - doc.text('Ihaleist ile olusturulmustur — ihaleist.seymata.com', M, 288); - - const safe = (ikn || tender.baslik || 'teklif').replace(/[^\w]+/g, '_').slice(0, 40); - doc.save(`teklif_cetveli_${safe}.pdf`); - return total; -}