odoo17 | 模型之间的交互

前言

在前一章中,我们使用继承来修改模块的行为。在我们的房地产场景中,我们希望更进一步,能够为我们的客户生成发票。Odoo提供了一个发票(Invoicing)模块,所以直接从我们的房地产模块创建一个发票会很简洁,也就是说,一旦一个属性被设置为“已售出”,发票就会在发票应用程序中被创建。

具体示例:账户移动

目标:

  • 应该创建一个新的模块estate_account
  • 房产出售时,应给买方开具发票
    在这里插入图片描述
  • 无论何时,我们与其他模块交互时,都需要牢记模块化。如果我们打算将我们的应用程序出售给房地产中介,一些人可能想要发票功能,但其他人可能不想要。

链接模块

此类用例的常见方法是创建一个“链接”模块。在我们的例子中,该模块将依赖于房地产和账户,并将包括房地产的发票创建逻辑。这样,房地产和会计模块可以独立安装。当两者都安装时,链接模块提供新功能。

锻炼

  • 创建链接模块。
  • 创建estate_account模块,该模块依赖于 estate和account模块。 现在,它将是一个空壳。

提示:您在本教程的开头已经这样做了。这个过程非常 类似。《odoo17 | 创建一个新应用程序》

estate_account模块出现在列表中时,继续安装它! 你会注意到发票(Invoicing)应用程序也已安装。这是意料之中的,因为您的模块依赖于它。如果您卸载了发票(Invoicing)应用程序,您的estate_account模块也将被卸载。

创建发票

现在是生成发票的时候了。我们想为房地产模型添加功能,即我们想为房产出售时添加一些额外的逻辑。这听起来很熟悉吗?如果没有,最好回到上一章,因为你可能错过了什么!

作为第一步,我们需要扩展在按下房产上的“出售”按钮时调用的动作。为此,我们需要在 estate_account 模块中为 estate.property 模型创建一个模型继承。目前,重写的动作将简单地返回 super 调用。也许一个例子会使事情变得更清楚。
示例代码

from odoo import models

class InheritedModel(models.Model):
    _inherit = "estate.property"

    def inherited_action(self):
        return super().inherited_action()

这里是一个完整代码

# -*- coding: utf-8 -*-

from odoo import api, models


class AccountMove(models.Model):
    _inherit = 'account.move'

    def action_invoice_paid(self):
        """ 当发票链接到销售订单时,销售注册是
			付费确认与会者。与会者确实不应该事先确认
			完整的付款。 """
        res = super(AccountMove, self).action_invoice_paid()
        self.mapped('line_ids.sale_line_ids')._update_registrations(confirm=True, mark_as_paid=True)
        return res

锻炼

添加发票创建的第一步。

  • estate_account 模块的models文件夹中创建 estate_property.py 文件。
  • _inherit 继承房地产estate.property模型。
  • 重写 action_sold 方法(您可能已将其重命名)以返回 super 调用。

提示:为了确保它工作正常,在重写的方法中添加一个打印或调试器断点。

启动项目是否报错?如果不是,请检查是否正确导入了所有Python文件。

如果覆盖有效,我们可以继续前进并创建发票。不幸的是,没有简单的方法知道如何在Odoo中创建任何给定的对象。大多数时候,有必要查看其模型以找到所需的字段并提供适当的值。

学习的一个好方法是看看其他模块是如何完成你想要做的事情的。例如,销售的基本流程之一是从销售订单中创建发票。这看起来是一个很好的起点,因为它正是我们想做的事情。花一些时间阅读和理解**_create_invoices**方法。

 def _create_invoices(self, grouped=False, final=False, date=None):
        """
        创建与销售订单关联的发票。
		:param grouped: 如果为 True,则按 SO ID 对发票进行分组。如果为 False,则按
		(合作伙伴发票ID,货币)
		:param final: 如果为True,则必要时将生成退款
		:returns: 创建的发票列表
        """
        if not self.env['account.move'].check_access_rights('create', False):
            try:
                self.check_access_rights('write')
                self.check_access_rule('write')
            except AccessError:
                return self.env['account.move']

        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')

        # 1) 创建发票.
        invoice_vals_list = []
        invoice_item_sequence = 0
        for order in self:
            order = order.with_company(order.company_id)
            current_section_vals = None
            down_payments = order.env['sale.order.line']

            # 发票金额
            invoice_vals = order._prepare_invoice()

            # 发票行值(仅保留必要的部分)
            invoice_lines_vals = []
            for line in order.order_line:
                if line.display_type == 'line_section':
                    current_section_vals = line._prepare_invoice_line(sequence=invoice_item_sequence + 1)
                    continue
                if line.display_type != 'line_note' and float_is_zero(line.qty_to_invoice, precision_digits=precision):
                    continue
                if line.qty_to_invoice > 0 or (line.qty_to_invoice < 0 and final) or line.display_type == 'line_note':
                    if line.is_downpayment:
                        down_payments += line
                        continue
                    if current_section_vals:
                        invoice_item_sequence += 1
                        invoice_lines_vals.append(current_section_vals)
                        current_section_vals = None
                    invoice_item_sequence += 1
                    prepared_line = line._prepare_invoice_line(sequence=invoice_item_sequence)
                    invoice_lines_vals.append(prepared_line)

            # 如果销售订单中有预付款,请将它们分组到共同部分下
            if down_payments:
                invoice_item_sequence += 1
                down_payments_section = order._prepare_down_payment_section_line(sequence=invoice_item_sequence)
                invoice_lines_vals.append(down_payments_section)
                for down_payment in down_payments:
                    invoice_item_sequence += 1
                    invoice_down_payment_vals = down_payment._prepare_invoice_line(sequence=invoice_item_sequence)
                    invoice_lines_vals.append(invoice_down_payment_vals)

            if not any(new_line['display_type'] is False for new_line in invoice_lines_vals):
                raise self._nothing_to_invoice_error()

            invoice_vals['invoice_line_ids'] = [(0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals]

            invoice_vals_list.append(invoice_vals)

        if not invoice_vals_list:
            raise self._nothing_to_invoice_error()

        # 2) 管理“grouped”参数:按(partner_id, currency_id)分组。
        if not grouped:
            new_invoice_vals_list = []
            invoice_grouping_keys = self._get_invoice_grouping_keys()
            for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: [x.get(grouping_key) for grouping_key in invoice_grouping_keys]):
                origins = set()
                payment_refs = set()
                refs = set()
                ref_invoice_vals = None
                for invoice_vals in invoices:
                    if not ref_invoice_vals:
                        ref_invoice_vals = invoice_vals
                    else:
                        ref_invoice_vals['invoice_line_ids'] += invoice_vals['invoice_line_ids']
                    origins.add(invoice_vals['invoice_origin'])
                    payment_refs.add(invoice_vals['payment_reference'])
                    refs.add(invoice_vals['ref'])
                ref_invoice_vals.update({
                    'ref': ', '.join(refs)[:2000],
                    'invoice_origin': ', '.join(origins),
                    'payment_reference': len(payment_refs) == 1 and payment_refs.pop() or False,
                })
                new_invoice_vals_list.append(ref_invoice_vals)
            invoice_vals_list = new_invoice_vals_list

        # 3)创建发票。
        # 使用sudo管理发票的创建,因为销售人员必须能够在没有“计费”访问权限的情况下从销售订单生成发票。然而,他不应该能够从头开始创建发票。
        moves = self.env['account.move'].sudo().with_context(default_move_type='out_invoice').create(invoice_vals_list)
        # 4) 有些动作实际上可能是退款:如果总金额为负,则转换它们
        # 我们会在创建交易后进行此操作,因为我们需要税收等数据来了解总金额实际上是否为负数
        if final:
            moves.sudo().filtered(lambda m: m.amount_total < 0).action_switch_invoice_into_refund_credit_note()
        for move in moves:
            move.message_post_with_view('mail.message_origin_link',
                values={'self': move, 'origin': move.line_ids.mapped('sale_line_ids.order_id')},
                subtype_id=self.env.ref('mail.mt_note').id
            )
        return moves

要创建发票,我们需要以下信息:

  • 一个 partner_id:客户

  • 一个 move_type:它有几个可能的值

  move_type = fields.Selection(selection=[
            ('entry', 'Journal Entry'),
            ('out_invoice', 'Customer Invoice'),
            ('out_refund', 'Customer Credit Note'),
            ('in_invoice', 'Vendor Bill'),
            ('in_refund', 'Vendor Credit Note'),
            ('out_receipt', 'Sales Receipt'),
            ('in_receipt', 'Purchase Receipt'),
        ], string='Type', required=True, store=True, index=True, readonly=True, tracking=True,
        default="entry", change_default=True)
  • 一个 journal_id:会计账簿

锻炼

添加发票创建的第二步。
action_sold 方法的覆盖中创建一个空的 account.move

  • partner_id 来自当前的 estate.property

  • move_type 应与“客户发票”相对应

提示:

  • 要创建对象,请使用 self.env[model_name].create(values),其中 values 是一个 dict

  • create 方法不接受记录集作为字段值。

当某个房产被设置为“已售出”时,您现在应该在“开票/客户/发票”中创建一张新的客户发票。

显然,到目前为止我们还没有任何发票行。要创建发票行,我们需要以下信息:

  • name:行描述
  • quantity:数量
  • price_unit: 价格单位

此外,发票行需要与发票关联。将行与发票关联的最简单和最有效的方法是在创建发票时包含所有行。为此,在创建帐户时包含 invoice_line_ids 字段。One2manyMany2many 使用特殊的“命令”,这些命令已通过 Command 命名空间转换为人类可读。此命名空间表示一组记录上执行的三元组命令。三元组最初是执行这些命令的唯一选项,但现在使用命名空间是标准。格式是将它们放在一个按顺序执行的列表中。这里是一个简单的例子,在创建 test_model 时包含 One2many 字段 line_ids

from odoo import Command

def inherited_action(self):
    self.env["test_model"].create(
        {
            "name": "Test",
            "line_ids": [
                Command.create({
                    "field_1": "value_1",
                    "field_2": "value_2",
                })
            ],
        }
    )
    return super().inherited_action()

锻炼

添加发票创建的第三步。

在创建account.move时添加两条发票行。每个出售的物业都将根据以下条件开具发票:

  • 售价的6%

  • 行政费用另加100.00

提示:按照上面的示例在创建时添加 invoice_line_ids。对于每一行,我们需要一个名称、数量和价格单位。

这一章可能是迄今为止所涵盖的最难的一章,但它与Odoo开发实践最为接近。在下一章中,我们将介绍Odoo中使用的模板机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/316297.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VS报错:error:LNK2005 _main 已经在 *.obj 中定义

应该是重定义了&#xff0c;但是又解决不了&#xff0c;看似又没有重定义啊&#xff0c;就在一个文件定义了啊&#xff1f;怎么会出现这种情况呢&#xff1f;关键是&#xff0c;编译报错&#xff0c;程序运行不了了。 这里提一下我的前期操作&#xff0c;是因为将一个头文件和…

图像监视:在 Visual Studio 调试器中查看内存中图像

先决条件 本教程假定您具有以下可用项&#xff1a; 安装了 Update 1 的 Visual Studio 2012 Professional&#xff08;或更高版本&#xff09;。更新 1 可在此处下载。在 Windows 计算机上安装 OpenCV&#xff08;教程&#xff1a;在 Windows 中安装&#xff09;。能够在 Visua…

【Spring 篇】深入探索:Spring集成Web环境的奇妙世界

嗨&#xff0c;亲爱的小白们&#xff01;欢迎来到这篇有关Spring集成Web环境的博客。如果你曾对如何在Spring中构建强大的Web应用程序感到好奇&#xff0c;那么这里将为你揭示Web开发的神秘面纱。我们将用情感丰富、语句通顺的文字&#xff0c;以小白友好的方式&#xff0c;一探…

survey和surveyCV:如何用R语言进行复杂抽样设计、权重计算和10折交叉验证?

一、引言 在实际调查和研究中&#xff0c;我们往往面临着样本选择的复杂性。复杂抽样设计能够更好地反映真实情况&#xff0c;提高数据的代表性和可靠性。例如&#xff0c;多阶段抽样可以有效地解决大规模调查的问题&#xff0c;整群抽样能够在保证样本的随机性的同时减少资源消…

D25XB100-ASEMI家用电器整流桥D25XB100

编辑&#xff1a;ll D25XB100-ASEMI家用电器整流桥D25XB100 型号&#xff1a;D25XB100 品牌&#xff1a;ASEMI 封装&#xff1a;GBJ-5&#xff08;带康铜丝&#xff09; 平均正向整流电流&#xff08;Id&#xff09;&#xff1a;25A 最大反向击穿电压&#xff08;VRM&…

thinkphp递归实现无限级子分类合并上级children

//设别分类列表public function getCategoryList(){$list = Db::name(categorys)->select(

玩转Mysql 七 (索引的创建与设计原则)

一、索引的创建与使用 1、索引的分类 MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 &#xff08;1&#xff09;从 功能逻辑 上说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。 &#xff08;…

20_GC垃圾回收机制

文章目录 GC如何确定垃圾如何回收垃圾回收垃圾的时机 GC 如何确定垃圾 引用计数算法 给对象添加一个引用计数器&#xff0c;每当一个地方引用它时&#xff0c;计数器加1&#xff0c;每当引用失效时&#xff0c;计数器减少1&#xff0c;当计数器的数值为0时&#xff0c;也就是对…

Django教程|数据统计图表(echarts、highchart)

前言 highchart&#xff0c;国外。 echarts&#xff0c;国内。 本项目集成 hightchart和echarts图表库实现数据统计功能。 包括&#xff1a;折线图&#xff0c;柱状图&#xff0c;饼图和数据集图。 效果图 echats Highcharts 源代码 编写模板&#xff08;Template&#x…

Docker安装Atlassian全家桶

文章目录 省流&#xff1a;1.docker-compose文件2.其他服务都正常启动&#xff0c;唯独Bitbucket不行。日志错误刚启动时候重启后查询分析原因再针对第一点排查看样子是安装的bitbucket和系统环境有冲突问题&#xff1f; 结论&#xff1a; 省流&#xff1a; bitbucket 只能安装…

idea使用ssh连接docker,并通过Dockerfile文件,直接在idea中启动docker应用,并进行远程debug

idea使用ssh连接docker&#xff0c;并通过Dockerfile文件&#xff0c;直接在idea中启动docker应用&#xff0c;并进行远程debug 第一步: idea通过ssh连接docker第二步&#xff1a;使用Dockerfile文件在远程启动应用第三步: 远程debug 容器运行的好处是减轻本地运行的负担(本地电…

数据结构初探:揭开数据结构奥秘

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、算法模板、汇编语言 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 数组结构起源二. 基本概念和术语2.1 数据2.2 数据元素2.3 数据项2.4 …

Rust-语句和表达式

if-else Rust中if-else表达式的作用是实现条件分支。if-else表达式的构成方式为&#xff1a;以if关键字开头&#xff0c;后面跟上条件表达式&#xff0c;后续是结果语句块&#xff0c;最后是可选的else块。条件表达式的类型必须是bool。 if-else结构还可以当表达式使用 loop …

苹果手机怎么恢复备份?详细攻略为你整理好了!

随着智能手机和互联网的普及&#xff0c;手机中存储的个人信息、照片、视频、聊天记录等数据会变得越来越多。一旦手机丢失、损坏或系统出现问题&#xff0c;我们很可能会面临数据丢失的风险。因此&#xff0c;越来越多的人开始意识到保护手机数据的重要性。 当苹果手机数据丢…

Mnajora 使用deb包安装软件

说明 Mnajora 安装deb软件包主要有两种方式 可以使用dpkg 直接安装也可是使用debtap将deb软件包转换成 使用dpkg sudo pacman -S dpkg #安装dpkgsudo dpkg -i ###.deb #使用dpkg安装deb软件包和在ubuntu上是一样的 安装成功 使用debtap debtap是一个用于将.deb包转换为A…

C++力扣题目617--合并二叉树

给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新二叉树。合并的规则是&#xff1a;如果两个节点重叠&#…

“+”连接符用法(Java)

""可以作为连接符使用&#xff0c;如果与字符串一起运算&#xff0c;结果依旧是一个字符串 比如"aaa"6 --> "aaa6" 在打印中&#xff0c;能算就算&#xff0c;不能计算的时候就会连接在一起 注意先后顺序 ascii编码&#xff1a; 字符串&…

VASP结合vaspkit+ShengBTE计算热电优值(一)

电导率σ&#xff0c;塞贝克系数S的计算&#xff1a; 使用vaspkit计算处对应的物理量&#xff0c;具体流程为&#xff1a; 准备好计算的材料对应的POSCAR。如果是二维材料可以使用vaspkit 的921或923功能对二维材料POSCAR进行标准化。进行结构优化。使用 vaspkit-681命令生成高…

卡尔曼滤波:理论与代码

卡尔曼滤波&#xff1a;理论与代码 引言 卡尔曼滤波是一种用于估计系统状态的优化技术&#xff0c;特别适用于含有噪声的测量数据和系统动态变化的情况。本文将简单探讨卡尔曼滤波的理论基础、数学公式的推导&#xff0c;并通过Python代码示例演示其在实际应用中的效果。 一…

python,序列的切片

序列的切片就是指从一个序列中取出子序列 语法&#xff1a; 序列[起始下标&#xff1a;结束下标&#xff1a;步长] 步长为1表示一个一个的取元素&#xff0c;步长为2表示每次跳过一个元素的取元素&#xff0c;步长为负数表示反向切片&#xff0c;取元素时取到结束下标&#…