Compare commits

..

3 Commits

Author SHA1 Message Date
Pierrick Brun
08cb6f2e86 [FIX] only show analytics to users in group 2018-10-17 10:59:34 +02:00
mpanarin
d01820cd39 [FIX] not being able to change analytic account and tags 2018-08-13 15:34:39 +03:00
Yannick Vaucher
71ecc03001 Allow to change analytic fields with account_invoice_update 2018-07-02 12:42:26 +02:00
56 changed files with 365 additions and 793 deletions

View File

@@ -12,21 +12,17 @@
<field name="model">account.invoice.line</field>
<field name="inherit_id" ref="account.view_invoice_line_form"/>
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="discount" position="after">
<field name="standard_price_company_currency"
groups="base.group_no_one"/>
groups="account.group_account_user"/>
<field name="standard_price_invoice_currency"
widget="monetary"
options="{'currency_field': 'currency_id'}"
groups="base.group_no_one"/>
groups="account.group_account_user"/>
<field name="margin_invoice_currency"
groups="base.group_no_one"/>
groups="account.group_account_user"/>
<field name="margin_company_currency"
groups="base.group_no_one"/>
<label for="margin_rate" groups="base.group_no_one"/>
<div name="margin_rate" groups="base.group_no_one">
<field name="margin_rate" class="oe_inline"/> %
</div>
groups="account.group_account_user"/>
</field>
</field>
</record>
@@ -38,10 +34,18 @@
<field name="arch" type="xml">
<field name="move_id" position="after">
<field name="margin_invoice_currency"
string="Margin" groups="base.group_no_one"/>
string="Margin" groups="account.group_account_user"/>
<field name="margin_company_currency"
groups="base.group_no_one"/>
groups="account.group_account_user"/>
</field>
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='price_subtotal']" position="after">
<field name="standard_price_invoice_currency" groups="base.group_no_one" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="standard_price_company_currency" groups="base.group_no_one" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
<field name="margin_invoice_currency" groups="base.group_no_one"/>
<field name="margin_company_currency" groups="base.group_no_one"/>
<field name="margin_rate" groups="base.group_no_one"/>
<field name="company_currency_id" invisible="1"/>
</xpath>
</field>
</record>

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# Copyright 2018 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
@@ -21,6 +22,8 @@ This module adds a button *Update Invoice* on Customer and Supplier invoices in
* Salesman
* Notes
* Description of invoice lines
* Analytic account
* Analytic tags
""",
'author': 'Akretion',

View File

@@ -0,0 +1 @@
from . import test_account_invoice_update_wizard

View File

@@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
class TestAccountInvoiceUpdateWizard(TransactionCase):
def setUp(self):
super(TestAccountInvoiceUpdateWizard, self).setUp()
self.customer12 = self.env.ref('base.res_partner_12')
self.product16 = self.env.ref('product.product_product_16')
self.product24 = self.env.ref('product.product_product_24')
uom_unit = self.env.ref('product.product_uom_categ_unit')
self.invoice1 = self.env['account.invoice'].create({
'name': 'Test invoice',
'partner_id': self.customer12.id,
})
self.inv_line1 = self.env['account.invoice.line'].create({
'invoice_id': self.invoice1.id,
'name': "Line1",
'product_id': self.product16.id,
'product_uom_id': uom_unit.id,
'account_id': self.invoice1.account_id.id,
'price_unit': 42.0,
})
self.inv_line2 = self.env['account.invoice.line'].create({
'invoice_id': self.invoice1.id,
'name': "Line2",
'product_id': self.product24.id,
'product_uom_id': uom_unit.id,
'account_id': self.invoice1.account_id.id,
'price_unit': 1111.1,
})
self.aa1 = self.env.ref('analytic.analytic_partners_camp_to_camp')
self.aa2 = self.env.ref('analytic.analytic_nebula')
self.atag1 = self.env.ref('analytic.tag_contract')
self.atag2 = self.env['account.analytic.tag'].create({
'name': u'',
})
def create_wizard(self):
UpdateWizard = self.env['account.invoice.update'].with_context(
active_model='account.invoice',
active_id=self.invoice1.id)
self.wiz = UpdateWizard.create({})
def test_add_analytic_account_line1(self):
""" Add analytic account on an invoice line
after the invoice has been approved.
This will:
- update the move line
- create a new analytic line.
"""
self.invoice1.action_invoice_open()
self.create_wizard()
wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1)
wiz_line.account_analytic_id = self.aa1
self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16)
self.assertEqual(related_ml.analytic_account_id, self.aa1)
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
def test_change_analytic_account_line1(self):
""" Change analytic account on an invoice line
after the invoice has been approved.
This will:
- update the move line
- update the existing analytic line."""
self.inv_line1.account_analytic_id = self.aa2
self.invoice1.action_invoice_open()
self.create_wizard()
wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1)
wiz_line.account_analytic_id = self.aa1
self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16)
self.assertEqual(related_ml.analytic_account_id, self.aa1)
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
def test_error_grouped_move_lines(self):
""" Change analytic account on an invoice line
after the invoice has been approved where both
lines were grouped in the same move line.
This will raise an error.
"""
self.invoice1.journal_id.group_invoice_lines = True
self.inv_line2.product_id = self.product16
self.inv_line2.unit_price = 42.0
self.invoice1.action_invoice_open()
self.create_wizard()
line1 = self.wiz.line_ids[0]
line1.account_analytic_id = self.aa1
with self.assertRaises(UserError):
self.wiz.run()
def test_add_analytic_tags_line1(self):
""" Add analytic tags on an invoice line
after the invoice has been approved.
This will update move line.
"""
self.invoice1.action_invoice_open()
self.create_wizard()
wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1)
wiz_line.analytic_tag_ids = self.atag2
self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16)
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
self.assertFalse(related_ml.analytic_line_ids)
def test_change_analytic_tags_line1(self):
""" Change analytic tags on an invoice line
after the invoice has been approved.
It will update move line and analytic line
"""
self.inv_line1.account_analytic_id = self.aa2
self.inv_line1.analytic_tag_ids = self.atag1
self.invoice1.action_invoice_open()
self.create_wizard()
wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1)
wiz_line.analytic_tag_ids = self.atag2
self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16)
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
def test_add_analytic_info_line1(self):
""" Add analytic account and tags on an invoice line
after the invoice has been approved.
This will:
- update move line
- create an analytic line
"""
self.invoice1.action_invoice_open()
self.create_wizard()
wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1)
wiz_line.account_analytic_id = self.aa1
wiz_line.analytic_tag_ids = self.atag2
self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16)
self.assertEqual(related_ml.analytic_account_id, self.aa1)
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.tag_ids, self.atag2)
def test_empty_analytic_account_line1(self):
""" Remove analytic account
after the invoice has been approved.
This will raise an error as it is not implemented.
"""
self.inv_line1.account_analytic_id = self.aa2
self.invoice1.action_invoice_open()
self.create_wizard()
wiz_line = self.wiz.line_ids.filtered(
lambda rec: rec.invoice_line_id == self.inv_line1)
wiz_line.account_analytic_id = False
self.wiz.run()
related_ml = self.invoice1.move_id.line_ids.filtered(
lambda rec: rec.product_id == self.product16)
self.assertFalse(related_ml.analytic_account_id)
self.assertFalse(related_ml.analytic_line_ids)

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# Copyright 2018 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
@@ -48,11 +49,15 @@ class AccountInvoiceUpdate(models.TransientModel):
for m2ofield in self._m2o_fields2update():
res[m2ofield] = invoice[m2ofield].id or False
for line in invoice.invoice_line_ids:
aa_tags = line.analytic_tag_ids
aa_tags = [(6, 0, aa_tags.ids)] if aa_tags else False
res['line_ids'].append([0, 0, {
'invoice_line_id': line.id,
'name': line.name,
'quantity': line.quantity,
'price_subtotal': line.price_subtotal,
'account_analytic_id': line.account_analytic_id.id,
'analytic_tag_ids': aa_tags,
}])
return res
@@ -93,11 +98,30 @@ class AccountInvoiceUpdate(models.TransientModel):
vals['date_due'] = max(line[0] for line in pterm_list)
return vals
@api.model
def _line_simple_fields2update(self):
return ["name",]
@api.model
def _line_m2o_fields2update(self):
return ["account_analytic_id",]
@api.model
def _line_m2m_fields2update(self):
return ["analytic_tag_ids",]
@api.model
def _prepare_invoice_line(self, line):
vals = {}
if line.name != line.invoice_line_id.name:
vals['name'] = line.name
for field in self._line_simple_fields2update():
if line[field] != line.invoice_line_id[field]:
vals[field] = line[field]
for field in self._line_m2o_fields2update():
if line[field] != line.invoice_line_id[field]:
vals[field] = line[field].id
for field in self._line_m2m_fields2update():
if line[field] != line.invoice_line_id[field]:
vals[field] = [(6, 0, line[field].ids)]
return vals
@api.multi
@@ -110,6 +134,57 @@ class AccountInvoiceUpdate(models.TransientModel):
mvals['ref'] = ref
return mvals
@api.multi
def _get_matching_inv_line(self, move_line):
""" Find matching invoice line by product """
# TODO make it accept more case as lines won't
# be grouped unless journal.group_invoice_line is True
inv_line = self.invoice_id.invoice_line_ids.filtered(
lambda rec: rec.product_id == move_line.product_id)
if len(inv_line) <> 1:
raise UserError(
"Cannot match a single invoice line to move line %s" %
move_line.name)
return inv_line
@api.multi
def _prepare_move_line(self, inv_line):
mlvals = {}
inv_line_upd = self.line_ids.filtered(
lambda rec: rec.invoice_line_id == inv_line)
ini_aa = inv_line.account_analytic_id
new_aa = inv_line_upd.account_analytic_id
if ini_aa != new_aa:
mlvals['analytic_account_id'] = new_aa.id
ini_aa_tags = inv_line.analytic_tag_ids
new_aa_tags = inv_line_upd.analytic_tag_ids
if ini_aa_tags != new_aa_tags:
mlvals['analytic_tag_ids'] = [(6, None, new_aa_tags.ids)]
return mlvals
@api.multi
def _prepare_analytic_line(self, inv_line):
alvals = {}
inv_line_upd = self.line_ids.filtered(
lambda rec: rec.invoice_line_id == inv_line)
ini_aa = inv_line.account_analytic_id
new_aa = inv_line_upd.account_analytic_id
if ini_aa != new_aa:
alvals['account_id'] = new_aa.id
ini_aa_tags = inv_line.analytic_tag_ids
new_aa_tags = inv_line_upd.analytic_tag_ids
if ini_aa_tags != new_aa_tags:
alvals['tag_ids'] = [(6, None, new_aa_tags.ids)]
return alvals
@api.multi
def _update_payment_term_move(self):
self.ensure_one()
@@ -169,15 +244,43 @@ class AccountInvoiceUpdate(models.TransientModel):
if ivals:
updated = True
inv.write(ivals)
if inv.move_id:
mvals = self._prepare_move()
if mvals:
inv.move_id.write(mvals)
for ml in inv.move_id.line_ids.filtered(
# we are only interested in invoice lines, not tax lines
lambda rec: bool(rec.product_id)
):
if ml.credit == 0.0:
continue
inv_line = self._get_matching_inv_line(ml)
mlvals = self._prepare_move_line(inv_line)
if mlvals:
updated = True
ml.write(mlvals)
aalines = ml.analytic_line_ids
alvals = self._prepare_analytic_line(inv_line)
if aalines and alvals:
updated = True
if ('account_id' in alvals and
alvals['account_id'] is False):
former_aa = inv_line.account_analytic_id
to_remove_aalines = aalines.filtered(
lambda rec: rec.account_id == former_aa)
# remove existing analytic line
to_remove_aalines.unlink()
else:
aalines.write(alvals)
elif 'account_id' in alvals:
# Create analytic lines if analytic account
# is added later
ml.create_analytic_lines()
for line in self.line_ids:
ilvals = self._prepare_invoice_line(line)
if ilvals:
updated = True
line.invoice_line_id.write(ilvals)
if inv.move_id:
mvals = self._prepare_move()
if mvals:
inv.move_id.write(mvals)
if updated:
inv.message_post(_(
'Non-legal fields of invoice updated via the Invoice Update '
@@ -199,3 +302,7 @@ class AccountInvoiceLineUpdate(models.TransientModel):
readonly=True)
price_subtotal = fields.Float(
string='Amount', readonly=True, digits=dp.get_precision('Account'))
account_analytic_id = fields.Many2one(
'account.analytic.account', string='Analytic Account')
analytic_tag_ids = fields.Many2many(
'account.analytic.tag', string='Analytic Tags')

View File

@@ -30,7 +30,9 @@
<field name="name"/>
<field name="quantity"/>
<field name="price_subtotal"/>
</tree>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
<field name="analytic_tag_ids" groups="analytic.group_analytic_accounting" widget="many2many_tags"/>
</tree>
</field>
</group>
<footer>

View File

@@ -18,7 +18,7 @@ class AccountMoveLineFilterWizard(models.TransientModel):
account_reconcile = fields.Boolean(
related='account_id.reconcile', readonly=True)
reconcile = fields.Selection([
('unreconciled', 'Unreconciled or Partially Reconciled'),
('unreconciled', 'Unreconciled'),
('reconciled', 'Fully Reconciled'),
# ('partial_reconciled', 'Partially Reconciled'),
], string='Reconciliation Filter')

View File

@@ -114,7 +114,7 @@ class AccountInvoiceLine(models.Model):
related='invoice_id.date_invoice', store=True, readonly=True)
commercial_partner_id = fields.Many2one(
related='invoice_id.partner_id.commercial_partner_id',
store=True, readonly=True, compute_sudo=True)
store=True, readonly=True)
state = fields.Selection(
related='invoice_id.state', store=True, readonly=True,
string='Invoice State')
@@ -225,7 +225,7 @@ class AccountAccount(models.Model):
journal_accounts_bank_type += account
accounts = aao.search([
('user_type_id', '=', bank_type.id)], order='company_id, code')
for account in accounts:
for account in aao.search([('user_type_id', '=', bank_type.id)]):
if account not in journal_accounts_bank_type:
account.user_type_id = asset_type.id
logger.info(
@@ -243,7 +243,9 @@ class AccountAnalyticAccount(models.Model):
if self._context.get('analytic_account_show_code_only'):
res = []
for record in self:
res.append((record.id, record.code or record.name))
res.append((
record.id,
record.code or record._get_one_full_name(record)))
return res
else:
return super(AccountAnalyticAccount, self).name_get()
@@ -263,7 +265,6 @@ class AccountMove(models.Model):
# By default, we can still modify "ref" when account move is posted
# which seems a bit lazy for me...
ref = fields.Char(states={'posted': [('readonly', True)]})
date = fields.Date(copy=False)
class AccountMoveLine(models.Model):
@@ -277,7 +278,6 @@ class AccountMoveLine(models.Model):
# Update field only to add a string (there is no string in account module)
invoice_id = fields.Many2one(string='Invoice')
date_maturity = fields.Date(copy=False)
@api.onchange('credit')
def _credit_onchange(self):

View File

@@ -19,9 +19,6 @@
<field name="invoice_line_ids" position="before">
<button name="delete_lines_qty_zero" states="draft" string="⇒ Delete lines qty=0" type="object" class="oe_link oe_right" groups="account.group_account_invoice"/>
</field>
<xpath expr="//field[@name='tax_line_ids']/tree/field[@name='amount']" position="before">
<field name="base" readonly="1"/>
</xpath>
</field>
</record>
@@ -38,9 +35,6 @@
<field name="move_id" position="before">
<field name="sent"/>
</field>
<xpath expr="//field[@name='tax_line_ids']/tree/field[@name='amount']" position="before">
<field name="base" readonly="1"/>
</xpath>
</field>
</record>
@@ -247,17 +241,6 @@ module -->
</field>
</record>
<record id="view_account_payment_form" model="ir.ui.view">
<field name="name">usability.account.payment.form</field>
<field name="model">account.payment</field>
<field name="inherit_id" ref="account.view_account_payment_form"/>
<field name="arch" type="xml">
<field name="communication" position="after">
<field name="payment_reference"/>
</field>
</field>
</record>
<!-- model account.move.line / Journal Items -->
<record id="account.action_account_moves_all_a" model="ir.actions.act_window">
<field name="limit">200</field>
@@ -316,9 +299,6 @@ module -->
<field name="name" position="attributes">
<attribute name="string">Name or Reference</attribute>
</field>
<field name="partner_id" position="attributes">
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
</field>
</field>
</record>
@@ -352,17 +332,6 @@ module -->
</field>
</record>
<record id="view_account_move_filter" model="ir.ui.view">
<field name="name">account_usability.account_move_search</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_move_filter"/>
<field name="arch" type="xml">
<field name="partner_id" position="attributes">
<attribute name="domain">['|', ('parent_id', '=', False), ('is_company', '=', True)]</attribute>
</field>
</field>
</record>
<record id="view_account_search" model="ir.ui.view">
<field name="name">account.account.search</field>
<field name="model">account.account</field>

View File

@@ -1,33 +0,0 @@
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index b1d8012329d..b8a8e2a673d 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -210,6 +210,7 @@ class account_payment(models.Model):
payment_difference = fields.Monetary(compute='_compute_payment_difference', readonly=True)
payment_difference_handling = fields.Selection([('open', 'Keep open'), ('reconcile', 'Mark invoice as fully paid')], default='open', string="Payment Difference", copy=False)
writeoff_account_id = fields.Many2one('account.account', string="Difference Account", domain=[('deprecated', '=', False)], copy=False)
+ writeoff_analytic_account_id = fields.Many2one('account.analytic.account', string="Difference Analytic Account", copy=False)
# FIXME: ondelete='restrict' not working (eg. cancel a bank statement reconciliation with a payment)
move_line_ids = fields.One2many('account.move.line', 'payment_id', readonly=True, copy=False, ondelete='restrict')
@@ -431,6 +432,7 @@ class account_payment(models.Model):
amount_currency_wo = -abs(amount_currency_wo)
writeoff_line['name'] = _('Counterpart')
writeoff_line['account_id'] = self.writeoff_account_id.id
+ writeoff_line['analytic_account_id'] = self.writeoff_analytic_account_id.id or False
writeoff_line['debit'] = debit_wo
writeoff_line['credit'] = credit_wo
writeoff_line['amount_currency'] = amount_currency_wo
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 2460458fbaa..4065d8f9952 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -206,6 +206,8 @@
</div>
<field name="writeoff_account_id" string="Post Difference In"
attrs="{'invisible': [('payment_difference_handling','=','open')], 'required': [('payment_difference_handling', '=', 'reconcile')]}"/>
+ <field name="writeoff_analytic_account_id" string="Post Difference In Analytic Account"
+ attrs="{'invisible': [('payment_difference_handling','=','open')]}"/>
</group>
</group>
</sheet>

View File

@@ -31,7 +31,6 @@ A group by 'State' is added to module search view.
'security/group.xml',
'security/ir.model.access.csv',
'partner_view.xml',
'partner_bank_view.xml',
'users_view.xml',
'country_view.xml',
'module_view.xml',

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_partner_bank_tree" model="ir.ui.view">
<field name="name">base_usability.res.partner.bank.tree</field>
<field name="model">res.partner.bank</field>
<field name="inherit_id" ref="base.view_partner_bank_tree"/>
<field name="arch" type="xml">
<field name="sequence" position="attributes">
<attribute name="invisible">0</attribute>
<attribute name="widget">handle</attribute>
</field>
</field>
</record>
</odoo>

View File

@@ -19,10 +19,6 @@
<xpath expr="//field[@name='child_ids']/form//field[@name='email']" position="attributes">
<attribute name="widget">email</attribute>
</xpath>
<!-- Show title not only on Contacts -->
<xpath expr="//field[@name='child_ids']/form//field[@name='title']" position="attributes">
<attribute name="attrs"></attribute>
</xpath>
</field>
</record>

View File

@@ -22,7 +22,6 @@ def formatLang(
if (
'base.usability.installed' in env and
int_no_digits and
not monetary and
isinstance(value, float) and
dp):
prec = env['decimal.precision'].precision_get(dp)
@@ -34,5 +33,4 @@ def formatLang(
grouping=grouping, monetary=monetary, dp=dp, currency_obj=currency_obj)
return res
report_sxw.rml_parse.formatLang = formatLang

View File

@@ -1,37 +0,0 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
========================
User Authentication Logs
========================
This module adds user authentication logs in Odoo. It logs both authentication success and failures.
Usage
=====
The authentication logs can be seen:
* on the users's form view in the *Auth Logs* tab,
* in the menu *Settings > Technical > Security > Authentication Logs*.
Authentication failure logs are displayed in red. Authentication success logs are displayed in black.
To have read access to the logs, you need to be part of the *Access Rights* group.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/akretion/odoo-usability/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Alexis de Lattre <alexis.delattre@akretion.com>

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Akretion France
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Users authentification logs',
'version': '10.0.1.0.0',
'category': 'Tools',
'license': 'AGPL-3',
'summary': 'Adds users authentication logs',
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/res_users_auth_log.xml',
'views/res_users.xml',
'data/ir_cron.xml',
],
'installable': True,
}

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017-2018 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<record id="purge_auth_log_cron" model="ir.cron">
<field name="name">Purge old authentication logs</field>
<field name="active" eval="True"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">months</field>
<field name="numbercall">-1</field> <!-- don't limit the number of calls -->
<field name="doall" eval="False"/>
<field name="model" eval="'res.users.auth.log'"/>
<field name="function" eval="'_purge_old_auth_logs'" />
<field name="args" eval="'()'"/>
</record>
</odoo>

View File

@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
from . import res_users_auth_log
from . import res_users

View File

@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2017-2018 Akretion France
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, registry, SUPERUSER_ID
import logging
logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
auth_log_ids = fields.One2many(
'res.users.auth.log', 'user_id', string='Authentication Logs')
@classmethod
def _login(cls, db, login, password):
user_id = super(ResUsers, cls)._login(db, login, password)
with registry(db).cursor() as cr:
if user_id:
result = 'success'
user_log_id = user_id
else:
# To write a null value, psycopg2 wants None
user_log_id = None
result = 'failure'
cr.execute(
"SELECT id FROM res_users WHERE login=%s", (login, ))
user_select = cr.fetchall()
if user_select:
user_log_id = user_select[0][0]
cr.execute("""
INSERT INTO res_users_auth_log (
create_uid,
create_date,
date,
login,
result,
user_id
) VALUES (
%s, NOW() AT TIME ZONE 'UTC', NOW() AT TIME ZONE 'UTC',
%s, %s, %s)""", (SUPERUSER_ID, login, result, user_log_id))
logger.info('Auth log created for login %s type %s', login, result)
return user_id

View File

@@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from datetime import datetime, timedelta
import logging
logger = logging.getLogger(__name__)
class ResUsersAuthLog(models.Model):
_name = 'res.users.auth.log'
_description = 'Users Authentication Logs'
_order = 'date desc'
_rec_name = 'date'
user_id = fields.Many2one(
'res.users', string='User', ondelete='cascade', readonly=True)
login = fields.Char(string='Login', readonly=True)
date = fields.Datetime(
string='Authentication Date', required=True, readonly=True)
result = fields.Selection([
('success', 'Success'),
('failure', 'Failure'),
], string='Result', required=True, readonly=True)
@api.model
def create(self, vals):
if not self._context.get('authenticate_create'):
raise UserError(_(
"You cannot manually create an authentication log."))
return super(ResUsersAuthLog, self).create(vals)
@api.multi
def write(self, vals):
raise UserError(_("You cannot modify an authentication log."))
@api.model
def _purge_old_auth_logs(self):
expiry_date = datetime.today() - timedelta(days=365)
self._cr.execute(
"DELETE FROM res_users_auth_log WHERE date <= %s", (expiry_date, ))
logger.info('Auth logs older than %s have been purged', expiry_date)

View File

@@ -1,2 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_res_users_auth_log,Read access to Access rights group,model_res_users_auth_log,base.group_erp_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_res_users_auth_log Read access to Access rights group model_res_users_auth_log base.group_erp_manager 1 0 0 0

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017-2018 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="name">auth_logs.res.users.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Auth Logs" name="auth_logs">
<field name="auth_log_ids" nolabel="1"/>
</page>
</notebook>
</field>
</record>
</odoo>

View File

@@ -1,80 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017-2018 Akretion France
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="res_users_auth_log_form" model="ir.ui.view">
<field name="name">res.users.auth.logs.form</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<form string="Authentication Log">
<group name="main">
<field name="date"/>
<field name="user_id"/>
<field name="login"/>
<field name="result"/>
</group>
</form>
</field>
</record>
<record id="res_users_auth_log_tree" model="ir.ui.view">
<field name="name">res.users.auth.logs.tree</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<tree string="Authentication Logs" colors="red:result=='failure'">
<field name="date"/>
<field name="user_id" invisible="not context.get('auth_logs_main_view')"/>
<field name="login" invisible="not context.get('auth_logs_main_view')"/>
<field name="result"/>
</tree>
</field>
</record>
<record id="res_users_auth_log_search" model="ir.ui.view">
<field name="name">res.users.auth.logs.search</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<search string="Search Authentication Logs">
<field name="user_id"/>
<filter name="success" string="Success" domain="[('result', '=', 'success')]"/>
<filter name="failure" string="Failure" domain="[('result', '=', 'failure')]"/>
<group string="Group By" name="groupby">
<filter name="day_groupby" string="Day" context="{'group_by': 'date:day'}"/>
<filter name="week_groupby" string="Week" context="{'group_by': 'date:week'}"/>
<filter name="month_groupby" string="Month" context="{'group_by': 'date:month'}"/>
<filter name="user_groupby" string="User" context="{'group_by': 'user_id'}"/>
<filter name="result_groupby" string="Result" context="{'group_by': 'result'}"/>
</group>
</search>
</field>
</record>
<record id="res_users_auth_log_graph" model="ir.ui.view">
<field name="name">res.users.auth.logs.graph</field>
<field name="model">res.users.auth.log</field>
<field name="arch" type="xml">
<graph string="Analyze Authentication Logs" type="pivot">
<field name="date" type="row" interval="week"/>
<field name="user_id" type="col"/>
</graph>
</field>
</record>
<record id="res_users_auth_log_action" model="ir.actions.act_window">
<field name="name">Authentication Logs</field>
<field name="res_model">res.users.auth.log</field>
<field name="view_mode">tree,form,graph</field>
<field name="context">{'auth_logs_main_view': True}</field>
</record>
<menuitem id="res_users_auth_log_menu" action="res_users_auth_log_action"
parent="base.menu_security" sequence="100"/>
</odoo>

View File

@@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-
from . import stock

View File

@@ -24,7 +24,6 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
'data': [
'delivery_view.xml',
'sale_view.xml',
'stock_view.xml',
],
'installable': True,
}

View File

@@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class StockPicking(models.Model):
_inherit = 'stock.picking'
carrier_id = fields.Many2one(track_visibility='onchange')

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_picking_withcarrier_out_form" model="ir.ui.view">
<field name="name">delivery_usability.stock.picking.form</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="delivery.view_picking_withcarrier_out_form"/>
<field name="arch" type="xml">
<field name="carrier_id" position="attributes">
<!-- Sometimes we have to modify carrier_id when state is done
so remove readonly when state = done from view and add tracking on_change in
field definition -->
<attribute name="attrs">{}</attribute>
</field>
</field>
</record>
</odoo>

View File

@@ -103,7 +103,7 @@ class HrExpense(models.Model):
string='Untaxed Amount', currency_field='currency_id',
readonly=True, states={'draft': [('readonly', False)]})
company_currency_id = fields.Many2one(
related='company_id.currency_id', readonly=True, store=True, compute_sudo=True)
related='company_id.currency_id', readonly=True, store=True)
total_amount_company_currency = fields.Monetary(
compute='compute_amount_company_currency', readonly=True,
store=True, string='Total in Company Currency',
@@ -309,7 +309,7 @@ class HrExpenseSheet(models.Model):
responsible_id = fields.Many2one(track_visibility='onchange')
accounting_date = fields.Date(track_visibility='onchange')
company_currency_id = fields.Many2one(
related='company_id.currency_id', readonly=True, store=True, compute_sudo=True)
related='company_id.currency_id', readonly=True, store=True)
total_amount_company_currency = fields.Monetary(
compute='compute_total_company_currency',
currency_field='company_currency_id', readonly=True, store=True,

View File

@@ -9,7 +9,7 @@ class PurchaseConfigSettings(models.TransientModel):
_inherit = 'purchase.config.settings'
lunch_voucher_product_id = fields.Many2one(
related='company_id.lunch_voucher_product_id', compute_sudo=True)
related='company_id.lunch_voucher_product_id')
lunch_voucher_employer_price = fields.Monetary(
related='company_id.lunch_voucher_employer_price',
currency_field='company_currency_id', compute_sudo=True)
currency_field='company_currency_id')

View File

@@ -10,6 +10,6 @@ class PurchaseConfigSettings(models.TransientModel):
_inherit = 'purchase.config.settings'
lunch_voucher_natixis_customer_code = fields.Char(
related='company_id.lunch_voucher_natixis_customer_code', compute_sudo=True)
related='company_id.lunch_voucher_natixis_customer_code')
lunch_voucher_natixis_delivery_code = fields.Char(
related='company_id.lunch_voucher_natixis_delivery_code', compute_sudo=True)
related='company_id.lunch_voucher_natixis_delivery_code')

View File

@@ -223,7 +223,7 @@ class HrHolidays(models.Model):
readonly=True)
limit = fields.Boolean( # pose des pbs de droits
related='holiday_status_id.limit', string='Allow to Override Limit',
readonly=True, compute_sudo=True)
readonly=True)
payslip_date = fields.Date(
string='Transfer to Payslip Date', track_visibility='onchange',
readonly=True)
@@ -245,7 +245,7 @@ class HrHolidays(models.Model):
# by default, there is no company_id field on hr.holidays !
company_id = fields.Many2one(
related='employee_id.resource_id.company_id', store=True,
readonly=True, compute_sudo=True)
readonly=True)
state = fields.Selection(default='draft') # hr_holidays, default='confirm'
@api.constrains(
@@ -441,4 +441,4 @@ class BaseConfigSettings(models.TransientModel):
_inherit = 'base.config.settings'
mass_allocation_default_holiday_status_id = fields.Many2one(
related='company_id.mass_allocation_default_holiday_status_id', compute_sudo=True)
related='company_id.mass_allocation_default_holiday_status_id')

View File

@@ -41,41 +41,3 @@ class ResPartner(models.Model):
message, force_send=force_send,
send_after_commit=send_after_commit,
user_signature=user_signature)
class TemplatePreview(models.TransientModel):
_inherit = "email_template.preview"
res_id = fields.Integer(compute='_compute_res_id')
object_id = fields.Reference(selection='_reference_models')
@api.model
def default_get(self, fields):
result = super(TemplatePreview, self).default_get(fields)
if result.get('model_id'):
model = self.env['ir.model'].browse(result['model_id'])
result['object_id'] = model.model
return result
def _reference_models(self):
result = self.default_get(['model_id'])
if result.get('model_id'):
model = self.env['ir.model'].browse(result['model_id'])
return [(model.model, model.name)]
else:
models = self.env['ir.model'].search([('state', '!=', 'manual')])
return [(model.model, model.name)
for model in models
if not model.model.startswith('ir.')]
@api.depends('object_id')
def _compute_res_id(self):
for record in self:
if self.object_id:
record.res_id = self.object_id.id
def send(self):
template = self.env['mail.template'].browse(
self._context['template_id'])
template.send_mail(
self.res_id, force_send=True, raise_exception=True)

View File

@@ -19,23 +19,4 @@
</field>
</record>
<record id="email_template_preview_form" model="ir.ui.view">
<field name="model">email_template.preview</field>
<field name="inherit_id" ref="mail.email_template_preview_form"/>
<field name="arch" type="xml">
<field name="res_id" position="attributes">
<attribute name="invisible">True</attribute>
</field>
<field name="res_id" position="after">
<field name="object_id"/>
</field>
<footer position="inside">
<button
string="Send"
name="send"
class="btn-primary"
type='object'/>
</footer>
</field>
</record>
</odoo>

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
# © 2017 Chafique DELLI @ Akretion
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

View File

@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# © 2017 Chafique DELLI @ Akretion
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Product Unit Manager Group',
'summary': 'Add a group Product Unit of Measure Manager',
'version': '10.0.1.0.1',
'category': 'Product',
'description': 'Splits the use from the mangement for uom rights',
'website': 'https://akretion.com',
'author': 'Akretion',
'license': 'AGPL-3',
'installable': True,
'depends': [
'sale',
'purchase',
'mrp',
],
'data': [
'security/product_security.xml',
'security/ir.model.access.csv',
'views/product_view.xml',
],
}

View File

@@ -1,29 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * product_unit_manager_group
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-13 15:35+0000\n"
"PO-Revision-Date: 2017-03-13 15:35+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: product_unit_manager_group
#: model:res.groups,comment:product_unit_manager_group.group_uom_manager
#: model:res.groups,name:product_unit_manager_group.group_uom_manager
msgid "Manage Multiple Units of Measure"
msgstr "Gérer plusieurs unités de mesure"
#. module: product_unit_manager_group
#: model:res.groups,comment:product.group_uom
#: model:res.groups,name:product.group_uom
msgid "Use Multiple Units of Measure"
msgstr "Utiliser plusieurs unités de mesure"

View File

@@ -1,13 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_product_uom_manager,product.uom.manager,product.model_product_uom,group_uom_manager,1,1,1,1
access_product_uom_categ_manager,product.uom.categ.manager,product.model_product_uom_categ,group_uom_manager,1,1,1,1
access_product_uom_user,product.uom.user,product.model_product_uom,product.group_uom,1,0,0,0
access_product_uom_categ_user,product.uom.categ.user,product.model_product_uom_categ,product.group_uom,1,0,0,0
sale.access_product_uom_sale_manager,product.uom salemanager,product.model_product_uom,sales_team.group_sale_manager,1,0,0,0
sale.access_product_uom_categ_sale_manager,product.uom.categ salemanager,product.model_product_uom_categ,sales_team.group_sale_manager,1,0,0,0
purchase.access_product_uom_purchase_manager,product.uom purchase_manager,product.model_product_uom,purchase.group_purchase_manager,1,0,0,0
purchase.access_product_uom_categ_purchase_manager,product.uom.categ purchase_manager,product.model_product_uom_categ,purchase.group_purchase_manager,1,0,0,0
mrp.access_product_uom_mrp_manager,product.uom mrp_manager,product.model_product_uom,mrp.group_mrp_manager,1,0,0,0
mrp.access_product_uom_categ_mrp_manager,product.uom.categ mrp_manager,product.model_product_uom_categ,mrp.group_mrp_manager,1,0,0,0
stock.access_product_uom_stock_manager,product.uom stock_manager,product.model_product_uom,stock.group_stock_manager,1,0,0,0
stock.access_product_uom_categ_stock_manager,product.uom.categ stock_manager,product.model_product_uom_categ,stock.group_stock_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_product_uom_manager product.uom.manager product.model_product_uom group_uom_manager 1 1 1 1
3 access_product_uom_categ_manager product.uom.categ.manager product.model_product_uom_categ group_uom_manager 1 1 1 1
4 access_product_uom_user product.uom.user product.model_product_uom product.group_uom 1 0 0 0
5 access_product_uom_categ_user product.uom.categ.user product.model_product_uom_categ product.group_uom 1 0 0 0
6 sale.access_product_uom_sale_manager product.uom salemanager product.model_product_uom sales_team.group_sale_manager 1 0 0 0
7 sale.access_product_uom_categ_sale_manager product.uom.categ salemanager product.model_product_uom_categ sales_team.group_sale_manager 1 0 0 0
8 purchase.access_product_uom_purchase_manager product.uom purchase_manager product.model_product_uom purchase.group_purchase_manager 1 0 0 0
9 purchase.access_product_uom_categ_purchase_manager product.uom.categ purchase_manager product.model_product_uom_categ purchase.group_purchase_manager 1 0 0 0
10 mrp.access_product_uom_mrp_manager product.uom mrp_manager product.model_product_uom mrp.group_mrp_manager 1 0 0 0
11 mrp.access_product_uom_categ_mrp_manager product.uom.categ mrp_manager product.model_product_uom_categ mrp.group_mrp_manager 1 0 0 0
12 stock.access_product_uom_stock_manager product.uom stock_manager product.model_product_uom stock.group_stock_manager 1 0 0 0
13 stock.access_product_uom_categ_stock_manager product.uom.categ stock_manager product.model_product_uom_categ stock.group_stock_manager 1 0 0 0

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="product.group_uom" model="res.groups">
<field name="name">Use Multiple Units of Measure</field>
<field name="comment">Use multiple units of measure</field>
</record>
<record id="group_uom_manager" model="res.groups">
<field name="name">Manage Multiple Units of Measure (Manager)</field>
<field name="comment">Manage Multiple Units of Measure</field>
<field name="category_id" ref="base.module_category_hidden"/>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
<field name="implied_ids" eval="[(6, 0, [ref('product.group_uom')])]"/>
</record>
</data>
</odoo>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="sale.next_id_16" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
<record id="sale.menu_product_uom_form_action" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
<record id="sale.menu_product_uom_categ_form_action" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
<record id="stock.menu_stock_unit_measure_stock" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
<record id="stock.menu_stock_uom_categ_form_action" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
<record id="stock.menu_stock_uom_form_action" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
<record id="purchase.menu_purchase_uom_categ_form_action" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
<record id="purchase.menu_purchase_uom_form_action" model="ir.ui.menu">
<field name="groups_id" eval="[(6, 0, [ref('product_unit_manager_group.group_uom_manager')])]"/>
</record>
</data>
</odoo>

View File

@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields
@@ -69,5 +70,5 @@ class ProductPriceHistory(models.Model):
_inherit = 'product.price.history'
company_currency_id = fields.Many2one(
related='company_id.currency_id', readonly=True, compute_sudo=True,
related='company_id.currency_id', readonly=True,
string='Company Currency')

View File

@@ -88,15 +88,11 @@
</field>
</record>
<!-- It also adds on product.product search view -->
<record id="product_template_search_view" model="ir.ui.view">
<field name="name">usability.product.template.search</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_search_view" />
<field name="arch" type="xml">
<field name="categ_id" position="after">
<field name="seller_ids" string="Supplier" filter_domain="[('seller_ids.name', 'ilike', self)]"/>
</field>
<field name="pricelist_id" position="after">
<group string="Group By" name="groupby">
<filter name="categ_groupby" string="Internal Category" context="{'group_by': 'categ_id'}"/>

View File

@@ -1,31 +0,0 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
====================
Purchase Order Buyer
====================
Adds a buyer on the Purchase Order. (Like a vendor on sales).
A prefered buyer can be set on the supplier.
Usage
=====
To use this module, you need to go to Purchase > Purchase Order: there a new field "Buyer"
It will use by default the user_id of the supplier and fallback to current user.
Credits
=======
Contributors
------------
* Raphaël Reverdy <raphael.reverdy@akretion.com>
Maintainer
----------
Akretion

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Akretion (https://akretion.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Purchase Order Buyer",
"summary": "Add a buyer (user) on POs",
"version": "10.0.1.1.0",
"author": "Akretion",
"website": "https://github.com/akretion/odoo-usability",
"category": "Purchases",
"depends": ["purchase"],
"data": [
'views/purchase_order.xml',
],
"license": "AGPL-3",
"installable": True,
"application": False,
}

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import purchase_order

View File

@@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Raphael Reverdy https://akretion.com
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
user_id = fields.Many2one(
'res.users',
string='Buyer', index=True,
track_visibility='onchange',
default=lambda self: self.env.user)
@api.multi
@api.onchange('partner_id')
def onchange_partner_id(self):
"""Update the user_id (buyer)"""
for rec in self:
if rec.partner_id and rec.partner_id.user_id:
user_id = rec.partner_id.user_id.id
else:
user_id = self.env.user
return rec.update({
'user_id': user_id,
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="purchase_order_tree" model="ir.ui.view">
<field name="name">purchase.order.tree</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_tree"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="user_id" />
</field>
</field>
</record>
<record id="purchase_order_form" model="ir.ui.view">
<field name="name">purchase.order.form</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="user_id" />
</field>
</field>
</record>
</odoo>

View File

@@ -4,7 +4,6 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
from odoo.tools.misc import formatLang
class PurchaseOrder(models.Model):
@@ -55,20 +54,6 @@ class PurchaseOrder(models.Model):
self, 'purchase.report_purchaseorder')
return action
# Re-write native name_get() to use amount_untaxed instead of amount_total
@api.multi
@api.depends('name', 'partner_ref')
def name_get(self):
result = []
for po in self:
name = po.name
if po.partner_ref:
name += ' ('+po.partner_ref+')'
if po.amount_untaxed:
name += ': ' + formatLang(self.env, po.amount_untaxed, currency_obj=po.currency_id)
result.append((po.id, name))
return result
class StockPicking(models.Model):
_inherit = 'stock.picking'

View File

@@ -23,8 +23,7 @@ class CrmLeadLost(models.TransientModel):
])
if quotes:
quotes.action_cancel()
for quote in quotes:
quote.message_post(_(
"Quotation automatically cancelled upon marking "
"the related opportunity as lost."))
quotes.message_post(_(
"Quotation automatically cancelled upon marking "
"the related opportunity as lost."))
return super(CrmLeadLost, self).action_lost_reason_apply()

View File

@@ -14,7 +14,7 @@ class SaleOrderLine(models.Model):
# Also defined in bi_sale_company_currency
company_currency_id = fields.Many2one(
related='order_id.company_id.currency_id',
readonly=True, store=True, compute_sudo=True, string='Company Currency')
readonly=True, store=True, string='Company Currency')
standard_price_company_currency = fields.Float(
string='Cost Price in Company Currency', readonly=True,
digits=dp.get_precision('Product Price'),
@@ -33,7 +33,7 @@ class SaleOrderLine(models.Model):
string='Margin in Company Currency', readonly=True, store=True,
compute='_compute_margin', currency_field='company_currency_id')
margin_rate = fields.Float(
string="Margin Rate", readonly=True, store=True,
string="Margin (%)", readonly=True, store=True,
compute='_compute_margin',
digits=(16, 2), help="Margin rate in percentage of the sale price")
@@ -108,7 +108,7 @@ class SaleOrder(models.Model):
# Also defined in bi_sale_company_currency
company_currency_id = fields.Many2one(
related='company_id.currency_id', readonly=True, store=True, compute_sudo=True,
related='company_id.currency_id', readonly=True, store=True,
string="Company Currency")
margin_sale_currency = fields.Monetary(
string='Margin in Sale Currency',

View File

@@ -20,17 +20,13 @@
groups="account.group_account_user"/>
<field name="company_currency_id" invisible="1"/>
</field>
<xpath expr="//field[@name='order_line']/form//field[@name='analytic_tag_ids']/.." position="after">
<field name="standard_price_sale_currency" groups="base.group_no_one"/>
<field name="standard_price_company_currency" groups="base.group_no_one"/>
<field name="margin_sale_currency" groups="base.group_no_one"/>
<field name="margin_company_currency" groups="base.group_no_one"/>
<label for="margin_rate"/>
<div name="margin_rate">
<field name="margin_rate" groups="base.group_no_one" class="oe_inline"/> %
</div>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after">
<field name="standard_price_sale_currency" groups="base.group_no_one" string="Cost Price Sale Cur."/>
<field name="standard_price_company_currency" groups="base.group_no_one" string="Cost Price Comp. Cur."/>
<field name="margin_sale_currency" groups="base.group_no_one" string="Margin Sale Cur."/>
<field name="margin_company_currency" groups="base.group_no_one" string="Margin Comp. Cur."/>
<field name="margin_rate" groups="base.group_no_one"/>
<field name="company_currency_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
</xpath>
</field>
</record>

View File

@@ -15,9 +15,7 @@
<xpath expr="//field[@name='order_line']/form//label[@for='analytic_tag_ids']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/form//field[@name='analytic_tag_ids']/.." position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/form//field[@name='analytic_tag_ids']/.." position="replace"/>
<xpath expr="//field[@name='order_line']/tree/field[@name='analytic_tag_ids']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>

View File

@@ -9,4 +9,4 @@ class MrpBom(models.Model):
_inherit = 'mrp.bom'
_rec_name = 'product_id'
sale_ok = fields.Boolean(related='product_id.sale_ok', store=True, compute_sudo=True)
sale_ok = fields.Boolean(related='product_id.sale_ok', store=True)

View File

@@ -32,13 +32,6 @@
<button name="action_cancel" type="object" position="attributes">
<attribute name="confirm">Are you sure you want to cancel this picking?</attribute>
</button>
<!-- This sum is useful to check the 'number of items' to transfer... -->
<xpath expr="//field[@name='pack_operation_product_ids']/tree/field[@name='product_qty']" position="attributes">
<attribute name="sum">1</attribute>
</xpath>
<xpath expr="//field[@name='pack_operation_product_ids']/tree/field[@name='qty_done']" position="attributes">
<attribute name="sum">1</attribute>
</xpath>
</field>
</record>