[IMP] pre-commit: first run on whole repo

This commit is contained in:
Kevin Khao
2021-11-26 18:54:38 +03:00
parent a04b8980e1
commit 167aefee13
289 changed files with 6020 additions and 4170 deletions

View File

@@ -1,7 +1,29 @@
exclude: | exclude: |
(?x) (?x)
# NOT INSTALLABLE ADDONS # NOT INSTALLABLE ADDONS
^account_fiscal_position_payable_receivable/|
^account_invoice_margin/|
^account_invoice_update_wizard/|
^company_code/|
^developer_menu/|
^intrastat_product_type/|
^mass_mailing_usability/|
^mrp_average_cost/|
^mrp_no_product_template_menu/|
^mrp_product_tree_default/|
^sale_down_payment/|
^sale_margin_no_onchange/|
^sale_no_configurator_button/|
^sale_quotation_title/|
^service_line_qty_update_base/|
^service_line_qty_update_purchase/|
^service_line_qty_update_sale/|
^stock_no_product_template_menu/|
^stock_user_default_warehouse_base/|
^stock_user_default_warehouse_mrp/|
^stock_user_default_warehouse_purchase/|
^stock_user_default_warehouse_sale/|
^volume_precision/|
# END NOT INSTALLABLE ADDONS # END NOT INSTALLABLE ADDONS
# Files and folders generated by bots, to avoid loops # Files and folders generated by bots, to avoid loops
^setup/|/static/description/index\.html$| ^setup/|/static/description/index\.html$|

View File

@@ -16,7 +16,7 @@ This module allows to configure a special *Partner Receivable Account* and a spe
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>. This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""", """,
"author": "Akretion", "author": "Akretion",
"website": "http://www.akretion.com", "website": "https://github.com/OCA/odoo-usability",
"depends": ["account"], "depends": ["account"],
"data": ["views/account_fiscal_position_view.xml"], "data": ["views/account_fiscal_position_view.xml"],
"installable": False, "installable": False,

View File

@@ -1,7 +1,7 @@
# © 2016-2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) # © 2016-2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields from odoo import fields, models
class AccountFiscalPosition(models.Model): class AccountFiscalPosition(models.Model):

View File

@@ -1,7 +1,7 @@
# © 2016-2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) # © 2016-2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, api from odoo import api, models
class ResPartner(models.Model): class ResPartner(models.Model):

View File

@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
@@ -13,8 +12,8 @@
<field name="inherit_id" ref="account.view_account_position_form" /> <field name="inherit_id" ref="account.view_account_position_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="company_id" position="after"> <field name="company_id" position="after">
<field name="receivable_account_id"/> <field name="receivable_account_id" />
<field name="payable_account_id"/> <field name="payable_account_id" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -3,22 +3,22 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Account Invoice Margin', "name": "Account Invoice Margin",
'version': '12.0.1.0.0', "version": "12.0.1.0.0",
'category': 'Invoicing Management', "category": "Invoicing Management",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Copy standard price on invoice line and compute margins', "summary": "Copy standard price on invoice line and compute margins",
'description': """ "description": """
This module copies the field *standard_price* of the product on the invoice line when the invoice line is created. The allows the computation of the margin of the invoice. This module copies the field *standard_price* of the product on the invoice line when the invoice line is created. The allows the computation of the margin of the invoice.
This module has been written by Alexis de Lattre from Akretion This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>. <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['account'], "depends": ["account"],
'data': [ "data": [
'account_invoice_view.xml', "account_invoice_view.xml",
], ],
'installable': False, "installable": False,
} }

View File

@@ -3,39 +3,62 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models from odoo import api, fields, models
import odoo.addons.decimal_precision as dp import odoo.addons.decimal_precision as dp
class AccountInvoiceLine(models.Model): class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line' _inherit = "account.invoice.line"
standard_price_company_currency = fields.Float( standard_price_company_currency = fields.Float(
string='Cost Price in Company Currency', readonly=True, string="Cost Price in Company Currency",
digits=dp.get_precision('Product Price'), readonly=True,
digits=dp.get_precision("Product Price"),
help="Cost price in company currency in the unit of measure " help="Cost price in company currency in the unit of measure "
"of the invoice line (which may be different from the unit " "of the invoice line (which may be different from the unit "
"of measure of the product).") "of measure of the product).",
)
standard_price_invoice_currency = fields.Float( standard_price_invoice_currency = fields.Float(
string='Cost Price in Invoice Currency', readonly=True, string="Cost Price in Invoice Currency",
compute='_compute_margin', store=True, readonly=True,
digits=dp.get_precision('Product Price'), compute="_compute_margin",
store=True,
digits=dp.get_precision("Product Price"),
help="Cost price in invoice currency in the unit of measure " help="Cost price in invoice currency in the unit of measure "
"of the invoice line") "of the invoice line",
)
margin_invoice_currency = fields.Monetary( margin_invoice_currency = fields.Monetary(
string='Margin in Invoice Currency', readonly=True, store=True, string="Margin in Invoice Currency",
compute='_compute_margin', currency_field='currency_id') readonly=True,
store=True,
compute="_compute_margin",
currency_field="currency_id",
)
margin_company_currency = fields.Monetary( margin_company_currency = fields.Monetary(
string='Margin in Company Currency', readonly=True, store=True, string="Margin in Company Currency",
compute='_compute_margin', currency_field='company_currency_id') readonly=True,
store=True,
compute="_compute_margin",
currency_field="company_currency_id",
)
margin_rate = fields.Float( margin_rate = fields.Float(
string="Margin Rate", readonly=True, store=True, string="Margin Rate",
compute='_compute_margin', readonly=True,
digits=(16, 2), help="Margin rate in percentage of the sale price") store=True,
compute="_compute_margin",
digits=(16, 2),
help="Margin rate in percentage of the sale price",
)
@api.depends( @api.depends(
'standard_price_company_currency', 'invoice_id.currency_id', "standard_price_company_currency",
'invoice_id.type', 'invoice_id.company_id', "invoice_id.currency_id",
'invoice_id.date_invoice', 'quantity', 'price_subtotal') "invoice_id.type",
"invoice_id.company_id",
"invoice_id.date_invoice",
"quantity",
"price_subtotal",
)
def _compute_margin(self): def _compute_margin(self):
for il in self: for il in self:
standard_price_inv_cur = 0.0 standard_price_inv_cur = 0.0
@@ -43,27 +66,27 @@ class AccountInvoiceLine(models.Model):
margin_comp_cur = 0.0 margin_comp_cur = 0.0
margin_rate = 0.0 margin_rate = 0.0
inv = il.invoice_id inv = il.invoice_id
if inv and inv.type in ('out_invoice', 'out_refund'): if inv and inv.type in ("out_invoice", "out_refund"):
# it works in _get_current_rate # it works in _get_current_rate
# even if we set date = False in context # even if we set date = False in context
# standard_price_inv_cur is in the UoM of the invoice line # standard_price_inv_cur is in the UoM of the invoice line
date = inv._get_currency_rate_date() or\ date = inv._get_currency_rate_date() or fields.Date.context_today(self)
fields.Date.context_today(self)
company = inv.company_id company = inv.company_id
company_currency = company.currency_id company_currency = company.currency_id
standard_price_inv_cur =\ standard_price_inv_cur = company_currency._convert(
company_currency._convert( il.standard_price_company_currency, inv.currency_id, company, date
il.standard_price_company_currency, )
inv.currency_id, company, date) margin_inv_cur = (
margin_inv_cur =\
il.price_subtotal - il.quantity * standard_price_inv_cur il.price_subtotal - il.quantity * standard_price_inv_cur
)
margin_comp_cur = inv.currency_id._convert( margin_comp_cur = inv.currency_id._convert(
margin_inv_cur, company_currency, company, date) margin_inv_cur, company_currency, company, date
)
if il.price_subtotal: if il.price_subtotal:
margin_rate = 100 * margin_inv_cur / il.price_subtotal margin_rate = 100 * margin_inv_cur / il.price_subtotal
# for a refund, margin should be negative # for a refund, margin should be negative
# but margin rate should stay positive # but margin rate should stay positive
if inv.type == 'out_refund': if inv.type == "out_refund":
margin_inv_cur *= -1 margin_inv_cur *= -1
margin_comp_cur *= -1 margin_comp_cur *= -1
il.standard_price_invoice_currency = standard_price_inv_cur il.standard_price_invoice_currency = standard_price_inv_cur
@@ -79,35 +102,32 @@ class AccountInvoiceLine(models.Model):
# because we don't have access to the 'type' of the invoice # because we don't have access to the 'type' of the invoice
@api.model @api.model
def create(self, vals): def create(self, vals):
if vals.get('product_id'): if vals.get("product_id"):
pp = self.env['product.product'].browse(vals['product_id']) pp = self.env["product.product"].browse(vals["product_id"])
std_price = pp.standard_price std_price = pp.standard_price
inv_uom_id = vals.get('uom_id') inv_uom_id = vals.get("uom_id")
if inv_uom_id and inv_uom_id != pp.uom_id.id: if inv_uom_id and inv_uom_id != pp.uom_id.id:
inv_uom = self.env['uom.uom'].browse(inv_uom_id) inv_uom = self.env["uom.uom"].browse(inv_uom_id)
std_price = pp.uom_id._compute_price( std_price = pp.uom_id._compute_price(std_price, inv_uom)
std_price, inv_uom) vals["standard_price_company_currency"] = std_price
vals['standard_price_company_currency'] = std_price
return super(AccountInvoiceLine, self).create(vals) return super(AccountInvoiceLine, self).create(vals)
def write(self, vals): def write(self, vals):
if not vals: if not vals:
vals = {} vals = {}
if 'product_id' in vals or 'uom_id' in vals: if "product_id" in vals or "uom_id" in vals:
for il in self: for il in self:
if 'product_id' in vals: if "product_id" in vals:
if vals.get('product_id'): if vals.get("product_id"):
pp = self.env['product.product'].browse( pp = self.env["product.product"].browse(vals["product_id"])
vals['product_id'])
else: else:
pp = False pp = False
else: else:
pp = il.product_id or False pp = il.product_id or False
# uom_id is NOT a required field # uom_id is NOT a required field
if 'uom_id' in vals: if "uom_id" in vals:
if vals.get('uom_id'): if vals.get("uom_id"):
inv_uom = self.env['uom.uom'].browse( inv_uom = self.env["uom.uom"].browse(vals["uom_id"])
vals['uom_id'])
else: else:
inv_uom = False inv_uom = False
else: else:
@@ -116,37 +136,43 @@ class AccountInvoiceLine(models.Model):
if pp: if pp:
std_price = pp.standard_price std_price = pp.standard_price
if inv_uom and inv_uom != pp.uom_id: if inv_uom and inv_uom != pp.uom_id:
std_price = pp.uom_id._compute_price( std_price = pp.uom_id._compute_price(std_price, inv_uom)
std_price, inv_uom) il.write({"standard_price_company_currency": std_price})
il.write({'standard_price_company_currency': std_price})
return super(AccountInvoiceLine, self).write(vals) return super(AccountInvoiceLine, self).write(vals)
class AccountInvoice(models.Model): class AccountInvoice(models.Model):
_inherit = 'account.invoice' _inherit = "account.invoice"
margin_invoice_currency = fields.Monetary( margin_invoice_currency = fields.Monetary(
string='Margin in Invoice Currency', string="Margin in Invoice Currency",
compute='_compute_margin', store=True, readonly=True, compute="_compute_margin",
currency_field='currency_id') store=True,
readonly=True,
currency_field="currency_id",
)
margin_company_currency = fields.Monetary( margin_company_currency = fields.Monetary(
string='Margin in Company Currency', string="Margin in Company Currency",
compute='_compute_margin', store=True, readonly=True, compute="_compute_margin",
currency_field='company_currency_id') store=True,
readonly=True,
currency_field="company_currency_id",
)
@api.depends( @api.depends(
'type', "type",
'invoice_line_ids.margin_invoice_currency', "invoice_line_ids.margin_invoice_currency",
'invoice_line_ids.margin_company_currency') "invoice_line_ids.margin_company_currency",
)
def _compute_margin(self): def _compute_margin(self):
res = self.env['account.invoice.line'].read_group( res = self.env["account.invoice.line"].read_group(
[('invoice_id', 'in', self.ids)], [("invoice_id", "in", self.ids)],
['invoice_id', 'margin_invoice_currency', ["invoice_id", "margin_invoice_currency", "margin_company_currency"],
'margin_company_currency'], ["invoice_id"],
['invoice_id']) )
for re in res: for re in res:
if re['invoice_id']: if re["invoice_id"]:
inv = self.browse(re['invoice_id'][0]) inv = self.browse(re["invoice_id"][0])
if inv.type in ('out_invoice', 'out_refund'): if inv.type in ("out_invoice", "out_refund"):
inv.margin_invoice_currency = re['margin_invoice_currency'] inv.margin_invoice_currency = re["margin_invoice_currency"]
inv.margin_company_currency = re['margin_company_currency'] inv.margin_company_currency = re["margin_company_currency"]

View File

@@ -6,47 +6,70 @@ from odoo import api, fields, models
class AccountInvoiceReport(models.Model): class AccountInvoiceReport(models.Model):
_inherit = 'account.invoice.report' _inherit = "account.invoice.report"
margin = fields.Float(string='Margin', readonly=True) margin = fields.Float(string="Margin", readonly=True)
# why digits=0 ??? Why is it like that in the native "account" module # why digits=0 ??? Why is it like that in the native "account" module
user_currency_margin = fields.Float( user_currency_margin = fields.Float(
string="Margin", compute='_compute_user_currency_margin', digits=0) string="Margin", compute="_compute_user_currency_margin", digits=0
)
_depends = { _depends = {
'account.invoice': [ "account.invoice": [
'account_id', 'amount_total_company_signed', "account_id",
'commercial_partner_id', 'company_id', "amount_total_company_signed",
'currency_id', 'date_due', 'date_invoice', 'fiscal_position_id', "commercial_partner_id",
'journal_id', 'number', 'partner_bank_id', 'partner_id', "company_id",
'payment_term_id', 'residual', 'state', 'type', 'user_id', "currency_id",
"date_due",
"date_invoice",
"fiscal_position_id",
"journal_id",
"number",
"partner_bank_id",
"partner_id",
"payment_term_id",
"residual",
"state",
"type",
"user_id",
], ],
'account.invoice.line': [ "account.invoice.line": [
'account_id', 'invoice_id', 'price_subtotal', 'product_id', "account_id",
'quantity', 'uom_id', 'account_analytic_id', "invoice_id",
'margin_company_currency', "price_subtotal",
"product_id",
"quantity",
"uom_id",
"account_analytic_id",
"margin_company_currency",
], ],
'product.product': ['product_tmpl_id'], "product.product": ["product_tmpl_id"],
'product.template': ['categ_id'], "product.template": ["categ_id"],
'uom.uom': ['category_id', 'factor', 'name', 'uom_type'], "uom.uom": ["category_id", "factor", "name", "uom_type"],
'res.currency.rate': ['currency_id', 'name'], "res.currency.rate": ["currency_id", "name"],
'res.partner': ['country_id'], "res.partner": ["country_id"],
} }
@api.depends('currency_id', 'date', 'margin') @api.depends("currency_id", "date", "margin")
def _compute_user_currency_margin(self): def _compute_user_currency_margin(self):
user_currency = self.env.user.company_id.currency_id user_currency = self.env.user.company_id.currency_id
currency_rate = self.env['res.currency.rate'].search([ currency_rate = self.env["res.currency.rate"].search(
('rate', '=', 1), [
'|', ("rate", "=", 1),
('company_id', '=', self.env.user.company_id.id), "|",
('company_id', '=', False)], limit=1) ("company_id", "=", self.env.user.company_id.id),
("company_id", "=", False),
],
limit=1,
)
base_currency = currency_rate.currency_id base_currency = currency_rate.currency_id
for record in self: for record in self:
date = record.date or fields.Date.today() date = record.date or fields.Date.today()
company = record.company_id company = record.company_id
record.user_currency_margin = base_currency._convert( record.user_currency_margin = base_currency._convert(
record.margin, user_currency, company, date) record.margin, user_currency, company, date
)
# TODO check for refunds # TODO check for refunds
def _sub_select(self): def _sub_select(self):

View File

@@ -1,33 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
© 2015-2017 Akretion (http://www.akretion.com/) © 2015-2017 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_invoice_line_form" model="ir.ui.view"> <record id="view_invoice_line_form" model="ir.ui.view">
<field name="name">margin.account.invoice.line.form</field> <field name="name">margin.account.invoice.line.form</field>
<field name="model">account.invoice.line</field> <field name="model">account.invoice.line</field>
<field name="inherit_id" ref="account.view_invoice_line_form"/> <field name="inherit_id" ref="account.view_invoice_line_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='analytic_tag_ids']/.." position="inside"> <xpath expr="//field[@name='analytic_tag_ids']/.." position="inside">
<field name="standard_price_company_currency" <field
name="standard_price_company_currency"
string="Cost Price in Comp. Cur." string="Cost Price in Comp. Cur."
groups="base.group_no_one"/> groups="base.group_no_one"
<field name="standard_price_invoice_currency" />
<field
name="standard_price_invoice_currency"
string="Cost Price in Inv. Cur." string="Cost Price in Inv. Cur."
groups="base.group_no_one"/> groups="base.group_no_one"
<field name="margin_invoice_currency" />
<field
name="margin_invoice_currency"
string="Margin in Inv. Cur." string="Margin in Inv. Cur."
groups="base.group_no_one"/> groups="base.group_no_one"
<field name="margin_company_currency" />
<field
name="margin_company_currency"
string="Margin in Comp. Cur." string="Margin in Comp. Cur."
groups="base.group_no_one"/> groups="base.group_no_one"
<label for="margin_rate" groups="base.group_no_one"/> />
<label for="margin_rate" groups="base.group_no_one" />
<div name="margin_rate" groups="base.group_no_one"> <div name="margin_rate" groups="base.group_no_one">
<field name="margin_rate" class="oe_inline"/> % <field name="margin_rate" class="oe_inline" /> %
</div> </div>
</xpath> </xpath>
</field> </field>
@@ -36,13 +43,19 @@
<record id="invoice_form" model="ir.ui.view"> <record id="invoice_form" model="ir.ui.view">
<field name="name">margin.account.invoice.form</field> <field name="name">margin.account.invoice.form</field>
<field name="model">account.invoice</field> <field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/> <field name="inherit_id" ref="account.invoice_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="move_id" position="after"> <field name="move_id" position="after">
<field name="margin_invoice_currency" <field
string="Margin in Inv. Cur." groups="base.group_no_one"/> name="margin_invoice_currency"
<field name="margin_company_currency" string="Margin in Inv. Cur."
string="Margin in Comp. Cur." groups="base.group_no_one"/> groups="base.group_no_one"
/>
<field
name="margin_company_currency"
string="Margin in Comp. Cur."
groups="base.group_no_one"
/>
</field> </field>
</field> </field>
</record> </record>

View File

@@ -3,19 +3,19 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Account Invoice Update Wizard', "name": "Account Invoice Update Wizard",
'version': '12.0.1.0.0', "version": "12.0.1.0.0",
'category': 'Accounting & Finance', "category": "Accounting & Finance",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Wizard to update non-legal fields of an open/paid invoice', "summary": "Wizard to update non-legal fields of an open/paid invoice",
'author': 'Akretion', "author": "Akretion",
'website': 'https://github.com/akretion/odoo-usability', "website": "https://github.com/OCA/odoo-usability",
'depends': [ "depends": [
'account', "account",
], ],
'data': [ "data": [
'wizard/account_invoice_update_view.xml', "wizard/account_invoice_update_view.xml",
'views/account_invoice.xml', "views/account_invoice.xml",
], ],
'installable': False, "installable": False,
} }

View File

@@ -1,22 +1,19 @@
# Copyright 2019 Camptocamp # Copyright 2019 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _ from odoo import models
from odoo.exceptions import UserError
import odoo.addons.decimal_precision as dp
class AccountInvoice(models.Model): class AccountInvoice(models.Model):
_inherit = 'account.invoice' _inherit = "account.invoice"
def prepare_update_wizard(self): def prepare_update_wizard(self):
self.ensure_one() self.ensure_one()
wizard = self.env['account.invoice.update'] wizard = self.env["account.invoice.update"]
res = wizard._prepare_default_get(self) res = wizard._prepare_default_get(self)
action = self.env.ref( action = self.env.ref(
'account_invoice_update_wizard.account_invoice_update_action' "account_invoice_update_wizard.account_invoice_update_action"
).read()[0] ).read()[0]
action['name'] = "Update Wizard" action["name"] = "Update Wizard"
action['res_id'] = wizard.create(res).id action["res_id"] = wizard.create(res).id
return action return action

View File

@@ -1,54 +1,61 @@
# Copyright 2018-2019 Camptocamp # Copyright 2018-2019 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import SavepointCase
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tests.common import SavepointCase
class TestAccountInvoiceUpdateWizard(SavepointCase): class TestAccountInvoiceUpdateWizard(SavepointCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
cls.customer12 = cls.env.ref('base.res_partner_12') cls.customer12 = cls.env.ref("base.res_partner_12")
cls.product16 = cls.env.ref('product.product_product_16') cls.product16 = cls.env.ref("product.product_product_16")
cls.product24 = cls.env.ref('product.product_product_24') cls.product24 = cls.env.ref("product.product_product_24")
uom_unit = cls.env.ref('uom.product_uom_categ_unit') uom_unit = cls.env.ref("uom.product_uom_categ_unit")
cls.invoice1 = cls.env['account.invoice'].create({ cls.invoice1 = cls.env["account.invoice"].create(
'name': 'Test invoice', {
'partner_id': cls.customer12.id, "name": "Test invoice",
}) "partner_id": cls.customer12.id,
cls.inv_line1 = cls.env['account.invoice.line'].create({ }
'invoice_id': cls.invoice1.id, )
'name': "Line1", cls.inv_line1 = cls.env["account.invoice.line"].create(
'product_id': cls.product16.id, {
'product_uom_id': uom_unit.id, "invoice_id": cls.invoice1.id,
'account_id': cls.invoice1.account_id.id, "name": "Line1",
'price_unit': 42.0, "product_id": cls.product16.id,
}) "product_uom_id": uom_unit.id,
cls.inv_line2 = cls.env['account.invoice.line'].create({ "account_id": cls.invoice1.account_id.id,
'invoice_id': cls.invoice1.id, "price_unit": 42.0,
'name': "Line2", }
'product_id': cls.product24.id, )
'product_uom_id': uom_unit.id, cls.inv_line2 = cls.env["account.invoice.line"].create(
'account_id': cls.invoice1.account_id.id, {
'price_unit': 1111.1, "invoice_id": cls.invoice1.id,
}) "name": "Line2",
"product_id": cls.product24.id,
"product_uom_id": uom_unit.id,
"account_id": cls.invoice1.account_id.id,
"price_unit": 1111.1,
}
)
cls.aa1 = cls.env.ref('analytic.analytic_partners_camp_to_camp') cls.aa1 = cls.env.ref("analytic.analytic_partners_camp_to_camp")
cls.aa2 = cls.env.ref('analytic.analytic_nebula') cls.aa2 = cls.env.ref("analytic.analytic_nebula")
cls.atag1 = cls.env.ref('analytic.tag_contract') cls.atag1 = cls.env.ref("analytic.tag_contract")
cls.atag2 = cls.env['account.analytic.tag'].create({ cls.atag2 = cls.env["account.analytic.tag"].create(
'name': '', {
}) "name": "",
}
)
def create_wizard(self, invoice): def create_wizard(self, invoice):
res = self.invoice1.prepare_update_wizard() res = self.invoice1.prepare_update_wizard()
self.wiz = self.env['account.invoice.update'].browse(res['res_id']) self.wiz = self.env["account.invoice.update"].browse(res["res_id"])
def test_add_analytic_account_line1(self): def test_add_analytic_account_line1(self):
""" Add analytic account on an invoice line """Add analytic account on an invoice line
after the invoice has been approved. after the invoice has been approved.
This will: This will:
@@ -59,17 +66,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
self.create_wizard(self.invoice1) self.create_wizard(self.invoice1)
wiz_line = self.wiz.line_ids.filtered( wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1) lambda rec: rec.invoice_line_id == self.inv_line1
)
wiz_line.account_analytic_id = self.aa1 wiz_line.account_analytic_id = self.aa1
self.wiz.run() self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered( related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16) lambda rec: rec.product_id == self.product16
)
self.assertEqual(related_ml.analytic_account_id, self.aa1) self.assertEqual(related_ml.analytic_account_id, self.aa1)
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1) self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
def test_change_analytic_account_line1(self): def test_change_analytic_account_line1(self):
""" Change analytic account on an invoice line """Change analytic account on an invoice line
after the invoice has been approved. after the invoice has been approved.
This will: This will:
@@ -81,17 +90,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
self.create_wizard(self.invoice1) self.create_wizard(self.invoice1)
wiz_line = self.wiz.line_ids.filtered( wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1) lambda rec: rec.invoice_line_id == self.inv_line1
)
wiz_line.account_analytic_id = self.aa1 wiz_line.account_analytic_id = self.aa1
self.wiz.run() self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered( related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16) lambda rec: rec.product_id == self.product16
)
self.assertEqual(related_ml.analytic_account_id, self.aa1) self.assertEqual(related_ml.analytic_account_id, self.aa1)
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1) self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
def test_error_grouped_move_lines(self): def test_error_grouped_move_lines(self):
""" Change analytic account on an invoice line """Change analytic account on an invoice line
after the invoice has been approved where both after the invoice has been approved where both
lines were grouped in the same move line. lines were grouped in the same move line.
@@ -111,7 +122,7 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
self.wiz.run() self.wiz.run()
def test_add_analytic_tags_line1(self): def test_add_analytic_tags_line1(self):
""" Add analytic tags on an invoice line """Add analytic tags on an invoice line
after the invoice has been approved. after the invoice has been approved.
This will update move line. This will update move line.
@@ -120,17 +131,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
self.create_wizard(self.invoice1) self.create_wizard(self.invoice1)
wiz_line = self.wiz.line_ids.filtered( wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1) lambda rec: rec.invoice_line_id == self.inv_line1
)
wiz_line.analytic_tag_ids = self.atag2 wiz_line.analytic_tag_ids = self.atag2
self.wiz.run() self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered( related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16) lambda rec: rec.product_id == self.product16
)
self.assertEqual(related_ml.analytic_tag_ids, self.atag2) self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
self.assertFalse(related_ml.analytic_line_ids) self.assertFalse(related_ml.analytic_line_ids)
def test_change_analytic_tags_line1(self): def test_change_analytic_tags_line1(self):
""" Change analytic tags on an invoice line """Change analytic tags on an invoice line
after the invoice has been approved. after the invoice has been approved.
It will update move line and analytic line It will update move line and analytic line
@@ -142,17 +155,19 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
self.create_wizard(self.invoice1) self.create_wizard(self.invoice1)
wiz_line = self.wiz.line_ids.filtered( wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1) lambda rec: rec.invoice_line_id == self.inv_line1
)
wiz_line.analytic_tag_ids = self.atag2 wiz_line.analytic_tag_ids = self.atag2
self.wiz.run() self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered( related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16) lambda rec: rec.product_id == self.product16
)
self.assertEqual(related_ml.analytic_tag_ids, self.atag2) self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2) self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
def test_add_analytic_info_line1(self): def test_add_analytic_info_line1(self):
""" Add analytic account and tags on an invoice line """Add analytic account and tags on an invoice line
after the invoice has been approved. after the invoice has been approved.
This will: This will:
@@ -163,20 +178,22 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
self.create_wizard(self.invoice1) self.create_wizard(self.invoice1)
wiz_line = self.wiz.line_ids.filtered( wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1) lambda rec: rec.invoice_line_id == self.inv_line1
)
wiz_line.account_analytic_id = self.aa1 wiz_line.account_analytic_id = self.aa1
wiz_line.analytic_tag_ids = self.atag2 wiz_line.analytic_tag_ids = self.atag2
self.wiz.run() self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered( related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16) lambda rec: rec.product_id == self.product16
)
self.assertEqual(related_ml.analytic_account_id, self.aa1) self.assertEqual(related_ml.analytic_account_id, self.aa1)
self.assertEqual(related_ml.analytic_tag_ids, self.atag2) self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1) self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2) self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
def test_empty_analytic_account_line1(self): def test_empty_analytic_account_line1(self):
""" Remove analytic account """Remove analytic account
after the invoice has been approved. after the invoice has been approved.
This will raise an error as it is not implemented. This will raise an error as it is not implemented.
@@ -187,10 +204,12 @@ class TestAccountInvoiceUpdateWizard(SavepointCase):
self.create_wizard(self.invoice1) self.create_wizard(self.invoice1)
wiz_line = self.wiz.line_ids.filtered( wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1) lambda rec: rec.invoice_line_id == self.inv_line1
)
wiz_line.account_analytic_id = False wiz_line.account_analytic_id = False
self.wiz.run() self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered( related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16) lambda rec: rec.product_id == self.product16
)
self.assertFalse(related_ml.analytic_account_id) self.assertFalse(related_ml.analytic_account_id)
self.assertFalse(related_ml.analytic_line_ids) self.assertFalse(related_ml.analytic_line_ids)

View File

@@ -1,27 +1,38 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="invoice_supplier_form" model="ir.ui.view"> <record id="invoice_supplier_form" model="ir.ui.view">
<field name="model">account.invoice</field> <field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_supplier_form"/> <field name="inherit_id" ref="account.invoice_supplier_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<button name="action_invoice_draft" position="before"> <button name="action_invoice_draft" position="before">
<button name="prepare_update_wizard" type="object" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/> <button
name="prepare_update_wizard"
type="object"
string="Update Invoice"
states="open,paid"
groups="account.group_account_invoice"
/>
</button> </button>
</field> </field>
</record> </record>
<record id="invoice_form" model="ir.ui.view"> <record id="invoice_form" model="ir.ui.view">
<field name="model">account.invoice</field> <field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/> <field name="inherit_id" ref="account.invoice_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<button name="action_invoice_draft" position="before"> <button name="action_invoice_draft" position="before">
<button name="prepare_update_wizard" type="object" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/> <button
name="prepare_update_wizard"
type="object"
string="Update Invoice"
states="open,paid"
groups="account.group_account_invoice"
/>
</button> </button>
</field> </field>
</record> </record>

View File

@@ -2,47 +2,45 @@
# Copyright 2018-2019 Camptocamp # Copyright 2018-2019 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _ from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
import odoo.addons.decimal_precision as dp import odoo.addons.decimal_precision as dp
class AccountInvoiceUpdate(models.TransientModel): class AccountInvoiceUpdate(models.TransientModel):
_name = 'account.invoice.update' _name = "account.invoice.update"
_description = 'Wizard to update non-legal fields of invoice' _description = "Wizard to update non-legal fields of invoice"
invoice_id = fields.Many2one( invoice_id = fields.Many2one(
'account.invoice', string='Invoice', required=True, "account.invoice", string="Invoice", required=True, readonly=True
readonly=True) )
type = fields.Selection(related='invoice_id.type', readonly=True) type = fields.Selection(related="invoice_id.type", readonly=True)
company_id = fields.Many2one( company_id = fields.Many2one(related="invoice_id.company_id", readonly=True)
related='invoice_id.company_id', readonly=True) partner_id = fields.Many2one(related="invoice_id.partner_id", readonly=True)
partner_id = fields.Many2one( user_id = fields.Many2one("res.users", string="Salesperson")
related='invoice_id.partner_id', readonly=True) payment_term_id = fields.Many2one("account.payment.term", string="Payment Term")
user_id = fields.Many2one('res.users', string='Salesperson') reference = fields.Char(string="Invoice Reference")
payment_term_id = fields.Many2one( name = fields.Char(string="Reference/Description")
'account.payment.term', string='Payment Term') origin = fields.Char(string="Source Document")
reference = fields.Char(string='Invoice Reference') comment = fields.Text("Additional Information")
name = fields.Char(string='Reference/Description') partner_bank_id = fields.Many2one("res.partner.bank", string="Bank Account")
origin = fields.Char(string='Source Document')
comment = fields.Text('Additional Information')
partner_bank_id = fields.Many2one(
'res.partner.bank', string='Bank Account')
line_ids = fields.One2many( line_ids = fields.One2many(
'account.invoice.line.update', 'parent_id', string='Invoice Lines') "account.invoice.line.update", "parent_id", string="Invoice Lines"
)
@api.model @api.model
def _simple_fields2update(self): def _simple_fields2update(self):
'''List boolean, date, datetime, char, text fields''' """List boolean, date, datetime, char, text fields"""
return ['reference', 'name', 'origin', 'comment'] return ["reference", "name", "origin", "comment"]
@api.model @api.model
def _m2o_fields2update(self): def _m2o_fields2update(self):
return ['payment_term_id', 'user_id', 'partner_bank_id'] return ["payment_term_id", "user_id", "partner_bank_id"]
@api.model @api.model
def _prepare_default_get(self, invoice): def _prepare_default_get(self, invoice):
res = {'invoice_id': invoice.id, 'line_ids': []} res = {"invoice_id": invoice.id, "line_ids": []}
for sfield in self._simple_fields2update(): for sfield in self._simple_fields2update():
res[sfield] = invoice[sfield] res[sfield] = invoice[sfield]
for m2ofield in self._m2o_fields2update(): for m2ofield in self._m2o_fields2update():
@@ -50,26 +48,32 @@ class AccountInvoiceUpdate(models.TransientModel):
for line in invoice.invoice_line_ids: for line in invoice.invoice_line_ids:
aa_tags = line.analytic_tag_ids aa_tags = line.analytic_tag_ids
aa_tags = [(6, 0, aa_tags.ids)] if aa_tags else False aa_tags = [(6, 0, aa_tags.ids)] if aa_tags else False
res['line_ids'].append([0, 0, { res["line_ids"].append(
'invoice_line_id': line.id, [
'name': line.name, 0,
'quantity': line.quantity, 0,
'price_subtotal': line.price_subtotal, {
'account_analytic_id': line.account_analytic_id.id, "invoice_line_id": line.id,
'analytic_tag_ids': aa_tags, "name": line.name,
'display_type': line.display_type, "quantity": line.quantity,
}]) "price_subtotal": line.price_subtotal,
"account_analytic_id": line.account_analytic_id.id,
"analytic_tag_ids": aa_tags,
"display_type": line.display_type,
},
]
)
return res return res
@api.onchange('type') @api.onchange("type")
def type_on_change(self): def type_on_change(self):
res = {'domain': {}} res = {"domain": {}}
if self.type in ('out_invoice', 'out_refund'): if self.type in ("out_invoice", "out_refund"):
res['domain']['partner_bank_id'] =\ res["domain"][
"[('partner_id.ref_company_ids', 'in', [company_id])]" "partner_bank_id"
] = "[('partner_id.ref_company_ids', 'in', [company_id])]"
else: else:
res['domain']['partner_bank_id'] =\ res["domain"]["partner_bank_id"] = "[('partner_id', '=', partner_id)]"
"[('partner_id', '=', partner_id)]"
return res return res
@api.multi @api.multi
@@ -82,24 +86,31 @@ class AccountInvoiceUpdate(models.TransientModel):
for m2ofield in self._m2o_fields2update(): for m2ofield in self._m2o_fields2update():
if self[m2ofield] != inv[m2ofield]: if self[m2ofield] != inv[m2ofield]:
vals[m2ofield] = self[m2ofield].id or False vals[m2ofield] = self[m2ofield].id or False
if 'payment_term_id' in vals: if "payment_term_id" in vals:
pterm_list = self.payment_term_id.compute( pterm_list = self.payment_term_id.compute(
value=1, date_ref=inv.date_invoice)[0] value=1, date_ref=inv.date_invoice
)[0]
if pterm_list: if pterm_list:
vals['date_due'] = max(line[0] for line in pterm_list) vals["date_due"] = max(line[0] for line in pterm_list)
return vals return vals
@api.model @api.model
def _line_simple_fields2update(self): def _line_simple_fields2update(self):
return ["name",] return [
"name",
]
@api.model @api.model
def _line_m2o_fields2update(self): def _line_m2o_fields2update(self):
return ["account_analytic_id",] return [
"account_analytic_id",
]
@api.model @api.model
def _line_m2m_fields2update(self): def _line_m2m_fields2update(self):
return ["analytic_tag_ids",] return [
"analytic_tag_ids",
]
@api.model @api.model
def _prepare_invoice_line(self, line): def _prepare_invoice_line(self, line):
@@ -122,7 +133,7 @@ class AccountInvoiceUpdate(models.TransientModel):
ini_ref = inv.move_id.ref ini_ref = inv.move_id.ref
ref = inv.reference or inv.name ref = inv.reference or inv.name
if ini_ref != ref: if ini_ref != ref:
mvals['ref'] = ref mvals["ref"] = ref
return mvals return mvals
@api.multi @api.multi
@@ -131,49 +142,52 @@ class AccountInvoiceUpdate(models.TransientModel):
# TODO make it accept more case as lines won't # TODO make it accept more case as lines won't
# be grouped unless journal.group_invoice_line is True # be grouped unless journal.group_invoice_line is True
inv_line = self.invoice_id.invoice_line_ids.filtered( inv_line = self.invoice_id.invoice_line_ids.filtered(
lambda rec: rec.product_id == move_line.product_id) lambda rec: rec.product_id == move_line.product_id
)
if len(inv_line) != 1: if len(inv_line) != 1:
raise UserError( raise UserError(
"Cannot match a single invoice line to move line %s" % "Cannot match a single invoice line to move line %s" % move_line.name
move_line.name) )
return inv_line return inv_line
@api.multi @api.multi
def _prepare_move_line(self, inv_line): def _prepare_move_line(self, inv_line):
mlvals = {} mlvals = {}
inv_line_upd = self.line_ids.filtered( inv_line_upd = self.line_ids.filtered(
lambda rec: rec.invoice_line_id == inv_line) lambda rec: rec.invoice_line_id == inv_line
)
ini_aa = inv_line.account_analytic_id ini_aa = inv_line.account_analytic_id
new_aa = inv_line_upd.account_analytic_id new_aa = inv_line_upd.account_analytic_id
if ini_aa != new_aa: if ini_aa != new_aa:
mlvals['analytic_account_id'] = new_aa.id mlvals["analytic_account_id"] = new_aa.id
ini_aa_tags = inv_line.analytic_tag_ids ini_aa_tags = inv_line.analytic_tag_ids
new_aa_tags = inv_line_upd.analytic_tag_ids new_aa_tags = inv_line_upd.analytic_tag_ids
if ini_aa_tags != new_aa_tags: if ini_aa_tags != new_aa_tags:
mlvals['analytic_tag_ids'] = [(6, None, new_aa_tags.ids)] mlvals["analytic_tag_ids"] = [(6, None, new_aa_tags.ids)]
return mlvals return mlvals
@api.multi @api.multi
def _prepare_analytic_line(self, inv_line): def _prepare_analytic_line(self, inv_line):
alvals = {} alvals = {}
inv_line_upd = self.line_ids.filtered( inv_line_upd = self.line_ids.filtered(
lambda rec: rec.invoice_line_id == inv_line) lambda rec: rec.invoice_line_id == inv_line
)
ini_aa = inv_line.account_analytic_id ini_aa = inv_line.account_analytic_id
new_aa = inv_line_upd.account_analytic_id new_aa = inv_line_upd.account_analytic_id
if ini_aa != new_aa: if ini_aa != new_aa:
alvals['account_id'] = new_aa.id alvals["account_id"] = new_aa.id
ini_aa_tags = inv_line.analytic_tag_ids ini_aa_tags = inv_line.analytic_tag_ids
new_aa_tags = inv_line_upd.analytic_tag_ids new_aa_tags = inv_line_upd.analytic_tag_ids
if ini_aa_tags != new_aa_tags: if ini_aa_tags != new_aa_tags:
alvals['tag_ids'] = [(6, None, new_aa_tags.ids)] alvals["tag_ids"] = [(6, None, new_aa_tags.ids)]
return alvals return alvals
@api.multi @api.multi
@@ -181,22 +195,27 @@ class AccountInvoiceUpdate(models.TransientModel):
self.ensure_one() self.ensure_one()
inv = self.invoice_id inv = self.invoice_id
if ( if (
self.payment_term_id and self.payment_term_id
self.payment_term_id != inv.payment_term_id and and self.payment_term_id != inv.payment_term_id
inv.move_id): and inv.move_id
):
# I don't update pay term when the invoice is partially (or fully) # I don't update pay term when the invoice is partially (or fully)
# paid because if you have a payment term with several lines # paid because if you have a payment term with several lines
# of the same amount, you would also have to take into account # of the same amount, you would also have to take into account
# the reconcile marks to put the new maturity date on the right # the reconcile marks to put the new maturity date on the right
# lines # lines
if inv.payment_ids: if inv.payment_ids:
raise UserError(_( raise UserError(
_(
"This wizard doesn't support the update of payment " "This wizard doesn't support the update of payment "
"terms on an invoice which is partially or fully " "terms on an invoice which is partially or fully "
"paid.")) "paid."
prec = self.env['decimal.precision'].precision_get('Account') )
term_res = self.payment_term_id.compute( )
inv.amount_total, inv.date_invoice)[0] prec = self.env["decimal.precision"].precision_get("Account")
term_res = self.payment_term_id.compute(inv.amount_total, inv.date_invoice)[
0
]
new_pterm = {} # key = int(amount * 100), value = [date1, date2] new_pterm = {} # key = int(amount * 100), value = [date1, date2]
for entry in term_res: for entry in term_res:
amount = int(entry[1] * 10 * prec) amount = int(entry[1] * 10 * prec)
@@ -214,13 +233,16 @@ class AccountInvoiceUpdate(models.TransientModel):
mlines[amount] = [line] mlines[amount] = [line]
for iamount, lines in mlines.items(): for iamount, lines in mlines.items():
if len(lines) != len(new_pterm.get(iamount, [])): if len(lines) != len(new_pterm.get(iamount, [])):
raise UserError(_( raise UserError(
_(
"The original payment term '%s' doesn't have the " "The original payment term '%s' doesn't have the "
"same terms (number of terms and/or amount) as the " "same terms (number of terms and/or amount) as the "
"new payment term '%s'. You can only switch to a " "new payment term '%s'. You can only switch to a "
"payment term that has the same number of terms " "payment term that has the same number of terms "
"with the same amount.") % ( "with the same amount."
inv.payment_term_id.name, self.payment_term_id.name)) )
% (inv.payment_term_id.name, self.payment_term_id.name)
)
for line in lines: for line in lines:
line.date_maturity = new_pterm[iamount].pop() line.date_maturity = new_pterm[iamount].pop()
@@ -254,16 +276,16 @@ class AccountInvoiceUpdate(models.TransientModel):
alvals = self._prepare_analytic_line(inv_line) alvals = self._prepare_analytic_line(inv_line)
if aalines and alvals: if aalines and alvals:
updated = True updated = True
if ('account_id' in alvals and if "account_id" in alvals and alvals["account_id"] is False:
alvals['account_id'] is False):
former_aa = inv_line.account_analytic_id former_aa = inv_line.account_analytic_id
to_remove_aalines = aalines.filtered( to_remove_aalines = aalines.filtered(
lambda rec: rec.account_id == former_aa) lambda rec: rec.account_id == former_aa
)
# remove existing analytic line # remove existing analytic line
to_remove_aalines.unlink() to_remove_aalines.unlink()
else: else:
aalines.write(alvals) aalines.write(alvals)
elif 'account_id' in alvals: elif "account_id" in alvals:
# Create analytic lines if analytic account # Create analytic lines if analytic account
# is added later # is added later
ml.create_analytic_lines() ml.create_analytic_lines()
@@ -273,30 +295,40 @@ class AccountInvoiceUpdate(models.TransientModel):
updated = True updated = True
line.invoice_line_id.write(ilvals) line.invoice_line_id.write(ilvals)
if updated: if updated:
inv.message_post(body=_( inv.message_post(
'Non-legal fields of invoice updated via the Invoice Update ' body=_(
'wizard.')) "Non-legal fields of invoice updated via the Invoice Update "
"wizard."
)
)
return True return True
class AccountInvoiceLineUpdate(models.TransientModel): class AccountInvoiceLineUpdate(models.TransientModel):
_name = 'account.invoice.line.update' _name = "account.invoice.line.update"
_description = 'Update non-legal fields of invoice lines' _description = "Update non-legal fields of invoice lines"
parent_id = fields.Many2one( parent_id = fields.Many2one(
'account.invoice.update', string='Wizard', ondelete='cascade') "account.invoice.update", string="Wizard", ondelete="cascade"
)
invoice_line_id = fields.Many2one( invoice_line_id = fields.Many2one(
'account.invoice.line', string='Invoice Line', readonly=True) "account.invoice.line", string="Invoice Line", readonly=True
name = fields.Text(string='Description', required=True) )
display_type = fields.Selection([ name = fields.Text(string="Description", required=True)
('line_section', "Section"), display_type = fields.Selection(
('line_note', "Note")], default=False, help="Technical field for UX purpose.") [("line_section", "Section"), ("line_note", "Note")],
default=False,
help="Technical field for UX purpose.",
)
quantity = fields.Float( quantity = fields.Float(
string='Quantity', digits=dp.get_precision('Product Unit of Measure'), string="Quantity",
readonly=True) digits=dp.get_precision("Product Unit of Measure"),
readonly=True,
)
price_subtotal = fields.Float( price_subtotal = fields.Float(
string='Amount', readonly=True, digits=dp.get_precision('Account')) string="Amount", readonly=True, digits=dp.get_precision("Account")
)
account_analytic_id = fields.Many2one( account_analytic_id = fields.Many2one(
'account.analytic.account', string='Analytic Account') "account.analytic.account", string="Analytic Account"
analytic_tag_ids = fields.Many2many( )
'account.analytic.tag', string='Analytic Tags') analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags")

View File

@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
© 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="account_invoice_update_form" model="ir.ui.view"> <record id="account_invoice_update_form" model="ir.ui.view">
@@ -11,34 +10,62 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Update Invoice Wizard"> <form string="Update Invoice Wizard">
<group name="main"> <group name="main">
<field name="invoice_id" invisible="1"/> <field name="invoice_id" invisible="1" />
<field name="type" invisible="1"/> <field name="type" invisible="1" />
<field name="company_id" invisible="1"/> <field name="company_id" invisible="1" />
<field name="partner_id" invisible="1"/> <field name="partner_id" invisible="1" />
<field name="reference" attrs="{'invisible': [('type', 'not in', ('in_invoice', 'in_refund'))]}"/> <field
<field name="origin"/> name="reference"
<field name="name"/> attrs="{'invisible': [('type', 'not in', ('in_invoice', 'in_refund'))]}"
<field name="payment_term_id" widget="selection"/> />
<field name="partner_bank_id"/> <field name="origin" />
<field name="user_id"/> <field name="name" />
<field name="comment"/> <field name="payment_term_id" widget="selection" />
<field name="partner_bank_id" />
<field name="user_id" />
<field name="comment" />
</group> </group>
<group name="lines"> <group name="lines">
<field name="line_ids" nolabel="1"> <field name="line_ids" nolabel="1">
<tree editable="bottom" create="false" delete="false" edit="true"> <tree
<field name="invoice_line_id" invisible="1"/> editable="bottom"
<field name="display_type" invisible="1"/> create="false"
<field name="name"/> delete="false"
<field name="quantity" attrs="{'invisible': [('display_type', '!=', False)]}"/> edit="true"
<field name="price_subtotal" attrs="{'invisible': [('display_type', '!=', False)]}"/> >
<field name="account_analytic_id" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting"/> <field name="invoice_line_id" invisible="1" />
<field name="analytic_tag_ids" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting" widget="many2many_tags"/> <field name="display_type" invisible="1" />
<field name="name" />
<field
name="quantity"
attrs="{'invisible': [('display_type', '!=', False)]}"
/>
<field
name="price_subtotal"
attrs="{'invisible': [('display_type', '!=', False)]}"
/>
<field
name="account_analytic_id"
attrs="{'invisible': [('display_type', '!=', False)]}"
groups="analytic.group_analytic_accounting"
/>
<field
name="analytic_tag_ids"
attrs="{'invisible': [('display_type', '!=', False)]}"
groups="analytic.group_analytic_accounting"
widget="many2many_tags"
/>
</tree> </tree>
</field> </field>
</group> </group>
<footer> <footer>
<button name="run" type="object" class="oe_highlight" string="Update"/> <button
<button special="cancel" string="Cancel" class="oe_link"/> name="run"
type="object"
class="oe_highlight"
string="Update"
/>
<button special="cancel" string="Cancel" class="oe_link" />
</footer> </footer>
</form> </form>
</field> </field>

View File

@@ -3,36 +3,36 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Account Usability', "name": "Account Usability",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Accounting & Finance', "category": "Accounting & Finance",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Small usability enhancements in account module', "summary": "Small usability enhancements in account module",
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': [ "depends": [
'account', "account",
'base_view_inheritance_extension', "base_view_inheritance_extension",
'base_usability', # needed only to access base_usability.group_nobody "base_usability", # needed only to access base_usability.group_nobody
# in v12, I may create a module only for group_nobody # in v12, I may create a module only for group_nobody
], ],
'data': [ "data": [
'views/account_account_type.xml', "views/account_account_type.xml",
'views/account_account.xml', "views/account_account.xml",
'views/account_bank_statement.xml', "views/account_bank_statement.xml",
'views/account_invoice_report.xml', "views/account_invoice_report.xml",
'views/account_journal.xml', "views/account_journal.xml",
'views/account_move.xml', "views/account_move.xml",
'views/account_menu.xml', "views/account_menu.xml",
'views/account_tax.xml', "views/account_tax.xml",
'views/product.xml', "views/product.xml",
'views/res_config_settings.xml', "views/res_config_settings.xml",
'views/res_partner.xml', "views/res_partner.xml",
'views/account_report.xml', "views/account_report.xml",
'wizard/account_invoice_mark_sent_view.xml', "wizard/account_invoice_mark_sent_view.xml",
'wizard/account_group_generate_view.xml', "wizard/account_group_generate_view.xml",
'wizard/account_payment_register_views.xml', "wizard/account_payment_register_views.xml",
'security/ir.model.access.csv', "security/ir.model.access.csv",
], ],
'installable': True, "installable": True,
} }

View File

@@ -2,18 +2,19 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
import logging import logging
from odoo import api, models
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AccountAccount(models.Model): class AccountAccount(models.Model):
_inherit = 'account.account' _inherit = "account.account"
@api.depends('name', 'code') @api.depends("name", "code")
def name_get(self): def name_get(self):
if self._context.get('account_account_show_code_only'): if self._context.get("account_account_show_code_only"):
res = [] res = []
for record in self: for record in self:
res.append((record.id, record.code)) res.append((record.id, record.code))
@@ -24,36 +25,42 @@ class AccountAccount(models.Model):
# https://github.com/odoo/odoo/issues/23040 # https://github.com/odoo/odoo/issues/23040
# TODO mig to v14 ? # TODO mig to v14 ?
def fix_bank_account_types(self): def fix_bank_account_types(self):
aao = self.env['account.account'] aao = self.env["account.account"]
companies = self.env['res.company'].search([]) companies = self.env["res.company"].search([])
if len(companies) > 1: if len(companies) > 1:
self = self.sudo() self = self.sudo()
logger.info("START the script 'fix bank and cash account types'") logger.info("START the script 'fix bank and cash account types'")
bank_type = self.env.ref('account.data_account_type_liquidity') bank_type = self.env.ref("account.data_account_type_liquidity")
asset_type = self.env.ref('account.data_account_type_current_assets') asset_type = self.env.ref("account.data_account_type_current_assets")
journals = self.env['account.journal'].search( journals = self.env["account.journal"].search(
[('type', 'in', ('bank', 'cash'))], order='company_id') [("type", "in", ("bank", "cash"))], order="company_id"
)
journal_accounts_bank_type = aao journal_accounts_bank_type = aao
for journal in journals: for journal in journals:
for account in [ for account in [
journal.default_credit_account_id, journal.default_credit_account_id,
journal.default_debit_account_id]: journal.default_debit_account_id,
]:
if account: if account:
if account.user_type_id != bank_type: if account.user_type_id != bank_type:
account.user_type_id = bank_type.id account.user_type_id = bank_type.id
logger.info( logger.info(
'Company %s: Account %s updated to Bank ' "Company %s: Account %s updated to Bank " "and Cash type",
'and Cash type', account.company_id.display_name,
account.company_id.display_name, account.code) account.code,
)
if account not in journal_accounts_bank_type: if account not in journal_accounts_bank_type:
journal_accounts_bank_type += account journal_accounts_bank_type += account
accounts = aao.search([ accounts = aao.search(
('user_type_id', '=', bank_type.id)], order='company_id, code') [("user_type_id", "=", bank_type.id)], order="company_id, code"
)
for account in accounts: for account in accounts:
if account not in journal_accounts_bank_type: if account not in journal_accounts_bank_type:
account.user_type_id = asset_type.id account.user_type_id = asset_type.id
logger.info( logger.info(
'Company %s: Account %s updated to Current Asset type', "Company %s: Account %s updated to Current Asset type",
account.company_id.display_name, account.code) account.company_id.display_name,
account.code,
)
logger.info("END of the script 'fix bank and cash account types'") logger.info("END of the script 'fix bank and cash account types'")
return True return True

View File

@@ -6,10 +6,10 @@ from odoo import models
class AccountAnalyticAccount(models.Model): class AccountAnalyticAccount(models.Model):
_inherit = 'account.analytic.account' _inherit = "account.analytic.account"
def name_get(self): def name_get(self):
if self._context.get('analytic_account_show_code_only'): if self._context.get("analytic_account_show_code_only"):
res = [] res = []
for record in self: for record in self:
res.append((record.id, record.code or record.name)) res.append((record.id, record.code or record.name))
@@ -17,8 +17,11 @@ class AccountAnalyticAccount(models.Model):
else: else:
return super().name_get() return super().name_get()
_sql_constraints = [( _sql_constraints = [
'code_company_unique', (
'unique(code, company_id)', "code_company_unique",
'An analytic account with the same code already ' "unique(code, company_id)",
'exists in the same company!')] "An analytic account with the same code already "
"exists in the same company!",
)
]

View File

@@ -7,18 +7,19 @@ from odoo.tools.misc import format_date
class AccountBankStatement(models.Model): class AccountBankStatement(models.Model):
_inherit = 'account.bank.statement' _inherit = "account.bank.statement"
start_date = fields.Date( start_date = fields.Date(
compute='_compute_dates', string='Start Date', readonly=True, compute="_compute_dates", string="Start Date", readonly=True, store=True
store=True) )
end_date = fields.Date( end_date = fields.Date(
compute='_compute_dates', string='End Date', readonly=True, compute="_compute_dates", string="End Date", readonly=True, store=True
store=True) )
hide_bank_statement_balance = fields.Boolean( hide_bank_statement_balance = fields.Boolean(
related='journal_id.hide_bank_statement_balance', readonly=True) related="journal_id.hide_bank_statement_balance", readonly=True
)
@api.depends('line_ids.date') @api.depends("line_ids.date")
def _compute_dates(self): def _compute_dates(self):
for st in self: for st in self:
dates = [line.date for line in st.line_ids] dates = [line.date for line in st.line_ids]
@@ -30,26 +31,31 @@ class AccountBankStatement(models.Model):
if stmt.hide_bank_statement_balance: if stmt.hide_bank_statement_balance:
continue continue
else: else:
super(AccountBankStatement, stmt)._check_balance_end_real_same_as_computed() super(
AccountBankStatement, stmt
)._check_balance_end_real_same_as_computed()
return True return True
@api.depends('name', 'start_date', 'end_date') @api.depends("name", "start_date", "end_date")
def name_get(self): def name_get(self):
res = [] res = []
for statement in self: for statement in self:
name = "%s (%s => %s)" % ( name = "%s (%s => %s)" % (
statement.name, statement.name,
statement.start_date and format_date(self.env, statement.start_date) or '', statement.start_date
statement.end_date and format_date(self.env, statement.end_date) or '') and format_date(self.env, statement.start_date)
or "",
statement.end_date and format_date(self.env, statement.end_date) or "",
)
res.append((statement.id, name)) res.append((statement.id, name))
return res return res
class AccountBankStatementLine(models.Model): class AccountBankStatementLine(models.Model):
_inherit = 'account.bank.statement.line' _inherit = "account.bank.statement.line"
# Native order is: # Native order is:
# _order = 'statement_id desc, sequence, id desc' # _order = 'statement_id desc, sequence, id desc'
_order = 'statement_id desc, date desc, sequence, id desc' _order = "statement_id desc, date desc, sequence, id desc"
# Disable guessing for reconciliation # Disable guessing for reconciliation
# because my experience with several customers shows that it is a problem # because my experience with several customers shows that it is a problem
@@ -80,12 +86,14 @@ class AccountBankStatementLine(models.Model):
def show_account_move(self): def show_account_move(self):
self.ensure_one() self.ensure_one()
action = self.env.ref('account.action_move_line_form').read()[0] action = self.env.ref("account.action_move_line_form").read()[0]
# Note: this action is on account.move, not account.move.line ! # Note: this action is on account.move, not account.move.line !
action.update({ action.update(
'views': False, {
'view_id': False, "views": False,
'view_mode': 'form,tree', "view_id": False,
'res_id': self.move_id.id, "view_mode": "form,tree",
}) "res_id": self.move_id.id,
}
)
return action return action

View File

@@ -6,11 +6,11 @@ from odoo import api, models
class AccountIncoterms(models.Model): class AccountIncoterms(models.Model):
_inherit = 'account.incoterms' _inherit = "account.incoterms"
@api.depends('code', 'name') @api.depends("code", "name")
def name_get(self): def name_get(self):
res = [] res = []
for rec in self: for rec in self:
res.append((rec.id, '[%s] %s' % (rec.code, rec.name))) res.append((rec.id, "[%s] %s" % (rec.code, rec.name)))
return res return res

View File

@@ -6,22 +6,22 @@ from odoo import api, fields, models
class AccountJournal(models.Model): class AccountJournal(models.Model):
_inherit = 'account.journal' _inherit = "account.journal"
hide_bank_statement_balance = fields.Boolean( hide_bank_statement_balance = fields.Boolean(
string='Hide Bank Statement Balance', string="Hide Bank Statement Balance",
help="You may want to enable this option when your bank " help="You may want to enable this option when your bank "
"journal is generated from a bank statement file that " "journal is generated from a bank statement file that "
"doesn't handle start/end balance (QIF for instance) and " "doesn't handle start/end balance (QIF for instance) and "
"you don't want to enter the start/end balance manually: it " "you don't want to enter the start/end balance manually: it "
"will prevent the display of wrong information in the accounting " "will prevent the display of wrong information in the accounting "
"dashboard and on bank statements.") "dashboard and on bank statements.",
)
@api.depends( @api.depends("name", "currency_id", "company_id", "company_id.currency_id", "code")
'name', 'currency_id', 'company_id', 'company_id.currency_id', 'code')
def name_get(self): def name_get(self):
res = [] res = []
if self._context.get('journal_show_code_only'): if self._context.get("journal_show_code_only"):
for journal in self: for journal in self:
res.append((journal.id, journal.code)) res.append((journal.id, journal.code))
return res return res
@@ -29,12 +29,14 @@ class AccountJournal(models.Model):
for journal in self: for journal in self:
name = "[%s] %s" % (journal.code, journal.name) name = "[%s] %s" % (journal.code, journal.name)
if ( if (
journal.currency_id and journal.currency_id
journal.currency_id != journal.company_id.currency_id): and journal.currency_id != journal.company_id.currency_id
):
name = "%s (%s)" % (name, journal.currency_id.name) name = "%s (%s)" % (name, journal.currency_id.name)
res.append((journal.id, name)) res.append((journal.id, name))
return res return res
# @api.constrains('default_credit_account_id', 'default_debit_account_id') # @api.constrains('default_credit_account_id', 'default_debit_account_id')
# def _check_account_type_on_bank_journal(self): # def _check_account_type_on_bank_journal(self):
# bank_acc_type = self.env.ref('account.data_account_type_liquidity') # bank_acc_type = self.env.ref('account.data_account_type_liquidity')

View File

@@ -3,17 +3,17 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models from odoo import api, fields, models
from odoo.osv import expression
from odoo.tools import float_is_zero from odoo.tools import float_is_zero
from odoo.tools.misc import format_date from odoo.tools.misc import format_date
from odoo.osv import expression
class AccountMove(models.Model): class AccountMove(models.Model):
_inherit = 'account.move' _inherit = "account.move"
# By default, we can still modify "ref" when account move is posted # By default, we can still modify "ref" when account move is posted
# which seems a bit lazy for me... # which seems a bit lazy for me...
ref = fields.Char(states={'posted': [('readonly', True)]}) ref = fields.Char(states={"posted": [("readonly", True)]})
date = fields.Date(tracking=True) date = fields.Date(tracking=True)
invoice_date_due = fields.Date(tracking=True) invoice_date_due = fields.Date(tracking=True)
invoice_payment_term_id = fields.Many2one(tracking=True) invoice_payment_term_id = fields.Many2one(tracking=True)
@@ -22,51 +22,63 @@ class AccountMove(models.Model):
fiscal_position_id = fields.Many2one(tracking=True) fiscal_position_id = fields.Many2one(tracking=True)
amount_total = fields.Monetary(tracking=True) amount_total = fields.Monetary(tracking=True)
# for invoice report # for invoice report
has_discount = fields.Boolean( has_discount = fields.Boolean(compute="_compute_has_discount", readonly=True)
compute='_compute_has_discount', readonly=True)
# has_attachment is useful for those who use attachment to archive # has_attachment is useful for those who use attachment to archive
# supplier invoices. It allows them to find supplier invoices # supplier invoices. It allows them to find supplier invoices
# that don't have any attachment # that don't have any attachment
has_attachment = fields.Boolean( has_attachment = fields.Boolean(
compute='_compute_has_attachment', compute="_compute_has_attachment",
search='_search_has_attachment', readonly=True) search="_search_has_attachment",
readonly=True,
)
sale_dates = fields.Char( sale_dates = fields.Char(
compute="_compute_sales_dates", readonly=True, compute="_compute_sales_dates",
readonly=True,
help="This information appears on invoice qweb report " help="This information appears on invoice qweb report "
"(you may use it for your own report)") "(you may use it for your own report)",
)
def _compute_has_discount(self): def _compute_has_discount(self):
prec = self.env['decimal.precision'].precision_get('Discount') prec = self.env["decimal.precision"].precision_get("Discount")
for inv in self: for inv in self:
has_discount = False has_discount = False
for line in inv.invoice_line_ids: for line in inv.invoice_line_ids:
if not line.display_type and not float_is_zero(line.discount, precision_digits=prec): if not line.display_type and not float_is_zero(
line.discount, precision_digits=prec
):
has_discount = True has_discount = True
break break
inv.has_discount = has_discount inv.has_discount = has_discount
def _compute_has_attachment(self): def _compute_has_attachment(self):
iao = self.env['ir.attachment'] iao = self.env["ir.attachment"]
for move in self: for move in self:
if iao.search_count([ if iao.search_count(
('res_model', '=', 'account.move'), [
('res_id', '=', move.id), ("res_model", "=", "account.move"),
('type', '=', 'binary'), ("res_id", "=", move.id),
('company_id', '=', move.company_id.id)]): ("type", "=", "binary"),
("company_id", "=", move.company_id.id),
]
):
move.has_attachment = True move.has_attachment = True
else: else:
move.has_attachment = False move.has_attachment = False
def _search_has_attachment(self, operator, value): def _search_has_attachment(self, operator, value):
att_inv_ids = {} att_inv_ids = {}
if operator == '=': if operator == "=":
search_res = self.env['ir.attachment'].search_read([ search_res = self.env["ir.attachment"].search_read(
('res_model', '=', 'account.move'), [
('type', '=', 'binary'), ("res_model", "=", "account.move"),
('res_id', '!=', False)], ['res_id']) ("type", "=", "binary"),
("res_id", "!=", False),
],
["res_id"],
)
for att in search_res: for att in search_res:
att_inv_ids[att['res_id']] = True att_inv_ids[att["res_id"]] = True
res = [('id', value and 'in' or 'not in', list(att_inv_ids))] res = [("id", value and "in" or "not in", list(att_inv_ids))]
return res return res
# when you have an invoice created from a lot of sale orders, the 'name' # when you have an invoice created from a lot of sale orders, the 'name'
@@ -81,10 +93,10 @@ class AccountMove(models.Model):
name = old_re[1] name = old_re[1]
if name and len(name) > 100: if name and len(name) > 100:
# nice cut # nice cut
name = '%s ...' % ', '.join(name.split(', ')[:3]) name = "%s ..." % ", ".join(name.split(", ")[:3])
# if not enough, hard cut # if not enough, hard cut
if len(name) > 120: if len(name) > 120:
name = '%s ...' % old_re[1][:120] name = "%s ..." % old_re[1][:120]
res.append((old_re[0], name)) res.append((old_re[0], name))
return res return res
@@ -94,23 +106,26 @@ class AccountMove(models.Model):
# write a rubbish '/' in it ! # write a rubbish '/' in it !
# 2) the 'name' field of the account.move.line is used in the overdue # 2) the 'name' field of the account.move.line is used in the overdue
# letter, and '/' is not meaningful for our customer ! # letter, and '/' is not meaningful for our customer !
# TODO mig to v12 # TODO mig to v12
# def action_move_create(self): # def action_move_create(self):
# res = super().action_move_create() # res = super().action_move_create()
# for inv in self: # for inv in self:
# self._cr.execute( # self._cr.execute(
# "UPDATE account_move_line SET name= " # "UPDATE account_move_line SET name= "
# "CASE WHEN name='/' THEN %s " # "CASE WHEN name='/' THEN %s "
# "ELSE %s||' - '||name END " # "ELSE %s||' - '||name END "
# "WHERE move_id=%s", (inv.number, inv.number, inv.move_id.id)) # "WHERE move_id=%s", (inv.number, inv.number, inv.move_id.id))
# self.invalidate_cache() # self.invalidate_cache()
# return res # return res
def delete_lines_qty_zero(self): def delete_lines_qty_zero(self):
lines = self.env['account.move.line'].search([ lines = self.env["account.move.line"].search(
('display_type', '=', False), [
('move_id', 'in', self.ids), ("display_type", "=", False),
('quantity', '=', 0)]) ("move_id", "in", self.ids),
("quantity", "=", 0),
]
)
lines.unlink() lines.unlink()
return True return True
@@ -120,24 +135,27 @@ class AccountMove(models.Model):
res = [] res = []
has_sections = False has_sections = False
subtotal = 0.0 subtotal = 0.0
sign = self.move_type == 'out_refund' and -1 or 1 sign = self.move_type == "out_refund" and -1 or 1
# Warning: the order of invoice line is forced in the view # Warning: the order of invoice line is forced in the view
# <tree editable="bottom" default_order="sequence, date desc, move_name desc, id" # <tree editable="bottom" default_order="sequence, date desc, move_name desc, id"
# it's not the same as the _order in the class AccountMoveLine # it's not the same as the _order in the class AccountMoveLine
lines = self.env['account.move.line'].search([('exclude_from_invoice_tab', '=', False), ('move_id', '=', self.id)], order="sequence, date desc, move_name desc, id") lines = self.env["account.move.line"].search(
[("exclude_from_invoice_tab", "=", False), ("move_id", "=", self.id)],
order="sequence, date desc, move_name desc, id",
)
for line in lines: for line in lines:
if line.display_type == 'line_section': if line.display_type == "line_section":
# insert line # insert line
if has_sections: if has_sections:
res.append({'subtotal': subtotal}) res.append({"subtotal": subtotal})
subtotal = 0.0 # reset counter subtotal = 0.0 # reset counter
has_sections = True has_sections = True
else: else:
if not line.display_type: if not line.display_type:
subtotal += line.price_subtotal * sign subtotal += line.price_subtotal * sign
res.append({'line': line}) res.append({"line": line})
if has_sections: # insert last subtotal line if has_sections: # insert last subtotal line
res.append({'subtotal': subtotal}) res.append({"subtotal": subtotal})
# res: # res:
# [ # [
# {'line': account_invoice_line(1) with display_type=='line_section'}, # {'line': account_invoice_line(1) with display_type=='line_section'},
@@ -149,44 +167,55 @@ class AccountMove(models.Model):
return res return res
def _compute_sales_dates(self): def _compute_sales_dates(self):
""" French law requires to set sale order dates into invoice """French law requires to set sale order dates into invoice
returned string: "sale1 (date1), sale2 (date2) ..." returned string: "sale1 (date1), sale2 (date2) ..."
""" """
for inv in self: for inv in self:
sales = inv.invoice_line_ids.mapped( sales = inv.invoice_line_ids.mapped("sale_line_ids").mapped("order_id")
'sale_line_ids').mapped('order_id') dates = [
dates = ["%s (%s)" % ( "%s (%s)" % (x.name, format_date(inv.env, self.date_order))
x.name, format_date(inv.env, self.date_order)) for x in sales
for x in sales] ]
inv.sale_dates = ", ".join(dates) inv.sale_dates = ", ".join(dates)
# allow to manually create moves not only in general journals, # allow to manually create moves not only in general journals,
# but also in cash journal and check journals (= bank journals not linked to a bank account) # but also in cash journal and check journals (= bank journals not linked to a bank account)
@api.depends('company_id', 'invoice_filter_type_domain') @api.depends("company_id", "invoice_filter_type_domain")
def _compute_suitable_journal_ids(self): def _compute_suitable_journal_ids(self):
for move in self: for move in self:
if move.invoice_filter_type_domain: if move.invoice_filter_type_domain:
super(AccountMove, move)._compute_suitable_journal_ids() super(AccountMove, move)._compute_suitable_journal_ids()
else: else:
company_id = move.company_id.id or self.env.company.id company_id = move.company_id.id or self.env.company.id
domain = expression.AND([ domain = expression.AND(
[('company_id', '=', company_id)], [
expression.OR([ [("company_id", "=", company_id)],
[('type', 'in', ('general', 'cash'))], expression.OR(
[('type', '=', 'bank'), ('bank_account_id', '=', False)] [
]) [("type", "in", ("general", "cash"))],
]) [
move.suitable_journal_ids = self.env['account.journal'].search(domain) ("type", "=", "bank"),
("bank_account_id", "=", False),
],
]
),
]
)
move.suitable_journal_ids = self.env["account.journal"].search(domain)
def button_draft(self): def button_draft(self):
super().button_draft() super().button_draft()
# Delete attached pdf invoice # Delete attached pdf invoice
try: try:
report_invoice = self.env['ir.actions.report']._get_report_from_name('account.report_invoice') report_invoice = self.env["ir.actions.report"]._get_report_from_name(
"account.report_invoice"
)
except IndexError: except IndexError:
report_invoice = False report_invoice = False
if report_invoice and report_invoice.attachment: if report_invoice and report_invoice.attachment:
for move in self.filtered(lambda x: x.move_type in ('out_invoice', 'out_refund')): for move in self.filtered(
lambda x: x.move_type in ("out_invoice", "out_refund")
):
# The pb is that the filename is dynamic and related to move.state # The pb is that the filename is dynamic and related to move.state
# in v12, the feature was native and they used that kind of code: # in v12, the feature was native and they used that kind of code:
# with invoice.env.do_in_draft(): # with invoice.env.do_in_draft():
@@ -194,22 +223,25 @@ class AccountMove(models.Model):
# attachment = self.env.ref('account.account_invoices').retrieve_attachment(invoice) # attachment = self.env.ref('account.account_invoices').retrieve_attachment(invoice)
# But do_in_draft() doesn't exists in v14 # But do_in_draft() doesn't exists in v14
# If you know how we could do that, please update the code below # If you know how we could do that, please update the code below
attachment = self.env['ir.attachment'].search([ attachment = self.env["ir.attachment"].search(
('name', '=', self._get_invoice_attachment_name()), [
('res_id', '=', move.id), ("name", "=", self._get_invoice_attachment_name()),
('res_model', '=', self._name), ("res_id", "=", move.id),
('type', '=', 'binary'), ("res_model", "=", self._name),
], limit=1) ("type", "=", "binary"),
],
limit=1,
)
if attachment: if attachment:
attachment.unlink() attachment.unlink()
def _get_invoice_attachment_name(self): def _get_invoice_attachment_name(self):
self.ensure_one() self.ensure_one()
return '%s.pdf' % (self.name and self.name.replace('/', '_') or 'INV') return "%s.pdf" % (self.name and self.name.replace("/", "_") or "INV")
class AccountMoveLine(models.Model): class AccountMoveLine(models.Model):
_inherit = 'account.move.line' _inherit = "account.move.line"
# Native order: # Native order:
# _order = "date desc, move_name desc, id" # _order = "date desc, move_name desc, id"
# Problem: when you manually create a journal entry, the # Problem: when you manually create a journal entry, the
@@ -220,34 +252,42 @@ class AccountMoveLine(models.Model):
# In the 'account' module, we have related stored field for: # In the 'account' module, we have related stored field for:
# name (move_name), date, ref, state (parent_state), # name (move_name), date, ref, state (parent_state),
# journal_id, company_id, payment_id, statement_line_id, # journal_id, company_id, payment_id, statement_line_id,
account_reconcile = fields.Boolean(related='account_id.reconcile') account_reconcile = fields.Boolean(related="account_id.reconcile")
full_reconcile_id = fields.Many2one(string='Full Reconcile') full_reconcile_id = fields.Many2one(string="Full Reconcile")
matched_debit_ids = fields.One2many(string='Partial Reconcile Debit') matched_debit_ids = fields.One2many(string="Partial Reconcile Debit")
matched_credit_ids = fields.One2many(string='Partial Reconcile Credit') matched_credit_ids = fields.One2many(string="Partial Reconcile Credit")
reconcile_string = fields.Char( reconcile_string = fields.Char(
compute='_compute_reconcile_string', string='Reconcile', store=True) compute="_compute_reconcile_string", string="Reconcile", store=True
)
# for optional display in tree view # for optional display in tree view
product_barcode = fields.Char(related='product_id.barcode', string="Product Barcode") product_barcode = fields.Char(
related="product_id.barcode", string="Product Barcode"
)
def show_account_move_form(self): def show_account_move_form(self):
self.ensure_one() self.ensure_one()
action = self.env.ref('account.action_move_line_form').read()[0] action = self.env.ref("account.action_move_line_form").read()[0]
action.update({ action.update(
'res_id': self.move_id.id, {
'view_id': False, "res_id": self.move_id.id,
'views': False, "view_id": False,
'view_mode': 'form,tree', "views": False,
}) "view_mode": "form,tree",
}
)
return action return action
@api.depends( @api.depends("full_reconcile_id", "matched_debit_ids", "matched_credit_ids")
'full_reconcile_id', 'matched_debit_ids', 'matched_credit_ids')
def _compute_reconcile_string(self): def _compute_reconcile_string(self):
for line in self: for line in self:
rec_str = False rec_str = False
if line.full_reconcile_id: if line.full_reconcile_id:
rec_str = line.full_reconcile_id.name rec_str = line.full_reconcile_id.name
else: else:
rec_str = ', '.join([ rec_str = ", ".join(
'a%d' % pr.id for pr in line.matched_debit_ids + line.matched_credit_ids]) [
"a%d" % pr.id
for pr in line.matched_debit_ids + line.matched_credit_ids
]
)
line.reconcile_string = rec_str line.reconcile_string = rec_str

View File

@@ -17,7 +17,8 @@ class AccountPartialReconcile(models.Model):
# Prefix for full rec: 'A' (upper case) # Prefix for full rec: 'A' (upper case)
# Prefix for partial rec: 'a' (lower case) # Prefix for partial rec: 'a' (lower case)
amount_fmt = formatLang( amount_fmt = formatLang(
self.env, rec.amount, currency_obj=rec.company_currency_id) self.env, rec.amount, currency_obj=rec.company_currency_id
name = 'a%d (%s)' % (rec.id, amount_fmt) )
name = "a%d (%s)" % (rec.id, amount_fmt)
res.append((rec.id, name)) res.append((rec.id, name))
return res return res

View File

@@ -2,44 +2,61 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
class ProductTemplate(models.Model): class ProductTemplate(models.Model):
_inherit = 'product.template' _inherit = "product.template"
# DON'T put store=True on those fields, because they are company dependent # DON'T put store=True on those fields, because they are company dependent
sale_price_type = fields.Selection( sale_price_type = fields.Selection(
'_sale_purchase_price_type_sel', compute='_compute_sale_price_type', "_sale_purchase_price_type_sel",
string='Sale Price Type', compute_sudo=False, readonly=True) compute="_compute_sale_price_type",
string="Sale Price Type",
compute_sudo=False,
readonly=True,
)
purchase_price_type = fields.Selection( purchase_price_type = fields.Selection(
'_sale_purchase_price_type_sel', compute='_compute_purchase_price_type', "_sale_purchase_price_type_sel",
string='Purchase Price Type', compute_sudo=False, readonly=True) compute="_compute_purchase_price_type",
string="Purchase Price Type",
compute_sudo=False,
readonly=True,
)
@api.model @api.model
def _sale_purchase_price_type_sel(self): def _sale_purchase_price_type_sel(self):
return [('incl', _('Tax incl.')), ('excl', _('Tax excl.'))] return [("incl", _("Tax incl.")), ("excl", _("Tax excl."))]
@api.depends('taxes_id') @api.depends("taxes_id")
def _compute_sale_price_type(self): def _compute_sale_price_type(self):
for pt in self: for pt in self:
sale_price_type = 'incl' sale_price_type = "incl"
if pt.taxes_id and all([not t.price_include for t in pt.taxes_id if t.amount_type == 'percent']): if pt.taxes_id and all(
sale_price_type = 'excl' [not t.price_include for t in pt.taxes_id if t.amount_type == "percent"]
):
sale_price_type = "excl"
pt.sale_price_type = sale_price_type pt.sale_price_type = sale_price_type
@api.depends('supplier_taxes_id') @api.depends("supplier_taxes_id")
def _compute_purchase_price_type(self): def _compute_purchase_price_type(self):
for pt in self: for pt in self:
purchase_price_type = 'incl' purchase_price_type = "incl"
if pt.supplier_taxes_id and all([not t.price_include for t in pt.supplier_taxes_id if t.amount_type == 'percent']): if pt.supplier_taxes_id and all(
purchase_price_type = 'excl' [
not t.price_include
for t in pt.supplier_taxes_id
if t.amount_type == "percent"
]
):
purchase_price_type = "excl"
pt.purchase_price_type = purchase_price_type pt.purchase_price_type = purchase_price_type
class ProductSupplierinfo(models.Model): class ProductSupplierinfo(models.Model):
_inherit = 'product.supplierinfo' _inherit = "product.supplierinfo"
# DON'T put store=True on those fields, because they are company dependent # DON'T put store=True on those fields, because they are company dependent
purchase_price_type = fields.Selection( purchase_price_type = fields.Selection(
related='product_tmpl_id.purchase_price_type', related_sudo=False) related="product_tmpl_id.purchase_price_type", related_sudo=False
)

View File

@@ -6,7 +6,7 @@ from odoo import fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = "res.partner"
invoice_warn = fields.Selection(tracking=True) invoice_warn = fields.Selection(tracking=True)
property_account_position_id = fields.Many2one(tracking=True) property_account_position_id = fields.Many2one(tracking=True)

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<template id="report_invoice_document" inherit_id="account.report_invoice_document"> <template id="report_invoice_document" inherit_id="account.report_invoice_document">
<xpath expr="//div[@name='origin']/p" position="replace"> <xpath expr="//div[@name='origin']/p" position="replace">
<p class="m-0" t-field="o.sale_dates"/> <p class="m-0" t-field="o.sale_dates" />
</xpath> </xpath>
</template> </template>

View File

@@ -1,20 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2021 Akretion France (http://www.akretion.com/) Copyright 2015-2021 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_account_form" model="ir.ui.view"> <record id="view_account_form" model="ir.ui.view">
<field name="name">account.account.form</field> <field name="name">account.account.form</field>
<field name="model">account.account</field> <field name="model">account.account</field>
<field name="inherit_id" ref="account.view_account_form"/> <field name="inherit_id" ref="account.view_account_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="deprecated" position="before"> <field name="deprecated" position="before">
<field name="reconcile" attrs="{'invisible': ['|', ('internal_type','=','liquidity'), ('internal_group', '=', 'off_balance')]}"/> <field
name="reconcile"
attrs="{'invisible': ['|', ('internal_type','=','liquidity'), ('internal_group', '=', 'off_balance')]}"
/>
</field> </field>
</field> </field>
</record> </record>
@@ -23,13 +25,17 @@
<record id="view_account_search" model="ir.ui.view"> <record id="view_account_search" model="ir.ui.view">
<field name="name">account.account.search</field> <field name="name">account.account.search</field>
<field name="model">account.account</field> <field name="model">account.account</field>
<field name="inherit_id" ref="account.view_account_search"/> <field name="inherit_id" ref="account.view_account_search" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<!-- The native "name" filter uses a domain ['|', ('name','ilike',self), ('code','=like',str(self)+'%')] <!-- The native "name" filter uses a domain ['|', ('name','ilike',self), ('code','=like',str(self)+'%')]
This is good because it uses '=like' on 'code', but sometimes there are digits in account names, This is good because it uses '=like' on 'code', but sometimes there are digits in account names,
so you get additionnal unexpected accounts in the result of the search --> so you get additionnal unexpected accounts in the result of the search -->
<field name="name" position="after"> <field name="name" position="after">
<field name="code" filter_domain="[('code', '=like', str(self)+'%')]" string="Code"/> <field
name="code"
filter_domain="[('code', '=like', str(self)+'%')]"
string="Code"
/>
</field> </field>
</field> </field>
</record> </record>

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_account_type_tree" model="ir.ui.view"> <record id="view_account_type_tree" model="ir.ui.view">
@@ -13,7 +12,7 @@
<field name="inherit_id" ref="account.view_account_type_tree" /> <field name="inherit_id" ref="account.view_account_type_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="type" position="after"> <field name="type" position="after">
<field name="include_initial_balance" optional="show"/> <field name="include_initial_balance" optional="show" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -1,45 +1,61 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_bank_statement_form" model="ir.ui.view"> <record id="view_bank_statement_form" model="ir.ui.view">
<field name="name">usability.account.bank.statement.form</field> <field name="name">usability.account.bank.statement.form</field>
<field name="model">account.bank.statement</field> <field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_form"/> <field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='line_ids']/tree/button[@name='button_undo_reconciliation']" position="after"> <xpath
<field name="move_id" invisible="1"/> expr="//field[@name='line_ids']/tree/button[@name='button_undo_reconciliation']"
<button name="show_account_move" type="object" position="after"
title="View Journal Entry" icon="fa-arrow-right"/> >
<field name="move_id" invisible="1" />
<button
name="show_account_move"
type="object"
title="View Journal Entry"
icon="fa-arrow-right"
/>
</xpath> </xpath>
<field name="date" position="after"> <field name="date" position="after">
<field name="start_date"/> <field name="start_date" />
<field name="end_date"/> <field name="end_date" />
<field name="hide_bank_statement_balance" invisible="1"/> <field name="hide_bank_statement_balance" invisible="1" />
</field> </field>
<field name="date" position="attributes"> <field name="date" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</field> </field>
<label for="balance_start" position="attributes"> <label for="balance_start" position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute> <attribute
name="attrs"
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</label> </label>
<label for="balance_end_real" position="attributes"> <label for="balance_end_real" position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute> <attribute
name="attrs"
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</label> </label>
<xpath expr="//field[@name='balance_start']/.." position="attributes"> <xpath expr="//field[@name='balance_start']/.." position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute> <attribute
name="attrs"
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='balance_end_real']/.." position="attributes"> <xpath expr="//field[@name='balance_end_real']/.." position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute> <attribute
name="attrs"
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</xpath> </xpath>
<group name="sale_total" position="attributes"> <group name="sale_total" position="attributes">
<attribute name="attrs">{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute> <attribute
name="attrs"
>{'invisible': [('hide_bank_statement_balance', '=', True)]}</attribute>
</group> </group>
</field> </field>
</record> </record>
@@ -47,14 +63,14 @@
<record id="view_bank_statement_tree" model="ir.ui.view"> <record id="view_bank_statement_tree" model="ir.ui.view">
<field name="name">usability.account.bank.statement.tree</field> <field name="name">usability.account.bank.statement.tree</field>
<field name="model">account.bank.statement</field> <field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_tree"/> <field name="inherit_id" ref="account.view_bank_statement_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="date" position="attributes"> <field name="date" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</field> </field>
<field name="journal_id" position="after"> <field name="journal_id" position="after">
<field name="start_date"/> <field name="start_date" />
<field name="end_date"/> <field name="end_date" />
</field> </field>
</field> </field>
</record> </record>
@@ -62,7 +78,7 @@
<record id="view_bank_statement_search" model="ir.ui.view"> <record id="view_bank_statement_search" model="ir.ui.view">
<field name="name">usability.account.bank.statement.search</field> <field name="name">usability.account.bank.statement.search</field>
<field name="model">account.bank.statement</field> <field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_search"/> <field name="inherit_id" ref="account.view_bank_statement_search" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="date" position="attributes"> <field name="date" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
@@ -74,14 +90,20 @@
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</filter> </filter>
<field name="date" position="after"> <field name="date" position="after">
<field name="start_date"/> <field name="start_date" />
<field name="end_date"/> <field name="end_date" />
</field> </field>
<filter name="date" position="after"> <filter name="date" position="after">
<filter name="start_date_groupby" string="Start Date" <filter
context="{'group_by': 'start_date'}"/> name="start_date_groupby"
<filter name="end_date_groupby" string="End Date" string="Start Date"
context="{'group_by': 'end_date'}"/> context="{'group_by': 'start_date'}"
/>
<filter
name="end_date_groupby"
string="End Date"
context="{'group_by': 'end_date'}"
/>
</filter> </filter>
</field> </field>
</record> </record>

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2018-2020 Akretion (http://www.akretion.com/) Copyright 2018-2020 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
@@ -13,36 +12,43 @@
<field name="model">account.invoice.report</field> <field name="model">account.invoice.report</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Invoices Analysis"> <tree string="Invoices Analysis">
<field name="move_id"/> <field name="move_id" />
<field name="invoice_date"/> <field name="invoice_date" />
<field name="invoice_date_due"/> <field name="invoice_date_due" />
<field name="move_type"/> <field name="move_type" />
<field name="commercial_partner_id"/> <field name="commercial_partner_id" />
<field name="invoice_user_id"/> <field name="invoice_user_id" />
<field name="product_id"/> <field name="product_id" />
<field name="quantity" sum="1"/> <field name="quantity" sum="1" />
<field name="product_uom_id" groups="uom.group_uom"/> <field name="product_uom_id" groups="uom.group_uom" />
<field name="price_subtotal" sum="1"/> <field name="price_subtotal" sum="1" />
<field name="state"/> <field name="state" />
</tree> </tree>
</field> </field>
</record> </record>
<record id="account.action_account_invoice_report_all_supp" model="ir.actions.act_window"> <record
<field name="context">{'search_default_current': 1, 'search_default_supplier': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view --> id="account.action_account_invoice_report_all_supp"
model="ir.actions.act_window"
>
<field
name="context"
>{'search_default_current': 1, 'search_default_supplier': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
</record> </record>
<record id="account.action_account_invoice_report_all" model="ir.actions.act_window"> <record id="account.action_account_invoice_report_all" model="ir.actions.act_window">
<field name="context">{'search_default_current': 1, 'search_default_customer': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view --> <field
name="context"
>{'search_default_current': 1, 'search_default_customer': 1, 'group_by': ['invoice_date']}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
</record> </record>
<record id="view_account_invoice_report_pivot" model="ir.ui.view"> <record id="view_account_invoice_report_pivot" model="ir.ui.view">
<field name="name">usability.account.invoice.report</field> <field name="name">usability.account.invoice.report</field>
<field name="model">account.invoice.report</field> <field name="model">account.invoice.report</field>
<field name="inherit_id" ref="account.view_account_invoice_report_pivot"/> <field name="inherit_id" ref="account.view_account_invoice_report_pivot" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<pivot position="attributes"> <pivot position="attributes">
<attribute name="disable_linking"></attribute> <attribute name="disable_linking" />
</pivot> </pivot>
</field> </field>
</record> </record>

View File

@@ -1,19 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_account_journal_form" model="ir.ui.view"> <record id="view_account_journal_form" model="ir.ui.view">
<field name="name">usability.account.journal.form</field> <field name="name">usability.account.journal.form</field>
<field name="model">account.journal</field> <field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/> <field name="inherit_id" ref="account.view_account_journal_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="bank_statements_source" position="after"> <field name="bank_statements_source" position="after">
<field name="hide_bank_statement_balance" groups="account.group_account_readonly"/> <field
name="hide_bank_statement_balance"
groups="account.group_account_readonly"
/>
</field> </field>
</field> </field>
</record> </record>
@@ -21,13 +23,15 @@
<record id="account_journal_dashboard_kanban_view" model="ir.ui.view"> <record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
<field name="name">usability.account.journal.dashboard</field> <field name="name">usability.account.journal.dashboard</field>
<field name="model">account.journal</field> <field name="model">account.journal</field>
<field name="inherit_id" ref="account.account_journal_dashboard_kanban_view"/> <field name="inherit_id" ref="account.account_journal_dashboard_kanban_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="kanban_dashboard" position="after"> <field name="kanban_dashboard" position="after">
<field name="hide_bank_statement_balance"/> <field name="hide_bank_statement_balance" />
</field> </field>
<xpath expr="//div[@name='latest_statement']/.." position="attributes"> <xpath expr="//div[@name='latest_statement']/.." position="attributes">
<attribute name="t-if">dashboard.has_at_least_one_statement and dashboard.account_balance != dashboard.last_balance and !record.hide_bank_statement_balance.raw_value</attribute> <attribute
name="t-if"
>dashboard.has_at_least_one_statement and dashboard.account_balance != dashboard.last_balance and !record.hide_bank_statement_balance.raw_value</attribute>
</xpath> </xpath>
</field> </field>
</record> </record>
@@ -35,10 +39,10 @@
<record id="view_account_journal_tree" model="ir.ui.view"> <record id="view_account_journal_tree" model="ir.ui.view">
<field name="name">usability.account.journal.tree</field> <field name="name">usability.account.journal.tree</field>
<field name="model">account.journal</field> <field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_tree"/> <field name="inherit_id" ref="account.view_account_journal_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="name" position="after"> <field name="name" position="after">
<field name="code" optional="show"/> <field name="code" optional="show" />
</field> </field>
</field> </field>
</record> </record>
@@ -46,11 +50,15 @@
<record id="view_account_journal_search" model="ir.ui.view"> <record id="view_account_journal_search" model="ir.ui.view">
<field name="name">usability.account.journal.search</field> <field name="name">usability.account.journal.search</field>
<field name="model">account.journal</field> <field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_search"/> <field name="inherit_id" ref="account.view_account_journal_search" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<filter name="inactive" position="after"> <filter name="inactive" position="after">
<group name="groupby" string="Group By"> <group name="groupby" string="Group By">
<filter name="type_groupby" string="Type" context="{'group_by': 'type'}"/> <filter
name="type_groupby"
string="Type"
context="{'group_by': 'type'}"
/>
</group> </group>
</filter> </filter>
</field> </field>

View File

@@ -1,16 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<!-- Duplicate the menu "Sales > Configuration > Contacts > Bank Accounts" <!-- Duplicate the menu "Sales > Configuration > Contacts > Bank Accounts"
under "Accounting > Configuration", because most users will try to find it there --> under "Accounting > Configuration", because most users will try to find it there -->
<menuitem id="res_bank_account_config_menu" action="base.action_res_bank_form" parent="account.account_banks_menu" sequence="10"/> <menuitem
id="res_bank_account_config_menu"
action="base.action_res_bank_form"
parent="account.account_banks_menu"
sequence="10"
/>
<menuitem id="res_partner_bank_account_config_menu" action="base.action_res_partner_bank_account_form" parent="account.account_banks_menu" sequence="20"/> <menuitem
id="res_partner_bank_account_config_menu"
action="base.action_res_partner_bank_account_form"
parent="account.account_banks_menu"
sequence="20"
/>
</odoo> </odoo>

View File

@@ -1,16 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_move_form" model="ir.ui.view"> <record id="view_move_form" model="ir.ui.view">
<field name="name">account_usability.account.move.form</field> <field name="name">account_usability.account.move.form</field>
<field name="model">account.move</field> <field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/> <field name="inherit_id" ref="account.view_move_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="fiscal_position_id" position="attributes"> <field name="fiscal_position_id" position="attributes">
<attribute name="widget">selection</attribute> <attribute name="widget">selection</attribute>
@@ -22,28 +21,45 @@
<attribute name="class">btn-default</attribute> <attribute name="class">btn-default</attribute>
</button> </button>
<button name="action_register_payment" position="before"> <button name="action_register_payment" position="before">
<button name="%(account.account_invoices)d" type="action" string="Print" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"/> <button
name="%(account.account_invoices)d"
type="action"
string="Print"
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"
/>
</button> </button>
<button name="preview_invoice" position="attributes"> <button name="preview_invoice" position="attributes">
<attribute name="attrs">{'invisible': 1}</attribute> <attribute name="attrs">{'invisible': 1}</attribute>
</button> </button>
<!-- move sent field and make it visible --> <!-- move sent field and make it visible -->
<field name="is_move_sent" position="replace"/> <field name="is_move_sent" position="replace" />
<field name="invoice_origin" position="attributes"> <field name="invoice_origin" position="attributes">
<attribute name="invisible">0</attribute> <attribute name="invisible">0</attribute>
</field> </field>
<field name="invoice_origin" position="after"> <field name="invoice_origin" position="after">
<field name="is_move_sent" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"/> <field
name="is_move_sent"
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"
/>
</field> </field>
<xpath expr="//field[@name='line_ids']/tree/field[@name='analytic_account_id']" position="attributes"> <xpath
expr="//field[@name='line_ids']/tree/field[@name='analytic_account_id']"
position="attributes"
>
<attribute name="optional">show</attribute> <attribute name="optional">show</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='line_ids']/tree/field[@name='tax_tag_ids']" position="after"> <xpath
<field name="matching_number" optional="hide"/> expr="//field[@name='line_ids']/tree/field[@name='tax_tag_ids']"
<field name="reconcile_string" optional="show"/> position="after"
>
<field name="matching_number" optional="hide" />
<field name="reconcile_string" optional="show" />
</xpath> </xpath>
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='product_id']" position="after"> <xpath
<field name="product_barcode" optional="hide"/> expr="//field[@name='invoice_line_ids']/tree/field[@name='product_id']"
position="after"
>
<field name="product_barcode" optional="hide" />
</xpath> </xpath>
</field> </field>
</record> </record>
@@ -51,24 +67,41 @@
<record id="view_account_invoice_filter" model="ir.ui.view"> <record id="view_account_invoice_filter" model="ir.ui.view">
<field name="name">account_usability.account.move.search</field> <field name="name">account_usability.account.move.search</field>
<field name="model">account.move</field> <field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_invoice_filter"/> <field name="inherit_id" ref="account.view_account_invoice_filter" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<filter name="due_date" position="after"> <filter name="due_date" position="after">
<separator/> <separator />
<filter name="to_send" string="To Send" domain="[('is_move_sent', '=', False), ('state', '=', 'posted'), ('move_type', 'in', ('out_invoice', 'out_refund'))]"/> <filter
<filter name="sent" string="Sent" domain="[('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund'))]"/> name="to_send"
<separator/> string="To Send"
<filter name="no_attachment" string="Missing Attachment" domain="[('has_attachment', '=', False)]"/> domain="[('is_move_sent', '=', False), ('state', '=', 'posted'), ('move_type', 'in', ('out_invoice', 'out_refund'))]"
/>
<filter
name="sent"
string="Sent"
domain="[('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund'))]"
/>
<separator />
<filter
name="no_attachment"
string="Missing Attachment"
domain="[('has_attachment', '=', False)]"
/>
</filter> </filter>
</field> </field>
</record> </record>
<record id="view_move_line_tree" model="ir.ui.view"> <record id="view_move_line_tree" model="ir.ui.view">
<field name="model">account.move.line</field> <field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree"/> <field name="inherit_id" ref="account.view_move_line_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="matching_number" position="after"> <field name="matching_number" position="after">
<button title="View Journal Entry Form" type="object" name="show_account_move_form" icon="fa-arrow-right"/> <button
title="View Journal Entry Form"
type="object"
name="show_account_move_form"
icon="fa-arrow-right"
/>
</field> </field>
</field> </field>
</record> </record>
@@ -76,19 +109,35 @@
<record id="view_account_move_line_filter" model="ir.ui.view"> <record id="view_account_move_line_filter" model="ir.ui.view">
<field name="name">account_usability.account_move_line_search</field> <field name="name">account_usability.account_move_line_search</field>
<field name="model">account.move.line</field> <field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_account_move_line_filter"/> <field name="inherit_id" ref="account.view_account_move_line_filter" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<filter name="unposted" position="before"> <filter name="unposted" position="before">
<filter name="current_year" string="Current Year" domain="[('date', '&gt;=', (context_today().strftime('%Y-01-01'))), ('date', '&lt;=', (context_today().strftime('%Y-12-31')))]"/> <filter
<filter name="previous_year" string="Previous Year" domain="[('date', '&gt;=', (context_today() + relativedelta(day=1, month=1, years=-1)).strftime('%Y-%m-%d')), ('date', '&lt;=', (context_today() + relativedelta(day=31, month=12, years=-1)).strftime('%Y-%m-%d'))]"/> name="current_year"
<separator/> string="Current Year"
domain="[('date', '&gt;=', (context_today().strftime('%Y-01-01'))), ('date', '&lt;=', (context_today().strftime('%Y-12-31')))]"
/>
<filter
name="previous_year"
string="Previous Year"
domain="[('date', '&gt;=', (context_today() + relativedelta(day=1, month=1, years=-1)).strftime('%Y-%m-%d')), ('date', '&lt;=', (context_today() + relativedelta(day=31, month=12, years=-1)).strftime('%Y-%m-%d'))]"
/>
<separator />
</filter> </filter>
<field name="partner_id" position="after"> <field name="partner_id" position="after">
<field name="reconcile_string" /> <field name="reconcile_string" />
<field name="debit" filter_domain="['|', ('debit', '=', self), ('credit', '=', self)]" string="Debit or Credit"/> <field
name="debit"
filter_domain="['|', ('debit', '=', self), ('credit', '=', self)]"
string="Debit or Credit"
/>
</field> </field>
<filter name="unreconciled" position="before"> <filter name="unreconciled" position="before">
<filter name="reconciled" string="Fully Reconciled" domain="[('full_reconcile_id', '!=', False)]"/> <filter
name="reconciled"
string="Fully Reconciled"
domain="[('full_reconcile_id', '!=', False)]"
/>
</filter> </filter>
<filter name="unreconciled" position="attributes"> <filter name="unreconciled" position="attributes">
<attribute name="string">Unreconciled or Partially Reconciled</attribute> <attribute name="string">Unreconciled or Partially Reconciled</attribute>
@@ -98,13 +147,17 @@
<attribute name="string">Name or Reference</attribute> <attribute name="string">Name or Reference</attribute>
</field> --> </field> -->
<field name="partner_id" position="attributes"> <field name="partner_id" position="attributes">
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute> <attribute
name="domain"
>['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
</field> </field>
</field> </field>
</record> </record>
<record id="account.action_move_journal_line" model="ir.actions.act_window"> <record id="account.action_move_journal_line" model="ir.actions.act_window">
<field name="context">{'default_move_type': 'entry', 'view_no_maturity': True}</field> <field
name="context"
>{'default_move_type': 'entry', 'view_no_maturity': True}</field>
<!-- Remove 'search_default_misc_filter': 1 --> <!-- Remove 'search_default_misc_filter': 1 -->
</record> </record>

View File

@@ -1,20 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2018-2020 Akretion (http://www.akretion.com/) Copyright 2018-2020 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="account.account_invoices" model="ir.actions.report"> <record id="account.account_invoices" model="ir.actions.report">
<!-- Attach only on customer invoices/refunds --> <!-- Attach only on customer invoices/refunds -->
<field name="attachment">(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field> <field
name="attachment"
>(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field>
</record> </record>
<record id="account.account_invoices_without_payment" model="ir.actions.report"> <record id="account.account_invoices_without_payment" model="ir.actions.report">
<!-- Attach only on customer invoices/refunds --> <!-- Attach only on customer invoices/refunds -->
<field name="attachment">(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field> <field
name="attachment"
>(object.move_type in ('out_invoice', 'out_refund')) and (object.state == 'posted') and ((object.name or 'INV').replace('/','_')+'.pdf')</field>
</record> </record>

View File

@@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_tax_tree" model="ir.ui.view"> <record id="view_tax_tree" model="ir.ui.view">
<field name="model">account.tax</field> <field name="model">account.tax</field>
<field name="inherit_id" ref="account.view_tax_tree"/> <field name="inherit_id" ref="account.view_tax_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="description" position="after"> <field name="description" position="after">
<field name="price_include" optional="show"/> <field name="price_include" optional="show" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2017-2020 Akretion (http://www.akretion.com/) Copyright 2017-2020 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<!-- In the official account module, on product category and product template, <!-- In the official account module, on product category and product template,
@@ -16,8 +15,10 @@ Here, we set all those fields on account.group_account_invoice
<record id="product_template_form_view" model="ir.ui.view"> <record id="product_template_form_view" model="ir.ui.view">
<field name="name">account_usability.product.template.form</field> <field name="name">account_usability.product.template.form</field>
<field name="model">product.template</field> <field name="model">product.template</field>
<field name="priority">100</field> <!-- when you replace a field, it's always better to inherit at the end --> <field
<field name="inherit_id" ref="account.product_template_form_view"/> name="priority"
>100</field> <!-- when you replace a field, it's always better to inherit at the end -->
<field name="inherit_id" ref="account.product_template_form_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="property_account_income_id" position="attributes"> <field name="property_account_income_id" position="attributes">
<attribute name="groups">account.group_account_invoice</attribute> <attribute name="groups">account.group_account_invoice</attribute>
@@ -27,9 +28,14 @@ Here, we set all those fields on account.group_account_invoice
</field> </field>
<field name="list_price" position="replace"> <field name="list_price" position="replace">
<div name="list_price"> <div name="list_price">
<field name="list_price" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}" class="oe_inline"/> <field
<label for="sale_price_type" string=" "/> name="list_price"
<field name="sale_price_type"/> widget='monetary'
options="{'currency_field': 'currency_id', 'field_digits': True}"
class="oe_inline"
/>
<label for="sale_price_type" string=" " />
<field name="sale_price_type" />
</div> </div>
</field> </field>
</field> </field>
@@ -38,7 +44,7 @@ Here, we set all those fields on account.group_account_invoice
<record id="view_category_property_form" model="ir.ui.view"> <record id="view_category_property_form" model="ir.ui.view">
<field name="name">account_usability.product.category.form</field> <field name="name">account_usability.product.category.form</field>
<field name="model">product.category</field> <field name="model">product.category</field>
<field name="inherit_id" ref="account.view_category_property_form"/> <field name="inherit_id" ref="account.view_category_property_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<group name="account_property" position="attributes"> <group name="account_property" position="attributes">
<attribute name="groups">account.group_account_invoice</attribute> <attribute name="groups">account.group_account_invoice</attribute>
@@ -49,10 +55,10 @@ Here, we set all those fields on account.group_account_invoice
<record id="product_supplierinfo_form_view" model="ir.ui.view"> <record id="product_supplierinfo_form_view" model="ir.ui.view">
<field name="name">account_usability.product.supplierinfo.form</field> <field name="name">account_usability.product.supplierinfo.form</field>
<field name="model">product.supplierinfo</field> <field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_form_view"/> <field name="inherit_id" ref="product.product_supplierinfo_form_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="currency_id" position="after"> <field name="currency_id" position="after">
<field name="purchase_price_type"/> <field name="purchase_price_type" />
</field> </field>
</field> </field>
</record> </record>
@@ -60,10 +66,10 @@ Here, we set all those fields on account.group_account_invoice
<record id="product_supplierinfo_tree_view" model="ir.ui.view"> <record id="product_supplierinfo_tree_view" model="ir.ui.view">
<field name="name">account_usability.product.supplierinfo.tree</field> <field name="name">account_usability.product.supplierinfo.tree</field>
<field name="model">product.supplierinfo</field> <field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_tree_view"/> <field name="inherit_id" ref="product.product_supplierinfo_tree_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="price" position="after"> <field name="price" position="after">
<field name="purchase_price_type" string="Tax"/> <field name="purchase_price_type" string="Tax" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -1,26 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="res_config_settings_view_form" model="ir.ui.view"> <record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">account_usability account config page</field> <field name="name">account_usability account config page</field>
<field name="model">res.config.settings</field> <field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/> <field name="inherit_id" ref="account.res_config_settings_view_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@id='bank_cash']" position="inside"> <xpath expr="//div[@id='bank_cash']" position="inside">
<div class="col-xs-12 col-md-6 o_setting_box" id="transfer_account"> <div class="col-xs-12 col-md-6 o_setting_box" id="transfer_account">
<div class="o_setting_left_pane"/> <div class="o_setting_left_pane" />
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">
<label for="transfer_account_id"/> <label for="transfer_account_id" />
<div class="text-muted"> <div class="text-muted">
Transit account when you transfer money from a bank account of your company to another bank account of your company. Transit account when you transfer money from a bank account of your company to another bank account of your company.
</div> </div>
<field name="transfer_account_id"/> <field name="transfer_account_id" />
</div> </div>
</div> </div>
</xpath> </xpath>

View File

@@ -1,23 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2017-2020 Akretion (http://www.akretion.com/) Copyright 2017-2020 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_partner_property_form" model="ir.ui.view"> <record id="view_partner_property_form" model="ir.ui.view">
<field name="name">account_usability.res.partner.form</field> <field name="name">account_usability.res.partner.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form"/> <field name="inherit_id" ref="account.view_partner_property_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="property_account_position_id" position="attributes"> <field name="property_account_position_id" position="attributes">
<attribute name="widget">selection</attribute> <attribute name="widget">selection</attribute>
</field> </field>
<xpath expr="//field[@name='bank_ids']/tree/field[@name='acc_number']" position="after"> <xpath
<field name="acc_type"/> expr="//field[@name='bank_ids']/tree/field[@name='acc_number']"
position="after"
>
<field name="acc_type" />
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -2,58 +2,66 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, _ from odoo import _, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
class AccountGroupGenerate(models.TransientModel): class AccountGroupGenerate(models.TransientModel):
_name = 'account.group.generate' _name = "account.group.generate"
_description = 'Generate Account Groups' _description = "Generate Account Groups"
name_prefix = fields.Char(string='Prefix', required=True, default='Comptes') name_prefix = fields.Char(string="Prefix", required=True, default="Comptes")
level = fields.Integer(default=2, required=True) level = fields.Integer(default=2, required=True)
def run(self): def run(self):
if self.level < 1: if self.level < 1:
raise UserError(_("The level must be >= 1.")) raise UserError(_("The level must be >= 1."))
ago = self.env['account.group'] ago = self.env["account.group"]
aao = self.env['account.account'] aao = self.env["account.account"]
company = self.env.company company = self.env.company
groups = ago.search([('company_id', '=', company.id)]) groups = ago.search([("company_id", "=", company.id)])
if groups: if groups:
raise UserError(_( raise UserError(
_(
"%d account groups already exists in company '%s'. This wizard is " "%d account groups already exists in company '%s'. This wizard is "
"designed to generate account groups from scratch.") "designed to generate account groups from scratch."
% (len(groups), company.display_name)) )
accounts = aao.search([('company_id', '=', company.id)]) % (len(groups), company.display_name)
struct = {'childs': {}} )
accounts = aao.search([("company_id", "=", company.id)])
struct = {"childs": {}}
for account in accounts: for account in accounts:
if len(account.code) <= self.level: if len(account.code) <= self.level:
raise UserError(_( raise UserError(
_(
"The code of account '%s' is %d caracters. " "The code of account '%s' is %d caracters. "
"It cannot be inferior to level (%d).") "It cannot be inferior to level (%d)."
% (account.display_name, len(account.code), self.level)) )
% (account.display_name, len(account.code), self.level)
)
n = 1 n = 1
parent = struct parent = struct
gparent = False gparent = False
while n <= self.level: while n <= self.level:
group_code = account.code[:n] group_code = account.code[:n]
if group_code not in parent['childs']: if group_code not in parent["childs"]:
new_group = ago.create({ new_group = ago.create(
'name': '%s %s' % (self.name_prefix or '', group_code), {
'code_prefix_start': group_code, "name": "%s %s" % (self.name_prefix or "", group_code),
'parent_id': gparent and gparent.id or False, "code_prefix_start": group_code,
'company_id': company.id, "parent_id": gparent and gparent.id or False,
}) "company_id": company.id,
parent['childs'][group_code] = {'obj': new_group, 'childs': {}} }
parent = parent['childs'][group_code] )
gparent = parent['obj'] parent["childs"][group_code] = {"obj": new_group, "childs": {}}
parent = parent["childs"][group_code]
gparent = parent["obj"]
n += 1 n += 1
account.write({'group_id': gparent.id}) account.write({"group_id": gparent.id})
action = { action = {
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'name': _('Account Groups'), "name": _("Account Groups"),
'view_mode': 'tree,form', "view_mode": "tree,form",
'res_model': 'account.group', "res_model": "account.group",
} }
return action return action

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2020 Akretion France (http://www.akretion.com/) Copyright 2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="account_group_generate_form" model="ir.ui.view"> <record id="account_group_generate_form" model="ir.ui.view">
@@ -16,12 +15,17 @@
This wizard is designed to auto-generate account groups from the chart of account. This wizard is designed to auto-generate account groups from the chart of account.
</p> </p>
<group name="main"> <group name="main">
<field name="name_prefix"/> <field name="name_prefix" />
<field name="level"/> <field name="level" />
</group> </group>
<footer> <footer>
<button type="object" name="run" string="Generate" class="btn-primary"/> <button
<button special="cancel" string="Cancel"/> type="object"
name="run"
string="Generate"
class="btn-primary"
/>
<button special="cancel" string="Cancel" />
</footer> </footer>
</form> </form>
</field> </field>
@@ -34,9 +38,11 @@
<field name="target">new</field> <field name="target">new</field>
</record> </record>
<menuitem id="account_group_generate_menu" <menuitem
id="account_group_generate_menu"
action="account_group_generate_action" action="account_group_generate_action"
parent="account.account_account_menu" parent="account.account_account_menu"
sequence="51"/> sequence="51"
/>
</odoo> </odoo>

View File

@@ -2,23 +2,29 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
import logging import logging
from odoo import models
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AccountInvoiceMarkSent(models.TransientModel): class AccountInvoiceMarkSent(models.TransientModel):
_name = 'account.invoice.mark.sent' _name = "account.invoice.mark.sent"
_description = 'Mark invoices as sent' _description = "Mark invoices as sent"
def run(self): def run(self):
assert self.env.context.get('active_model') == 'account.move',\ assert (
'Source model must be invoices' self.env.context.get("active_model") == "account.move"
assert self.env.context.get('active_ids'), 'No invoices selected' ), "Source model must be invoices"
invoices = self.env['account.move'].search([ assert self.env.context.get("active_ids"), "No invoices selected"
('id', 'in', self.env.context.get('active_ids')), invoices = self.env["account.move"].search(
('move_type', 'in', ('out_invoice', 'out_refund')), [
('state', '=', 'posted')]) ("id", "in", self.env.context.get("active_ids")),
invoices.write({'is_move_sent': True}) ("move_type", "in", ("out_invoice", "out_refund")),
logger.info('Marking invoices with ID %s as sent', invoices.ids) ("state", "=", "posted"),
]
)
invoices.write({"is_move_sent": True})
logger.info("Marking invoices with ID %s as sent", invoices.ids)
return return

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2017-2020 Akretion France Copyright 2017-2020 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="account_invoice_mark_sent_form" model="ir.ui.view"> <record id="account_invoice_mark_sent_form" model="ir.ui.view">
@@ -13,11 +12,17 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Mark invoices as sent"> <form string="Mark invoices as sent">
<p> <p>
This wizard will mark as <i>sent</i> all the selected invoices in open or paid state. This wizard will mark as <i
>sent</i> all the selected invoices in open or paid state.
</p> </p>
<footer> <footer>
<button type="object" name="run" string="Mark as Sent" class="btn-primary"/> <button
<button special="cancel" string="Cancel"/> type="object"
name="run"
string="Mark as Sent"
class="btn-primary"
/>
<button special="cancel" string="Cancel" />
</footer> </footer>
</form> </form>
</field> </field>

View File

@@ -2,20 +2,21 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
class AccountMoveReversal(models.TransientModel): class AccountMoveReversal(models.TransientModel):
_inherit = 'account.move.reversal' _inherit = "account.move.reversal"
@api.model @api.model
def _default_date(self): def _default_date(self):
date_dt = None date_dt = None
if ( if self._context.get("active_model") == "account.move" and self._context.get(
self._context.get('active_model') == 'account.move' and "active_id"
self._context.get('active_id')): ):
move = self.env['account.move'].browse(self._context['active_id']) move = self.env["account.move"].browse(self._context["active_id"])
date_dt = move.date + relativedelta(days=1) date_dt = move.date + relativedelta(days=1)
return date_dt return date_dt

View File

@@ -1,20 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2021 Akretion France (http://www.akretion.com/) Copyright 2021 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<!-- When you change the date, it resets the amount via the onchange <!-- When you change the date, it resets the amount via the onchange
So, in the view, the date should be BEFORE the amount --> So, in the view, the date should be BEFORE the amount -->
<record id="view_account_payment_register_form" model="ir.ui.view"> <record id="view_account_payment_register_form" model="ir.ui.view">
<field name="model">account.payment.register</field> <field name="model">account.payment.register</field>
<field name="inherit_id" ref="account.view_account_payment_register_form"/> <field name="inherit_id" ref="account.view_account_payment_register_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<label for="amount" position="before"> <label for="amount" position="before">
<field name="payment_date" position="move"/> <field name="payment_date" position="move" />
</label> </label>
</field> </field>
</record> </record>

View File

@@ -6,7 +6,8 @@ from odoo import fields, models
class ResConfigSettings(models.TransientModel): class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings' _inherit = "res.config.settings"
transfer_account_id = fields.Many2one( transfer_account_id = fields.Many2one(
related='company_id.transfer_account_id', readonly=False) related="company_id.transfer_account_id", readonly=False
)

View File

@@ -3,15 +3,15 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Base Company Extension', "name": "Base Company Extension",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Partner', "category": "Partner",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Adds capital and title on company', "summary": "Adds capital and title on company",
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
# I depend on base_usability only for _report_company_legal_name() # I depend on base_usability only for _report_company_legal_name()
'depends': ['base_usability'], "depends": ["base_usability"],
'data': ['company_view.xml'], "data": ["company_view.xml"],
'installable': True, "installable": True,
} }

View File

@@ -2,27 +2,30 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields from odoo import fields, models
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = "res.company" _inherit = "res.company"
capital_amount = fields.Monetary(string='Capital Amount') capital_amount = fields.Monetary(string="Capital Amount")
# in v9, title is only for contacts, not for companies # in v9, title is only for contacts, not for companies
legal_type = fields.Char( legal_type = fields.Char(
string="Legal Type", help="Type of Company, e.g. SARL, SAS, ...") string="Legal Type", help="Type of Company, e.g. SARL, SAS, ..."
)
def _report_company_legal_name(self): def _report_company_legal_name(self):
self.ensure_one() self.ensure_one()
if self.legal_type: if self.legal_type:
name = '%s %s' % (self.name, self.legal_type) name = "%s %s" % (self.name, self.legal_type)
else: else:
name = self.name name = self.name
return name return name
_sql_constraints = [( _sql_constraints = [
'capital_amount_positive', (
'CHECK (capital_amount >= 0)', "capital_amount_positive",
"The value of the field 'Capital Amount' must be positive." "CHECK (capital_amount >= 0)",
)] "The value of the field 'Capital Amount' must be positive.",
)
]

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2014-2020 Akretion (http://www.akretion.com/) Copyright 2014-2020 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com> @author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_company_form" model="ir.ui.view"> <record id="view_company_form" model="ir.ui.view">
@@ -13,8 +12,8 @@
<field name="inherit_id" ref="base.view_company_form" /> <field name="inherit_id" ref="base.view_company_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="company_registry" position="after"> <field name="company_registry" position="after">
<field name="capital_amount"/> <field name="capital_amount" />
<field name="legal_type"/> <field name="legal_type" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -3,12 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Base Dynamic List', "name": "Base Dynamic List",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Tools', "category": "Tools",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Dynamic lists', "summary": "Dynamic lists",
'description': """ "description": """
Base Dynamic List Base Dynamic List
================= =================
@@ -51,12 +51,12 @@ parent="parent_menu_xmlid"/>
Limitation: when you want to have different access rights on these lists depending on the source object, you should prefer to use dedicated objects. Limitation: when you want to have different access rights on these lists depending on the source object, you should prefer to use dedicated objects.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['base'], "depends": ["base"],
'data': [ "data": [
'security/ir.model.access.csv', "security/ir.model.access.csv",
'views/dynamic_list.xml', "views/dynamic_list.xml",
], ],
'installable': True, "installable": True,
} }

View File

@@ -6,110 +6,96 @@ from odoo import api, fields, models
class DynamicList(models.Model): class DynamicList(models.Model):
_name = 'dynamic.list' _name = "dynamic.list"
_description = 'Dynamic List (non translatable)' _description = "Dynamic List (non translatable)"
_order = 'sequence, id' _order = "sequence, id"
name = fields.Char(required=True) name = fields.Char(required=True)
sequence = fields.Integer(default=10) sequence = fields.Integer(default=10)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True) domain = fields.Selection([], string="Domain", required=True, index=True)
_sql_constraint = [( _sql_constraint = [
'domain_name_uniq', ("domain_name_uniq", "unique(domain, name)", "This entry already exists!")
'unique(domain, name)', ]
'This entry already exists!'
)]
class DynamicListTranslate(models.Model): class DynamicListTranslate(models.Model):
_name = 'dynamic.list.translate' _name = "dynamic.list.translate"
_description = 'Translatable Dynamic List' _description = "Translatable Dynamic List"
_order = 'sequence, id' _order = "sequence, id"
name = fields.Char(translate=True, required=True) name = fields.Char(translate=True, required=True)
sequence = fields.Integer(default=10) sequence = fields.Integer(default=10)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True) domain = fields.Selection([], string="Domain", required=True, index=True)
_sql_constraint = [( _sql_constraint = [
'domain_name_uniq', ("domain_name_uniq", "unique(domain, name)", "This entry already exists!")
'unique(domain, name)', ]
'This entry already exists!'
)]
class DynamicListCode(models.Model): class DynamicListCode(models.Model):
_name = 'dynamic.list.code' _name = "dynamic.list.code"
_description = 'Dynamic list with code' _description = "Dynamic list with code"
_order = 'sequence, id' _order = "sequence, id"
code = fields.Char(required=True) code = fields.Char(required=True)
name = fields.Char(translate=True, required=True) name = fields.Char(translate=True, required=True)
sequence = fields.Integer(default=10) sequence = fields.Integer(default=10)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True) domain = fields.Selection([], string="Domain", required=True, index=True)
_sql_constraint = [( _sql_constraint = [
'domain_code_uniq', ("domain_code_uniq", "unique(domain, code)", "This code already exists!")
'unique(domain, code)', ]
'This code already exists!'
)]
@api.depends('code', 'name') @api.depends("code", "name")
def name_get(self): def name_get(self):
res = [] res = []
for rec in self: for rec in self:
res.append((rec.id, '[%s] %s' % (rec.code, rec.name))) res.append((rec.id, "[%s] %s" % (rec.code, rec.name)))
return res return res
@api.model @api.model
def name_search( def name_search(self, name="", args=None, operator="ilike", limit=80):
self, name='', args=None, operator='ilike', limit=80):
if args is None: if args is None:
args = [] args = []
if name and operator == 'ilike': if name and operator == "ilike":
recs = self.search( recs = self.search([("code", "=", name)] + args, limit=limit)
[('code', '=', name)] + args, limit=limit)
if recs: if recs:
return recs.name_get() return recs.name_get()
return super().name_search( return super().name_search(name=name, args=args, operator=operator, limit=limit)
name=name, args=args, operator=operator, limit=limit)
class DynamicListCodeTranslate(models.Model): class DynamicListCodeTranslate(models.Model):
_name = 'dynamic.list.code.translate' _name = "dynamic.list.code.translate"
_description = 'Translatable dynamic list with code' _description = "Translatable dynamic list with code"
_order = 'sequence, id' _order = "sequence, id"
code = fields.Char(required=True) code = fields.Char(required=True)
name = fields.Char(translate=True, required=True) name = fields.Char(translate=True, required=True)
sequence = fields.Integer(default=10) sequence = fields.Integer(default=10)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
domain = fields.Selection([], string='Domain', required=True, index=True) domain = fields.Selection([], string="Domain", required=True, index=True)
_sql_constraint = [( _sql_constraint = [
'domain_code_uniq', ("domain_code_uniq", "unique(domain, code)", "This code already exists!")
'unique(domain, code)', ]
'This code already exists!'
)]
@api.depends('code', 'name') @api.depends("code", "name")
def name_get(self): def name_get(self):
res = [] res = []
for rec in self: for rec in self:
res.append((rec.id, '[%s] %s' % (rec.code, rec.name))) res.append((rec.id, "[%s] %s" % (rec.code, rec.name)))
return res return res
@api.model @api.model
def name_search( def name_search(self, name="", args=None, operator="ilike", limit=80):
self, name='', args=None, operator='ilike', limit=80):
if args is None: if args is None:
args = [] args = []
if name and operator == 'ilike': if name and operator == "ilike":
recs = self.search( recs = self.search([("code", "=", name)] + args, limit=limit)
[('code', '=', name)] + args, limit=limit)
if recs: if recs:
return recs.name_get() return recs.name_get()
return super().name_search( return super().name_search(name=name, args=args, operator=operator, limit=limit)
name=name, args=args, operator=operator, limit=limit)

View File

@@ -1,25 +1,37 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2020-2021 Akretion France (http://www.akretion.com/) Copyright 2020-2021 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<menuitem id="dynamic_list_root_menu" name="Dynamic Lists" parent="base.menu_custom" sequence="100"/> <menuitem
id="dynamic_list_root_menu"
name="Dynamic Lists"
parent="base.menu_custom"
sequence="100"
/>
<record id="dynamic_list_form" model="ir.ui.view"> <record id="dynamic_list_form" model="ir.ui.view">
<field name="model">dynamic.list</field> <field name="model">dynamic.list</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<sheet> <sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/> <widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<group name="main"> <group name="main">
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/> <field
<field name="active" invisible="1"/> name="domain"
invisible="not context.get('dynamic_list_main_view')"
/>
<field name="active" invisible="1" />
</group> </group>
</sheet> </sheet>
</form> </form>
@@ -30,9 +42,12 @@
<field name="model">dynamic.list</field> <field name="model">dynamic.list</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle" />
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_main_view')"/> <field
name="domain"
invisible="not context.get('dynamic_list_main_view')"
/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -41,11 +56,19 @@
<field name="model">dynamic.list</field> <field name="model">dynamic.list</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name"/> <field name="name" />
<separator/> <separator />
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/> <filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<group string="Group By" name="groupby"> <group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/> <filter
name="domain_groupby"
string="Domain"
context="{'group_by': 'domain'}"
/>
</group> </group>
</search> </search>
</field> </field>
@@ -55,21 +78,36 @@
<field name="name">Simple List</field> <field name="name">Simple List</field>
<field name="res_model">dynamic.list</field> <field name="res_model">dynamic.list</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_main_view': True, 'search_default_domain_groupby': True}</field> <field
name="context"
>{'dynamic_list_main_view': True, 'search_default_domain_groupby': True}</field>
</record> </record>
<menuitem id="dynamic_list_menu" action="dynamic_list_action" parent="dynamic_list_root_menu" sequence="10"/> <menuitem
id="dynamic_list_menu"
action="dynamic_list_action"
parent="dynamic_list_root_menu"
sequence="10"
/>
<record id="dynamic_list_translate_form" model="ir.ui.view"> <record id="dynamic_list_translate_form" model="ir.ui.view">
<field name="model">dynamic.list.translate</field> <field name="model">dynamic.list.translate</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<sheet> <sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/> <widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<group name="main"> <group name="main">
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/> <field
<field name="active" invisible="1"/> name="domain"
invisible="not context.get('dynamic_list_translate_main_view')"
/>
<field name="active" invisible="1" />
</group> </group>
</sheet> </sheet>
</form> </form>
@@ -80,9 +118,12 @@
<field name="model">dynamic.list.translate</field> <field name="model">dynamic.list.translate</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle" />
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_translate_main_view')"/> <field
name="domain"
invisible="not context.get('dynamic_list_translate_main_view')"
/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -91,11 +132,19 @@
<field name="model">dynamic.list.translate</field> <field name="model">dynamic.list.translate</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name"/> <field name="name" />
<separator/> <separator />
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/> <filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<group string="Group By" name="groupby"> <group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/> <filter
name="domain_groupby"
string="Domain"
context="{'group_by': 'domain'}"
/>
</group> </group>
</search> </search>
</field> </field>
@@ -105,22 +154,37 @@
<field name="name">Translatable Simple List</field> <field name="name">Translatable Simple List</field>
<field name="res_model">dynamic.list.translate</field> <field name="res_model">dynamic.list.translate</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_translate_main_view': True, 'search_default_domain_groupby': True}</field> <field
name="context"
>{'dynamic_list_translate_main_view': True, 'search_default_domain_groupby': True}</field>
</record> </record>
<menuitem id="dynamic_list_translate_menu" action="dynamic_list_translate_action" parent="dynamic_list_root_menu" sequence="20"/> <menuitem
id="dynamic_list_translate_menu"
action="dynamic_list_translate_action"
parent="dynamic_list_root_menu"
sequence="20"
/>
<record id="dynamic_list_code_form" model="ir.ui.view"> <record id="dynamic_list_code_form" model="ir.ui.view">
<field name="model">dynamic.list.code</field> <field name="model">dynamic.list.code</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<sheet> <sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/> <widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<group name="main"> <group name="main">
<field name="code"/> <field name="code" />
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/> <field
<field name="active" invisible="1"/> name="domain"
invisible="not context.get('dynamic_list_code_main_view')"
/>
<field name="active" invisible="1" />
</group> </group>
</sheet> </sheet>
</form> </form>
@@ -131,10 +195,13 @@
<field name="model">dynamic.list.code</field> <field name="model">dynamic.list.code</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle" />
<field name="code"/> <field name="code" />
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_code_main_view')"/> <field
name="domain"
invisible="not context.get('dynamic_list_code_main_view')"
/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -143,12 +210,24 @@
<field name="model">dynamic.list.code</field> <field name="model">dynamic.list.code</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/> <field
<separator/> name="name"
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/> string="Name or Code"
<field name="code"/> filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"
/>
<separator />
<filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<field name="code" />
<group string="Group By" name="groupby"> <group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/> <filter
name="domain_groupby"
string="Domain"
context="{'group_by': 'domain'}"
/>
</group> </group>
</search> </search>
</field> </field>
@@ -158,22 +237,37 @@
<field name="name">Code List</field> <field name="name">Code List</field>
<field name="res_model">dynamic.list.code</field> <field name="res_model">dynamic.list.code</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_code_main_view': True, 'search_default_domain_groupby': True}</field> <field
name="context"
>{'dynamic_list_code_main_view': True, 'search_default_domain_groupby': True}</field>
</record> </record>
<menuitem id="dynamic_list_code_menu" action="dynamic_list_code_action" parent="dynamic_list_root_menu" sequence="30"/> <menuitem
id="dynamic_list_code_menu"
action="dynamic_list_code_action"
parent="dynamic_list_root_menu"
sequence="30"
/>
<record id="dynamic_list_code_translate_form" model="ir.ui.view"> <record id="dynamic_list_code_translate_form" model="ir.ui.view">
<field name="model">dynamic.list.code.translate</field> <field name="model">dynamic.list.code.translate</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<sheet> <sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/> <widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<group name="main"> <group name="main">
<field name="code"/> <field name="code" />
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/> <field
<field name="active" invisible="1"/> name="domain"
invisible="not context.get('dynamic_list_code_translate_main_view')"
/>
<field name="active" invisible="1" />
</group> </group>
</sheet> </sheet>
</form> </form>
@@ -184,10 +278,13 @@
<field name="model">dynamic.list.code.translate</field> <field name="model">dynamic.list.code.translate</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle" />
<field name="code"/> <field name="code" />
<field name="name"/> <field name="name" />
<field name="domain" invisible="not context.get('dynamic_list_code_translate_main_view')"/> <field
name="domain"
invisible="not context.get('dynamic_list_code_translate_main_view')"
/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -196,12 +293,24 @@
<field name="model">dynamic.list.code.translate</field> <field name="model">dynamic.list.code.translate</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name" string="Name or Code" filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"/> <field
<field name="code"/> name="name"
<separator/> string="Name or Code"
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/> filter_domain="['|', ('name', 'ilike', self), ('code', 'ilike', self)]"
/>
<field name="code" />
<separator />
<filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<group string="Group By" name="groupby"> <group string="Group By" name="groupby">
<filter name="domain_groupby" string="Domain" context="{'group_by': 'domain'}"/> <filter
name="domain_groupby"
string="Domain"
context="{'group_by': 'domain'}"
/>
</group> </group>
</search> </search>
</field> </field>
@@ -211,10 +320,17 @@
<field name="name">Translatable Code List</field> <field name="name">Translatable Code List</field>
<field name="res_model">dynamic.list.code.translate</field> <field name="res_model">dynamic.list.code.translate</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="context">{'dynamic_list_code_translate_main_view': True, 'search_default_domain_groupby': True}</field> <field
name="context"
>{'dynamic_list_code_translate_main_view': True, 'search_default_domain_groupby': True}</field>
</record> </record>
<menuitem id="dynamic_list_code_translate_menu" action="dynamic_list_code_translate_action" parent="dynamic_list_root_menu" sequence="40"/> <menuitem
id="dynamic_list_code_translate_menu"
action="dynamic_list_code_translate_action"
parent="dynamic_list_root_menu"
sequence="40"
/>
</odoo> </odoo>

View File

@@ -5,12 +5,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Base Partner One2many Phone', "name": "Base Partner One2many Phone",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Phone', "category": "Phone",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'One2many link between partners and phone numbers/emails', "summary": "One2many link between partners and phone numbers/emails",
'description': """ "description": """
Base Partner One2many Phone Base Partner One2many Phone
=========================== ===========================
@@ -18,14 +18,14 @@ With this module, one partner can have several phone numbers and several emails.
It has been developped by brother Bernard from Barroux Abbey and Alexis de Lattre from Akretion. It has been developped by brother Bernard from Barroux Abbey and Alexis de Lattre from Akretion.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'https://akretion.com/', "website": "https://github.com/OCA/odoo-usability",
'depends': ['contacts', 'base_usability', 'phone_validation'], "depends": ["contacts", "base_usability", "phone_validation"],
'excludes': ['sms'], # because sms introduces big changes in partner form view "excludes": ["sms"], # because sms introduces big changes in partner form view
'data': [ "data": [
'partner_phone_view.xml', "partner_phone_view.xml",
'security/ir.model.access.csv', "security/ir.model.access.csv",
], ],
'installable': True, "installable": True,
'post_init_hook': 'migrate_to_partner_phone', "post_init_hook": "migrate_to_partner_phone",
} }

View File

@@ -4,38 +4,50 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
EMAIL_TYPES = ('1_email_primary', '2_email_secondary') EMAIL_TYPES = ("1_email_primary", "2_email_secondary")
PHONE_TYPES = ('3_phone_primary', '4_phone_secondary', '5_mobile_primary', '6_mobile_secondary', '7_fax_primary', '8_fax_secondary') PHONE_TYPES = (
"3_phone_primary",
"4_phone_secondary",
"5_mobile_primary",
"6_mobile_secondary",
"7_fax_primary",
"8_fax_secondary",
)
class ResPartnerPhone(models.Model): class ResPartnerPhone(models.Model):
_name = 'res.partner.phone' _name = "res.partner.phone"
_order = 'partner_id, type' _order = "partner_id, type"
_phone_name_sequence = 8 _phone_name_sequence = 8
_inherit = ['phone.validation.mixin'] _inherit = ["phone.validation.mixin"]
_description = 'Multiple emails and phones for partners' _description = "Multiple emails and phones for partners"
partner_id = fields.Many2one( partner_id = fields.Many2one(
'res.partner', string='Related Partner', index=True, ondelete='cascade') "res.partner", string="Related Partner", index=True, ondelete="cascade"
type = fields.Selection([ )
('1_email_primary', 'Primary E-mail'), type = fields.Selection(
('2_email_secondary', 'Secondary E-mail'), [
('3_phone_primary', 'Primary Phone'), ("1_email_primary", "Primary E-mail"),
('4_phone_secondary', 'Secondary Phone'), ("2_email_secondary", "Secondary E-mail"),
('5_mobile_primary', 'Primary Mobile'), ("3_phone_primary", "Primary Phone"),
('6_mobile_secondary', 'Secondary Mobile'), ("4_phone_secondary", "Secondary Phone"),
('7_fax_primary', 'Primary Fax'), ("5_mobile_primary", "Primary Mobile"),
('8_fax_secondary', 'Secondary Fax'), ("6_mobile_secondary", "Secondary Mobile"),
("7_fax_primary", "Primary Fax"),
("8_fax_secondary", "Secondary Fax"),
], ],
string='Type', required=True, index=True) string="Type",
phone = fields.Char(string='Phone') required=True,
email = fields.Char(string='E-Mail') index=True,
note = fields.Char('Note') )
phone = fields.Char(string="Phone")
email = fields.Char(string="E-Mail")
note = fields.Char("Note")
@api.onchange('type') @api.onchange("type")
def type_change(self): def type_change(self):
if self.type: if self.type:
if self.type in EMAIL_TYPES: if self.type in EMAIL_TYPES:
@@ -43,67 +55,89 @@ class ResPartnerPhone(models.Model):
elif self.type in PHONE_TYPES: elif self.type in PHONE_TYPES:
self.email = False self.email = False
@api.onchange('phone', 'partner_id') @api.onchange("phone", "partner_id")
def _onchange_phone_validation(self): def _onchange_phone_validation(self):
if self.phone: if self.phone:
self.phone = self.phone_format(self.phone, country=self.partner_id.country_id) self.phone = self.phone_format(
self.phone, country=self.partner_id.country_id
)
@api.constrains('type', 'phone', 'email') @api.constrains("type", "phone", "email")
def _check_partner_phone(self): def _check_partner_phone(self):
for rec in self: for rec in self:
if rec.type in EMAIL_TYPES: if rec.type in EMAIL_TYPES:
if not rec.email: if not rec.email:
raise ValidationError(_( raise ValidationError(
"E-mail field must have a value when type is Primary E-mail or Secondary E-mail.")) _(
"E-mail field must have a value when type is Primary E-mail or Secondary E-mail."
)
)
if rec.phone: if rec.phone:
raise ValidationError(_( raise ValidationError(
"Phone field must be empty when type is Primary E-mail or Secondary E-mail.")) _(
"Phone field must be empty when type is Primary E-mail or Secondary E-mail."
)
)
elif rec.type in PHONE_TYPES: elif rec.type in PHONE_TYPES:
if not rec.phone: if not rec.phone:
raise ValidationError(_( raise ValidationError(
"Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax.")) _(
"Phone field must have a value when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
)
)
if rec.email: if rec.email:
raise ValidationError(_( raise ValidationError(
"E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax.")) _(
"E-mail field must be empty when type is Primary/Secondary Phone, Primary/Secondary Mobile or Primary/Secondary Fax."
)
)
def name_get(self): def name_get(self):
res = [] res = []
for pphone in self: for pphone in self:
if pphone.partner_id: if pphone.partner_id:
if self._context.get('callerid'): if self._context.get("callerid"):
name = pphone.partner_id.display_name name = pphone.partner_id.display_name
else: else:
name = u'%s (%s)' % (pphone.phone, pphone.partner_id.name) name = u"%s (%s)" % (pphone.phone, pphone.partner_id.name)
else: else:
name = pphone.phone name = pphone.phone
res.append((pphone.id, name)) res.append((pphone.id, name))
return res return res
def init(self): def init(self):
self._cr.execute(''' self._cr.execute(
"""
CREATE UNIQUE INDEX IF NOT EXISTS single_email_primary CREATE UNIQUE INDEX IF NOT EXISTS single_email_primary
ON res_partner_phone (partner_id, type) ON res_partner_phone (partner_id, type)
WHERE (type='1_email_primary') WHERE (type='1_email_primary')
''') """
self._cr.execute(''' )
self._cr.execute(
"""
CREATE UNIQUE INDEX IF NOT EXISTS single_phone_primary CREATE UNIQUE INDEX IF NOT EXISTS single_phone_primary
ON res_partner_phone (partner_id, type) ON res_partner_phone (partner_id, type)
WHERE (type='3_phone_primary') WHERE (type='3_phone_primary')
''') """
self._cr.execute(''' )
self._cr.execute(
"""
CREATE UNIQUE INDEX IF NOT EXISTS single_mobile_primary CREATE UNIQUE INDEX IF NOT EXISTS single_mobile_primary
ON res_partner_phone (partner_id, type) ON res_partner_phone (partner_id, type)
WHERE (type='5_mobile_primary') WHERE (type='5_mobile_primary')
''') """
self._cr.execute(''' )
self._cr.execute(
"""
CREATE UNIQUE INDEX IF NOT EXISTS single_fax_primary CREATE UNIQUE INDEX IF NOT EXISTS single_fax_primary
ON res_partner_phone (partner_id, type) ON res_partner_phone (partner_id, type)
WHERE (type='7_fax_primary') WHERE (type='7_fax_primary')
''') """
)
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = "res.partner"
# in v10, we are supposed to have in DB E.164 format # in v10, we are supposed to have in DB E.164 format
# with the current implementation, we have: # with the current implementation, we have:
@@ -115,78 +149,85 @@ class ResPartner(models.Model):
# for the future :) # for the future :)
phone_ids = fields.One2many( phone_ids = fields.One2many(
'res.partner.phone', 'partner_id', string='Phones/Emails') "res.partner.phone", "partner_id", string="Phones/Emails"
)
phone = fields.Char( phone = fields.Char(
compute='_compute_partner_phone', compute="_compute_partner_phone", store=True, readonly=True, compute_sudo=True
store=True, readonly=True, compute_sudo=True) )
mobile = fields.Char( mobile = fields.Char(
compute='_compute_partner_phone', compute="_compute_partner_phone", store=True, readonly=True, compute_sudo=True
store=True, readonly=True, compute_sudo=True) )
email = fields.Char( email = fields.Char(
compute='_compute_partner_phone', compute="_compute_partner_phone", store=True, readonly=True, compute_sudo=True
store=True, readonly=True, compute_sudo=True) )
@api.depends('phone_ids.phone', 'phone_ids.type', 'phone_ids.email') @api.depends("phone_ids.phone", "phone_ids.type", "phone_ids.email")
def _compute_partner_phone(self): def _compute_partner_phone(self):
for partner in self: for partner in self:
phone = mobile = email = False phone = mobile = email = False
for pphone in partner.phone_ids: for pphone in partner.phone_ids:
if pphone.type == '1_email_primary' and pphone.email: if pphone.type == "1_email_primary" and pphone.email:
email = pphone.email email = pphone.email
elif pphone.phone: elif pphone.phone:
if pphone.type == '5_mobile_primary': if pphone.type == "5_mobile_primary":
mobile = pphone.phone mobile = pphone.phone
elif pphone.type == '3_phone_primary': elif pphone.type == "3_phone_primary":
phone = pphone.phone phone = pphone.phone
partner.phone = phone partner.phone = phone
partner.mobile = mobile partner.mobile = mobile
partner.email = email partner.email = email
def _update_create_vals( def _update_create_vals(self, vals, type, partner_field, partner_phone_field):
self, vals, type, partner_field, partner_phone_field):
if vals.get(partner_field): if vals.get(partner_field):
vals['phone_ids'].append( vals["phone_ids"].append(
(0, 0, {'type': type, partner_phone_field: vals[partner_field]})) (0, 0, {"type": type, partner_phone_field: vals[partner_field]})
)
@api.model @api.model
def create(self, vals): def create(self, vals):
if 'phone_ids' not in vals: if "phone_ids" not in vals:
vals['phone_ids'] = [] vals["phone_ids"] = []
self._update_create_vals(vals, '1_email_primary', 'email', 'email') self._update_create_vals(vals, "1_email_primary", "email", "email")
self._update_create_vals(vals, '3_phone_primary', 'phone', 'phone') self._update_create_vals(vals, "3_phone_primary", "phone", "phone")
self._update_create_vals(vals, '5_mobile_primary', 'mobile', 'phone') self._update_create_vals(vals, "5_mobile_primary", "mobile", "phone")
# self._update_create_vals(vals, '7_fax_primary', 'fax', 'phone') # self._update_create_vals(vals, '7_fax_primary', 'fax', 'phone')
return super().create(vals) return super().create(vals)
def _update_write_vals( def _update_write_vals(self, vals, type, partner_field, partner_phone_field):
self, vals, type, partner_field, partner_phone_field):
self.ensure_one() self.ensure_one()
rppo = self.env['res.partner.phone'] rppo = self.env["res.partner.phone"]
if partner_field in vals: if partner_field in vals:
pphone = rppo.search([ pphone = rppo.search(
('partner_id', '=', self.id), [("partner_id", "=", self.id), ("type", "=", type)], limit=1
('type', '=', type)], limit=1) )
if vals[partner_field]: if vals[partner_field]:
if pphone: if pphone:
vals['phone_ids'].append((1, pphone.id, { vals["phone_ids"].append(
partner_phone_field: vals[partner_field]})) (1, pphone.id, {partner_phone_field: vals[partner_field]})
)
else: else:
vals['phone_ids'].append((0, 0, { vals["phone_ids"].append(
'type': type, (
0,
0,
{
"type": type,
partner_phone_field: vals[partner_field], partner_phone_field: vals[partner_field],
})) },
)
)
else: else:
if pphone: if pphone:
vals['phone_ids'].append((2, pphone.id)) vals["phone_ids"].append((2, pphone.id))
def write(self, vals): def write(self, vals):
if 'phone_ids' not in vals: if "phone_ids" not in vals:
for rec in self: for rec in self:
vals['phone_ids'] = [] vals["phone_ids"] = []
rec._update_write_vals(vals, '1_email_primary', 'email', 'email') rec._update_write_vals(vals, "1_email_primary", "email", "email")
rec._update_write_vals(vals, '3_phone_primary', 'phone', 'phone') rec._update_write_vals(vals, "3_phone_primary", "phone", "phone")
rec._update_write_vals(vals, '5_mobile_primary', 'mobile', 'phone') rec._update_write_vals(vals, "5_mobile_primary", "mobile", "phone")
rec._update_write_vals(vals, '7_fax_primary', 'fax', 'phone') rec._update_write_vals(vals, "7_fax_primary", "fax", "phone")
super(ResPartner, rec).write(vals) super(ResPartner, rec).write(vals)
return True return True
else: else:

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2014-2020 Abbaye du Barroux (http://www.barroux.org) Copyright 2014-2020 Abbaye du Barroux (http://www.barroux.org)
Copyright 2016-2020 Akretion (http://www.akretion.com>) Copyright 2016-2020 Akretion (http://www.akretion.com>)
@@ -6,7 +6,6 @@
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<!-- Partner phones --> <!-- Partner phones -->
@@ -15,11 +14,23 @@
<field name="model">res.partner.phone</field> <field name="model">res.partner.phone</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree editable="bottom"> <tree editable="bottom">
<field name="partner_id" invisible="not context.get('partner_phone_main_view')"/> <field
<field name="type"/> name="partner_id"
<field name="phone" widget="phone" options="{'enable_sms': false}" attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'readonly': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/> invisible="not context.get('partner_phone_main_view')"
<field name="email" widget="email" attrs="{'readonly': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/> />
<field name="note"/> <field name="type" />
<field
name="phone"
widget="phone"
options="{'enable_sms': false}"
attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'readonly': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
/>
<field
name="email"
widget="email"
attrs="{'readonly': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
/>
<field name="note" />
</tree> </tree>
</field> </field>
</record> </record>
@@ -30,11 +41,23 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<group name="main"> <group name="main">
<field name="partner_id" invisible="not context.get('partner_phone_main_view')"/> <field
<field name="type"/> name="partner_id"
<field name="phone" widget="phone" options="{'enable_sms': false}" attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'invisible': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/> invisible="not context.get('partner_phone_main_view')"
<field name="email" widget="email" attrs="{'invisible': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"/> />
<field name="note"/> <field name="type" />
<field
name="phone"
widget="phone"
options="{'enable_sms': false}"
attrs="{'required': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'invisible': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
/>
<field
name="email"
widget="email"
attrs="{'invisible': [('type', 'not in', ('1_email_primary', '2_email_secondary'))], 'required': [('type', 'in', ('1_email_primary', '2_email_secondary'))]}"
/>
<field name="note" />
</group> </group>
</form> </form>
</field> </field>
@@ -48,7 +71,11 @@
<field name="phone" /> <field name="phone" />
<field name="email" /> <field name="email" />
<group name="groupby"> <group name="groupby">
<filter name="type_groupby" string="Type" context="{'group_by': 'type'}"/> <filter
name="type_groupby"
string="Type"
context="{'group_by': 'type'}"
/>
</group> </group>
</search> </search>
</field> </field>
@@ -61,8 +88,12 @@
<field name="context">{'partner_phone_main_view': True}</field> <field name="context">{'partner_phone_main_view': True}</field>
</record> </record>
<menuitem id="res_partner_phone_menu" action="res_partner_phone_action" <menuitem
parent="contacts.menu_contacts" sequence="10"/> id="res_partner_phone_menu"
action="res_partner_phone_action"
parent="contacts.menu_contacts"
sequence="10"
/>
<record id="contacts.res_partner_menu_config" model="ir.ui.menu"> <record id="contacts.res_partner_menu_config" model="ir.ui.menu">
<field name="sequence">20</field> <field name="sequence">20</field>
@@ -72,12 +103,12 @@
<record id="view_partner_form" model="ir.ui.view"> <record id="view_partner_form" model="ir.ui.view">
<field name="name">add.phone_ids.on.partner.form</field> <field name="name">add.phone_ids.on.partner.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="mail.res_partner_view_form_inherit_mail"/> <field name="inherit_id" ref="mail.res_partner_view_form_inherit_mail" />
<!-- This module depends on contacts which depends on mail <!-- This module depends on contacts which depends on mail
and the mail module replaces the email field --> and the mail module replaces the email field -->
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="phone" position="after"> <field name="phone" position="after">
<field name="phone_ids" nolabel="1" colspan="2"/> <field name="phone_ids" nolabel="1" colspan="2" />
</field> </field>
<field name="phone" position="attributes"> <field name="phone" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
@@ -101,13 +132,22 @@
<field name="phone_ids" nolabel="1" colspan="2" widget="many2many_tags"/> <field name="phone_ids" nolabel="1" colspan="2" widget="many2many_tags"/>
</xpath> </xpath>
--> -->
<xpath expr="//field[@name='child_ids']/form//field[@name='phone']" position="attributes"> <xpath
expr="//field[@name='child_ids']/form//field[@name='phone']"
position="attributes"
>
<attribute name="readonly">1</attribute> <attribute name="readonly">1</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='mobile']" position="attributes"> <xpath
expr="//field[@name='child_ids']/form//field[@name='mobile']"
position="attributes"
>
<attribute name="readonly">1</attribute> <attribute name="readonly">1</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='email']" position="attributes"> <xpath
expr="//field[@name='child_ids']/form//field[@name='email']"
position="attributes"
>
<attribute name="readonly">1</attribute> <attribute name="readonly">1</attribute>
</xpath> </xpath>
</field> </field>
@@ -116,10 +156,10 @@
<record id="view_partner_simple_form" model="ir.ui.view"> <record id="view_partner_simple_form" model="ir.ui.view">
<field name="name">add.phone_ids.on.res.partner.simplified.form</field> <field name="name">add.phone_ids.on.res.partner.simplified.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_simple_form"/> <field name="inherit_id" ref="base.view_partner_simple_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="phone" position="after"> <field name="phone" position="after">
<field name="phone_ids" nolabel="1" colspan="2"/> <field name="phone_ids" nolabel="1" colspan="2" />
</field> </field>
<field name="phone" position="attributes"> <field name="phone" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
@@ -136,10 +176,10 @@
<record id="res_partner_view_form_private" model="ir.ui.view"> <record id="res_partner_view_form_private" model="ir.ui.view">
<field name="name">add.phone_ids.on.res.partner.private.form</field> <field name="name">add.phone_ids.on.res.partner.private.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.res_partner_view_form_private"/> <field name="inherit_id" ref="base.res_partner_view_form_private" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="phone" position="after"> <field name="phone" position="after">
<field name="phone_ids" nolabel="1" colspan="2"/> <field name="phone_ids" nolabel="1" colspan="2" />
</field> </field>
<field name="phone" position="attributes"> <field name="phone" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
@@ -156,10 +196,12 @@
<record id="view_res_partner_filter" model="ir.ui.view"> <record id="view_res_partner_filter" model="ir.ui.view">
<field name="name">phone.one2many.res.partner.search</field> <field name="name">phone.one2many.res.partner.search</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base_usability.view_res_partner_filter"/> <field name="inherit_id" ref="base_usability.view_res_partner_filter" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="name" position="attributes"> <field name="name" position="attributes">
<attribute name="filter_domain">['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute> <attribute
name="filter_domain"
>['|', '|', ('display_name', 'ilike', self), ('ref', '=ilike', self + '%'), ('phone_ids.email', 'ilike', self)]</attribute>
</field> </field>
</field> </field>
</record> </record>

View File

@@ -2,48 +2,61 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, SUPERUSER_ID
import logging import logging
from odoo import SUPERUSER_ID, api
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def create_partner_phone(cr, phone_field, phone_type): def create_partner_phone(cr, phone_field, phone_type):
cr.execute( cr.execute(
'SELECT id, ' + phone_field + ' FROM res_partner WHERE ' + "SELECT id, "
phone_field + ' IS NOT null AND ' + phone_field + "!= ''") + phone_field
+ " FROM res_partner WHERE "
+ phone_field
+ " IS NOT null AND "
+ phone_field
+ "!= ''"
)
to_create = [] to_create = []
for partner in cr.fetchall(): for partner in cr.fetchall():
to_create.append({ to_create.append(
'partner_id': partner[0], {
'type': phone_type, "partner_id": partner[0],
'phone': partner[1], "type": phone_type,
}) "phone": partner[1],
}
)
return to_create return to_create
def create_partner_email(cr): def create_partner_email(cr):
cr.execute( cr.execute(
"SELECT id, email FROM res_partner WHERE email IS NOT null AND email != ''") "SELECT id, email FROM res_partner WHERE email IS NOT null AND email != ''"
)
to_create = [] to_create = []
for partner in cr.fetchall(): for partner in cr.fetchall():
to_create.append({ to_create.append(
'partner_id': partner[0], {
'type': '1_email_primary', "partner_id": partner[0],
'email': partner[1], "type": "1_email_primary",
}) "email": partner[1],
}
)
return to_create return to_create
def migrate_to_partner_phone(cr, registry): def migrate_to_partner_phone(cr, registry):
logger.info('start data migration for one2many_phone') logger.info("start data migration for one2many_phone")
with api.Environment.manage(): with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {}) env = api.Environment(cr, SUPERUSER_ID, {})
rppo = env['res.partner.phone'] rppo = env["res.partner.phone"]
to_create = [] to_create = []
to_create += create_partner_phone(cr, 'phone', '3_phone_primary') to_create += create_partner_phone(cr, "phone", "3_phone_primary")
to_create += create_partner_phone(cr, 'mobile', '5_mobile_primary') to_create += create_partner_phone(cr, "mobile", "5_mobile_primary")
to_create += create_partner_email(cr) to_create += create_partner_email(cr)
# I need to create all at the end for invalidation purposes # I need to create all at the end for invalidation purposes
rppo.create(to_create) rppo.create(to_create)
logger.info('end data migration for one2many_phone') logger.info("end data migration for one2many_phone")
return return

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Barroux Abbey # Copyright 2019 Barroux Abbey
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -7,39 +6,40 @@ from odoo.tests.common import TransactionCase
class TestPartnerPhone(TransactionCase): class TestPartnerPhone(TransactionCase):
def setUp(self): def setUp(self):
super(TestPartnerPhone, self).setUp() super(TestPartnerPhone, self).setUp()
def _check_result(self, partner, result): def _check_result(self, partner, result):
rppo = self.env['res.partner.phone'] rppo = self.env["res.partner.phone"]
pphone_email = rppo.search( pphone_email = rppo.search(
[('type', '=', '1_email_primary'), ('partner_id', '=', partner.id)]) [("type", "=", "1_email_primary"), ("partner_id", "=", partner.id)]
if result['email']: )
self.assertEqual(partner.email, result['email']) if result["email"]:
self.assertEqual(partner.email, result["email"])
self.assertEqual(len(pphone_email), 1) self.assertEqual(len(pphone_email), 1)
self.assertEqual(pphone_email.email, result['email']) self.assertEqual(pphone_email.email, result["email"])
else: else:
self.assertFalse(partner.email) self.assertFalse(partner.email)
self.assertFalse(pphone_email) self.assertFalse(pphone_email)
if result['phone']: if result["phone"]:
self.assertEqual(partner.phone, result['phone']) self.assertEqual(partner.phone, result["phone"])
else: else:
self.assertFalse(partner.phone) self.assertFalse(partner.phone)
if result['mobile']: if result["mobile"]:
self.assertEqual(partner.mobile, result['mobile']) self.assertEqual(partner.mobile, result["mobile"])
else: else:
self.assertFalse(partner.mobile) self.assertFalse(partner.mobile)
field2type = { field2type = {
'phone': '3_phone_primary', "phone": "3_phone_primary",
'mobile': '5_mobile_primary', "mobile": "5_mobile_primary",
'fax': '7_fax_primary', "fax": "7_fax_primary",
} }
for field, value in result.items(): for field, value in result.items():
if field in field2type: if field in field2type:
type = field2type[field] type = field2type[field]
pphone = rppo.search( pphone = rppo.search(
[('type', '=', type), ('partner_id', '=', partner.id)]) [("type", "=", type), ("partner_id", "=", partner.id)]
)
if value: if value:
self.assertEqual(len(pphone), 1) self.assertEqual(len(pphone), 1)
self.assertEqual(pphone.phone, value) self.assertEqual(pphone.phone, value)
@@ -47,88 +47,105 @@ class TestPartnerPhone(TransactionCase):
self.assertFalse(pphone) self.assertFalse(pphone)
def test_create_partner(self): def test_create_partner(self):
rpo = self.env['res.partner'] rpo = self.env["res.partner"]
p = rpo.create({ p = rpo.create(
'name': 'Test Me', {
'email': 'testme@example.com', "name": "Test Me",
'phone': '+33198089246', "email": "testme@example.com",
'mobile': '+33198089247', "phone": "+33198089246",
}) "mobile": "+33198089247",
}
)
result = { result = {
'email': 'testme@example.com', "email": "testme@example.com",
'phone': '+33198089246', "phone": "+33198089246",
'mobile': '+33198089247', "mobile": "+33198089247",
} }
self._check_result(p, result) self._check_result(p, result)
p2 = rpo.create({ p2 = rpo.create(
'name': 'Test me now', {
'email': 'testmenow@example.com', "name": "Test me now",
'phone': '+33972727272', "email": "testmenow@example.com",
}) "phone": "+33972727272",
}
)
result = { result = {
'email': 'testmenow@example.com', "email": "testmenow@example.com",
'phone': '+33972727272', "phone": "+33972727272",
'mobile': False, "mobile": False,
} }
self._check_result(p2, result) self._check_result(p2, result)
p3 = rpo.create({ p3 = rpo.create(
'name': 'Test me now', {
'phone_ids': [ "name": "Test me now",
(0, 0, {'type': '3_phone_primary', 'phone': '+33972727272'}), "phone_ids": [
(0, 0, {'type': '1_email_primary', 'email': 'tutu@example.fr'})], (0, 0, {"type": "3_phone_primary", "phone": "+33972727272"}),
}) (0, 0, {"type": "1_email_primary", "email": "tutu@example.fr"}),
],
}
)
result = { result = {
'email': 'tutu@example.fr', "email": "tutu@example.fr",
'phone': '+33972727272', "phone": "+33972727272",
'mobile': False, "mobile": False,
} }
self._check_result(p3, result) self._check_result(p3, result)
def test_write_partner(self): def test_write_partner(self):
p1 = self.env['res.partner'].create({ p1 = self.env["res.partner"].create(
'name': 'test me now', {
'country_id': self.env.ref('base.fr').id, "name": "test me now",
}) "country_id": self.env.ref("base.fr").id,
}
)
result_none = { result_none = {
'email': False, "email": False,
'phone': False, "phone": False,
'mobile': False, "mobile": False,
} }
self._check_result(p1, result_none) self._check_result(p1, result_none)
p1.write({ p1.write(
'mobile': '+33198089247', {
'email': 'testmenow@example.com', "mobile": "+33198089247",
}) "email": "testmenow@example.com",
}
)
result = { result = {
'email': 'testmenow@example.com', "email": "testmenow@example.com",
'phone': False, "phone": False,
'mobile': '+33198089247', "mobile": "+33198089247",
} }
self._check_result(p1, result) self._check_result(p1, result)
p1.write({ p1.write(
'email': 'testmenow2@example.com', {
'phone': False, "email": "testmenow2@example.com",
'mobile': '+33472727272', "phone": False,
}) "mobile": "+33472727272",
}
)
result = { result = {
'email': 'testmenow2@example.com', "email": "testmenow2@example.com",
'phone': False, "phone": False,
'mobile': '+33472727272', "mobile": "+33472727272",
} }
self._check_result(p1, result) self._check_result(p1, result)
p1.write({ p1.write(
'phone': False, {
'mobile': False, "phone": False,
'email': False, "mobile": False,
}) "email": False,
}
)
self._check_result(p1, result_none) self._check_result(p1, result_none)
p2 = self.env['res.partner'].create({'name': 'Toto', 'email': 'toto@example.com'}) p2 = self.env["res.partner"].create(
{"name": "Toto", "email": "toto@example.com"}
)
p_multi = p1 + p2 p_multi = p1 + p2
p_multi.write({'email': 'all@example.com', 'phone': '+33560606070'}) p_multi.write({"email": "all@example.com", "phone": "+33560606070"})
result = { result = {
'email': 'all@example.com', "email": "all@example.com",
'phone': '+33560606070', "phone": "+33560606070",
'mobile': False, "mobile": False,
} }
self._check_result(p1, result) self._check_result(p1, result)
self._check_result(p2, result) self._check_result(p2, result)

View File

@@ -3,12 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Base Partner Reference', "name": "Base Partner Reference",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Partner', "category": "Partner",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': "Improve usage of partner's Internal Reference", "summary": "Improve usage of partner's Internal Reference",
'description': """ "description": """
Base Partner Reference Base Partner Reference
====================== ======================
@@ -18,9 +18,9 @@ Base Partner Reference
* Adds unicity constraint on Internal Reference * Adds unicity constraint on Internal Reference
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['base'], "depends": ["base"],
'data': ['views/res_partner.xml'], "data": ["views/res_partner.xml"],
'installable': True, "installable": True,
} }

View File

@@ -6,34 +6,37 @@ from odoo import api, fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = "res.partner"
ref = fields.Char(copy=False) # To avoid blocking duplicate ref = fields.Char(copy=False) # To avoid blocking duplicate
invalidate_display_name = fields.Boolean() invalidate_display_name = fields.Boolean()
_sql_constraints = [( _sql_constraints = [
'ref_unique', (
'unique(ref)', "ref_unique",
'A partner already exists with this internal reference!' "unique(ref)",
)] "A partner already exists with this internal reference!",
)
]
# add 'ref' in depends # add 'ref' in depends
@api.depends('ref', 'invalidate_display_name') @api.depends("ref", "invalidate_display_name")
def _compute_display_name(self): def _compute_display_name(self):
super()._compute_display_name() super()._compute_display_name()
def _get_name(self): def _get_name(self):
partner = self partner = self
name = partner.name or '' name = partner.name or ""
# START modif of native method # START modif of native method
if partner.ref: if partner.ref:
name = "[%s] %s" % (partner.ref, name) name = "[%s] %s" % (partner.ref, name)
# END modif of native method # END modif of native method
if partner.company_name or partner.parent_id: if partner.company_name or partner.parent_id:
if not name and partner.type in ['invoice', 'delivery', 'other']: if not name and partner.type in ["invoice", "delivery", "other"]:
name = dict(self.fields_get( name = dict(self.fields_get(["type"])["type"]["selection"])[
['type'])['type']['selection'])[partner.type] partner.type
]
if not partner.is_company: if not partner.is_company:
# START modif of native name_get() method # START modif of native name_get() method
company_name = partner.commercial_company_name or partner.parent_id.name company_name = partner.commercial_company_name or partner.parent_id.name
@@ -41,30 +44,30 @@ class ResPartner(models.Model):
company_name = "[%s] %s" % (partner.parent_id.ref, company_name) company_name = "[%s] %s" % (partner.parent_id.ref, company_name)
name = "%s, %s" % (company_name, name) name = "%s, %s" % (company_name, name)
# END modif of native name_get() method # END modif of native name_get() method
if self._context.get('show_address_only'): if self._context.get("show_address_only"):
name = partner._display_address(without_company=True) name = partner._display_address(without_company=True)
if self._context.get('show_address'): if self._context.get("show_address"):
name = name + "\n" + partner._display_address(without_company=True) name = name + "\n" + partner._display_address(without_company=True)
name = name.replace('\n\n', '\n') name = name.replace("\n\n", "\n")
name = name.replace('\n\n', '\n') name = name.replace("\n\n", "\n")
if self._context.get('address_inline'): if self._context.get("address_inline"):
splitted_names = name.split("\n") splitted_names = name.split("\n")
name = ", ".join([n for n in splitted_names if n.strip()]) name = ", ".join([n for n in splitted_names if n.strip()])
if self._context.get('show_email') and partner.email: if self._context.get("show_email") and partner.email:
name = "%s <%s>" % (name, partner.email) name = "%s <%s>" % (name, partner.email)
if self._context.get('html_format'): if self._context.get("html_format"):
name = name.replace('\n', '<br/>') name = name.replace("\n", "<br/>")
if self._context.get('show_vat') and partner.vat: if self._context.get("show_vat") and partner.vat:
name = "%s %s" % (name, partner.vat) name = "%s %s" % (name, partner.vat)
return name return name
@api.model @api.model
def name_search(self, name='', args=None, operator='ilike', limit=100): def name_search(self, name="", args=None, operator="ilike", limit=100):
if args is None: if args is None:
args = [] args = []
if name and operator == 'ilike': if name and operator == "ilike":
recs = self.search([('ref', '=', name)] + args, limit=limit) recs = self.search([("ref", "=", name)] + args, limit=limit)
if recs: if recs:
rec_childs = self.search([('id', 'child_of', recs.ids)]) rec_childs = self.search([("id", "child_of", recs.ids)])
return rec_childs.name_get() return rec_childs.name_get()
return super().name_search(name=name, args=args, operator=operator, limit=limit) return super().name_search(name=name, args=args, operator=operator, limit=limit)

View File

@@ -1,22 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2017-2019 Akretion (http://www.akretion.com/) Copyright 2017-2019 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_partner_form" model="ir.ui.view"> <record id="view_partner_form" model="ir.ui.view">
<field name="name">Move ref in partner form to make it more visible</field> <field name="name">Move ref in partner form to make it more visible</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/> <field name="inherit_id" ref="base.view_partner_form" />
<field name="priority">1000</field> <!-- inherit after l10n_fr --> <field name="priority">1000</field> <!-- inherit after l10n_fr -->
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="type" position="after"> <field name="type" position="after">
<field name="ref"/> <field name="ref" />
</field> </field>
<xpath expr="//page[@name='sales_purchases']//field[@name='ref']" position="attributes"> <xpath
expr="//page[@name='sales_purchases']//field[@name='ref']"
position="attributes"
>
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</xpath> </xpath>
</field> </field>
@@ -43,13 +45,13 @@
<record id="res_partner_kanban_view" model="ir.ui.view"> <record id="res_partner_kanban_view" model="ir.ui.view">
<field name="name">Add ref in partner kanban view</field> <field name="name">Add ref in partner kanban view</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.res_partner_kanban_view"/> <field name="inherit_id" ref="base.res_partner_kanban_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="display_name" position="after"> <field name="display_name" position="after">
<field name="ref"/> <field name="ref" />
</field> </field>
<li t-if="record.email.raw_value" position="after"> <li t-if="record.email.raw_value" position="after">
<li t-if="record.ref.raw_value">Ref: <field name="ref"/></li> <li t-if="record.ref.raw_value">Ref: <field name="ref" /></li>
</li> </li>
</field> </field>
</record> </record>

View File

@@ -3,22 +3,22 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Base Usability', "name": "Base Usability",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Partner', "category": "Partner",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Better usability in base module', "summary": "Better usability in base module",
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['base'], "depends": ["base"],
'data': [ "data": [
'security/group.xml', "security/group.xml",
'security/ir.model.access.csv', "security/ir.model.access.csv",
'views/res_partner.xml', "views/res_partner.xml",
'views/res_partner_bank.xml', "views/res_partner_bank.xml",
'views/res_country.xml', "views/res_country.xml",
'views/ir_module.xml', "views/ir_module.xml",
'views/ir_sequence.xml', "views/ir_sequence.xml",
], ],
'installable': True, "installable": True,
} }

View File

@@ -16,4 +16,3 @@ index a3baf47c615..e546d450107 100644
+ user.partner_id.write({'company_id': False}) + user.partner_id.write({'company_id': False})
user.partner_id.active = user.active user.partner_id.active = user.active
return users return users

View File

@@ -2,10 +2,12 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
from odoo.addons.base.models.ir_mail_server import extract_rfc2822_addresses
import logging import logging
from odoo import api, models
from odoo.addons.base.models.ir_mail_server import extract_rfc2822_addresses
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -14,23 +16,42 @@ class IrMailServer(models.Model):
@api.model @api.model
def send_email( def send_email(
self, message, mail_server_id=None, smtp_server=None, self,
smtp_port=None, smtp_user=None, smtp_password=None, message,
smtp_encryption=None, smtp_debug=False, smtp_session=None): mail_server_id=None,
smtp_server=None,
smtp_port=None,
smtp_user=None,
smtp_password=None,
smtp_encryption=None,
smtp_debug=False,
smtp_session=None,
):
# Start copy from native method # Start copy from native method
smtp_from = message['Return-Path'] or\ smtp_from = (
self._get_default_bounce_address() or message['From'] message["Return-Path"]
or self._get_default_bounce_address()
or message["From"]
)
from_rfc2822 = extract_rfc2822_addresses(smtp_from) from_rfc2822 = extract_rfc2822_addresses(smtp_from)
smtp_from = from_rfc2822[-1] smtp_from = from_rfc2822[-1]
# End copy from native method # End copy from native method
logger.info( logger.info(
"Sending email from '%s' to '%s' Cc '%s' Bcc '%s' " "Sending email from '%s' to '%s' Cc '%s' Bcc '%s' " "with subject '%s'",
"with subject '%s'", smtp_from,
smtp_from, message.get('To'), message.get('Cc'), message.get("To"),
message.get('Bcc'), message.get('Subject')) message.get("Cc"),
message.get("Bcc"),
message.get("Subject"),
)
return super().send_email( return super().send_email(
message, mail_server_id=mail_server_id, message,
smtp_server=smtp_server, smtp_port=smtp_port, mail_server_id=mail_server_id,
smtp_user=smtp_user, smtp_password=smtp_password, smtp_server=smtp_server,
smtp_encryption=smtp_encryption, smtp_debug=smtp_debug, smtp_port=smtp_port,
smtp_session=smtp_session) smtp_user=smtp_user,
smtp_password=smtp_password,
smtp_encryption=smtp_encryption,
smtp_debug=smtp_debug,
smtp_session=smtp_session,
)

View File

@@ -6,11 +6,11 @@ from odoo import api, models
class IrModel(models.Model): class IrModel(models.Model):
_inherit = 'ir.model' _inherit = "ir.model"
@api.depends('name', 'model') @api.depends("name", "model")
def name_get(self): def name_get(self):
res = [] res = []
for rec in self: for rec in self:
res.append((rec.id, '%s (%s)' % (rec.name, rec.model))) res.append((rec.id, "%s (%s)" % (rec.name, rec.model)))
return res return res

View File

@@ -2,14 +2,14 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models, _ from odoo import _, api, models
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = 'res.company' _inherit = "res.company"
@api.model @api.model
def generate_line(self, fields, options, icon=True, separator=' - '): def generate_line(self, fields, options, icon=True, separator=" - "):
assert fields assert fields
assert options assert options
content = [] content = []
@@ -20,13 +20,13 @@ class ResCompany(models.Model):
label = field[1] label = field[1]
uicon = False uicon = False
elif isinstance(field, str) and field in options: elif isinstance(field, str) and field in options:
value = options[field]['value'] value = options[field]["value"]
label = options[field].get('label') label = options[field].get("label")
uicon = options[field].get('icon') uicon = options[field].get("icon")
if value: if value:
prefix = icon and uicon or label prefix = icon and uicon or label
if prefix: if prefix:
content.append('%s %s' % (prefix, value)) content.append("%s %s" % (prefix, value))
else: else:
content.append(value) content.append(value)
line = separator.join(content) line = separator.join(content)
@@ -35,46 +35,51 @@ class ResCompany(models.Model):
def _prepare_header_options(self): def _prepare_header_options(self):
self.ensure_one() self.ensure_one()
options = { options = {
'phone': { "phone": {
'value': self.phone, "value": self.phone,
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm # http://www.fileformat.info/info/unicode/char/1f4de/index.htm
'icon': '\U0001F4DE', "icon": "\U0001F4DE",
'label': _('Tel:')}, "label": _("Tel:"),
'email': { },
'value': self.email, "email": {
"value": self.email,
# http://www.fileformat.info/info/unicode/char/2709/index.htm # http://www.fileformat.info/info/unicode/char/2709/index.htm
'icon': '\u2709', "icon": "\u2709",
'label': _('E-mail:')}, "label": _("E-mail:"),
'website': { },
'value': self.website, "website": {
'icon': '\U0001f310', "value": self.website,
'label': _('Website:')}, "icon": "\U0001f310",
'vat': { "label": _("Website:"),
'value': self.vat, },
'label': _('VAT:')}, "vat": {"value": self.vat, "label": _("VAT:")},
} }
return options return options
def _report_company_legal_name(self): def _report_company_legal_name(self):
'''Method inherited in the module base_company_extension''' """Method inherited in the module base_company_extension"""
self.ensure_one() self.ensure_one()
return self.name return self.name
# for reports # for reports
def _display_report_header( def _display_report_header(
self, line_details=[['phone', 'website'], ['vat']], self,
icon=True, line_separator=' - '): line_details=[["phone", "website"], ["vat"]],
icon=True,
line_separator=" - ",
):
self.ensure_one() self.ensure_one()
res = '' res = ""
address = self.partner_id._display_address(without_company=True) address = self.partner_id._display_address(without_company=True)
address = address.replace('\n', ' - ') address = address.replace("\n", " - ")
line1 = '%s - %s' % (self._report_company_legal_name(), address) line1 = "%s - %s" % (self._report_company_legal_name(), address)
lines = [line1] lines = [line1]
options = self._prepare_header_options() options = self._prepare_header_options()
for details in line_details: for details in line_details:
line = self.generate_line( line = self.generate_line(
details, options, icon=icon, separator=line_separator) details, options, icon=icon, separator=line_separator
)
lines.append(line) lines.append(line)
res = '\n'.join(lines) res = "\n".join(lines)
return res return res

View File

@@ -2,21 +2,20 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _ from odoo import _, api, fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = "res.partner"
# tracking=True is handled in the 'mail' module, and base_usability # tracking=True is handled in the 'mail' module, and base_usability
# doesn't depend on 'mail', so adding tracking on res.partner fields # doesn't depend on 'mail', so adding tracking on res.partner fields
# has been moved to mail_usability # has been moved to mail_usability
ref = fields.Char(copy=False) ref = fields.Char(copy=False)
# For reports # For reports
name_title = fields.Char( name_title = fields.Char(compute="_compute_name_title", string="Name with Title")
compute='_compute_name_title', string='Name with Title')
@api.depends('name', 'title') @api.depends("name", "title")
def _compute_name_title(self): def _compute_name_title(self):
for partner in self: for partner in self:
name_title = partner.name name_title = partner.name
@@ -28,21 +27,22 @@ class ResPartner(models.Model):
if partner.lang: if partner.lang:
partner_lg = partner.with_context(lang=partner.lang) partner_lg = partner.with_context(lang=partner.lang)
title = partner_lg.title.shortcut or partner_lg.title.name title = partner_lg.title.shortcut or partner_lg.title.name
name_title = ' '.join([title, name_title]) name_title = " ".join([title, name_title])
partner.name_title = name_title partner.name_title = name_title
def _display_address(self, without_company=False): def _display_address(self, without_company=False):
'''Remove empty lines''' """Remove empty lines"""
res = super()._display_address(without_company=without_company) res = super()._display_address(without_company=without_company)
while "\n\n" in res: while "\n\n" in res:
res = res.replace('\n\n', '\n') res = res.replace("\n\n", "\n")
return res return res
# for reports # for reports
def _display_full_address( def _display_full_address(
self, self,
details=['company', 'name', 'address', 'phone', 'mobile', 'email'], details=["company", "name", "address", "phone", "mobile", "email"],
icon=True): icon=True,
):
self.ensure_one() self.ensure_one()
# To make the icons work with py3o with PDF export, on the py3o server: # To make the icons work with py3o with PDF export, on the py3o server:
# 1) sudo apt-get install fonts-symbola # 1) sudo apt-get install fonts-symbola
@@ -54,85 +54,89 @@ class ResPartner(models.Model):
title = False title = False
title_short = False title_short = False
else: else:
company = self.parent_id and self.parent_id.is_company and\ company = (
self.parent_id.name or False self.parent_id
and self.parent_id.is_company
and self.parent_id.name
or False
)
name = self.name_title name = self.name_title
name_no_title = self.name name_no_title = self.name
title = self.title.name title = self.title.name
title_short = self.title.shortcut title_short = self.title.shortcut
options = { options = {
'name': { "name": {
'value': name, "value": name,
}, },
'company': { "company": {
'value': company, "value": company,
}, },
'title': { "title": {
'value': title, "value": title,
}, },
'title_short': { "title_short": {
'value': title_short, "value": title_short,
}, },
'name_no_title': { "name_no_title": {
'value': name_no_title, "value": name_no_title,
}, },
'phone': { "phone": {
'value': self.phone, "value": self.phone,
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm # http://www.fileformat.info/info/unicode/char/1f4de/index.htm
'icon': '\U0001F4DE', "icon": "\U0001F4DE",
'label': _('Tel:'), "label": _("Tel:"),
}, },
'mobile': { "mobile": {
'value': self.mobile, "value": self.mobile,
# http://www.fileformat.info/info/unicode/char/1f4f1/index.htm # http://www.fileformat.info/info/unicode/char/1f4f1/index.htm
'icon': '\U0001F4F1', "icon": "\U0001F4F1",
'label': _('Mobile:'), "label": _("Mobile:"),
}, },
'email': { "email": {
'value': self.email, "value": self.email,
# http://www.fileformat.info/info/unicode/char/2709/index.htm # http://www.fileformat.info/info/unicode/char/2709/index.htm
'icon': '\u2709', "icon": "\u2709",
'label': _('E-mail:'), "label": _("E-mail:"),
}, },
'website': { "website": {
'value': self.website, "value": self.website,
# http://www.fileformat.info/info/unicode/char/1f310/index.htm # http://www.fileformat.info/info/unicode/char/1f310/index.htm
'icon': '\U0001f310', "icon": "\U0001f310",
'label': _('Website:'), "label": _("Website:"),
}, },
'address': { "address": {
'value': self._display_address(without_company=True), "value": self._display_address(without_company=True),
}, },
'vat': { "vat": {
'value': self.commercial_partner_id.vat, "value": self.commercial_partner_id.vat,
'label': _('VAT Number:'), "label": _("VAT Number:"),
}, },
'commercial_ref': { "commercial_ref": {
'value': self.commercial_partner_id.ref, "value": self.commercial_partner_id.ref,
'label': _('Customer Number:'), "label": _("Customer Number:"),
}, },
'ref': { "ref": {
'value': self.ref, "value": self.ref,
'label': _('Customer Number:'), "label": _("Customer Number:"),
}, },
# Same with 'supplier_' prefix, to change the label # Same with 'supplier_' prefix, to change the label
'supplier_commercial_ref': { "supplier_commercial_ref": {
'value': self.commercial_partner_id.ref, "value": self.commercial_partner_id.ref,
'label': _('Supplier Number:'), "label": _("Supplier Number:"),
}, },
'supplier_ref': { "supplier_ref": {
'value': self.ref, "value": self.ref,
'label': _('Supplier Number:'), "label": _("Supplier Number:"),
}, },
} }
res = [] res = []
for detail in details: for detail in details:
if options.get(detail) and options[detail]['value']: if options.get(detail) and options[detail]["value"]:
entry = options[detail] entry = options[detail]
prefix = icon and entry.get('icon') or entry.get('label') prefix = icon and entry.get("icon") or entry.get("label")
if prefix: if prefix:
res.append('%s %s' % (prefix, entry['value'])) res.append("%s %s" % (prefix, entry["value"]))
else: else:
res.append('%s' % entry['value']) res.append("%s" % entry["value"])
res = '\n'.join(res) res = "\n".join(res)
return res return res

View File

@@ -6,9 +6,9 @@ from odoo import fields, models
class ResPartnerBank(models.Model): class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank' _inherit = "res.partner.bank"
# In the 'base' module, they didn't put any string, so the bank name is # In the 'base' module, they didn't put any string, so the bank name is
# displayed as 'Name', which the string of the related field it # displayed as 'Name', which the string of the related field it
# points to # points to
bank_name = fields.Char(string='Bank Name') bank_name = fields.Char(string="Bank Name")

View File

@@ -6,6 +6,6 @@ from odoo import fields, models
class ResPartnerCategory(models.Model): class ResPartnerCategory(models.Model):
_inherit = 'res.partner.category' _inherit = "res.partner.category"
name = fields.Char(translate=False) name = fields.Char(translate=False)

View File

@@ -2,26 +2,26 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models, SUPERUSER_ID
import logging import logging
from odoo import SUPERUSER_ID, api, models
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ResUsers(models.Model): class ResUsers(models.Model):
_inherit = 'res.users' _inherit = "res.users"
@api.model @api.model
def _script_partners_linked_to_users_no_company(self): def _script_partners_linked_to_users_no_company(self):
if self.env.user.id != SUPERUSER_ID: if self.env.user.id != SUPERUSER_ID:
self = self.sudo() self = self.sudo()
logger.info( logger.info("START to set company_id=False on partners related to users")
'START to set company_id=False on partners related to users')
users = self.with_context(active_test=False).search([]) users = self.with_context(active_test=False).search([])
for user in users: for user in users:
if user.partner_id.company_id: if user.partner_id.company_id:
user.partner_id.write({'company_id': False}) user.partner_id.write({"company_id": False})
logger.info( logger.info(
'Wrote company_id=False on user %s ID %d', "Wrote company_id=False on user %s ID %d", user.login, user.id
user.login, user.id) )
logger.info( logger.info("END setting company_id=False on partners related to users")
'END setting company_id=False on partners related to users')

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2016-2020 Akretion France (http://www.akretion.com/) Copyright 2016-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo noupdate="1"> <odoo noupdate="1">
<!-- This group is used to hide menu entries to everybody, <!-- This group is used to hide menu entries to everybody,

View File

@@ -1,19 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_module_filter" model="ir.ui.view"> <record id="view_module_filter" model="ir.ui.view">
<field name="model">ir.module.module</field> <field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.view_module_filter"/> <field name="inherit_id" ref="base.view_module_filter" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//filter[@name='extra']" position="after"> <xpath expr="//filter[@name='extra']" position="after">
<filter name="installable" string="Installable" domain="[('state', '!=', 'uninstallable')]"/> <filter
name="installable"
string="Installable"
domain="[('state', '!=', 'uninstallable')]"
/>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_sequence_search" model="ir.ui.view"> <record id="view_sequence_search" model="ir.ui.view">
<field name="model">ir.sequence</field> <field name="model">ir.sequence</field>
<field name="inherit_id" ref="base.view_sequence_search"/> <field name="inherit_id" ref="base.view_sequence_search" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="name" position="after"> <field name="name" position="after">
<field name="prefix"/> <field name="prefix" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -1,22 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2015-2020 Akretion France (http://www.akretion.com/) Copyright 2015-2020 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_country_state_search" model="ir.ui.view"> <record id="view_country_state_search" model="ir.ui.view">
<field name="name">base_usability.res.country.state.search</field> <field name="name">base_usability.res.country.state.search</field>
<field name="model">res.country.state</field> <field name="model">res.country.state</field>
<field name="inherit_id" ref="base.view_country_state_search"/> <field name="inherit_id" ref="base.view_country_state_search" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="name" position="attributes"> <field name="name" position="attributes">
<attribute name="filter_domain">['|', ('name', 'ilike', self), ('code', '=', self)]</attribute> <attribute
name="filter_domain"
>['|', ('name', 'ilike', self), ('code', '=', self)]</attribute>
</field> </field>
<field name="name" position="after"> <field name="name" position="after">
<field name="code"/> <field name="code" />
</field> </field>
</field> </field>
</record> </record>
@@ -26,11 +27,19 @@
<field name="model">res.country</field> <field name="model">res.country</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name" filter_domain="['|', ('name', 'ilike', self), ('code', '=', self)]" string="Name or Code"/> <field
<field name="code"/> name="name"
<field name="currency_id"/> filter_domain="['|', ('name', 'ilike', self), ('code', '=', self)]"
string="Name or Code"
/>
<field name="code" />
<field name="currency_id" />
<group string="Group By" name="groupby"> <group string="Group By" name="groupby">
<filter name="currency_groupby" string="Currency" context="{'group_by': 'currency_id'}"/> <filter
name="currency_groupby"
string="Currency"
context="{'group_by': 'currency_id'}"
/>
</group> </group>
</search> </search>
</field> </field>
@@ -39,10 +48,10 @@
<record id="view_country_form" model="ir.ui.view"> <record id="view_country_form" model="ir.ui.view">
<field name="name">base_usability.res.country.form</field> <field name="name">base_usability.res.country.form</field>
<field name="model">res.country</field> <field name="model">res.country</field>
<field name="inherit_id" ref="base.view_country_form"/> <field name="inherit_id" ref="base.view_country_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="code" position="after"> <field name="code" position="after">
<field name="country_group_ids" widget="many2many_tags"/> <field name="country_group_ids" widget="many2many_tags" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -1,23 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
© 2014-2016 Akretion (http://www.akretion.com/) © 2014-2016 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_partner_form" model="ir.ui.view"> <record id="view_partner_form" model="ir.ui.view">
<field name="name">base_usability.title.on.partner.form</field> <field name="name">base_usability.title.on.partner.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/> <field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<!-- Show title not only on Contacts --> <!-- Show title not only on Contacts -->
<xpath expr="//field[@name='child_ids']/form//field[@name='title']" position="attributes"> <xpath
<attribute name="attrs"></attribute> expr="//field[@name='child_ids']/form//field[@name='title']"
position="attributes"
>
<attribute name="attrs" />
</xpath> </xpath>
<!-- Show double VAT partner even when not in editable mode --> <!-- Show double VAT partner even when not in editable mode -->
<div attrs="{'invisible': [('same_vat_partner_id', '=', False)]}" position="attributes"> <div
attrs="{'invisible': [('same_vat_partner_id', '=', False)]}"
position="attributes"
>
<attribute name="class">alert alert-warning</attribute> <attribute name="class">alert alert-warning</attribute>
</div> </div>
</field> </field>
@@ -26,10 +31,10 @@
<record id="view_partner_simple_form" model="ir.ui.view"> <record id="view_partner_simple_form" model="ir.ui.view">
<field name="name">base_usability.title.on.partner.simplified.form</field> <field name="name">base_usability.title.on.partner.simplified.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_simple_form"/> <field name="inherit_id" ref="base.view_partner_simple_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="function" position="before"> <field name="function" position="before">
<field name="title"/> <field name="title" />
</field> </field>
</field> </field>
</record> </record>
@@ -37,13 +42,13 @@
<record id="view_partner_tree" model="ir.ui.view"> <record id="view_partner_tree" model="ir.ui.view">
<field name="name">base_usability.res.partner.tree</field> <field name="name">base_usability.res.partner.tree</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/> <field name="inherit_id" ref="base.view_partner_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="display_name" position="after"> <field name="display_name" position="after">
<field name="ref" optional="hide"/> <field name="ref" optional="hide" />
</field> </field>
<field name="phone" position="after"> <field name="phone" position="after">
<field name="mobile" optional="show" widget="phone" class="o_force_ltr"/> <field name="mobile" optional="show" widget="phone" class="o_force_ltr" />
</field> </field>
</field> </field>
</record> </record>
@@ -51,12 +56,14 @@
<record id="view_res_partner_filter" model="ir.ui.view"> <record id="view_res_partner_filter" model="ir.ui.view">
<field name="name">base_usability.partner.search.form</field> <field name="name">base_usability.partner.search.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/> <field name="inherit_id" ref="base.view_res_partner_filter" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="name" position="attributes"> <field name="name" position="attributes">
<attribute name="string">Name or Email or Reference</attribute> <attribute name="string">Name or Email or Reference</attribute>
<!-- for 'ref', change '=' to 'start with' --> <!-- for 'ref', change '=' to 'start with' -->
<attribute name="filter_domain">['|','|',('display_name','ilike',self),('ref','=ilike',self + '%'),('email','ilike',self)]</attribute> <attribute
name="filter_domain"
>['|','|',('display_name','ilike',self),('ref','=ilike',self + '%'),('email','ilike',self)]</attribute>
</field> </field>
</field> </field>
</record> </record>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2018-2020 Akretion (http://www.akretion.com/) Copyright 2018-2020 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
@@ -8,13 +8,13 @@
<record id="view_partner_bank_tree" model="ir.ui.view"> <record id="view_partner_bank_tree" model="ir.ui.view">
<field name="name">base_usability.res.partner.bank.tree</field> <field name="name">base_usability.res.partner.bank.tree</field>
<field name="model">res.partner.bank</field> <field name="model">res.partner.bank</field>
<field name="inherit_id" ref="base.view_partner_bank_tree"/> <field name="inherit_id" ref="base.view_partner_bank_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="bank_name" position="attributes"> <field name="bank_name" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</field> </field>
<field name="bank_name" position="after"> <field name="bank_name" position="after">
<field name="bank_id"/> <field name="bank_id" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -2,19 +2,19 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Company Code', "name": "Company Code",
'summary': 'Add a code field in company', "summary": "Add a code field in company",
'version': '12.0.0.0.1', "version": "12.0.0.0.1",
'author': 'Akretion', "author": "Akretion",
'maintainer': 'Akretion', "maintainer": "Akretion",
'license': 'AGPL-3', "license": "AGPL-3",
'category': 'base', "category": "base",
'depends': [ "depends": [
'base', "base",
], ],
'website': 'http://www.akretion.com/', "website": "https://github.com/OCA/odoo-usability",
'data': [ "data": [
'views/company_view.xml', "views/company_view.xml",
], ],
'installable': False, "installable": False,
} }

View File

@@ -1,15 +1,15 @@
# Copyright 2019 David BEAL @ Akretion # Copyright 2019 David BEAL @ Akretion
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields from odoo import fields, models
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = 'res.company' _inherit = "res.company"
code = fields.Char( code = fields.Char(
required=True, default='CODE', required=True, default="CODE", help="Field used in object name as suffix"
help="Field used in object name as suffix") )
def _add_company_code(self, super_object): def _add_company_code(self, super_object):
"" ""
@@ -19,15 +19,20 @@ class ResCompany(models.Model):
return self.env['res.company']._add_company_code(super()) return self.env['res.company']._add_company_code(super())
""" """
records = super_object.__self__ records = super_object.__self__
if records and records[0]._name == 'res.company': if records and records[0]._name == "res.company":
codes = {x.id: x.code for x in records} codes = {x.id: x.code for x in records}
else: else:
codes = {x.id: x['company_id']['code'] for x in records codes = {
if getattr(x, 'company_id')} x.id: x["company_id"]["code"]
for x in records
if getattr(x, "company_id")
}
if not codes: if not codes:
return super_object.name_get() return super_object.name_get()
return [(elm[0], '%s (%s)' % (elm[1], codes[elm[0]] or '')) return [
for elm in super_object.name_get()] (elm[0], "%s (%s)" % (elm[1], codes[elm[0]] or ""))
for elm in super_object.name_get()
]
def name_get(self): def name_get(self):
return self.env['res.company']._add_company_code(super()) return self.env["res.company"]._add_company_code(super())

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_company_form" model="ir.ui.view"> <record id="view_company_form" model="ir.ui.view">
<field name="model">res.company</field> <field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/> <field name="inherit_id" ref="base.view_company_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="partner_id" position="before"> <field name="partner_id" position="before">
<field name="code"/> <field name="code" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -3,23 +3,23 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
{ {
'name': 'CRM Usability', "name": "CRM Usability",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Customer Relationship Management', "category": "Customer Relationship Management",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'CRM usability enhancements', "summary": "CRM usability enhancements",
'description': """ "description": """
CRM Usability CRM Usability
============= =============
This module has been written by Alexis de Lattre from Akretion This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>. <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['crm'], "depends": ["crm"],
'data': [ "data": [
'views/crm_lead.xml', "views/crm_lead.xml",
], ],
'installable': True, "installable": True,
} }

View File

@@ -6,7 +6,7 @@ from odoo import fields, models
class CrmLead(models.Model): class CrmLead(models.Model):
_inherit = 'crm.lead' _inherit = "crm.lead"
probability = fields.Float(tracking=100) probability = fields.Float(tracking=100)
date_deadline = fields.Date(tracking=110) date_deadline = fields.Date(tracking=110)

View File

@@ -1,20 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2017-2021 Akretion (http://www.akretion.com/) Copyright 2017-2021 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<!-- SEARCH OPPOR --> <!-- SEARCH OPPOR -->
<record id="view_crm_case_opportunities_filter" model="ir.ui.view"> <record id="view_crm_case_opportunities_filter" model="ir.ui.view">
<field name="name">usability.crm.lead.opportunity.search</field> <field name="name">usability.crm.lead.opportunity.search</field>
<field name="model">crm.lead</field> <field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.view_crm_case_opportunities_filter"/> <field name="inherit_id" ref="crm.view_crm_case_opportunities_filter" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<filter name="saleschannel" position="after"> <filter name="saleschannel" position="after">
<filter name="partner_groupby" string="Customer" context="{'group_by': 'partner_id'}"/> <filter
name="partner_groupby"
string="Customer"
context="{'group_by': 'partner_id'}"
/>
</filter> </filter>
</field> </field>
</record> </record>

View File

@@ -3,12 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Delivery Usability', "name": "Delivery Usability",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Stock', "category": "Stock",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Several usability enhancements in Delivery', "summary": "Several usability enhancements in Delivery",
'description': """ "description": """
Delivery Usability Delivery Usability
=================== ===================
@@ -17,11 +17,11 @@ The usability enhancements include:
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>. This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['delivery'], "depends": ["delivery"],
'data': [ "data": [
'views/stock_picking.xml', "views/stock_picking.xml",
], ],
'installable': True, "installable": True,
} }

View File

@@ -6,7 +6,7 @@ from odoo import fields, models
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = "stock.picking"
carrier_id = fields.Many2one(tracking=True) carrier_id = fields.Many2one(tracking=True)
carrier_tracking_ref = fields.Char(tracking=True) carrier_tracking_ref = fields.Char(tracking=True)

View File

@@ -1,16 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2018-2021 Akretion France Copyright 2018-2021 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="view_picking_withcarrier_out_form" model="ir.ui.view"> <record id="view_picking_withcarrier_out_form" model="ir.ui.view">
<field name="name">delivery_usability.stock.picking.form</field> <field name="name">delivery_usability.stock.picking.form</field>
<field name="model">stock.picking</field> <field name="model">stock.picking</field>
<field name="inherit_id" ref="delivery.view_picking_withcarrier_out_form"/> <field name="inherit_id" ref="delivery.view_picking_withcarrier_out_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="carrier_id" position="attributes"> <field name="carrier_id" position="attributes">
<!-- Sometimes we have to modify carrier_id when state is done <!-- Sometimes we have to modify carrier_id when state is done

View File

@@ -1,12 +1,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Developer Menu', "name": "Developer Menu",
'version': '12.0.0.0.0', "version": "12.0.0.0.0",
'category': 'Tools', "category": "Tools",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': "Menu Shortcut for developer usage", "summary": "Menu Shortcut for developer usage",
'description': """ "description": """
Developer menu Developer menu
============== ==============
@@ -21,11 +21,9 @@ near `Technical` menu
This module has been written by David Béal This module has been written by David Béal
from Akretion <david.beal@akretion.com>. from Akretion <david.beal@akretion.com>.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['mail'], "depends": ["mail"],
'data': [ "data": ["menu_view.xml"],
'menu_view.xml' "installable": False,
],
'installable': False,
} }

View File

@@ -1,21 +1,104 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<menuitem id="conf_tech" parent="base.menu_administration" name="🧰" groups="base.group_erp_manager" sequence="100"/> <menuitem
<menuitem id="model" name="Model" parent="conf_tech" action="base.action_model_model" sequence="10"/> id="conf_tech"
<menuitem id="view" name="View" parent="conf_tech" action="base.action_ui_view" sequence="20" /> parent="base.menu_administration"
<menuitem id="rec_rule" name="Record Rule" parent="conf_tech" action="base.action_rule" sequence="30" /> name="🧰"
<menuitem id="menu" name="Menu" parent="conf_tech" action="base.grant_menu_access" sequence="100" /> groups="base.group_erp_manager"
<menuitem id="seq" name="Sequence" parent="conf_tech" action="base.ir_sequence_form" sequence="100" /> sequence="100"
<menuitem id="model_data" name="Model Data" parent="conf_tech" action="base.action_model_data" sequence="100" /> />
<menuitem id="param" name="Param" parent="conf_tech" action="base.ir_config_list_action" sequence="100" /> <menuitem
<menuitem id="cron" name="Cron" parent="conf_tech" action="base.ir_cron_act" sequence="100" /> id="model"
<menuitem id="window" name="Act Window" parent="conf_tech" action="base.ir_action_window" sequence="100" /> name="Model"
<menuitem id="server" name="Act Server" parent="conf_tech" action="base.action_server_action" sequence="100" /> parent="conf_tech"
<menuitem id="report" name="Report" parent="conf_tech" action="base.ir_action_report" sequence="100" /> action="base.action_model_model"
<menuitem id="mail_tmpl" name="Mail Tmpl" parent="conf_tech" action="mail.action_email_template_tree_all" sequence="100" /> sequence="10"
<menuitem id="property" name="Property" parent="conf_tech" action="base.ir_property_form" sequence="100" /> />
<menuitem
id="view"
name="View"
parent="conf_tech"
action="base.action_ui_view"
sequence="20"
/>
<menuitem
id="rec_rule"
name="Record Rule"
parent="conf_tech"
action="base.action_rule"
sequence="30"
/>
<menuitem
id="menu"
name="Menu"
parent="conf_tech"
action="base.grant_menu_access"
sequence="100"
/>
<menuitem
id="seq"
name="Sequence"
parent="conf_tech"
action="base.ir_sequence_form"
sequence="100"
/>
<menuitem
id="model_data"
name="Model Data"
parent="conf_tech"
action="base.action_model_data"
sequence="100"
/>
<menuitem
id="param"
name="Param"
parent="conf_tech"
action="base.ir_config_list_action"
sequence="100"
/>
<menuitem
id="cron"
name="Cron"
parent="conf_tech"
action="base.ir_cron_act"
sequence="100"
/>
<menuitem
id="window"
name="Act Window"
parent="conf_tech"
action="base.ir_action_window"
sequence="100"
/>
<menuitem
id="server"
name="Act Server"
parent="conf_tech"
action="base.action_server_action"
sequence="100"
/>
<menuitem
id="report"
name="Report"
parent="conf_tech"
action="base.ir_action_report"
sequence="100"
/>
<menuitem
id="mail_tmpl"
name="Mail Tmpl"
parent="conf_tech"
action="mail.action_email_template_tree_all"
sequence="100"
/>
<menuitem
id="property"
name="Property"
parent="conf_tech"
action="base.ir_property_form"
sequence="100"
/>
</odoo> </odoo>

View File

@@ -2,12 +2,12 @@
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
{ {
'name': 'Eradicate Quick Create', "name": "Eradicate Quick Create",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Tools', "category": "Tools",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Disable quick create on all objects', "summary": "Disable quick create on all objects",
'description': """ "description": """
Eradicate Quick Create Eradicate Quick Create
====================== ======================
@@ -17,9 +17,9 @@ This module uses the module *web_m2x_options* from the OCA *web* project (in v10
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>. This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['web_m2x_options'], "depends": ["web_m2x_options"],
'post_init_hook': 'web_m2x_options_create', "post_init_hook": "web_m2x_options_create",
'installable': True, "installable": True,
} }

View File

@@ -8,12 +8,15 @@ from odoo.api import Environment
def web_m2x_options_create(cr, registry): def web_m2x_options_create(cr, registry):
env = Environment(cr, SUPERUSER_ID, {}) env = Environment(cr, SUPERUSER_ID, {})
config_parameter = env['ir.config_parameter'].search( config_parameter = env["ir.config_parameter"].search(
[('key', '=', 'web_m2x_options.create')]) [("key", "=", "web_m2x_options.create")]
if config_parameter and config_parameter.value != 'False': )
config_parameter.write({'value': 'False'}) if config_parameter and config_parameter.value != "False":
config_parameter.write({"value": "False"})
else: else:
env['ir.config_parameter'].create({ env["ir.config_parameter"].create(
'key': 'web_m2x_options.create', {
'value': 'False', "key": "web_m2x_options.create",
}) "value": "False",
}
)

View File

@@ -3,18 +3,18 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'HR Contract Usability', "name": "HR Contract Usability",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Human Resources/Contracts', "category": "Human Resources/Contracts",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Usability improvements on HR Contract module', "summary": "Usability improvements on HR Contract module",
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': [ "depends": [
'hr_contract', "hr_contract",
], ],
'data': [ "data": [
'views/hr_payroll_structure_type.xml', "views/hr_payroll_structure_type.xml",
], ],
'installable': True, "installable": True,
} }

View File

@@ -4,7 +4,8 @@
from odoo import fields, models from odoo import fields, models
class HrPayrollStructureType(models.Model): class HrPayrollStructureType(models.Model):
_inherit = 'hr.payroll.structure.type' _inherit = "hr.payroll.structure.type"
active = fields.Boolean(default=True) active = fields.Boolean(default=True)

View File

@@ -1,22 +1,26 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2021 Akretion France (http://www.akretion.com/) Copyright 2021 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="hr_payroll_structure_type_form" model="ir.ui.view"> <record id="hr_payroll_structure_type_form" model="ir.ui.view">
<field name="model">hr.payroll.structure.type</field> <field name="model">hr.payroll.structure.type</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/> <widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<group name="main"> <group name="main">
<field name="name"/> <field name="name" />
<field name="default_resource_calendar_id"/> <field name="default_resource_calendar_id" />
<field name="active" invisible="1"/> <field name="active" invisible="1" />
<field name="country_id"/> <field name="country_id" />
</group> </group>
</form> </form>
</field> </field>
@@ -26,9 +30,9 @@
<field name="model">hr.payroll.structure.type</field> <field name="model">hr.payroll.structure.type</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="name"/> <field name="name" />
<field name="default_resource_calendar_id" optional="show"/> <field name="default_resource_calendar_id" optional="show" />
<field name="country_id"/> <field name="country_id" />
</tree> </tree>
</field> </field>
</record> </record>
@@ -37,11 +41,19 @@
<field name="model">hr.payroll.structure.type</field> <field name="model">hr.payroll.structure.type</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name"/> <field name="name" />
<separator/> <separator />
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/> <filter
string="Archived"
name="inactive"
domain="[('active', '=', False)]"
/>
<group name="groupby"> <group name="groupby">
<filter name="country_groupby" string="Country" context="{'group_by': 'country_id'}"/> <filter
name="country_groupby"
string="Country"
context="{'group_by': 'country_id'}"
/>
</group> </group>
</search> </search>
</field> </field>
@@ -57,7 +69,8 @@
id="hr_payroll_structure_type_menu" id="hr_payroll_structure_type_menu"
action="hr_payroll_structure_type_action" action="hr_payroll_structure_type_action"
parent="hr_contract.menu_human_resources_configuration_contract" parent="hr_contract.menu_human_resources_configuration_contract"
sequence="10"/> sequence="10"
/>
</odoo> </odoo>

View File

@@ -1,4 +1,2 @@
# -*- coding: utf-8 -*-
from . import intrastat_product_type from . import intrastat_product_type
from .post_install import set_intrastat_type_on_products from .post_install import set_intrastat_type_on_products

View File

@@ -3,12 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Intrastat Product Type', "name": "Intrastat Product Type",
'version': '12.0.1.0.0', "version": "12.0.1.0.0",
'category': 'Accounting', "category": "Accounting",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Adds a special field Intrastat Type on Products', "summary": "Adds a special field Intrastat Type on Products",
'description': """ "description": """
Intrastat Product Type Intrastat Product Type
====================== ======================
@@ -18,10 +18,10 @@ This module adds a field *Intrastat Type* on the Product Form with 2 possible op
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>. This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['intrastat_product', 'l10n_fr_intrastat_service'], "depends": ["intrastat_product", "l10n_fr_intrastat_service"],
'data': ['product_view.xml'], "data": ["product_view.xml"],
'post_init_hook': 'set_intrastat_type_on_products', "post_init_hook": "set_intrastat_type_on_products",
'installable': False, "installable": False,
} }

View File

@@ -1,62 +1,72 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2019 Akretion (http://www.akretion.com) # Copyright 2016-2019 Akretion (http://www.akretion.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# @author Alexis de Lattre <alexis.delattre@akretion.com> # @author Alexis de Lattre <alexis.delattre@akretion.com>
from odoo import models, fields, api, _ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class ProductTemplate(models.Model): class ProductTemplate(models.Model):
_inherit = 'product.template' _inherit = "product.template"
intrastat_type = fields.Selection([ intrastat_type = fields.Selection(
('product', 'Product'), [
('service', 'Service'), ("product", "Product"),
], string='Intrastat Type', default='product', required=True, ("service", "Service"),
],
string="Intrastat Type",
default="product",
required=True,
help="Type of product used for the intrastat declarations. " help="Type of product used for the intrastat declarations. "
"For example, you can configure a product with " "For example, you can configure a product with "
"'Product Type' = 'Consumable' and 'Intrastat Type' = 'Service'.") "'Product Type' = 'Consumable' and 'Intrastat Type' = 'Service'.",
)
@api.multi @api.multi
@api.constrains('type', 'intrastat_type') @api.constrains("type", "intrastat_type")
def check_intrastat_type(self): def check_intrastat_type(self):
for pt in self: for pt in self:
if pt.intrastat_type == 'product' and pt.type == 'service': if pt.intrastat_type == "product" and pt.type == "service":
raise ValidationError(_( raise ValidationError(
_(
"On the product %s, you cannot set Product Type to " "On the product %s, you cannot set Product Type to "
"'Service' and Intrastat Type to 'Product'.") % pt.name) "'Service' and Intrastat Type to 'Product'."
if pt.intrastat_type == 'service' and pt.type == 'product': )
raise ValidationError(_( % pt.name
)
if pt.intrastat_type == "service" and pt.type == "product":
raise ValidationError(
_(
"On the product %s, you cannot set Intrastat Type to " "On the product %s, you cannot set Intrastat Type to "
"'Service' and Product Type to 'Stockable product' " "'Service' and Product Type to 'Stockable product' "
"(but you can set Product Type to 'Consumable' or " "(but you can set Product Type to 'Consumable' or "
"'Service').") % pt.name) "'Service')."
)
% pt.name
)
@api.onchange('type') @api.onchange("type")
def intrastat_type_onchange(self): def intrastat_type_onchange(self):
if self.type in ('product', 'consu'): if self.type in ("product", "consu"):
self.intrastat_type = 'product' self.intrastat_type = "product"
elif self.type == 'service': elif self.type == "service":
self.intrastat_type = 'service' self.intrastat_type = "service"
@api.model @api.model
def create(self, vals): def create(self, vals):
if vals.get('type'): if vals.get("type"):
if not vals.get('intrastat_type'): if not vals.get("intrastat_type"):
if vals['type'] in ('product', 'consu'): if vals["type"] in ("product", "consu"):
vals['intrastat_type'] = 'product' vals["intrastat_type"] = "product"
elif vals['type'] == 'service': elif vals["type"] == "service":
vals['intrastat_type'] = 'service' vals["intrastat_type"] = "service"
elif ( elif vals.get("intrastat_type") == "product" and vals["type"] == "service":
vals.get('intrastat_type') == 'product' and
vals['type'] == 'service'):
# usefull because intrastat_type = 'product' by default and # usefull because intrastat_type = 'product' by default and
# wizards in other modules that don't depend on this module # wizards in other modules that don't depend on this module
# (e.g. sale_rental) may create a product with only # (e.g. sale_rental) may create a product with only
# {'type': 'service'} and no 'intrastat_type' # {'type': 'service'} and no 'intrastat_type'
vals['intrastat_type'] = 'service' vals["intrastat_type"] = "service"
return super(ProductTemplate, self).create(vals) return super(ProductTemplate, self).create(vals)
@@ -64,19 +74,20 @@ class L10nFrIntrastatServiceDeclaration(models.Model):
_inherit = "l10n.fr.intrastat.service.declaration" _inherit = "l10n.fr.intrastat.service.declaration"
def _is_service(self, invoice_line): def _is_service(self, invoice_line):
if invoice_line.product_id.intrastat_type == 'service': if invoice_line.product_id.intrastat_type == "service":
return True return True
else: else:
return False return False
class IntrastatProductDeclaration(models.Model): class IntrastatProductDeclaration(models.Model):
_inherit = 'intrastat.product.declaration' _inherit = "intrastat.product.declaration"
def _is_product(self, invoice_line): def _is_product(self, invoice_line):
if ( if (
invoice_line.product_id and invoice_line.product_id
invoice_line.product_id.intrastat_type == 'product'): and invoice_line.product_id.intrastat_type == "product"
):
return True return True
else: else:
return False return False

View File

@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2019 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) # Copyright 2016-2019 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
def set_intrastat_type_on_products(cr, registry): def set_intrastat_type_on_products(cr, registry):
cr.execute( cr.execute(
"UPDATE product_template SET intrastat_type='service' " "UPDATE product_template SET intrastat_type='service' " "WHERE type='service'"
"WHERE type='service'") )
return return

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2016-2019 Akretion (http://www.akretion.com/) Copyright 2016-2019 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com> @author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
@@ -14,7 +13,7 @@
<field name="inherit_id" ref="product.product_template_form_view" /> <field name="inherit_id" ref="product.product_template_form_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="type" position="after"> <field name="type" position="after">
<field name="intrastat_type"/> <field name="intrastat_type" />
</field> </field>
</field> </field>
</record> </record>

View File

@@ -3,12 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Link Tracker Usability', "name": "Link Tracker Usability",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Marketing', "category": "Marketing",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Improve usability for link tracker', "summary": "Improve usability for link tracker",
'description': """ "description": """
Link Tracker Usability Link Tracker Usability
====================== ======================
@@ -17,11 +17,11 @@ Several small usability improvements.
This module has been written by Alexis de Lattre from Akretion This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>. <alexis.delattre@akretion.com>.
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['link_tracker'], "depends": ["link_tracker"],
'data': [ "data": [
'views/link_tracker_click.xml', "views/link_tracker_click.xml",
], ],
'installable': True, "installable": True,
} }

View File

@@ -1,20 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- <!--
Copyright 2019-2021 Akretion France (http://www.akretion.com/) Copyright 2019-2021 Akretion France (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com> @author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
--> -->
<odoo> <odoo>
<record id="link_tracker_click_view_tree" model="ir.ui.view"> <record id="link_tracker_click_view_tree" model="ir.ui.view">
<field name="name">usability.link.tracker.click.tree</field> <field name="name">usability.link.tracker.click.tree</field>
<field name="model">link.tracker.click</field> <field name="model">link.tracker.click</field>
<field name="inherit_id" ref="link_tracker.link_tracker_click_view_tree"/> <field name="inherit_id" ref="link_tracker.link_tracker_click_view_tree" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="country_id" position="after"> <field name="country_id" position="after">
<field name="create_date" string="Click Date"/> <field name="create_date" string="Click Date" />
</field> </field>
</field> </field>
</record> </record>
@@ -22,10 +21,10 @@
<record id="link_tracker_click_view_form" model="ir.ui.view"> <record id="link_tracker_click_view_form" model="ir.ui.view">
<field name="name">usability.link.tracker.click.form</field> <field name="name">usability.link.tracker.click.form</field>
<field name="model">link.tracker.click</field> <field name="model">link.tracker.click</field>
<field name="inherit_id" ref="link_tracker.link_tracker_click_view_form"/> <field name="inherit_id" ref="link_tracker.link_tracker_click_view_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="country_id" position="after"> <field name="country_id" position="after">
<field name="create_date" string="Click Date"/> <field name="create_date" string="Click Date" />
</field> </field>
</field> </field>
</record> </record>
@@ -33,10 +32,14 @@
<record id="link_tracker_click_view_search" model="ir.ui.view"> <record id="link_tracker_click_view_search" model="ir.ui.view">
<field name="name">usability.link.tracker.click.search</field> <field name="name">usability.link.tracker.click.search</field>
<field name="model">link.tracker.click</field> <field name="model">link.tracker.click</field>
<field name="inherit_id" ref="link_tracker.link_tracker_click_view_search"/> <field name="inherit_id" ref="link_tracker.link_tracker_click_view_search" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<filter name="groupby_link_id" position="before"> <filter name="groupby_link_id" position="before">
<filter name="create_date_groupby" string="Click Date" context="{'group_by': 'create_date'}"/> <filter
name="create_date_groupby"
string="Click Date"
context="{'group_by': 'create_date'}"
/>
</filter> </filter>
</field> </field>
</record> </record>

View File

@@ -4,12 +4,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'Mail Usability', "name": "Mail Usability",
'version': '14.0.1.0.0', "version": "14.0.1.0.0",
'category': 'Productivity/Discuss', "category": "Productivity/Discuss",
'license': 'AGPL-3', "license": "AGPL-3",
'summary': 'Usability improvements on mails', "summary": "Usability improvements on mails",
'description': """ "description": """
Mail Usability Mail Usability
============== ==============
@@ -19,14 +19,14 @@ Small usability improvements on mails:
* remove 'sent by' in notification footer (TODO mig v14) * remove 'sent by' in notification footer (TODO mig v14)
""", """,
'author': 'Akretion', "author": "Akretion",
'website': 'http://www.akretion.com', "website": "https://github.com/OCA/odoo-usability",
'depends': ['mail'], "depends": ["mail"],
'data': [ "data": [
#'views/mail_view.xml', #'views/mail_view.xml',
#'data/mail_data.xml', #'data/mail_data.xml',
#'wizard/email_template_preview_view.xml', #'wizard/email_template_preview_view.xml',
#'wizard/mail_compose_message_view.xml', #'wizard/mail_compose_message_view.xml',
], ],
'installable': True, "installable": True,
} }

View File

@@ -6,6 +6,6 @@ from odoo import fields, models
class MailTemplate(models.Model): class MailTemplate(models.Model):
_inherit = 'mail.template' _inherit = "mail.template"
auto_delete = fields.Boolean(default=False) auto_delete = fields.Boolean(default=False)

View File

@@ -2,11 +2,11 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields from odoo import fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = "res.partner"
# tracking=True is handled in the 'mail' module, so it's better # tracking=True is handled in the 'mail' module, so it's better
# to have this in mail_usability than in base_usability # to have this in mail_usability than in base_usability

Some files were not shown because too many files have changed in this diff Show More