在Odoo后端填充pdf form - xiaohao0576/odoo-doc GitHub Wiki

使用前注意事项

本方案旨在使用Odoo online SaaS版本,在纯后端使用pdf form模板和字段生成pdf文件。如果需要在前端生成pdf,完全可以使用javascript在浏览器填充表单,不要选择这个方案

实现思路

  1. 安装sale.pdf.quote_builder模块,这个模块扩展了_render_qweb_pdf_prepare_streams方法
  2. 使用pdf编辑软件,设计pdf模板,制作带字段的pdf表单
  3. 在产品中上传上一步制作的pdf到产品document, 设置attached_on_sale的值为inside
  4. 制作sale.order报价单,加入上一步的产品,在pdf builer中配置字段的值,Odoo会自动填充customizable_pdf_form_fields这个json字段
  5. 创建服务器动作,使用python代码先取出动态字段,填充到上一步的customizable_pdf_form_fields中
  6. 在python代码中调用sale.report_saleorder这个报表的_render_qweb_pdf_prepare_streams方法,生成stream数组,里面包含io.BytesIO类型
  7. 再调用_pdf_split(self, new_files=None, open_files=None):这个方法,上一步的steam数据,正好可以传入这个方法的 open_files参数中,用于提取出需要的pdf页面
  8. 最终可以得到一个ir.attachment,里面保存的是需要的pdf页面,并且是已经填充好的数据

注意事项

  • 在调用_pdf_split方法时,注意要在context中加入no_document,不去创建多余的document,只创建ir.attachment

相关代码

_render_qweb_pdf_prepare_streams

https://github.com/odoo/odoo/blob/707bde764139aedb29a77e896e59bbd9461ae988/addons/sale_pdf_quote_builder/models/ir_actions_report.py#L21

_pdf_split

这个代码在odoo企业版中,如下

    @api.model
    def _pdf_split(self, new_files=None, open_files=None):
        """Creates and returns new pdf attachments based on existing data.

        :param new_files: the array that represents the new pdf
            structure::

                [{
                    'name': 'New File Name',
                    'new_pages': [{
                        'old_file_index': 7,
                        'old_page_number': 5,
                    }],
                }]
        :param open_files: array of open file objects.
        :returns: the new PDF attachments
        """
        vals_list = []
        pdf_from_files = [OdooPdfFileReader(open_file, strict=False) for open_file in open_files]
        
        for new_file in new_files:
            output = OdooPdfFileWriter()
            used_pages_by_pdf = defaultdict(set)
            for page in new_file['new_pages']:
                file_index = int(page['old_file_index'])
                page_index = page['old_page_number'] - 1
                input_pdf = pdf_from_files[file_index]
                output.addPage(input_pdf.getPage(page_index))
                used_pages_by_pdf[file_index].add(page_index)
                if len(used_pages_by_pdf[file_index]) != input_pdf.getNumPages():
                    continue
                try:
                    for fname, fcontent in input_pdf.getAttachments():
                        output.addAttachment(name=fname, data=fcontent)
                except Exception:  # noqa: BLE001
                    _logger.warning(
                        'Impossible to add (all) attachments from pdf at index %i',
                        file_index, exc_info=True,
                    )
            with io.BytesIO() as stream:
                output.write(stream)
                vals_list.append({
                    'name': new_file['name'] + ".pdf",
                    'datas': base64.b64encode(stream.getvalue()),
                })
        return self.create(vals_list)

    @api.model_create_multi
    def create(self, vals_list):
        attachments = super().create(vals_list)
        for attachment, vals in zip(attachments, vals_list):
            # the context can indicate that this new attachment is created from documents, and therefore
            # doesn't need a new document to contain it.
            if not self.env.context.get('no_document') and not attachment.res_field:
                attachment.sudo()._create_document(dict(vals, res_model=attachment.res_model, res_id=attachment.res_id))
        return attachments