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
相关产品推荐
相关产品推荐

