Сейчас узнаем, какой подарок выпадет именно Вам
Крутите колесо
Как сформировать pdf из корзины в Tilda

Как сформировать pdf из корзины в Tilda

1
Создали товары в магазине и блок корзины ST100
2
Создали блок GL02 и заполнили как показано на скрине,
задали ему класс uc-pdf-data
3
Вставили код на страницу в блок Т123
Mo-ti Level Up
Видео инструкции по добавлению кода и работе с Zero Block.
Как скачать состав корзины в pdf на Tilda
Фрагмент видео
Библиотека для примера
<style>
.invoice-wrapper {
    max-width: 800px;
    margin: 0 auto;
    background: #fff;
    box-shadow: 0 2px 15px rgba(0,0,0,0.1);
    border-radius: 8px;
    overflow: hidden;
}

.main-content {
    padding: 0 20px;
}

.full-width-image {
    width: 100%;
    height: auto;
    display: block;
    object-fit: contain;
}

.invoice-header {
    margin-top: 30px;
}

.invoice-table {
    width: 100%;
    border-collapse: collapse;
    margin: 15px 0;
    font-size: 12px;
    page-break-inside: auto;
}

.invoice-table th {
    background: #8f8f8f;
    color: white;
    padding: 8px 4px;
    text-align: left;
    font-weight: 400;
}

.invoice-table td {
    padding: 8px 4px;
    border-bottom: 1px solid #eee;
    vertical-align: middle;
}

.invoice-table tr:nth-child(even) {
    background: #f9f9f9;
}

.invoice-table tr {
    page-break-inside: avoid;
}

.num-cell {
    width: 25px;
    text-align: center;
    font-weight: 500;
}

.photo-cell {
    width: 70px;
    text-align: center;
}

.title-cell {
    font-size: 13px;
    line-height: 1.4;
}

.price-cell,
.total-cell {
    text-align: right;
    white-space: nowrap;
    font-weight: 400;
}

.qty-cell {
    text-align: center;
    width: 60px;
}

.title-cell .t706__product-title {
    font-size: 12px;
    padding: 5px 0;
}

td.totals-cell .t706__cartwin-totalamount-wrap,
td.totals-cell .t706__cartpage-totals {
    padding-top: 0;
    font-size: 16px;
}

.uc-pdf-data{
    display: none;
}


.invoice-table th:first-child,
.invoice-table td:last-child {
    padding-left: 10px;
    padding-right: 10px;
}

.product-thumb {
    width: 50px;
    height: 50px;
    object-fit: cover;
    border-radius: 4px;
    border: 1px solid #ddd;
    display: block;
    margin: 0 auto;
}

.product-thumb.no-image {
    background: #f5f5f5;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #999;
    font-size: 18px;
    width: 50px;
    height: 50px;
    border-radius: 4px;
    border: 1px solid #ddd;
    margin: 0 auto;
}

.total-row {
    background: #ececec !important;
}

.invoice-footer {
    color: #555;
    margin-bottom: 35px;
    line-height: 1.5;
    padding-top: 8px;
}

.invoice-wrapper a,
.invoice-wrapper .t706__product-title a {
    text-decoration: none !important;
    color: inherit !important;
    border-bottom: none !important;
}

.print-price-button {
    position: relative;
    cursor: pointer;
    transition: all 0.2s ease;
    user-select: none;
}

.print-price-button.is-generating {
    opacity: 0.65;
    pointer-events: none;
    transform: scale(0.97);
    background-color: #e8e8e8 !important;
    border-color: #ccc !important;
}

.print-price-button.is-generating .button-text::after {
    content: '...';
    display: inline-block;
    animation: dots 1.2s infinite steps(4, end);
    width: 12px;
}


.t-rec .t706__cartpage-info .print-price-button {
    margin-top: 15px;
}


.t706__cartpage-totals {
    background: transparent;
    border-radius: 0;
    padding: 0;
    margin-top: 0px;
    margin-bottom: 0px;
    bottom: 0;
}


span.t706__cartwin-discounts__description-wrapper {
    display: none;
}

@keyframes dots {
    0% { content: '.'; width: 6px; }
    33% { content: '..'; width: 10px; }
    66% { content: '...'; width: 14px; }
}

@media print {
    body {
        background: white;
        padding: 0;
    }
}

.t706 .t706__cartpage-totals {
    padding-bottom: 0px;
}

.t706 .t706__cartwin-bottom+div {
    margin-top: 0;
}

.uc-download-data {
    display: none;
}

.load-time:after {
    content: "";
    opacity: 0.5;
    background-image: url(https://tilda.ru/tpl/img/ajax-loader.gif);
    background-size: 35px;
    background-position: center;
    background-repeat: no-repeat;
    height: 100%;
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
}

.load-time div {
    opacity: 0.2;
}

.js-successbox.load-time.t-form__successbox {
    opacity: 0.6;
}

.print-price-button.t-text {
    border-radius: 0px;
    margin-top: 5px;
    font-weight: 600;
    color: #000000;
    border: 1px solid #dddddd;
    cursor: pointer;
    transition: all 0.2s;
    width: 100%;
    box-sizing: border-box;
}

.button-wrapper {
    padding: 8px 0;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 16px;
    font-weight: 400;
}

.print-price-button.t-text:not(.load-time):hover {
    background-color: #f5f5f5;
}

.button-icon {
    background-image: url(https://static.tildacdn.com/tild6238-3065-4138-b936-303137363262/2849806-copy-interfa.svg);
    width: 25px;
    height: 25px;
    background-position: center;
    background-size: contain;
    background-repeat: no-repeat;
    margin-right: 12px;
}

@media screen and (max-width: 960px) {
    .print-price-button.t-text {
        width: calc(100% - 0px);
        margin: auto;
    }

    .t706__cartpage-form .print-price-button.t-text {
        width: 100%;
        margin: auto;
    }
}

@media screen and (max-width: 640px) {
    .t706__cartpage-form .t-form__submit {
        padding-bottom: 5px;
    }
}
</style>



<script>
document.addEventListener("DOMContentLoaded", function() {
    t_onReady(function() {
        setTimeout(function() {
            t_onFuncLoad('tcart__init', function() {

                function loadScript(src) {
                    return new Promise((resolve, reject) => {
                        if(document.querySelector(`script[src="${src}"]`)) return resolve();
                        const s = document.createElement('script');
                        s.src = src; s.async = true;
                        s.onload = resolve; s.onerror = reject;
                        document.head.appendChild(s);
                    });
                }

                async function ensureLibs() {
                    const promises = [];
                    if(typeof window.jspdf === 'undefined') promises.push(loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'));
                    if(typeof window.htmlToImage === 'undefined') promises.push(loadScript('https://cdn.jsdelivr.net/npm/html-to-image@1.11.11/dist/html-to-image.min.js'));
                    await Promise.all(promises);
                }

                function getCurrentDateFormatted() {
                    const now = new Date();
                    return [String(now.getDate()).padStart(2, '0'), String(now.getMonth() + 1).padStart(2, '0'), now.getFullYear()].join('.');
                }

                function replaceDateInHTML(html) {
                    return html.replace(/{date}/g, getCurrentDateFormatted());
                }

                function extractBgImageUrl(element) {
                    if(!element) return '';
                    const style = element.getAttribute('style') || '';
                    const match = style.match(/background-image:\s*url\(['"]?([^'")]+)['"]?\)/i);
                    return match ? match[1] : '';
                }

                function formatPrice(num) {
                    return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
                }

                function getProjectFont() {
                    const selectors = ['.t-text', '.t-descr', '.t-title', '.t-body', 'body'];
                    for (let sel of selectors) {
                        const el = document.querySelector(sel);
                        if (!el) continue;
                        let fam = window.getComputedStyle(el).fontFamily;
                        let clean = fam.replace(/['"]/g, '').split(',')[0].trim();
                        if (clean && clean !== 'inherit' && clean !== 'initial') return clean;
                    }
                    return 'Arial';
                }

                function collectPdfMetaData() {
                    const pdfDataBlock = document.querySelector('.uc-pdf-data');
                    let headerImage = '', footerImage = '';
                    let pdfTitleHTML = '', pdfDescrHTML = '';

                    if(pdfDataBlock) {
                        const rowWrapper = pdfDataBlock.querySelector('.t667__row');
                        if(rowWrapper) {
                            const bgImages = rowWrapper.querySelectorAll('.t-bgimg');
                            if(bgImages[0]) headerImage = bgImages[0].getAttribute('data-original') || '';
                            if(bgImages[1]) footerImage = bgImages[1].getAttribute('data-original') || '';
                        }
                        const titleEl = pdfDataBlock.querySelector('.js-block-header-title');
                        const descrEl = pdfDataBlock.querySelector('.js-block-header-descr');
                        pdfTitleHTML = titleEl ? replaceDateInHTML(titleEl.outerHTML) : '';
                        pdfDescrHTML = descrEl ? replaceDateInHTML(descrEl.outerHTML) : '';
                    }
                    return { headerImage, footerImage, pdfTitleHTML, pdfDescrHTML };
                }

                function collectCartProducts() {
                    const products = [];
                    if(typeof tcart === 'undefined' || !tcart.products) return products;

                    const productsWrapper = document.querySelector('.t706__cartpage-products, .t706__cartwin-products');
                    if(!productsWrapper) return products;

                    const prodElements = productsWrapper.querySelectorAll('.t706__product');

                    prodElements.forEach((prodEl, i) => {
                        let productImage = '';
                        const thumb = prodEl.querySelector('.t706__product-thumb');
                        if(thumb) { 
                            const imgDiv = thumb.querySelector('.t706__product-imgdiv'); 
                            if(imgDiv) productImage = extractBgImageUrl(imgDiv); 
                        }

                        const titleEl = prodEl.querySelector('.t706__product-title');
                        const titleHTML = titleEl ? replaceDateInHTML(titleEl.outerHTML) : '<span>Без названия</span>';

                        const skuEl = prodEl.querySelector('.t706__product-sku, .t706__product-descr, .t706__product-options');
                        const skuHTML = skuEl ? replaceDateInHTML(skuEl.outerHTML) : '';

                        const productIndex = prodEl.getAttribute('data-cart-product-i');
                        let pricePerOne = 0, totalPrice = 0, quantity = 0;

                        if(productIndex !== null && tcart.products[productIndex]) {
                            const p = tcart.products[productIndex]; 
                            pricePerOne = p.price || 0; 
                            totalPrice = p.amount || 0; 
                            quantity = p.quantity || 0;
                        }

                        products.push({ 
                            index: productIndex !== null ? Number(productIndex) + 1 : i + 1, 
                            image: productImage, 
                            titleHTML, 
                            skuHTML,
                            pricePerOne, 
                            totalPrice, 
                            quantity, 
                            pricePerOneFormatted: formatPrice(pricePerOne), 
                            totalPriceFormatted: formatPrice(totalPrice) 
                        });
                    });
                    return products;
                }


                function buildCompactTotals(currency) {
                    if(typeof tcart === 'undefined') return '';

                    const prodamount = tcart.prodamount || 0;
                    const amount = tcart.amount || 0;
                    const discount = prodamount - amount;

                    let html = '<div class="compact-totals">';
                    html += `<div class="totals-line"><span class="totals-label">Сумма:</span><span class="totals-value">${formatPrice(prodamount)} ${currency}</span></div>`;

                    if(discount > 0) {
                        html += `<div class="totals-line"><span class="totals-label">Скидка:</span><span class="totals-value">${formatPrice(discount)} ${currency}</span></div>`;
                    }

                    html += `<div class="totals-line total-final"><span class="totals-label">Итоговая сумма:</span><span class="totals-value">${formatPrice(amount)} ${currency}</span></div>`;
                    html += '</div>';

                    return html;
                }

                function collectCartTotals() {
                    const el = document.querySelector('.t706__cartpage-totals, .t706__cartwin-totalamount-wrap');

                    return el ? replaceDateInHTML(el.outerHTML) : '';
                }

                function buildInvoiceHTML(orderData, fontFam) {
                    const { meta, products, currency, totalsHTML } = orderData;

                    const headerImg = meta.headerImage 
                        ? `<img src="${meta.headerImage}" alt="Header" class="full-width-image" crossorigin="anonymous" onerror="this.style.display='none'">` 
                        : '';

                    const titleHTML = meta.pdfTitleHTML 
                        ? `<div class="header-title">${meta.pdfTitleHTML}</div>` 
                        : '<div class="header-title"><h1>КОММЕРЧЕСКОЕ ПРЕДЛОЖЕНИЕ</h1></div>';

                    const rows = products.map((p, i) => {
                        const photo = p.image 
                            ? `<img src="${p.image}" class="product-thumb" crossorigin="anonymous" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'"><div class="product-thumb no-image" style="display:none">—</div>` 
                            : `<div class="product-thumb no-image">—</div>`;

                        const titleBlock = p.skuHTML 
                            ? `<div class="product-title">${p.titleHTML}</div><div class="product-sku">${p.skuHTML}</div>`
                            : `<div class="product-title">${p.titleHTML}</div>`;

                        return `<tr>
                            <td class="num-cell">${p.index}</td>
                            <td class="photo-cell">${photo}</td>
                            <td class="title-cell">${titleBlock}</td>
                            <td class="price-cell">${p.pricePerOneFormatted} ${currency}</td>
                            <td class="qty-cell">${p.quantity}</td>
                            <td class="total-cell">${p.totalPriceFormatted} ${currency}</td>
                        </tr>`;
                    }).join('');

       
                    const compactTotals = buildCompactTotals(currency);
                    const totalRow = compactTotals 
                        ? `<tr class="total-row"><td colspan="6" class="totals-cell">${compactTotals}</td></tr>` 
                        : (totalsHTML ? `<tr class="total-row"><td colspan="6" class="totals-cell">${totalsHTML}</td></tr>` : '');

                    const footerDesc = meta.pdfDescrHTML 
                        ? `<div class="footer-desc">${meta.pdfDescrHTML}</div>` 
                        : '';

                    const footerImg = meta.footerImage 
                        ? `<img src="${meta.footerImage}" alt="Footer" class="full-width-image padding-image" crossorigin="anonymous" onerror="this.style.display='none'">` 
                        : '';

                    return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { 
        font-family: ${fontFam}, Arial, sans-serif; 
        -webkit-font-smoothing: antialiased;
        background: #fff;
        color: #000;
        line-height: 1.4;
        font-size: 14px;
    }
    .invoice-wrapper { width: 800px; }
    .full-width-image { width: 100%; display: block; }
    .padding-image { margin-top: 20px; }
    .main-content { padding: 20px 30px; }
    .invoice-header { margin-bottom: 20px; }
    .header-title h1 { font-size: 20px; font-weight: bold; margin: 0 0 10px 0; }
    .header-title h2 { font-size: 16px; font-weight: normal; margin: 0 0 5px 0; }
    .header-title p { font-size: 14px; margin: 0 0 3px 0; }

    .invoice-table { 
        width: 100%; 
        border-collapse: collapse; 
        font-size: 13px;
        margin-bottom: 0;
    }
    .invoice-table thead th {
        background: #999;
        color: #fff;
        padding: 10px 8px;
        text-align: left;
        font-weight: normal;
        border: none;
    }
    .invoice-table thead th.num-cell { width: 40px; text-align: center; }
    .invoice-table thead th.photo-cell { width: 80px; }
    .invoice-table thead th.price-cell { width: 110px; text-align: right; }
    .invoice-table thead th.qty-cell { width: 60px; text-align: center; }
    .invoice-table thead th.total-cell { width: 110px; text-align: right; }

    .invoice-table tbody td {
        padding: 12px 8px;
        border-bottom: 1px solid #e0e0e0;
        vertical-align: top;
    }
    .invoice-table tbody td.num-cell { text-align: center; width: 40px; }
    .invoice-table tbody td.photo-cell { width: 80px; }
    .invoice-table tbody td.price-cell { text-align: right; width: 110px; white-space: nowrap; }
    .invoice-table tbody td.qty-cell { text-align: center; width: 60px; }
    .invoice-table tbody td.total-cell { text-align: right; width: 110px; white-space: nowrap; }

    .product-thumb { 
        width: 60px; 
        height: 60px; 
        object-fit: cover; 
        display: block;
        border: 1px solid #ddd;
    }
    .product-thumb.no-image {
        display: flex;
        align-items: center;
        justify-content: center;
        background: #f5f5f5;
        font-size: 18px;
        color: #999;
    }
    .product-title { font-size: 13px; margin-bottom: 4px; }
    .product-title * { font-size: 13px !important; margin: 0 !important; }
    .product-sku { font-size: 11px; color: #666; }
    .product-sku * { font-size: 11px !important; color: #666 !important; margin: 0 !important; }

    /* === ИСПРАВЛЕНИЕ БЛОКА ИТОГОВ === */
    .total-row td { 
        border-top: 2px solid #333 !important; 
        background: #f9f9f9; 
        padding: 0 !important;
    }
    .totals-cell { 
        text-align: right !important; 
        padding: 0 !important;
    }

    /* Компактные итоги */
    .compact-totals {
        display: inline-block;
        text-align: right;
        padding: 12px 20px;
        min-width: 250px;
    }
    .compact-totals .totals-line {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        margin-bottom: 6px;
        font-size: 13px;
        white-space: nowrap;
    }
    .compact-totals .totals-line:last-child {
        margin-bottom: 0;
    }
    .compact-totals .totals-label {
        margin-right: 20px;
        color: #333;
    }
    .compact-totals .totals-value {
        font-weight: normal;
        white-space: nowrap;
    }
    .compact-totals .total-final {
        margin-top: 8px;
        padding-top: 8px;
        border-top: 1px solid #ccc;
        font-weight: bold;
        font-size: 14px;
    }
    .compact-totals .total-final .totals-label,
    .compact-totals .total-final .totals-value {
        font-weight: bold;
    }

    /* Fallback для Tilda-HTML итогов */
    .totals-cell > * { text-align: right !important; }
    .totals-cell > * > * { text-align: right !important; }

    .footer-desc { margin-top: 20px; font-size: 13px; }
    .footer-desc * { font-size: 13px !important; }
</style>
<base target="_blank">
</head>
<body>
<div class="invoice-wrapper t-text">
    ${headerImg}
    <div class="main-content">
        <div class="invoice-header">${titleHTML}</div>
        <table class="invoice-table">
            <thead>
                <tr>
                    <th class="num-cell">№</th>
                    <th class="photo-cell">Фото</th>
                    <th class="title-cell">Наименование товара / услуги</th>
                    <th class="price-cell">Цена</th>
                    <th class="qty-cell">Кол-во</th>
                    <th class="total-cell">Сумма</th>
                </tr>
            </thead>
            <tbody>${rows}${totalRow}</tbody>
        </table>
        <div class="invoice-footer">${footerDesc}</div>
    </div>
    ${footerImg}
</div>
</body>
</html>`;
                }

                async function renderAndCapture(fullHTML) {
                    return new Promise(async (resolve, reject) => {
                        const iframe = document.createElement('iframe');
                        iframe.style.cssText = 'position:fixed;left:-9999px;top:0;width:800px;height:100px;border:none;visibility:hidden;pointer-events:none;';
                        document.body.appendChild(iframe);

                        const doc = iframe.contentDocument || iframe.contentWindow.document;
                        doc.open();
                        doc.write(fullHTML);
                        doc.close();

                        const images = doc.querySelectorAll('img');
                        let loaded = 0;
                        const total = images.length;

                        const onReady = async () => {
                            try {
                                const wrapper = doc.querySelector('.invoice-wrapper');
                                const dataUrl = await window.htmlToImage.toPng(wrapper, {
                                    quality: 0.95,
                                    pixelRatio: 1.5,
                                    backgroundColor: '#ffffff',
                                    skipFonts: true,
                                    cacheBust: false
                                });
                                resolve({ dataUrl, iframe });
                            } catch(e) {
                                reject(e);
                            }
                        };

                        if(total === 0) {
                            setTimeout(onReady, 300);
                            return;
                        }

                        const checkComplete = () => {
                            loaded++;
                            if(loaded >= total) setTimeout(onReady, 200);
                        };

                        images.forEach(img => {
                            if(img.complete && img.naturalHeight > 0) {
                                checkComplete();
                            } else {
                                img.onload = checkComplete;
                                img.onerror = checkComplete;
                            }
                        });

                        setTimeout(onReady, 5000);
                    });
                }

                async function directGeneratePDF(clickedBtn) {
                    const startTime = performance.now();

                    await ensureLibs();
                    if (!window.jspdf || !window.htmlToImage) {
                        alert('Библиотеки не загрузились.');
                        return;
                    }

                    clickedBtn.classList.add('is-generating');
                    const btnTextEl = clickedBtn.querySelector('.button-text');
                    const originalText = btnTextEl.textContent.trim();
                    btnTextEl.textContent = 'Создание PDF...';

                    let iframe = null;

                    try {
                        const t1 = performance.now();
                        const meta = collectPdfMetaData();
                        const products = collectCartProducts();
                        const currency = (typeof tcart !== 'undefined' && tcart.currency_txt) ? tcart.currency_txt : '₽';
                        const totalsHTML = collectCartTotals();
                        const orderData = { meta, products, currency, totalsHTML };
                        const projectFont = getProjectFont();
                        console.log('[PDF] Data collection:', Math.round(performance.now() - t1), 'ms');

                        const t2 = performance.now();
                        const fullHTML = buildInvoiceHTML(orderData, projectFont);
                        console.log('[PDF] HTML build:', Math.round(performance.now() - t2), 'ms');

                        const t3 = performance.now();
                        const { dataUrl, iframe: ifr } = await renderAndCapture(fullHTML);
                        iframe = ifr;
                        console.log('[PDF] Render + capture:', Math.round(performance.now() - t3), 'ms');

                        const t4 = performance.now();
                        const { jsPDF } = window.jspdf;
                        const pdf = new jsPDF('p', 'mm', 'a4');
                        const pageW = pdf.internal.pageSize.getWidth();
                        const pageH = pdf.internal.pageSize.getHeight();

                        const img = new Image();
                        img.src = dataUrl;
                        await new Promise((resolve, reject) => {
                            img.onload = resolve;
                            img.onerror = reject;
                            setTimeout(resolve, 3000);
                        });

                        const imgW_mm = pageW;
                        const imgH_mm = (img.height * imgW_mm) / img.width;

                        let heightLeft = imgH_mm;
                        let position_mm = 0;
                        pdf.addImage(dataUrl, 'PNG', 0, position_mm, imgW_mm, imgH_mm);
                        heightLeft -= pageH;
                        while (heightLeft > 0.5) {
                            position_mm = heightLeft - imgH_mm;
                            pdf.addPage();
                            pdf.addImage(dataUrl, 'PNG', 0, position_mm, imgW_mm, imgH_mm);
                            heightLeft -= pageH;
                        }

                        pdf.save(`КП-${getCurrentDateFormatted().replace(/\./g, '-')}.pdf`);
                        console.log('[PDF] jsPDF generation:', Math.round(performance.now() - t4), 'ms');
                        console.log('[PDF] TOTAL:', Math.round(performance.now() - startTime), 'ms');

                    } catch (e) {
                        console.error('PDF generation error:', e);
                        alert('Ошибка генерации: ' + e.message);
                    } finally {
                        if (iframe) iframe.remove();
                        clickedBtn.classList.remove('is-generating');
                        btnTextEl.textContent = originalText;
                    }
                }

                function createPrintBtn() {
                    const div = document.createElement('div'); 
                    div.className = 'print-price-button t-text';
                    div.innerHTML = `<div class="button-wrapper"><div class="button-icon"></div><div class="button-text">Скачать PDF</div></div>`; 
                    return div;
                }

                function addBtnToCart() {
                    const btn = createPrintBtn(); 
                    const cp = document.querySelector('.t706__cartpage'); 
                    const simple = document.querySelector('.t706__cartwin-content');
                    if(cp) {
                        let wrap = cp.querySelector('.t706__cartpage-info-wrapper');
                        if(wrap) wrap.insertAdjacentElement('afterend', btn.cloneNode(true));
                    } else if(simple) { 
                        const wrap = simple.querySelector('.t706__cartwin-bottom'); 
                        if(wrap) wrap.insertAdjacentElement('afterend', btn.cloneNode(true)); 
                    }
                }
                addBtnToCart();

                document.addEventListener('click', function(e) {
                    const btn = e.target.closest('.print-price-button');
                    if(btn) {
                        e.preventDefault();
                        directGeneratePDF(btn);
                    }
                });
            });
        }, 200);
    });
});
</script>
КОММЕРЧЕСКОЕ ПРЕДЛОЖЕНИЕ ОТ {date}

ООО "СтройТехноМаш"
ИНН 7701234567
КПП 770101001
Реквизиты для оплаты:
Банк: ПАО Сбербанк
БИК: 044525225
Оплата в течение 10 банковских дней. НДС не облагается
Made on
Tilda