Compare commits
68 Commits
10.0-accou
...
10-account
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29481d91b7 | ||
|
|
fc45c44280 | ||
|
|
a370c9100c | ||
|
|
e5d6ef4bad | ||
|
|
c3f1d30bdd | ||
|
|
6405be3fcf | ||
|
|
7a65c63f99 | ||
|
|
13d8100d98 | ||
|
|
0151c6a6e3 | ||
|
|
ea5fdcf4df | ||
|
|
4ff06be1a6 | ||
|
|
6c3c6cd43b | ||
|
|
3fef559aac | ||
|
|
75d7b7eac4 | ||
|
|
4a2fff177c | ||
|
|
e83238becd | ||
|
|
8bebd1e2ef | ||
|
|
b23f03f79d | ||
|
|
dc8363d6d1 | ||
|
|
ad850024ec | ||
|
|
03564a20b2 | ||
|
|
c1618166fb | ||
|
|
cf2464dfa4 | ||
|
|
80191002b8 | ||
|
|
694c800df3 | ||
|
|
bdf51029c7 | ||
|
|
478ab1fc2b | ||
|
|
362dba5f90 | ||
|
|
ac2b70b66e | ||
|
|
b75a2e47a2 | ||
|
|
3f73f15e4a | ||
|
|
c196343ec0 | ||
|
|
7d359d6730 | ||
|
|
904bf6c4f4 | ||
|
|
e13a2aba3d | ||
|
|
8cb880771e | ||
|
|
087bb1fde2 | ||
|
|
806b1b4a86 | ||
|
|
23519f4027 | ||
|
|
b54ec10f10 | ||
|
|
118dd2a5c0 | ||
|
|
0afe9a39a6 | ||
|
|
159f163da5 | ||
|
|
d0620a4c83 | ||
|
|
5d2d5b1e63 | ||
|
|
ab1144850b | ||
|
|
4995403bf5 | ||
|
|
a415744f11 | ||
|
|
c943b4cd33 | ||
|
|
8800b94e5b | ||
|
|
ca5238a03c | ||
|
|
5f15d83c3c | ||
|
|
6370dc0ec8 | ||
|
|
0d27c0a830 | ||
|
|
5f6107f2e8 | ||
|
|
ebb8f1ad86 | ||
|
|
04118bbf46 | ||
|
|
9cba31b68a | ||
|
|
21d1454ab9 | ||
|
|
d4e673103e | ||
|
|
9ebf7cdb4c | ||
|
|
f34a731d95 | ||
|
|
6ef322be4c | ||
|
|
dd15e3d194 | ||
|
|
e3fc2764fb | ||
|
|
f0bb02edf1 | ||
|
|
dc05ed3b5d | ||
|
|
2c1b8fe657 |
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import account_invoice
|
||||
from . import account_invoice_report
|
||||
|
||||
62
account_invoice_margin/account_invoice_report.py
Normal file
62
account_invoice_margin/account_invoice_report.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- 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 models, fields, api
|
||||
|
||||
|
||||
class AccountInvoiceReport(models.Model):
|
||||
_inherit = 'account.invoice.report'
|
||||
|
||||
margin = fields.Float(string='Margin', readonly=True)
|
||||
# why digits=0 ??? Why is it like that in the native "account" module
|
||||
user_currency_margin = fields.Float(
|
||||
string="Margin", compute='_compute_user_currency_margin', digits=0)
|
||||
|
||||
_depends = {
|
||||
'account.invoice': [
|
||||
'account_id', 'amount_total_company_signed',
|
||||
'commercial_partner_id', 'company_id',
|
||||
'currency_id', 'date_due', 'date_invoice', 'fiscal_position_id',
|
||||
'journal_id', 'partner_bank_id', 'partner_id', 'payment_term_id',
|
||||
'residual', 'state', 'type', 'user_id',
|
||||
],
|
||||
'account.invoice.line': [
|
||||
'account_id', 'invoice_id', 'price_subtotal', 'product_id',
|
||||
'quantity', 'uom_id', 'account_analytic_id',
|
||||
'margin_company_currency',
|
||||
],
|
||||
'product.product': ['product_tmpl_id'],
|
||||
'product.template': ['categ_id'],
|
||||
'product.uom': ['category_id', 'factor', 'name', 'uom_type'],
|
||||
'res.currency.rate': ['currency_id', 'name'],
|
||||
'res.partner': ['country_id'],
|
||||
}
|
||||
|
||||
@api.depends('currency_id', 'date', 'margin')
|
||||
def _compute_user_currency_margin(self):
|
||||
context = dict(self._context or {})
|
||||
user_currency_id = self.env.user.company_id.currency_id
|
||||
currency_rate_id = self.env['res.currency.rate'].search([
|
||||
('rate', '=', 1),
|
||||
'|',
|
||||
('company_id', '=', self.env.user.company_id.id),
|
||||
('company_id', '=', False)], limit=1)
|
||||
base_currency_id = currency_rate_id.currency_id
|
||||
ctx = context.copy()
|
||||
for record in self:
|
||||
ctx['date'] = record.date
|
||||
record.user_currency_margin = base_currency_id.with_context(
|
||||
ctx).compute(record.margin, user_currency_id)
|
||||
|
||||
# TODO check for refunds
|
||||
def _sub_select(self):
|
||||
select_str = super(AccountInvoiceReport, self)._sub_select()
|
||||
select_str += ", SUM(ail.margin_company_currency) AS margin"
|
||||
return select_str
|
||||
|
||||
def _select(self):
|
||||
select_str = super(AccountInvoiceReport, self)._select()
|
||||
select_str += ", sub.margin AS margin"
|
||||
return select_str
|
||||
@@ -12,17 +12,21 @@
|
||||
<field name="model">account.invoice.line</field>
|
||||
<field name="inherit_id" ref="account.view_invoice_line_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="discount" position="after">
|
||||
<field name="company_id" position="after">
|
||||
<field name="standard_price_company_currency"
|
||||
groups="account.group_account_user"/>
|
||||
groups="base.group_no_one"/>
|
||||
<field name="standard_price_invoice_currency"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
groups="account.group_account_user"/>
|
||||
groups="base.group_no_one"/>
|
||||
<field name="margin_invoice_currency"
|
||||
groups="account.group_account_user"/>
|
||||
groups="base.group_no_one"/>
|
||||
<field name="margin_company_currency"
|
||||
groups="account.group_account_user"/>
|
||||
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>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -34,18 +38,10 @@
|
||||
<field name="arch" type="xml">
|
||||
<field name="move_id" position="after">
|
||||
<field name="margin_invoice_currency"
|
||||
string="Margin" groups="account.group_account_user"/>
|
||||
string="Margin" groups="base.group_no_one"/>
|
||||
<field name="margin_company_currency"
|
||||
groups="account.group_account_user"/>
|
||||
groups="base.group_no_one"/>
|
||||
</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>
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Account Invoice Margin Report module for Odoo
|
||||
# Copyright (C) 2015 Akretion (http://www.akretion.com)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
{
|
||||
'name': 'Account Invoice Margin Report',
|
||||
'version': '0.1',
|
||||
'category': 'Accounting & Finance',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Add margin measure in Invoices Analysis',
|
||||
'description': """
|
||||
This module adds the measure *Margin* in the Invoices Analysis pivot table. It is in a separate module because it depends on the module *bi_invoice_company_currency* (in which I re-wrote the Invoice Analysis pivot table).
|
||||
|
||||
This module has been written by Alexis de Lattre from Akretion
|
||||
<alexis.delattre@akretion.com>.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['account_invoice_margin', 'bi_invoice_company_currency'],
|
||||
'data': [],
|
||||
'installable': False,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import invoice_report
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Account Invoice Margin Report module for Odoo
|
||||
# Copyright (C) 2015 Akretion (http://www.akretion.com/)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import models, fields
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class AccountInvoiceReportBi(models.Model):
|
||||
_inherit = "account.invoice.report.bi"
|
||||
|
||||
margin_company_currency = fields.Float(
|
||||
string='Margin', readonly=True,
|
||||
digits=dp.get_precision('Account'))
|
||||
|
||||
def _select(self):
|
||||
select = super(AccountInvoiceReportBi, self)._select()
|
||||
select += """
|
||||
, sum(ail.margin_company_currency) AS margin_company_currency
|
||||
"""
|
||||
return select
|
||||
@@ -18,7 +18,7 @@ class AccountMoveLineFilterWizard(models.TransientModel):
|
||||
account_reconcile = fields.Boolean(
|
||||
related='account_id.reconcile', readonly=True)
|
||||
reconcile = fields.Selection([
|
||||
('unreconciled', 'Unreconciled'),
|
||||
('unreconciled', 'Unreconciled or Partially Reconciled'),
|
||||
('reconciled', 'Fully Reconciled'),
|
||||
# ('partial_reconciled', 'Partially Reconciled'),
|
||||
], string='Reconciliation Filter')
|
||||
|
||||
@@ -19,7 +19,7 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['account'],
|
||||
'depends': ['account', 'base_usability'],
|
||||
'data': ['account_view.xml'],
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
@@ -42,5 +42,20 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ANALYTIC ACCOUNT -->
|
||||
<record id="view_account_analytic_account_form" model="ir.ui.view">
|
||||
<field name="name">account_no_analytic_tags.analytic.account.form</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="tag_ids" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account.account_analytic_tag_menu" model="ir.ui.menu">
|
||||
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import account
|
||||
from . import account_invoice_report
|
||||
from . import partner
|
||||
from . import wizard
|
||||
|
||||
@@ -31,12 +31,14 @@ This module has been written by Alexis de Lattre from Akretion <alexis.delattre@
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': [
|
||||
'account',
|
||||
'base_view_inheritance_extension',
|
||||
'base_usability', # needed only to access base_usability.group_nobody
|
||||
# in v12, I may create a module only for group_nobody
|
||||
],
|
||||
'data': [
|
||||
'account_view.xml',
|
||||
'account_report.xml',
|
||||
'account_invoice_report_view.xml',
|
||||
'partner_view.xml',
|
||||
'product_view.xml',
|
||||
'wizard/account_invoice_mark_sent_view.xml',
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.tools import float_compare, float_is_zero
|
||||
from odoo.tools.misc import formatLang
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo import SUPERUSER_ID
|
||||
import logging
|
||||
@@ -43,7 +44,6 @@ class AccountInvoice(models.Model):
|
||||
compute='_compute_has_attachment',
|
||||
search='_search_has_attachment', readonly=True)
|
||||
|
||||
@api.multi
|
||||
def _compute_has_discount(self):
|
||||
prec = self.env['decimal.precision'].precision_get('Discount')
|
||||
for inv in self:
|
||||
@@ -78,6 +78,25 @@ class AccountInvoice(models.Model):
|
||||
res = [('id', value and 'in' or 'not in', att_inv_ids.keys())]
|
||||
return res
|
||||
|
||||
# when you have an invoice created from a lot of sale orders, the 'name'
|
||||
# field is very large, which makes the name_get() of that invoice very big
|
||||
# which screws-up the form view of that invoice because of the link at the
|
||||
# top of the screen
|
||||
# That's why we have to cut the name_get() when it's too long
|
||||
def name_get(self):
|
||||
old_res = super(AccountInvoice, self).name_get()
|
||||
res = []
|
||||
for old_re in old_res:
|
||||
name = old_re[1]
|
||||
if name and len(name) > 100:
|
||||
# nice cut
|
||||
name = u'%s ...' % ', '.join(name.split(', ')[:3])
|
||||
# if not enough, hard cut
|
||||
if len(name) > 120:
|
||||
name = u'%s ...' % old_re[1][:120]
|
||||
res.append((old_re[0], name))
|
||||
return res
|
||||
|
||||
# I really hate to see a "/" in the 'name' field of the account.move.line
|
||||
# generated from customer invoices linked to the partners' account because:
|
||||
# 1) the label of an account move line is an important field, we can't
|
||||
@@ -102,6 +121,30 @@ class AccountInvoice(models.Model):
|
||||
lines.unlink()
|
||||
return True
|
||||
|
||||
def fix_invoice_attachment_filename(self):
|
||||
# This script is designed to fix attachment of invoices
|
||||
# badly generated by Odoo v8. I found this problem in Nov 2018 at
|
||||
# Encres Dubuit when investigating a bug where Odoo would create a
|
||||
# new attachment when printing an old invoice that already had the
|
||||
# PDF of the invoice as attachment
|
||||
logger.info('START fix customer invoice attachment filename')
|
||||
# Run this script as admin to fix problem in all companies
|
||||
self = self.sudo()
|
||||
attachs = self.env['ir.attachment'].search([
|
||||
('res_model', '=', 'account.invoice'),
|
||||
('res_id', '!=', False),
|
||||
('type', '=', 'binary'),
|
||||
('name', '=like', 'INV%.pdf'),
|
||||
('datas_fname', '=like', 'INV%.pdf.pdf')])
|
||||
for attach in attachs:
|
||||
inv = self.browse(attach.res_id)
|
||||
if inv.type in ('out_invoice', 'out_refund'):
|
||||
attach.datas_fname = attach.name
|
||||
logger.info(
|
||||
'Fixed field datas_fname of attachment ID %s name %s',
|
||||
attach.id, attach.name)
|
||||
logger.info('END fix customer invoice attachment filename')
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = 'account.invoice.line'
|
||||
@@ -114,7 +157,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)
|
||||
store=True, readonly=True, compute_sudo=True)
|
||||
state = fields.Selection(
|
||||
related='invoice_id.state', store=True, readonly=True,
|
||||
string='Invoice State')
|
||||
@@ -225,7 +268,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 aao.search([('user_type_id', '=', bank_type.id)]):
|
||||
for account in accounts:
|
||||
if account not in journal_accounts_bank_type:
|
||||
account.user_type_id = asset_type.id
|
||||
logger.info(
|
||||
@@ -234,6 +277,43 @@ class AccountAccount(models.Model):
|
||||
logger.info("END of the script 'fix bank and cash account types'")
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def create_account_groups(self, level=2, name_prefix=u'Comptes '):
|
||||
'''Should be launched by a script. Make sure the account_group module is installed
|
||||
(the account_usability module doesn't depend on it currently'''
|
||||
assert level >= 1
|
||||
assert isinstance(level, int)
|
||||
companies = self.env['res.company'].search([])
|
||||
if len(companies) > 1:
|
||||
logger.info(
|
||||
'Multi-company detected: running script create_account_groups '
|
||||
'as admin')
|
||||
self = self.sudo()
|
||||
ago = self.env['account.group']
|
||||
groups = ago.search([])
|
||||
if groups:
|
||||
raise UserError(_("Some account groups already exists"))
|
||||
accounts = self.search([])
|
||||
struct = {'childs': {}}
|
||||
for account in accounts:
|
||||
assert len(account.code) > level
|
||||
n = 1
|
||||
parent = struct
|
||||
gparent = False
|
||||
while n <= level:
|
||||
group_code = account.code[:n]
|
||||
if group_code not in parent['childs']:
|
||||
new_group = ago.create({
|
||||
'name': u'%s%s' % (name_prefix or '', group_code),
|
||||
'code_prefix': group_code,
|
||||
'parent_id': gparent and gparent.id or False,
|
||||
})
|
||||
parent['childs'][group_code] = {'obj': new_group, 'childs': {}}
|
||||
parent = parent['childs'][group_code]
|
||||
gparent = parent['obj']
|
||||
n += 1
|
||||
account.group_id = gparent.id
|
||||
|
||||
|
||||
class AccountAnalyticAccount(models.Model):
|
||||
_inherit = 'account.analytic.account'
|
||||
@@ -243,9 +323,7 @@ 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._get_one_full_name(record)))
|
||||
res.append((record.id, record.code or record.name))
|
||||
return res
|
||||
else:
|
||||
return super(AccountAnalyticAccount, self).name_get()
|
||||
@@ -265,6 +343,29 @@ 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)
|
||||
default_account_id = fields.Many2one(
|
||||
related='journal_id.default_debit_account_id', readonly=True)
|
||||
default_credit = fields.Float(
|
||||
compute='_compute_default_credit_debit', readonly=True)
|
||||
default_debit = fields.Float(
|
||||
compute='_compute_default_credit_debit', readonly=True)
|
||||
|
||||
@api.depends('line_ids.credit', 'line_ids.debit')
|
||||
def _compute_default_credit_debit(self):
|
||||
for move in self:
|
||||
total_debit = total_credit = default_debit = default_credit = 0.0
|
||||
for l in move.line_ids:
|
||||
total_debit += l.debit
|
||||
total_credit += l.credit
|
||||
# I could use float_compare, but I don't think it's really needed
|
||||
# in this context
|
||||
if total_debit > total_credit:
|
||||
default_credit = total_debit - total_credit
|
||||
else:
|
||||
default_debit = total_credit - total_debit
|
||||
move.default_credit = default_credit
|
||||
move.default_debit = default_debit
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
@@ -278,6 +379,14 @@ 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)
|
||||
account_reconcile = fields.Boolean(
|
||||
related='account_id.reconcile', readonly=True)
|
||||
full_reconcile_id = fields.Many2one(string='Full Reconcile')
|
||||
matched_debit_ids = fields.One2many(string='Partial Reconcile Debit')
|
||||
matched_credit_ids = fields.One2many(string='Partial Reconcile Credit')
|
||||
reconcile_string = fields.Char(
|
||||
compute='_compute_reconcile_string', string='Reconcile', store=True)
|
||||
|
||||
@api.onchange('credit')
|
||||
def _credit_onchange(self):
|
||||
@@ -315,7 +424,6 @@ class AccountMoveLine(models.Model):
|
||||
else:
|
||||
self.credit = amount_company_currency
|
||||
|
||||
@api.multi
|
||||
def show_account_move_form(self):
|
||||
self.ensure_one()
|
||||
action = self.env['ir.actions.act_window'].for_xml_id(
|
||||
@@ -328,6 +436,34 @@ class AccountMoveLine(models.Model):
|
||||
})
|
||||
return action
|
||||
|
||||
@api.depends(
|
||||
'full_reconcile_id', 'matched_debit_ids', 'matched_credit_ids')
|
||||
def _compute_reconcile_string(self):
|
||||
for line in self:
|
||||
rec_str = False
|
||||
if line.full_reconcile_id:
|
||||
rec_str = line.full_reconcile_id.name
|
||||
else:
|
||||
rec_str = ', '.join([
|
||||
'a%d' % pr.id for pr in line.matched_debit_ids + line.matched_credit_ids])
|
||||
line.reconcile_string = rec_str
|
||||
|
||||
|
||||
class AccountPartialReconcile(models.Model):
|
||||
_inherit = "account.partial.reconcile"
|
||||
_rec_name = "id"
|
||||
|
||||
def name_get(self):
|
||||
res = []
|
||||
for rec in self:
|
||||
# There is no seq for partial rec, so I simulate one with the ID
|
||||
# Prefix for full rec: 'A' (upper case)
|
||||
# Prefix for partial rec: 'a' (lower case)
|
||||
amount_fmt = formatLang(self.env, rec.amount, currency_obj=rec.company_currency_id)
|
||||
name = 'a%d (%s)' % (rec.id, amount_fmt)
|
||||
res.append((rec.id, name))
|
||||
return res
|
||||
|
||||
|
||||
class AccountBankStatement(models.Model):
|
||||
_inherit = 'account.bank.statement'
|
||||
@@ -347,6 +483,16 @@ class AccountBankStatement(models.Model):
|
||||
st.start_date = dates and min(dates) or False
|
||||
st.end_date = dates and max(dates) or False
|
||||
|
||||
@api.multi
|
||||
@api.depends('name', 'start_date', 'end_date')
|
||||
def name_get(self):
|
||||
res = []
|
||||
for statement in self:
|
||||
name = "%s (%s => %s)" % (
|
||||
statement.name, statement.start_date, statement.end_date)
|
||||
res.append((statement.id, name))
|
||||
return res
|
||||
|
||||
|
||||
class AccountBankStatementLine(models.Model):
|
||||
_inherit = 'account.bank.statement.line'
|
||||
|
||||
27
account_usability/account_invoice_report.py
Normal file
27
account_usability/account_invoice_report.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- 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 models, fields
|
||||
|
||||
|
||||
class AccountInvoiceReport(models.Model):
|
||||
_inherit = 'account.invoice.report'
|
||||
|
||||
number = fields.Char(string="Number", readonly=True)
|
||||
|
||||
def _sub_select(self):
|
||||
select_str = super(AccountInvoiceReport, self)._sub_select()
|
||||
select_str += ", ai.number"
|
||||
return select_str
|
||||
|
||||
def _select(self):
|
||||
select_str = super(AccountInvoiceReport, self)._select()
|
||||
select_str += ", sub.number"
|
||||
return select_str
|
||||
|
||||
def _group_by(self):
|
||||
group_by_str = super(AccountInvoiceReport, self)._group_by()
|
||||
group_by_str += ", ai.number"
|
||||
return group_by_str
|
||||
51
account_usability/account_invoice_report_view.xml
Normal file
51
account_usability/account_invoice_report_view.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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="account_invoice_report_tree" model="ir.ui.view">
|
||||
<field name="name">usability.account.invoice.report.tree</field>
|
||||
<field name="model">account.invoice.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Invoices Analysis">
|
||||
<field name="number"/>
|
||||
<field name="date"/>
|
||||
<field name="date_due"/>
|
||||
<field name="type"/>
|
||||
<field name="commercial_partner_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty" sum="1"/>
|
||||
<field name="uom_name" groups="product.group_uom"/>
|
||||
<field name="price_total" sum="1"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account.action_account_invoice_report_all_supp" model="ir.actions.act_window">
|
||||
<field name="context">{'search_default_current': 1, 'search_default_supplier': 1, 'search_default_year': 1}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
|
||||
</record>
|
||||
|
||||
<record id="account.action_account_invoice_report_all" model="ir.actions.act_window">
|
||||
<field name="context">{'search_default_current': 1, 'search_default_customer': 1, 'search_default_year': 1}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
|
||||
</record>
|
||||
|
||||
<record id="view_account_invoice_report_pivot" model="ir.ui.view">
|
||||
<field name="name">usability.account.invoice.report</field>
|
||||
<field name="model">account.invoice.report</field>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_report_pivot"/>
|
||||
<field name="arch" type="xml">
|
||||
<pivot position="attributes">
|
||||
<attribute name="disable_linking"></attribute>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
@@ -19,6 +19,9 @@
|
||||
<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>
|
||||
|
||||
@@ -35,6 +38,16 @@
|
||||
<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>
|
||||
<!-- Warning: there are 2 invoice_print buttons in the native view... probably a bug -->
|
||||
<xpath expr="//button[@name='invoice_print']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('state', 'not in', ('open', 'paid'))]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='invoice_print'][2]" position="attributes">
|
||||
<attribute name="attrs">{'invisible': True}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -241,6 +254,17 @@ 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>
|
||||
@@ -271,12 +295,18 @@ module -->
|
||||
<field name="arch" type="xml">
|
||||
<field name="ref" position="after">
|
||||
<field name="default_move_line_name"/>
|
||||
<field name="default_account_id" invisible="1"/>
|
||||
<field name="default_credit" invisible="0"/>
|
||||
<field name="default_debit" invisible="0"/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='line_ids']" position="attributes">
|
||||
<attribute name="context">{'line_ids': line_ids, 'journal_id': journal_id, 'default_name': default_move_line_name}</attribute>
|
||||
<attribute name="context" operation="python_dict" key="default_name">default_move_line_name</attribute>
|
||||
<attribute name="context" operation="python_dict" key="default_account_id">default_account_id</attribute>
|
||||
<attribute name="context" operation="python_dict" key="default_credit">default_credit</attribute>
|
||||
<attribute name="context" operation="python_dict" key="default_debit">default_debit</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='date_maturity']" position="after">
|
||||
<field name="full_reconcile_id"/>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='credit']" position="after">
|
||||
<field name="reconcile_string"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -287,7 +317,8 @@ module -->
|
||||
<field name="inherit_id" ref="account.view_account_move_line_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="after">
|
||||
<field name="full_reconcile_id" />
|
||||
<field name="reconcile_string" />
|
||||
<field name="debit" filter_domain="['|', ('debit', '=', self), ('credit', '=', self)]" string="Debit or Credit"/>
|
||||
</field>
|
||||
<filter name="unreconciled" position="before">
|
||||
<filter name="reconciled" string="Fully Reconciled" domain="[('full_reconcile_id', '!=', False)]"/>
|
||||
@@ -299,6 +330,9 @@ 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>
|
||||
|
||||
@@ -312,7 +346,21 @@ module -->
|
||||
</field>
|
||||
<field name="move_id" position="after">
|
||||
<field name="invoice_id"/>
|
||||
<field name="account_reconcile" invisible="1"/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='full_reconcile_id']/.." position="replace">
|
||||
<field name="full_reconcile_id" nolabel="1"/> <!-- label is already in view -->
|
||||
<field name="matched_debit_ids" readonly="1" widget="many2many_tags" attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '=', [])]}"/>
|
||||
<field name="matched_credit_ids" readonly="1" widget="many2many_tags" attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), ('matched_credit_ids', '=', [])]}"/>
|
||||
<field name="reconciled" invisible="1"/>
|
||||
<button name="open_reconcile_view" class="oe_link" type="object"
|
||||
string="-> View partially reconciled entries" colspan="2"
|
||||
attrs="{'invisible': ['|', ('full_reconcile_id', '!=', False), '&', ('matched_debit_ids', '=', []),('matched_credit_ids', '=', [])]}"/>
|
||||
<span colspan="2" attrs="{'invisible': ['|', '|', ('full_reconcile_id', '!=', False), ('matched_debit_ids', '!=', []), ('matched_credit_ids', '!=', [])]}" class="o_form_field">No Partial Reconcile</span>
|
||||
</xpath>
|
||||
<xpath expr="//label[@for='full_reconcile_id']/.." position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('account_reconcile', '=', False)]}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -324,7 +372,8 @@ module -->
|
||||
<!-- Move reconcile_id to a better position -->
|
||||
<field name="full_reconcile_id" position="replace"/>
|
||||
<field name="credit" position="after">
|
||||
<field name="full_reconcile_id"/>
|
||||
<field name="balance" sum="Total Balance"/>
|
||||
<field name="reconcile_string"/>
|
||||
</field>
|
||||
<field name="date_maturity" position="after">
|
||||
<button name="show_account_move_form" type="object" icon="fa-arrows-h" string="Show Journal Entry"/>
|
||||
@@ -332,6 +381,17 @@ 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>
|
||||
@@ -375,6 +435,9 @@ module -->
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<button name="button_cancel" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</button>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='bank_account_id']" position="after">
|
||||
<!-- The cancel button is provided by the account_cancel module, but we don't want to depend on it -->
|
||||
<button name="show_account_move" type="object"
|
||||
@@ -471,6 +534,11 @@ because it is useless and confusing -->
|
||||
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
|
||||
</record>
|
||||
|
||||
<!-- Remove menu entry "Accounting > Reports > PDF Reports" as there are broken -->
|
||||
<record id="account.menu_finance_legal_statement" model="ir.ui.menu">
|
||||
<field name="groups_id" eval="[(6, 0, [ref('base_usability.group_nobody')])]"/>
|
||||
</record>
|
||||
|
||||
<!-- Duplicate the menu "Sales > Configuration > Contacts > Bank Accounts"
|
||||
under "Accounting > Configuration", because most users will try to find it there -->
|
||||
<menuitem id="bank_account_account_config_menu" name="Bank Accounts" parent="account.menu_finance_configuration" sequence="9"/>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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>
|
||||
@@ -31,6 +31,7 @@ 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',
|
||||
|
||||
22
base_usability/partner_bank_view.xml
Normal file
22
base_usability/partner_bank_view.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>
|
||||
@@ -19,6 +19,10 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ 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)
|
||||
@@ -33,4 +34,5 @@ def formatLang(
|
||||
grouping=grouping, monetary=monetary, dp=dp, currency_obj=currency_obj)
|
||||
return res
|
||||
|
||||
|
||||
report_sxw.rml_parse.formatLang = formatLang
|
||||
|
||||
37
base_user_auth_log/README.rst
Normal file
37
base_user_auth_log/README.rst
Normal file
@@ -0,0 +1,37 @@
|
||||
.. 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>
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import report
|
||||
from . import models
|
||||
22
base_user_auth_log/__manifest__.py
Normal file
22
base_user_auth_log/__manifest__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- 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,
|
||||
}
|
||||
25
base_user_auth_log/data/ir_cron.xml
Normal file
25
base_user_auth_log/data/ir_cron.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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>
|
||||
4
base_user_auth_log/models/__init__.py
Normal file
4
base_user_auth_log/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import res_users_auth_log
|
||||
from . import res_users
|
||||
46
base_user_auth_log/models/res_users.py
Normal file
46
base_user_auth_log/models/res_users.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- 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
|
||||
44
base_user_auth_log/models/res_users_auth_log.py
Normal file
44
base_user_auth_log/models/res_users_auth_log.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# -*- 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)
|
||||
2
base_user_auth_log/security/ir.model.access.csv
Normal file
2
base_user_auth_log/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
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
|
||||
|
25
base_user_auth_log/views/res_users.xml
Normal file
25
base_user_auth_log/views/res_users.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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>
|
||||
80
base_user_auth_log/views/res_users_auth_log.xml
Normal file
80
base_user_auth_log/views/res_users_auth_log.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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>
|
||||
@@ -1,2 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import stock
|
||||
|
||||
@@ -24,6 +24,7 @@ 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,
|
||||
}
|
||||
|
||||
12
delivery_usability/stock.py
Normal file
12
delivery_usability/stock.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- 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')
|
||||
23
delivery_usability/stock_view.xml
Normal file
23
delivery_usability/stock_view.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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>
|
||||
@@ -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)
|
||||
related='company_id.currency_id', readonly=True, store=True, compute_sudo=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)
|
||||
related='company_id.currency_id', readonly=True, store=True, compute_sudo=True)
|
||||
total_amount_company_currency = fields.Monetary(
|
||||
compute='compute_total_company_currency',
|
||||
currency_field='company_currency_id', readonly=True, store=True,
|
||||
|
||||
@@ -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')
|
||||
related='company_id.lunch_voucher_product_id', compute_sudo=True)
|
||||
lunch_voucher_employer_price = fields.Monetary(
|
||||
related='company_id.lunch_voucher_employer_price',
|
||||
currency_field='company_currency_id')
|
||||
currency_field='company_currency_id', compute_sudo=True)
|
||||
|
||||
@@ -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')
|
||||
related='company_id.lunch_voucher_natixis_customer_code', compute_sudo=True)
|
||||
lunch_voucher_natixis_delivery_code = fields.Char(
|
||||
related='company_id.lunch_voucher_natixis_delivery_code')
|
||||
related='company_id.lunch_voucher_natixis_delivery_code', compute_sudo=True)
|
||||
|
||||
@@ -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)
|
||||
readonly=True, compute_sudo=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)
|
||||
readonly=True, compute_sudo=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')
|
||||
related='company_id.mass_allocation_default_holiday_status_id', compute_sudo=True)
|
||||
|
||||
20
mail_usability/README.rst
Normal file
20
mail_usability/README.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
# Mail Usability
|
||||
|
||||
Take back the control on your email
|
||||
|
||||
## Feature
|
||||
|
||||
- do not follow automatically a object when sending an email
|
||||
- add the option 'All Messages Except Notifications' on partner and use it by default to avoid sending unwanted mail to partner
|
||||
- better email preview, allow to select between the whole database object and not only the last 10
|
||||
- use a light template version for notification without link (link should be explicit)
|
||||
- add some additional style in the white list when santizing html field (see tools.py)
|
||||
- make the email template by default not 'auto_delete'
|
||||
|
||||
## TIPS
|
||||
|
||||
Never, never tick the 'auto_delete' on mail template because it fucking hard to debug
|
||||
and understand what have been sent (we should create a module with a crontask, that drop them latter)
|
||||
|
||||
If the template of mail do not look like the same when saving it in odoo, maybe the sanitize style have drop some balise
|
||||
please run odoo with "LOG_STYLE_SANITIZE=True odoo" to understand what have been drop, magic warning logger will tell you everthing
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import mail
|
||||
from . import tools
|
||||
from . import mail_template
|
||||
|
||||
@@ -25,6 +25,9 @@ Small usability improvements on mails:
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['mail'],
|
||||
'data': ['mail_view.xml'],
|
||||
'data': [
|
||||
'mail_view.xml',
|
||||
'mail_data.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
@@ -15,29 +15,94 @@ class ResPartner(models.Model):
|
||||
('all_except_notification', 'All Messages Except Notifications')],
|
||||
default='all_except_notification')
|
||||
|
||||
@api.multi
|
||||
def _notify(
|
||||
self, message, force_send=False, send_after_commit=True,
|
||||
user_signature=True):
|
||||
def _should_be_notify_by_email(self, message):
|
||||
if message.message_type == 'notification':
|
||||
message_sudo = message.sudo()
|
||||
email_channels = message.channel_ids.filtered(
|
||||
lambda channel: channel.email_send)
|
||||
bad_email = message_sudo.author_id and\
|
||||
message_sudo.author_id.email or message.email_from
|
||||
self.sudo().search([
|
||||
'|',
|
||||
('id', 'in', self.ids),
|
||||
('channel_ids', 'in', email_channels.ids),
|
||||
('email', '!=', bad_email),
|
||||
('notify_email', '=', 'always')])._notify_by_email(
|
||||
message, force_send=force_send,
|
||||
send_after_commit=send_after_commit,
|
||||
user_signature=user_signature)
|
||||
self._notify_by_chat(message)
|
||||
return True
|
||||
if self.notify_email == 'always':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return super(ResPartner, self)._notify(
|
||||
message, force_send=force_send,
|
||||
send_after_commit=send_after_commit,
|
||||
user_signature=user_signature)
|
||||
return True
|
||||
|
||||
def _notify_by_email(
|
||||
self, message, force_send=False, send_after_commit=True,
|
||||
user_signature=True):
|
||||
|
||||
# use an empty layout for notification by default
|
||||
if not self._context.get('custom_layout'):
|
||||
self = self.with_context(
|
||||
custom_layout='mail_usability.mail_template_notification')
|
||||
|
||||
# Filter the partner that should receive the notification
|
||||
filtered_partners = self.filtered(
|
||||
lambda p: p._should_be_notify_by_email(message)
|
||||
)
|
||||
|
||||
return super(ResPartner, filtered_partners)._notify_by_email(
|
||||
message, force_send=force_send,
|
||||
send_after_commit=send_after_commit,
|
||||
user_signature=user_signature)
|
||||
|
||||
def _notify_prepare_email_values(self, message):
|
||||
res = super(ResPartner, self)._notify_prepare_email_values(message)
|
||||
# Never auto delete notification email
|
||||
# fucking to hard to debug when message have been delete
|
||||
res['auto_delete'] = False
|
||||
return res
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class MailThread(models.AbstractModel):
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def message_post(self, body='', subject=None, message_type='notification',
|
||||
subtype=None, parent_id=False, attachments=None,
|
||||
content_subtype='html', **kwargs):
|
||||
if not 'mail_create_nosubscribe' in self._context:
|
||||
# Do not implicitly follow an object by just sending a message
|
||||
self = self.with_context(mail_create_nosubscribe=True)
|
||||
return super(MailThread,
|
||||
self.with_context(mail_create_nosubscribe=True)
|
||||
).message_post(
|
||||
body=body, subject=subject, message_type=message_type,
|
||||
subtype=subtype, parent_id=parent_id, attachments=attachments,
|
||||
content_subtype=content_subtype, **kwargs)
|
||||
|
||||
15
mail_usability/mail_data.xml
Normal file
15
mail_usability/mail_data.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!--Default Notification Email template -->
|
||||
<record id="mail_template_notification" model="mail.template">
|
||||
<field name="name">Notification Email</field>
|
||||
<field name="subject">${object.subject}</field>
|
||||
<field name="model_id" ref="mail.model_mail_message"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html">${object.body | safe}</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
12
mail_usability/mail_template.py
Normal file
12
mail_usability/mail_template.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Akretion (http://www.akretion.com).
|
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import fields, models
|
||||
|
||||
|
||||
class MailTemplate(models.Model):
|
||||
_inherit = 'mail.template'
|
||||
|
||||
auto_delete = fields.Boolean(default=False)
|
||||
@@ -19,4 +19,23 @@
|
||||
</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>
|
||||
|
||||
45
mail_usability/tools.py
Normal file
45
mail_usability/tools.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Akretion (http://www.akretion.com).
|
||||
# @author Sébastien BEAU <sebastien.beau@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo.tools.mail import _Cleaner
|
||||
import os
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
_Cleaner._style_whitelist += [
|
||||
'word-wrap',
|
||||
'display'
|
||||
'border-top',
|
||||
'border-bottom',
|
||||
'border-left',
|
||||
'border-right',
|
||||
'text-transform',
|
||||
]
|
||||
|
||||
|
||||
if os.getenv('LOG_STYLE_SANITIZE'):
|
||||
# Monkey patch the parse style method to debug
|
||||
# the missing style
|
||||
def parse_style(self, el):
|
||||
attributes = el.attrib
|
||||
styling = attributes.get('style')
|
||||
if styling:
|
||||
valid_styles = {}
|
||||
styles = self._style_re.findall(styling)
|
||||
for style in styles:
|
||||
if style[0].lower() in self._style_whitelist:
|
||||
valid_styles[style[0].lower()] = style[1]
|
||||
# START HACK
|
||||
else:
|
||||
_logger.warning('Remove style %s %s', *style)
|
||||
# END HACK
|
||||
if valid_styles:
|
||||
el.attrib['style'] = '; '.join(
|
||||
'%s:%s' % (key, val)
|
||||
for (key, val) in valid_styles.iteritems())
|
||||
else:
|
||||
del el.attrib['style']
|
||||
import pdb; pdb.set_trace()
|
||||
_Cleaner.parse_style = parse_style
|
||||
@@ -13,6 +13,12 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page/group/group[@groups='stock.group_stock_multi_locations']/field[@name='location_src_id']" position="replace"/>
|
||||
<xpath expr="//page/group/group[@groups='stock.group_stock_multi_locations']/field[@name='location_dest_id']" position="replace"/>
|
||||
<field name="routing_id" position="after">
|
||||
<field name="location_src_id" domain="[('usage','=','internal')]" attrs="{'readonly': [('has_moves', '=', True)]}" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_dest_id" domain="[('usage','=','internal')]" attrs="{'readonly': [('has_moves', '=', True)]}" groups="stock.group_stock_multi_locations"/>
|
||||
</field>
|
||||
<field name="availability" position="after">
|
||||
<field name="date_start"/>
|
||||
<field name="date_finished"/>
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
'views/product_view.xml',
|
||||
'views/picking_view.xml',
|
||||
],
|
||||
'installable': False,
|
||||
'installable': True,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Translation of OpenERP Server.
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * product_search_supplier_code
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
||||
"Project-Id-Version: Odoo Server 10.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-04 15:18+0000\n"
|
||||
"PO-Revision-Date: 2015-11-04 15:18+0000\n"
|
||||
"PO-Revision-Date: 2018-04-26 15:18+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -17,6 +17,7 @@ msgstr ""
|
||||
|
||||
#. module: product_search_supplier_code
|
||||
#: view:product.product:0
|
||||
#: view:product.template:0
|
||||
#: view:stock.picking.in:0
|
||||
msgid "Supplier Default Code"
|
||||
msgstr "Code Fournisseur"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<record id="stock_picking_supplier_code_search" model="ir.ui.view">
|
||||
<field name="model">stock.picking</field>
|
||||
@@ -13,5 +12,4 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<record id="product_custom_search_view" model="ir.ui.view">
|
||||
<field name="model">product.product</field>
|
||||
@@ -13,5 +12,15 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
<record id="product_custom_search_view" model="ir.ui.view">
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_search_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="seller_ids" string="Supplier Default Code"
|
||||
filter_domain="[('seller_ids.product_code','ilike',self)]" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
3
product_unit_manager_group/__init__.py
Normal file
3
product_unit_manager_group/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# © 2017 Chafique DELLI @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
24
product_unit_manager_group/__manifest__.py
Normal file
24
product_unit_manager_group/__manifest__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- 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',
|
||||
],
|
||||
}
|
||||
29
product_unit_manager_group/i18n/fr.po
Normal file
29
product_unit_manager_group/i18n/fr.po
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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"
|
||||
|
||||
13
product_unit_manager_group/security/ir.model.access.csv
Normal file
13
product_unit_manager_group/security/ir.model.access.csv
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
|
19
product_unit_manager_group/security/product_security.xml
Normal file
19
product_unit_manager_group/security/product_security.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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>
|
||||
39
product_unit_manager_group/views/product_view.xml
Normal file
39
product_unit_manager_group/views/product_view.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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>
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- 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
|
||||
@@ -70,5 +69,5 @@ class ProductPriceHistory(models.Model):
|
||||
_inherit = 'product.price.history'
|
||||
|
||||
company_currency_id = fields.Many2one(
|
||||
related='company_id.currency_id', readonly=True,
|
||||
related='company_id.currency_id', readonly=True, compute_sudo=True,
|
||||
string='Company Currency')
|
||||
|
||||
@@ -88,11 +88,15 @@
|
||||
</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'}"/>
|
||||
|
||||
31
purchase_order_buyer/README.rst
Normal file
31
purchase_order_buyer/README.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
.. 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
|
||||
2
purchase_order_buyer/__init__.py
Normal file
2
purchase_order_buyer/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
19
purchase_order_buyer/__manifest__.py
Normal file
19
purchase_order_buyer/__manifest__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- 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,
|
||||
}
|
||||
3
purchase_order_buyer/models/__init__.py
Normal file
3
purchase_order_buyer/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import purchase_order
|
||||
28
purchase_order_buyer/models/purchase_order.py
Normal file
28
purchase_order_buyer/models/purchase_order.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- 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,
|
||||
})
|
||||
BIN
purchase_order_buyer/static/description/icon.png
Normal file
BIN
purchase_order_buyer/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
26
purchase_order_buyer/views/purchase_order.xml
Normal file
26
purchase_order_buyer/views/purchase_order.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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>
|
||||
@@ -4,6 +4,7 @@
|
||||
# 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):
|
||||
@@ -54,6 +55,20 @@ 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'
|
||||
|
||||
@@ -23,7 +23,8 @@ class CrmLeadLost(models.TransientModel):
|
||||
])
|
||||
if quotes:
|
||||
quotes.action_cancel()
|
||||
quotes.message_post(_(
|
||||
"Quotation automatically cancelled upon marking "
|
||||
"the related opportunity as lost."))
|
||||
for quote in quotes:
|
||||
quote.message_post(_(
|
||||
"Quotation automatically cancelled upon marking "
|
||||
"the related opportunity as lost."))
|
||||
return super(CrmLeadLost, self).action_lost_reason_apply()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from . import sale
|
||||
from . import sale_report
|
||||
|
||||
@@ -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 (%)", readonly=True, store=True,
|
||||
string="Margin Rate", readonly=True, store=True,
|
||||
compute='_compute_margin',
|
||||
digits=(16, 2), help="Margin rate in percentage of the sale price")
|
||||
|
||||
@@ -77,7 +77,7 @@ class SaleOrderLine(models.Model):
|
||||
sale_uom = self.env['product.uom'].browse(sale_uom_id)
|
||||
# convert from product UoM to sale UoM
|
||||
std_price = pp.uom_id._compute_price(
|
||||
standard_price, sale_uom)
|
||||
pp.standard_price, sale_uom)
|
||||
vals['standard_price_company_currency'] = std_price
|
||||
return super(SaleOrderLine, self).create(vals)
|
||||
|
||||
|
||||
17
sale_margin_no_onchange/sale_report.py
Normal file
17
sale_margin_no_onchange/sale_report.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- 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 SaleReport(models.Model):
|
||||
_inherit = 'sale.report'
|
||||
|
||||
margin = fields.Float(string='Margin', readonly=True)
|
||||
|
||||
def _select(self):
|
||||
select_str = super(SaleReport, self)._select()
|
||||
select_str += ", SUM(l.margin_company_currency) AS margin"
|
||||
return select_str
|
||||
@@ -20,13 +20,17 @@
|
||||
groups="account.group_account_user"/>
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
</field>
|
||||
<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"/>
|
||||
<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>
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Sale Margin Report module for Odoo
|
||||
# Copyright (C) 2016 Akretion (http://www.akretion.com)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
{
|
||||
'name': 'Sale Margin Report',
|
||||
'version': '0.1',
|
||||
'category': 'Sales Management',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Add margin measure in Sales Analysis',
|
||||
'description': """
|
||||
This module adds the measure *Margin* in the Sales Analysis pivot table. It is in a separate module because it depends on the module *bi_sale_company_currency* (in which I re-wrote the Sales Analysis pivot table).
|
||||
|
||||
This module has been written by Alexis de Lattre from Akretion
|
||||
<alexis.delattre@akretion.com>.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['sale_margin_no_onchange', 'bi_sale_company_currency'],
|
||||
'data': [],
|
||||
'installable': False,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import sale_report
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Sale Margin Report module for Odoo
|
||||
# Copyright (C) 2016 Akretion (http://www.akretion.com/)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import models, fields
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class SaleReportBi(models.Model):
|
||||
_inherit = "sale.report.bi"
|
||||
|
||||
margin_company_currency = fields.Float(
|
||||
string='Margin', readonly=True,
|
||||
digits=dp.get_precision('Account'))
|
||||
|
||||
def _select(self):
|
||||
select = super(SaleReportBi, self)._select()
|
||||
select += """
|
||||
, sum(sol.margin_company_currency) AS margin_company_currency
|
||||
"""
|
||||
return select
|
||||
@@ -15,7 +15,9 @@
|
||||
<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="replace"/>
|
||||
<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']/tree/field[@name='analytic_tag_ids']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
@@ -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)
|
||||
sale_ok = fields.Boolean(related='product_id.sale_ok', store=True, compute_sudo=True)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import report
|
||||
from . import sale
|
||||
30
sale_order_full_dropship/__manifest__.py
Normal file
30
sale_order_full_dropship/__manifest__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Akretion France (http://www.akretion.com)
|
||||
# @author Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
'name': 'Sale Order Full Dropship',
|
||||
'version': '10.0.1.0.0',
|
||||
'category': 'Sales',
|
||||
'license': 'AGPL-3',
|
||||
'summary': 'Adds an option full dropship on sale orders',
|
||||
'description': """
|
||||
Sale Order Full Dropship
|
||||
========================
|
||||
|
||||
This module adds a boolean field *Full Dropship* on sale order form. If enabled on a quotation, Odoo will enable the *Dropship* route on all sale order lines of that order. That way, for a full dropship order, the user won't have to open each order line to set the Dropship route.
|
||||
|
||||
This module has been written by Alexis de Lattre from Akretion <alexis.delattre@akretion.com>.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': [
|
||||
'sale_stock',
|
||||
'stock_dropshipping',
|
||||
],
|
||||
'data': [
|
||||
'sale_view.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
29
sale_order_full_dropship/sale.py
Normal file
29
sale_order_full_dropship/sale.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Akretion France (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, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
dropship = fields.Boolean(
|
||||
string='Dropship', readonly=True,
|
||||
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
||||
help="If enabled, all the order lines will be set with the "
|
||||
"'Drop Shipping' route upon confirmation of the order.")
|
||||
|
||||
def action_confirm(self):
|
||||
try:
|
||||
ds_route = self.env.ref('stock_dropshipping.route_drop_shipping')
|
||||
except ValueError:
|
||||
raise UserError(_("Drop shipping route not found."))
|
||||
for order in self:
|
||||
if order.dropship:
|
||||
# no need to exclude service lines
|
||||
# by default, the don't generate a procurement
|
||||
order.order_line.write({'route_id': ds_route.id})
|
||||
return super(SaleOrder, self).action_confirm()
|
||||
23
sale_order_full_dropship/sale_view.xml
Normal file
23
sale_order_full_dropship/sale_view.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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_order_form_inherit_sale_stock" model="ir.ui.view">
|
||||
<field name="name">sale_order_full_dropship.sale.order.form</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale_stock.view_order_form_inherit_sale_stock"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_shipping_id" position="after">
|
||||
<field name="dropship"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
5
sale_partner_shipping_filter_with_customer/__init__.py
Normal file
5
sale_partner_shipping_filter_with_customer/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# coding: utf-8
|
||||
# © 2017 Chafique DELLI @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import models
|
||||
21
sale_partner_shipping_filter_with_customer/__manifest__.py
Normal file
21
sale_partner_shipping_filter_with_customer/__manifest__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# coding: utf-8
|
||||
# © 2017 Chafique DELLI @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{
|
||||
'name': 'Partner Shipping Filter with Customer',
|
||||
'summary': "Shows only delivery addresses that are linked "
|
||||
"with the customer",
|
||||
'version': '10.0.1.0.0',
|
||||
'category': 'Sale Management',
|
||||
'website': 'http://akretion.com',
|
||||
'author': 'Akretion, Odoo Community Association (OCA)',
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
'depends': [
|
||||
'sale',
|
||||
],
|
||||
'data': [
|
||||
'views/sale_view.xml',
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# coding: utf-8
|
||||
# © 2017 Chafique DELLI @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import sale
|
||||
12
sale_partner_shipping_filter_with_customer/models/sale.py
Normal file
12
sale_partner_shipping_filter_with_customer/models/sale.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# coding: utf-8
|
||||
# © 2017 Chafique DELLI @ Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
commercial_partner_id = fields.Many2one(
|
||||
related='partner_id.commercial_partner_id', readonly=True)
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_order_form" model="ir.ui.view">
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_id']" position="after">
|
||||
<field name="commercial_partner_id" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='partner_shipping_id']" position="attributes">
|
||||
<attribute name="domain">['|',
|
||||
('id', '=', partner_id), '&',
|
||||
('type','=', 'delivery'), '&',
|
||||
('id', 'child_of', commercial_partner_id), ('parent_id', '!=', False)]</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -26,6 +26,7 @@ This module has been written by Alexis de Lattre from Akretion
|
||||
'depends': ['sale'],
|
||||
'data': [
|
||||
'sale_view.xml',
|
||||
'sale_report_view.xml',
|
||||
'product_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
@@ -15,7 +16,7 @@ class AccountInvoice(models.Model):
|
||||
# https://github.com/akretion/odoo-py3o-report-templates/tree/10.0/account_invoice_report_py3o
|
||||
def py3o_lines_layout(self):
|
||||
self.ensure_one()
|
||||
res1 = {}
|
||||
res1 = OrderedDict()
|
||||
# {'categ(6)': {'lines': [l1, l2], 'subtotal': 23.32}}
|
||||
for line in self.invoice_line_ids:
|
||||
categ = line.layout_category_id
|
||||
@@ -52,7 +53,7 @@ class AccountInvoice(models.Model):
|
||||
# defined above: you just have to change the call in the invoice
|
||||
# ODT template
|
||||
self.ensure_one()
|
||||
res1 = {}
|
||||
res1 = OrderedDict()
|
||||
# {categ(1): {'lines': [l1, l2], 'subtotal': 23.32}}
|
||||
soo = self.env['sale.order']
|
||||
for line in self.invoice_line_ids:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
from odoo import models, fields, api
|
||||
from odoo.tools import float_is_zero
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
@@ -55,7 +56,7 @@ class SaleOrder(models.Model):
|
||||
@api.multi
|
||||
def py3o_lines_layout(self):
|
||||
self.ensure_one()
|
||||
res1 = {}
|
||||
res1 = OrderedDict()
|
||||
# {categ(6): {'lines': [l1, l2], 'subtotal': 23.32}}
|
||||
for line in self.order_line:
|
||||
categ = line.layout_category_id
|
||||
|
||||
46
sale_usability/sale_report_view.xml
Normal file
46
sale_usability/sale_report_view.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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="sale_report_tree" model="ir.ui.view">
|
||||
<field name="name">usability.sale.report.tree</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Sales Analysis">
|
||||
<field name="name"/>
|
||||
<field name="date"/>
|
||||
<field name="commercial_partner_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom_qty" sum="1"/>
|
||||
<field name="qty_delivered" sum="1"/>
|
||||
<field name="qty_to_invoice" sum="1"/>
|
||||
<field name="product_uom" groups="product.group_uom"/>
|
||||
<field name="price_subtotal" sum="1"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale.action_order_report_all" model="ir.actions.act_window">
|
||||
<field name="context">{'search_default_Sales': 1}</field> <!-- Remove group_by_no_leaf, which breaks tree view -->
|
||||
</record>
|
||||
|
||||
<record id="view_order_product_pivot" model="ir.ui.view">
|
||||
<field name="name">usability.sale.report.pivot</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="inherit_id" ref="sale.view_order_product_pivot"/>
|
||||
<field name="arch" type="xml">
|
||||
<pivot position="attributes">
|
||||
<attribute name="disable_linking"></attribute>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -32,6 +32,13 @@
|
||||
<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>
|
||||
|
||||
@@ -216,6 +223,20 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_pack_operation_lot_form" model="ir.ui.view">
|
||||
<field name="name">stock_usability.stock.pack.operation.form</field>
|
||||
<field name="model">stock.pack.operation</field>
|
||||
<field name="inherit_id" ref="stock.view_pack_operation_lot_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="product_id" position="after">
|
||||
<field name="picking_source_location_id" invisible="1"/>
|
||||
<field name="picking_destination_location_id" invisible="1"/>
|
||||
<field name="location_id" domain="[('id', 'child_of', picking_source_location_id)]" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_dest_id" domain="[('id', 'child_of', picking_destination_location_id)]" groups="stock.group_stock_multi_locations"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_warehouse" model="ir.ui.view">
|
||||
<field name="name">stock.usability.warehouse.form</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
@@ -293,6 +314,10 @@ should be able to access it. So I add a menu entry under Inventory Control. -->
|
||||
<attribute name="decoration-info">product_qty > theoretical_qty</attribute>
|
||||
<attribute name="decoration-warning">product_qty < theoretical_qty</attribute>
|
||||
</xpath>
|
||||
<button name="reset_real_qty" type="object" position="attributes">
|
||||
<attribute name="confirm">Are you sure you want to reset all quantities to 0 ?</attribute>
|
||||
</button>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
3
stock_user_default_warehouse_mrp/__init__.py
Normal file
3
stock_user_default_warehouse_mrp/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import mrp
|
||||
25
stock_user_default_warehouse_mrp/__manifest__.py
Normal file
25
stock_user_default_warehouse_mrp/__manifest__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- 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).
|
||||
|
||||
{
|
||||
'name': 'Default Warehouse on User (MRP)',
|
||||
'version': '10.0.1.0.0',
|
||||
'category': 'Manufacturing',
|
||||
'license': 'AGPL-3',
|
||||
'summary': "Use the users's default warehouse on manufacturing orders",
|
||||
'description': """
|
||||
Default Warehouse on User (MRP)
|
||||
================================
|
||||
|
||||
The default warehouse configured in the preferences of the user will be used by default on manufacturing orders.
|
||||
|
||||
This module has been written by Alexis de Lattre from Akretion
|
||||
<alexis.delattre@akretion.com>.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
'depends': ['mrp', 'stock_user_default_warehouse_base'],
|
||||
'installable': True,
|
||||
}
|
||||
23
stock_user_default_warehouse_mrp/mrp.py
Normal file
23
stock_user_default_warehouse_mrp/mrp.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- 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 models, fields, api
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
@api.model
|
||||
def _default_pref_picking_type(self):
|
||||
default_manu_type = self.env.user.context_default_warehouse_id.\
|
||||
manu_type_id
|
||||
if default_manu_type:
|
||||
return default_manu_type.id
|
||||
return self._get_default_picking_type()
|
||||
|
||||
# No need to inherit the default value of location_src_id and
|
||||
# location_dest_id because it is immediately over-ridden
|
||||
# by the onchange of picking_type_id
|
||||
picking_type_id = fields.Many2one(default=_default_pref_picking_type)
|
||||
@@ -14,6 +14,6 @@ class PurchaseOrder(models.Model):
|
||||
default_in_type = self.env.user.context_default_warehouse_id.in_type_id
|
||||
if default_in_type:
|
||||
return default_in_type.id
|
||||
return self._default_picking_type
|
||||
return self._default_picking_type()
|
||||
|
||||
picking_type_id = fields.Many2one(default=_default_pref_picking_type)
|
||||
|
||||
Reference in New Issue
Block a user