You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Odoo 17自定义发票创建报错:应收款科目分录需设到期日

问题排查:Odoo 17创建发票时的"应收账户分录需有到期日"报错

背景

采购第三方货运软件后新增cartage模块,供CCC公司使用;原模块归AAA、BBB公司使用,三者为同母公司下属姊妹公司,需互相开具发票。原代码无需激活Odoo Accounting模块即可用默认发票模板运行,新模块参考原模板开发,需激活Accounting模块,采用多公司架构并使用自定义发票模板。

核心代码片段

获取合作伙伴方法

def _get_partner_from_bill_cartage(self, bill_cartage):
        if not bill_cartage:
            return self.env['res.partner']
        
        bill_code = bill_cartage.lower()
        
        # Hardcoded mapping for company codes to exact names
        partner_mapping = {
            'aaa': 'AAA',
            'bbb': 'BBB',
        }
        
        partner_name = partner_mapping.get(bill_code)
        
        if not partner_name:
            raise UserError(
                f"Unknown bill_cartage code: '{bill_cartage}'. "
                f"Valid codes are: {', '.join(partner_mapping.keys())}"
            )
        
        # For multi-company: Search for the partner record linked to the sister company
        # We need to find the res.partner that represents the company
        partner = self.env['res.partner'].sudo().search([
            ('name', '=', partner_name),
            ('is_company', '=', True)
        ], limit=1)
        
        if partner:
            return partner
        
        # Fallback to flexible search
        partner = self.env['res.partner'].sudo().search([
            ('name', 'ilike', partner_name),
            ('is_company', '=', True)
        ], limit=1)
        
        if not partner:
            raise UserError(
                f"Partner/Company '{partner_name}' not found in the system. "
                f"Please ensure the company exists and has a corresponding partner record."
            )
        
        return partner

创建发票方法

def action_create_invoice(self):
        current_company = self.env.company
        
        cartage_charges = self.env['cartage.service.charge'].with_company(current_company).search([
            ('commissioned_vehicle_id', '=', self.id),
            ('invoiced', '=', False)
        ])
        
        if not cartage_charges:
            raise UserError("No cartage service charges found to invoice.")
            
        bill_cartage_list = cartage_charges.mapped('bill_cartage')
        
        sale_journal = self.env['account.journal'].search([
            ('company_id', '=', current_company.id),
            ('type', '=', 'sale')
        ], limit=1)

        if not sale_journal:
            raise UserError(
                f"Configuration Error: No Sales Journal of type 'sale' found for company '{current_company.name}'."
            )
        
        for bill_cartage in set(bill_cartage_list):
            partner = self._get_partner_from_bill_cartage(bill_cartage)
            
            charges_to_invoice = cartage_charges.filtered(
                lambda c: c.bill_cartage == bill_cartage and not c.invoiced
            )
            
            if not charges_to_invoice:
                continue
            
            _logger.info(f"DEBUG: Creating invoice for bill_cartage: {bill_cartage}")
            _logger.info(f"DEBUG: Partner: {partner.name} (ID: {partner.id})")
            _logger.info(f"DEBUG: Current Company: {current_company.name} (ID: {current_company.id})")
            _logger.info(f"DEBUG: Sale Journal: {sale_journal.name} (ID: {sale_journal.id})")
            _logger.info(f"DEBUG: Number of charges to invoice: {len(charges_to_invoice)}")
    
            # Build invoice lines

            invoice_line_ids = []
            for charge in charges_to_invoice:
                if not charge.service_id:
                    raise UserError(f"Service ID is missing for charge.")
                
                product = charge.service_id
                
                # Verify income account exists
                income_account = product.property_account_income_id or \
                            product.categ_id.property_account_income_categ_id
                
                if not income_account:
                    raise UserError(
                        f"No income account configured for product '{product.display_name}'."
                    )
                
                invoice_line_ids.append((0, 0, {
                    'product_id': product.id,
                    'name': charge.name or product.name,
                    'quantity': 1.0,
                    'price_unit': charge.amount_price,
                }))
            
            # Create invoice - Odoo will auto-compute payment terms from partner
            invoice = self.env['account.move'].with_context(
                default_move_type='out_invoice'
            ).create({
                'partner_id': partner.id,
                'move_type': 'out_invoice',
                'invoice_date': fields.Date.today(),
                'invoice_date_due': fields.Date.today(),
                'commissioned_vehicle_id': self.id,
                'company_id': current_company.id,
                'journal_id': sale_journal.id,
                'invoice_line_ids': invoice_line_ids,
            })
            
            # Mark as invoiced
            charges_to_invoice.write({'invoiced': True})
        
        return self.button_customer_invoices()

查看发票按钮方法

def button_customer_invoices(self):
        invoices = self.env['account.move'].sudo().search([
            ('commissioned_vehicle_id', '=', self.id),
            ('move_type', '=', 'out_invoice')
        ])
        
        action = self.env["ir.actions.actions"]._for_xml_id(
            "account.action_move_out_invoice_type"
        )
        
        action['context'] = {
            'default_commissioned_vehicle_id': self.id,
            'default_move_type': 'out_invoice',
        }
        
        if len(invoices) == 1:
            form_view = [(self.env.ref('account.view_move_form').id, 'form')]
            if 'views' in action:
                action['views'] = form_view + [
                    (state, view) for state, view in action['views'] if view != 'form'
                ]
            else:
                action['views'] = form_view
            action['res_id'] = invoices.id
        else:
            action['domain'] = [('id', 'in', invoices.ids)]
        
        return action

问题现象

点击树视图按钮调用action_create_invoice创建发票时,触发报错:

Invalid Operation
Any journal item on a receivable account must have a due date and vice versa.

日志片段:

DEBUG: Creating invoice for bill_cartage: aaa
DEBUG: Partner: AAAA (ID: 875)
DEBUG: Current Company: CCCC (ID: 8)
DEBUG: Sale Journal: CCCC Invoices (ID: 9)
DEBUG: Number of charges to invoice: 1
odoo.http: Any journal item on a receivable account must have a due date and vice versa.

排查方向与解决方案

1. 合作伙伴付款期限配置缺失

Odoo的会计规则要求应收账户分录关联付款期限,即使代码中硬编码了invoice_date_due,若合作伙伴未设置付款期限,仍可能触发校验错误。

  • 解决:打开对应合作伙伴表单(如AAA公司的伙伴记录),切换到「会计」标签页,设置「付款期限」(例如选择"立即付款")。

2. 合作伙伴应收账户配置错误

若合作伙伴的应收账户未正确关联或账户类型不符,会导致分录校验失败。

  • 解决:检查合作伙伴的「应收账户」字段,确保该账户属于当前公司(CCC),且账户类型为「应收」(在会计科目表单中查看「类型」字段)。

3. 代码中到期日设置不符合Odoo逻辑

硬编码invoice_date_due为今日可能绕过了Odoo的付款期限计算逻辑,导致系统认为缺少必要的配置关联。

  • 优化代码:修改action_create_invoice中创建发票的参数,从合作伙伴的付款期限计算到期日:
# 替换原invoice_date_due字段赋值
payment_term = partner.property_payment_term_id
invoice_date_due = payment_term.compute(fields.Date.today())[0] if payment_term else fields.Date.today()

# 创建发票时使用计算后的日期
invoice = self.env['account.move'].with_context(
    default_move_type='out_invoice'
).create({
    # 其他字段保持不变
    'invoice_date_due': invoice_date_due,
})

4. 销售日记账配置问题

销售日记账的默认应收账户若未正确设置,会导致生成的分录缺少必要的账户关联。

  • 解决:打开销售日记账(如"CCCC Invoices"),检查「默认贷方账户」是否为当前公司的应收账户。

5. 产品/服务的税务配置缺失

若产品关联了税务,但税务未配置对应的应收/收入账户,可能导致分录结构错误。

  • 解决:检查产品的税务设置,确保税务科目已正确配置对应的会计账户。

内容的提问来源于stack exchange,提问作者Sheenergizer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 23:44:50