我是 odoo 的新手,在模块开发方面有一点经验,我有一个与使公司印章出现在销售报价中相关的问题,我通过继承视图并使用我的自定义模块添加印章使它出现在那里一切都工作正常,但问题是每个报价都会有邮票,所以我决定在视图上创建一个名为“无邮票报价”的按钮,这样如果我单击这个特定按钮,它将给我发票,而不会邮票我做了一些研究,我发现最好的解决方案是复制发票模板并将其粘贴在那里,它会正常打印而无需邮票,但它不起作用,我希望你能帮助或指导我。
这是代码和以下错误:
无需盖章即可生成pdf的按钮
model.py 文件 从 odoo 导入模型
class SaleOrder(models.Model):
_inherit = 'sale.order'
def print_without_stamp(self):
return self.env.ref('md_stamp.action_report_saleorder_document_without_stamp').report_action(self)
报告.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_report_saleorder_document_without_stamp" model="ir.actions.report">
<field name="name">Sales Order without Stamp</field>
<field name="model">sale.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">md_stamp.report_saleorder_document_without_stamp</field>
<field name="report_file">md_stamp.report_saleorder_document_without_stamp</field>
<field name="print_report_name">'Sales Order without Stamp - %s' % (object.name)</field>
</record>
</odoo>
report_saleorder_document.xml(用戳记 xml 打印)
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_saleorder_document_inherit" inherit_id="sale.report_saleorder_document">
<xpath expr="//div[@class='page']" position="inside">
<!-- Add the stamp image here -->
<div class="text-center mt-4">
<img src="/web/static/img/cachet_transp_270x161.png" alt="Stamp" style="width:270px;"/>
</div>
</xpath>
</template>
</odoo>
report_saleorder_document_without_stamp.xml(打印时不加盖章,我从销售报告中复制模板粘贴到这里并更改了id)
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_saleorder_document_without_stamp">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)" />
<t t-set="forced_vat" t-value="doc.fiscal_position_id.foreign_vat"/>
<t t-set="address">
<div t-field="doc.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
<p t-if="doc.partner_id.vat">
<t t-if="doc.company_id.account_fiscal_country_id.vat_label" t-out="doc.company_id.account_fiscal_country_id.vat_label"/>
<t t-else="">Tax ID</t>: <span t-field="doc.partner_id.vat"/>
</p>
</t>
<t t-if="doc.partner_shipping_id == doc.partner_invoice_id
and doc.partner_invoice_id != doc.partner_id
or doc.partner_shipping_id != doc.partner_invoice_id">
<t t-set="information_block">
<strong>
<t t-if="doc.partner_shipping_id == doc.partner_invoice_id">
Invoicing and Shipping Address:
</t>
<t t-else="">
Invoicing Address:
</t>
</strong>
<div t-field="doc.partner_invoice_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
<t t-if="doc.partner_shipping_id != doc.partner_invoice_id">
<strong>Shipping Address:</strong>
<div t-field="doc.partner_shipping_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
</t>
</t>
</t>
<div class="page">
<div class="oe_structure"/>
<h2 class="mt-4">
<span t-if="env.context.get('proforma', False) or is_pro_forma">Pro-Forma Invoice # </span>
<span t-elif="doc.state in ['draft','sent']">Quotation # </span>
<span t-else="">Order # </span>
<span t-field="doc.name">SO0000</span>
</h2>
<div class="row mt-4 mb-2" id="informations">
<div t-if="doc.client_order_ref" class="col-auto col-3 mw-100 mb-2" name="informations_reference">
<strong>Your Reference:</strong><br/>
<span class="m-0" t-field="doc.client_order_ref">SO0000</span>
</div>
<div t-if="doc.date_order" class="col-auto col-3 mw-100 mb-2" name="informations_date">
<strong t-if="doc.state in ['draft', 'sent']">Quotation Date:</strong>
<strong t-else="">Order Date:</strong><br/>
<span class="m-0" t-field="doc.date_order" t-options='{"widget": "date"}'>2023-12-31</span>
</div>
<div t-if="doc.validity_date and doc.state in ['draft', 'sent']"
class="col-auto col-3 mw-100 mb-2"
name="expiration_date">
<strong>Expiration:</strong><br/>
<span class="m-0" t-field="doc.validity_date">2023-12-31</span>
</div>
<div t-if="doc.user_id.name" class="col-auto col-3 mw-100 mb-2">
<strong>Salesperson:</strong><br/>
<span class="m-0" t-field="doc.user_id">Mitchell Admin</span>
</div>
</div>
<!-- Is there a discount on at least one line? -->
<t t-set="lines_to_report" t-value="doc._get_order_lines_to_report()"/>
<t t-set="display_discount" t-value="any(l.discount for l in lines_to_report)"/>
<div class="oe_structure"></div>
<table class="table table-sm o_main_table table-borderless mt-4">
<!-- In case we want to repeat the header, remove "display: table-row-group" -->
<thead style="display: table-row-group">
<tr>
<th name="th_description" class="text-start">Description</th>
<th name="th_quantity" class="text-end">Quantity</th>
<th name="th_priceunit" class="text-end">Unit Price</th>
<th name="th_discount" t-if="display_discount" class="text-end">
<span>Disc.%</span>
</th>
<th name="th_taxes" class="text-end">Taxes</th>
<th name="th_subtotal" class="text-end">
<span>Amount</span>
</th>
</tr>
</thead>
<tbody class="sale_tbody">
<t t-set="current_subtotal" t-value="0"/>
<t t-foreach="lines_to_report" t-as="line">
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal"/>
<tr t-att-class="'bg-200 fw-bold o_line_section' if line.display_type == 'line_section' else 'fst-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="not line.display_type">
<td name="td_name"><span t-field="line.name">Bacon Burger</span></td>
<td name="td_quantity" class="text-end">
<span t-field="line.product_uom_qty">3</span>
<span t-field="line.product_uom">units</span>
<span t-if="line.product_packaging_id">
(<span t-field="line.product_packaging_qty" t-options='{"widget": "integer"}'/> <span t-field="line.product_packaging_id"/>)
</span>
</td>
<td name="td_priceunit" class="text-end">
<span t-field="line.price_unit">3</span>
</td>
<td t-if="display_discount" class="text-end">
<span t-field="line.discount">-</span>
</td>
<t t-set="taxes" t-value="', '.join([(tax.invoice_label or tax.name) for tax in line.tax_id])"/>
<td name="td_taxes" t-attf-class="text-end {{ 'text-nowrap' if len(taxes) < 10 else '' }}">
<span t-out="taxes">Tax 15%</span>
</td>
<td t-if="not line.is_downpayment" name="td_subtotal" class="text-end o_price_total">
<span t-field="line.price_subtotal">27.00</span>
</td>
</t>
<t t-elif="line.display_type == 'line_section'">
<td name="td_section_line" colspan="99">
<span t-field="line.name">A section title</span>
</td>
<t t-set="current_section" t-value="line"/>
<t t-set="current_subtotal" t-value="0"/>
</t>
<t t-elif="line.display_type == 'line_note'">
<td name="td_note_line" colspan="99">
<span t-field="line.name">A note, whose content usually applies to the section or product above.</span>
</td>
</t>
</tr>
<t t-if="current_section and (line_last or lines_to_report[line_index+1].display_type == 'line_section') and not line.is_downpayment">
<tr class="is-subtotal text-end">
<td name="td_section_subtotal" colspan="99">
<strong class="mr16">Subtotal</strong>
<span
t-out="current_subtotal"
t-options='{"widget": "monetary", "display_currency": doc.currency_id}'
>31.05</span>
</td>
</tr>
</t>
</t>
</tbody>
</table>
<div class="clearfix" name="so_total_summary">
<div id="total" class="row" name="total">
<div t-attf-class="#{'col-6' if report_type != 'html' else 'col-sm-7 col-md-6'} ms-auto">
<table class="table table-sm table-borderless">
<!-- Tax totals -->
<t t-set="tax_totals" t-value="doc.tax_totals"/>
<t t-call="sale.document_tax_totals"/>
</table>
</div>
</div>
</div>
<div class="oe_structure"></div>
<div t-if="not doc.signature" class="oe_structure"></div>
<div t-else="" class="mt-4 ml64 mr4" name="signature">
<div class="offset-8">
<strong>Signature</strong>
</div>
<div class="offset-8">
<img t-att-src="image_data_uri(doc.signature)" style="max-height: 4cm; max-width: 8cm;"/>
</div>
<div class="offset-8 text-center">
<span t-field="doc.signed_by">Oscar Morgan</span>
</div>
</div>
<div>
<span t-field="doc.note" t-attf-style="#{'text-align:justify;text-justify:inter-word;' if doc.company_id.terms_type != 'html' else ''}" name="order_note"/>
<p t-if="not is_html_empty(doc.payment_term_id.note)">
<span t-field="doc.payment_term_id.note">The payment should also be transmitted with love</span>
</p>
<div class="oe_structure"/>
<p t-if="doc.fiscal_position_id and not is_html_empty(doc.fiscal_position_id.sudo().note)"
id="fiscal_position_remark">
<strong>Fiscal Position Remark:</strong>
<span t-field="doc.fiscal_position_id.sudo().note">No further requirements for this payment</span>
</p>
</div>
<div class="oe_structure"/>
</div>
</t>
</template>
</odoo>
view.xml(不带印章按钮的打印视图)
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<header>
<button name="print_without_stamp" type="object" string="Print without Stamp" class="oe_highlight"/>
</header>
</field>
</record>
</odoo>
清单文件
# -*- coding: utf-8 -*-
{
'name': "md_stamp",
'summary': "Short (1 phrase/line) summary of the module's purpose",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
'category': 'Uncategorized',
'version': '0.1',
'depends': ['base', 'sale'],
'data': [
'views/report_saleorder_document.xml',
'views/report_saleorder_document_without_stamp.xml',
'views/report.xml', # Include the report action definition
'views/views.xml',
],
'demo': [
'demo/demo.xml',
],
}
现在,当我打印 odoo 默认打印 pdf 按钮时,它会正常打印带有图章的内容,但是当我使用按钮“不带图章打印”时,我收到此错误:
RPC_ERROR
Odoo Server Error
Traceback (most recent call last):
File "<1825>", line 1358, in template_1825
File "<1825>", line 1340, in template_1825_content
File "<1825>", line 194, in template_1825_t_call_0
KeyError: 'doc'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\web\controllers\report.py", line 113, in report_download
response = self.report_routes(reportname, docids=docids, converter=converter, context=context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\http.py", line 741, in route_wrapper
result = endpoint(self, *args, **params_ok)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\web\controllers\report.py", line 42, in report_routes
pdf = report.with_context(context)._render_qweb_pdf(reportname, docids, data=data)[0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\account\models\ir_actions_report.py", line 61, in _render_qweb_pdf
return super()._render_qweb_pdf(report_ref, res_ids=res_ids, data=data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\base\models\ir_actions_report.py", line 899, in _render_qweb_pdf
collected_streams = self._render_qweb_pdf_prepare_streams(report_ref, data, res_ids=res_ids)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\sale_pdf_quote_builder\models\ir_actions_report.py", line 17, in _render_qweb_pdf_prepare_streams
result = super()._render_qweb_pdf_prepare_streams(report_ref, data, res_ids=res_ids)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\account\models\ir_actions_report.py", line 20, in _render_qweb_pdf_prepare_streams
return super()._render_qweb_pdf_prepare_streams(report_ref, data, res_ids=res_ids)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\base\models\ir_actions_report.py", line 749, in _render_qweb_pdf_prepare_streams
html = self.with_context(**additional_context)._render_qweb_html(report_ref, all_res_ids_wo_stream, data=data)[0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\base\models\ir_actions_report.py", line 949, in _render_qweb_html
return self._render_template(report.report_name, data), 'html'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\base\models\ir_actions_report.py", line 669, in _render_template
return view_obj._render_template(template, values).encode()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\base\models\ir_ui_view.py", line 2051, in _render_template
return self.env['ir.qweb']._render(template, values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\tools\profiler.py", line 292, in _tracked_method_render
return method_render(self, template, values, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Odoo 17.0.20240729\server\odoo\addons\base\models\ir_qweb.py", line 599, in _render
result = ''.join(rendering)
^^^^^^^^^^^^^^^^^^
File "<1825>", line 1364, in template_1825
odoo.addons.base.models.ir_qweb.QWebException: Error while render the template
KeyError: 'doc'
Template: md_stamp.report_saleorder_document_without_stamp
Path: /t/t/t[2]
Node: <t t-set="forced_vat" t-value="doc.fiscal_position_id.foreign_vat"/>
The above server error caused the following client error:
RPC_ERROR: Odoo Server Error
RPC_ERROR
at makeErrorFromResponse (http://localhost:8069/web/assets/ffa11b5/web.assets_web.min.js:2886:163)
at decoder.onload (http://localhost:8069/web/assets/ffa11b5/web.assets_web.min.js:2872:7)
使用我的自定义模块使“无印章打印”按钮正确运行
该错误表明第 5 行需要“doc”,但在
report_saleorder_document_without_stamp
模板中未定义。由于某种原因,错误没有发生在第 4 行,但如果您注意到第 4 行执行了 t-set="doc" 但需要预先存在的“doc”变量。
如果你看一下 Odoo 的“sale”插件,你会发现他们的 action_report_sale 订单调用了
report_saleorder
模板(大约第 189 行),该模板循环遍历“docs”以在每次迭代时设置一个“doc”变量,只有这样 report_saleorder_document
模板才会被调用。您复制的模板应该缺少此步骤。
<!-- addons/sale/report/ir_actions_report_templates.xml:line 189 -->
<template id="report_saleorder_raw">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="sale.report_saleorder_document" t-lang="doc.partner_id.lang"/>
</t>
</t>
</template>
补充一下,如果您打算批量复制模板,可以考虑直接在report.xml中的“report_name”字段中调用原始模板。
或者,您可以执行
<t t-call="sale.report_saleorder_document" />
而不是显式复制所有内容。这样,将来当“销售”插件更新时,您不必再次复制所有内容。