Odoo发票管理增强套件 - 全面提升企业开票效率

Odoo发票管理增强套件

项目描述

account-invoicing是一个专为Odoo企业资源规划系统设计的发票管理增强套件。该项目包含多个功能模块,全面覆盖发票处理的各个环节,从创建、折扣计算、发送到后期管理,为企业提供完整的发票解决方案。所有模块均基于Odoo Community Association (OCA)开发,确保代码质量和兼容性。

功能特性

核心功能模块

  • 全局折扣管理 - 支持在发票级别应用全局折扣,自动计算折扣金额并更新相关会计科目
  • 自动邮件发送 - 对配置为邮件传输方式的发票自动发送,支持队列作业处理
  • 到期日期编辑 - 允许在发票过账后编辑到期日期,增强灵活性
  • 税种强制检查 - 确保每张发票行都设置了正确的税种,避免税务错误
  • 供应商信息更新 - 自动检测并更新产品供应商信息,保持采购数据同步
  • 退款原因管理 - 为贷项通知单定义标准化的退款原因,便于统计分析
  • 多销售订单分组 - 在合并开票时按销售订单分组显示,提高可读性
  • 汇率显示 - 清晰展示发票使用的货币汇率,支持多币种环境
  • 供应商发票号唯一性检查 - 防止重复录入供应商发票,避免重复付款风险

技术特色

  • 完整的权限控制体系,确保数据安全
  • 支持预初始化钩子和后初始化钩子
  • 与Odoo标准模块无缝集成
  • 多公司、多币种支持
  • 响应式设计,适配各种设备

安装指南

系统要求

  • Odoo 18.0 或更高版本
  • Python 3.7+
  • PostgreSQL 9.6+

安装步骤

  1. 将项目克隆到Odoo的addons目录:
cd /path/to/odoo/addons
git clone https://github.com/OCA/account-invoicing.git
  1. 在Odoo中安装所需模块:
# 安装全局折扣模块
module install account_global_discount

# 安装自动邮件发送模块  
module install account_invoice_auto_send_by_email

# 安装其他需要的模块...
  1. 配置依赖项:
    各模块的依赖关系已在manifest文件中定义,Odoo会自动处理。

平台注意事项

  • 所有模块均支持Linux、Windows和macOS平台
  • 建议在生产环境使用前在测试环境充分验证
  • 确保有足够的数据库权限执行表结构变更

使用说明

全局折扣配置

  1. 进入 设置 > 参数 > 全局折扣
  2. 添加新的折扣百分比
  3. 选择折扣适用范围(销售或采购)
  4. 可限制特定公司使用
# 为合作伙伴设置全局折扣示例
partner_id = self.env['res.partner'].browse(partner_id)
partner_id.customer_global_discount_ids = [(6, 0, [discount_id])]

自动邮件发送

配置传输方法为邮件的发票会自动加入发送队列:

# 检查待发送的发票
invoices = self.env['account.move'].search([
    ('transmit_method_id', '=', email_method_id),
    ('state', '=', 'posted'),
    ('is_move_sent', '=', False)
])

到期日期管理

启用到期日期编辑功能:

<!-- 安全组配置 -->
<record id="group_account_invoice_date_due" model="res.groups">
    <field name="name">Allow to change due date</field>
</record>

供应商信息更新

在供应商发票中检查和更新产品信息:

def update_supplierinfo(self):
    """更新产品供应商信息"""
    for line in self.line_ids:
        if not line.supplierinfo_id:
            # 创建新的供应商信息
            vals = line._prepare_supplierinfo()
            self.env['product.supplierinfo'].create(vals)
        else:
            # 更新现有供应商信息
            vals = line._prepare_supplierinfo_update()
            line.supplierinfo_id.write(vals)

核心代码

全局折扣预初始化

def _pre_init_global_discount_fields(env):
    """预初始化全局折扣字段"""
    if not column_exists(env.cr, "account_move", "amount_global_discount"):
        env.cr.execute("""
            ALTER TABLE "account_move"
            ADD COLUMN "amount_global_discount" double precision DEFAULT 0
        """)
        env.cr.execute("""
            ALTER TABLE "account_move" ALTER COLUMN "amount_global_discount" DROP DEFAULT
        """)
    
    # 初始化税前金额字段
    if not column_exists(env.cr, "account_move", "amount_untaxed_before_global_discounts"):
        env.cr.execute("""
            ALTER TABLE "account_move"
            ADD COLUMN "amount_untaxed_before_global_discounts" double precision
        """)
        env.cr.execute("""
            update account_move set amount_untaxed_before_global_discounts = amount_untaxed
        """)

退款原因管理

class AccountMoveReversal(models.TransientModel):
    _inherit = "account.move.reversal"

    reason_id = fields.Many2one("account.move.refund.reason", string="退款原因")
    reason = fields.Char(
        compute="_compute_reason", precompute=True, store=True, readonly=False
    )

    @api.depends("reason_id")
    def _compute_reason(self):
        """计算退款原因"""
        for record in self:
            if record.reason_id:
                record.reason = record.reason_id.name

    def reverse_moves(self, is_modify=False):
        """重写退款创建方法,设置原因字段"""
        res = super().reverse_moves(is_modify=is_modify)
        # 为新创建的退款设置原因
        self.move_ids.reversal_move_ids.filtered(lambda x: not x.reason_id).write(
            {"reason_id": self.reason_id.id}
        )
        return res

供应商信息更新向导

class WizardUpdateInvoiceSupplierinfo(models.TransientModel):
    _name = "wizard.update.invoice.supplierinfo"
    _description = "更新供应商信息向导"

    line_ids = fields.One2many(
        comodel_name="wizard.update.invoice.supplierinfo.line",
        inverse_name="wizard_id",
        string="明细行",
    )
    invoice_id = fields.Many2one(
        comodel_name="account.move",
        required=True,
        readonly=True,
        ondelete="cascade",
    )

    def update_supplierinfo(self):
        """执行供应商信息更新"""
        self.ensure_one()
        supplierinfo_obj = self.env["product.supplierinfo"]
        for line in self.line_ids:
            supplierinfo = line.supplierinfo_id
            if not supplierinfo:
                # 创建新的供应商信息记录
                vals = line._prepare_supplierinfo()
                supplierinfo_obj.create(vals)
            else:
                # 更新现有供应商信息
                vals = line._prepare_supplierinfo_update()
                supplierinfo.write(vals)
            
            # 更新产品的采购单位
            if ("product_uom" in vals and 
                vals["product_uom"] != line.product_id.uom_po_id.id):
                line.product_id.uom_po_id = vals["product_uom"]

        # 标记发票为已检查
        self.invoice_id.write({"supplierinfo_ok": True})

发票行与原始行匹配

def match_origin_lines(refund):
    """通过产品或描述匹配退款行与原始发票行"""
    invoice = refund.reversed_entry_id
    invoice_lines = invoice.invoice_line_ids
    for refund_line in refund.invoice_line_ids:
        for invoice_line in invoice_lines:
            # 通过产品或名称匹配
            match = (
                refund_line.product_id
                and refund_line.product_id == invoice_line.product_id
                or refund_line.name == invoice_line.name
            )
            if match:
                invoice_lines -= invoice_line
                refund_line.origin_line_id = invoice_line.id
                break
        if not invoice_lines:
            break

这些核心代码展示了项目的技术实现,包括数据库操作、业务逻辑处理和用户交互设计,体现了代码的专业性和可维护性。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)

公众号二维码

公众号二维码

posted @ 2025-10-08 18:06  qife  阅读(5)  评论(0)    收藏  举报