[MERGE] forward port of branch 9.0 up to 8284c73

This commit is contained in:
Christophe Simonis
2016-06-17 11:32:09 +02:00
23 changed files with 212 additions and 30 deletions
@@ -421,20 +421,28 @@ class AccountBankStatementLine(models.Model):
def button_cancel_reconciliation(self):
moves_to_unbind = self.env['account.move']
moves_to_cancel = self.env['account.move']
payment_to_unreconcile = self.env['account.payment']
for st_line in self:
moves_to_unbind |= st_line.journal_entry_ids
for move in st_line.journal_entry_ids:
if any(line.payment_id for line in move.line_ids):
for line in move.line_ids:
payment_to_unreconcile |= line.payment_id
continue
moves_to_cancel |= st_line.journal_entry_ids
if moves_to_unbind:
moves_to_unbind.write({'statement_line_id': False})
moves_to_unbind.line_ids.filtered(lambda x:x.statement_id == st_line.statement_id).write({'statement_id': False})
if moves_to_cancel:
for move in moves_to_cancel:
move.line_ids.remove_move_reconcile()
moves_to_cancel.button_cancel()
moves_to_cancel.unlink()
if payment_to_unreconcile:
payment_to_unreconcile.write({'state': 'posted'})
####################################################
# Reconciliation interface methods
####################################################
@@ -566,7 +574,18 @@ class AccountBankStatementLine(models.Model):
else:
domain = [(f, '>', 0), (f, '<', amount)]
elif comparator == '=':
domain = [(f, '=', float_round(amount, precision_digits=p))]
if f == 'amount_residual':
domain = [
'|', (f, '=', float_round(amount, precision_digits=p)),
'&', ('account_id.internal_type', '=', 'liquidity'),
'|', ('debit', '=', amount), ('credit', '=', amount),
]
else:
domain = [
'|', (f, '=', float_round(amount, precision_digits=p)),
'&', ('account_id.internal_type', '=', 'liquidity'),
('amount_currency', '=', amount),
]
else:
raise UserError(_("Programmation error : domain_maker_move_line_amount requires comparator '=' or '<'"))
domain += [('currency_id', '=', c)]
@@ -142,11 +142,22 @@ class account_journal(models.Model):
if self.type in ['bank', 'cash']:
last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1)
last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0
ac_bnk_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids),('state', '=', 'open')])
for ac_bnk in ac_bnk_stmt:
for line in ac_bnk.line_ids:
if not line.journal_entry_ids:
number_to_reconcile += 1
#Get the number of items to reconcile for that bank journal
self.env.cr.execute("""SELECT COUNT(DISTINCT(statement_line_id))
FROM account_move where statement_line_id
IN (SELECT line.id
FROM account_bank_statement_line AS line
LEFT JOIN account_bank_statement AS st
ON line.statement_id = st.id
WHERE st.journal_id IN %s and st.state = 'open')""", (tuple(self.ids),))
already_reconciled = self.env.cr.fetchone()[0]
self.env.cr.execute("""SELECT COUNT(line.id)
FROM account_bank_statement_line AS line
LEFT JOIN account_bank_statement AS st
ON line.statement_id = st.id
WHERE st.journal_id IN %s and st.state = 'open'""", (tuple(self.ids),))
all_lines = self.env.cr.fetchone()[0]
number_to_reconcile = all_lines - already_reconciled
# optimization to read sum of balance from account_move_line
account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id]))
if account_ids:
+24 -8
View File
@@ -583,7 +583,14 @@ class AccountMoveLine(models.Model):
]
try:
amount = float(str)
amount_domain = ['|', ('amount_residual', '=', amount), '|', ('amount_residual_currency', '=', amount), '|', ('amount_residual', '=', -amount), ('amount_residual_currency', '=', -amount)]
amount_domain = [
'|', ('amount_residual', '=', amount),
'|', ('amount_residual_currency', '=', amount),
'|', ('amount_residual', '=', -amount),
'|', ('amount_residual_currency', '=', -amount),
'&', ('account_id.internal_type', '=', 'liquidity'),
'|', '|', ('debit', '=', amount), ('credit', '=', amount), ('amount_currency', '=', amount),
]
str_domain = expression.OR([str_domain, amount_domain])
except:
pass
@@ -1050,13 +1057,13 @@ class AccountMoveLine(models.Model):
if vals['debit'] != 0.0: vals['debit'] = res['total_excluded']
if vals['credit'] != 0.0: vals['credit'] = -res['total_excluded']
if vals.get('amount_currency'):
vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (amount / res['total_excluded']))
vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (res['total_excluded']/amount))
# Create tax lines
for tax_vals in res['taxes']:
if tax_vals['amount']:
account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id'])
if not account_id: account_id = vals['account_id']
tax_lines_vals.append({
temp = {
'account_id': account_id,
'name': vals['name'] + ' ' + tax_vals['name'],
'tax_line_id': tax_vals['id'],
@@ -1065,7 +1072,12 @@ class AccountMoveLine(models.Model):
'statement_id': vals.get('statement_id'),
'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0,
'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0,
})
}
bank = self.env["account.bank.statement"].browse(vals.get('statement_id'))
if bank.currency_id != bank.company_id.currency_id:
temp['currency_id'] = bank.currency_id.id
temp['amount_currency'] = bank.company_id.currency_id.compute(tax_vals['amount'], bank.currency_id, round=True)
tax_lines_vals.append(temp)
new_line = super(AccountMoveLine, self).create(vals)
for tax_line_vals in tax_lines_vals:
@@ -1107,10 +1119,11 @@ class AccountMoveLine(models.Model):
msg = _('New expected payment date: ') + vals['expected_pay_date'] + '.\n' + vals.get('internal_note', '')
self.invoice_id.message_post(body=msg) #TODO: check it is an internal note (not a regular email)!
#when making a reconciliation on an existing liquidity journal item, mark the payment as reconciled
if 'statement_id' in vals and self.payment_id:
# In case of an internal transfer, there are 2 liquidity move lines to match with a bank statement
if all(line.statement_id for line in self.payment_id.move_line_ids.filtered(lambda r: r.id != self.id and r.account_id.internal_type=='liquidity')):
self.payment_id.state = 'reconciled'
for record in self:
if 'statement_id' in vals and record.payment_id:
# In case of an internal transfer, there are 2 liquidity move lines to match with a bank statement
if all(line.statement_id for line in record.payment_id.move_line_ids.filtered(lambda r: r.id != record.id and r.account_id.internal_type=='liquidity')):
record.payment_id.state = 'reconciled'
result = super(AccountMoveLine, self).write(vals)
if self._context.get('check_move_validity', True):
@@ -1362,6 +1375,9 @@ class AccountPartialReconcile(models.Model):
partial_rec_set[x] = None
#then, if the total debit and credit are equal, or the total amount in currency is 0, the reconciliation is full
if currency and aml_to_balance and (float_is_zero(total_amount_currency, precision_rounding=currency.rounding) or float_compare(total_debit, total_credit, precision_rounding=currency.rounding) == 0):
# If both condition are satisfied, no need to create exchange rate entry
if float_is_zero(total_amount_currency, precision_rounding=currency.rounding) and float_compare(total_debit, total_credit, precision_rounding=currency.rounding) == 0:
return res
#eventually create a journal entry to book the difference due to foreign currency's exchange rate that fluctuates
aml_id, partial_rec_id = partial_rec.create_exchange_rate_entry(aml_to_balance, total_debit - total_credit, total_amount_currency, currency, maxdate)
return res
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import time
from openerp import api, models
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
class ReportPartnerLedger(models.AbstractModel):
@@ -26,7 +28,12 @@ class ReportPartnerLedger(models.AbstractModel):
self.env.cr.execute(query, tuple(params))
res = self.env.cr.dictfetchall()
sum = 0.0
lang_code = self.env.context.get('lang') or 'en_US'
lang = self.env['res.lang']
lang_id = lang._lang_get(lang_code)
date_format = lang.browse(lang_id).date_format
for r in res:
r['date'] = datetime.strptime(r['date'], DEFAULT_SERVER_DATE_FORMAT).strftime(date_format)
r['displayed_name'] = '-'.join(
r[field_name] for field_name in ('move_name', 'ref', 'name')
if r[field_name] not in (None, '', '/')
@@ -36,7 +36,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
if (match) {
var barcode = match[1];
core.bus.trigger('barcode_scanned', barcode);
// Send the target in case there are several barcode widgets on the same page (e.g.
// registering the lot numbers in a stock picking)
core.bus.trigger('barcode_scanned', barcode, this.buffered_key_events[0].target);
// Dispatch a barcode_scanned DOM event to elements that have barcode_events="true" set.
if (this.buffered_key_events[0].target.getAttribute("barcode_events") === "true")
@@ -13,8 +13,13 @@ return {
init: function() {
var self = this;
this._super.apply(this, arguments);
this.__on_barcode_scanned = function () {
self.on_barcode_scanned.apply(self, arguments);
this.__on_barcode_scanned = function (barcode, target) {
// Handle the case where there are several barcode widgets on the same page. Since the
// event is global on the page, all barcode widgets will be triggered. However, we only
// want to keep the event on the target widget.
if ($.contains(target, self.el)) {
self.on_barcode_scanned.call(self, barcode);
}
};
this.start_listening();
// Handlers inside a View managed by a ViewManager only listen to barcode events while their view is displayed
+1 -1
View File
@@ -20,7 +20,7 @@ class crm_stage(osv.Model):
_name = "crm.stage"
_description = "Stage of case"
_rec_name = 'name'
_order = "sequence"
_order = "sequence, name, id"
_columns = {
'name': fields.char('Stage Name', required=True, translate=True),
+1 -1
View File
@@ -97,7 +97,7 @@ class SaleOrder(models.Model):
for order in self:
order.order_line._compute_tax_id()
name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New'))
name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, states={'draft': [('readonly', False)]}, index=True, default=lambda self: _('New'))
origin = fields.Char(string='Source Document', help="Reference of the document that generated this sales order request.")
client_order_ref = fields.Char(string='Customer Reference', copy=False)
+2 -1
View File
@@ -37,6 +37,7 @@ from openerp.modules import get_resource_path
from openerp.tools import topological_sort
from openerp.tools.translate import _
from openerp.tools import ustr
from openerp.tools.misc import str2bool
from openerp import http
from openerp.http import request, serialize_exception as _serialize_exception
from openerp.exceptions import AccessError
@@ -711,7 +712,7 @@ class Database(http.Controller):
def restore(self, master_pwd, backup_file, name, copy=False):
try:
data = base64.b64encode(backup_file.read())
request.session.proxy("db").restore(master_pwd, name, data, copy)
request.session.proxy("db").restore(master_pwd, name, data, str2bool(copy))
return http.local_redirect('/web/database/manager')
except Exception, e:
error = "Database restore error: %s" % e
+12 -3
View File
@@ -208,10 +208,19 @@
<input id="name" type="text" name="name" class="form-control" required="required"/>
</div>
<div class="form-group">
<div class="checkbox">
<label for="copy">This database might have been moved or copied.</label>
<p class="help-block">In order to avoid conflicts between databases, Odoo needs to know if this database was moved or copied.
If you don't know, answer "This database is a copy".</p>
<div class="radio">
<label>
<input name="copy" type="checkbox" class="pull-right" value="1">
<span>Generate a new database uuid.</span>
<input name="copy" type="radio" class="pull-right" value="true" checked="1">
This database is a copy
</label>
</div>
<div class="radio">
<label>
<input name="copy" type="radio" class="pull-right" value="false">
This database was moved
</label>
</div>
</div>
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 9.0c\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-01-28 12:13+0000\n"
"PO-Revision-Date: 2016-01-28 12:13+0000\n"
"POT-Creation-Date: 2016-06-17 08:01+0000\n"
"PO-Revision-Date: 2016-06-17 08:01+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -25,14 +25,21 @@ msgstr ""
#. module: web_settings_dashboard
#. openerp-web
#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:49
#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:56
#, python-format
msgid "Active User"
msgstr ""
#. module: web_settings_dashboard
#. openerp-web
#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:9
#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:57
#, python-format
msgid "Active Users"
msgstr ""
#. module: web_settings_dashboard
#. openerp-web
#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:42
#, python-format
msgid "App store"
msgstr ""
@@ -52,7 +52,9 @@
<t t-set="active_users" t-value="widget.data.active_users"></t>
<t t-set="pending_users" t-value="widget.data.pending_users"></t>
<t t-if="active_users">
<t t-esc="active_users"></t> Active User<t t-if="active_users > 1">s</t>
<t t-esc="active_users"></t>
<t t-if="active_users &lt;= 1"> Active User</t>
<t t-if="active_users &gt; 1">Active Users</t>
</t>
</div>
<div class="text-center">
@@ -157,7 +157,7 @@
<t t-foreach="channels" t-as="channel">
<div class="media">
<a class="pull-left" t-attf-href="/livechat/channel/#{ slug(channel)}">
<img t-att-src="'data:image/png;base64,' + channel.image_small" t-att-alt="channel.name" />
<img t-att-src="channel.image_small and ('data:image/png;base64,' + channel.image_small) or '/web/static/src/img/placeholder.png'" t-att-alt="channel.name" />
</a>
<div class="media-body">
<h4 class="media-heading"><t t-esc="channel.name"/></h4>
@@ -60,6 +60,7 @@ class website_account(http.Controller):
error_message = []
mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id"]
optional_billing_fields = ["zipcode", "state_id", "vat", "street"]
# Validation
for field_name in mandatory_billing_fields:
@@ -86,4 +87,9 @@ class website_account(http.Controller):
if [err for err in error.values() if err == 'missing']:
error_message.append(_('Some required fields are empty.'))
unknown = [k for k in data.iterkeys() if k not in mandatory_billing_fields + optional_billing_fields]
if unknown:
error['common'] = 'Unknown field'
error_message.append("Unknown field '%s'" % ','.join(unknown))
return error, error_message
-1
View File
@@ -228,7 +228,6 @@ class sale_order(osv.osv):
'website_description': line.website_description,
'state': 'draft',
'customer_lead': self._get_customer_lead(cr, uid, line.product_id.product_tmpl_id),
'delay': line.product_id.sale_delay
})
lines.append((0, 0, data))
options = []
+20
View File
@@ -0,0 +1,20 @@
Canada, 2016-06-09
Microcom agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Martin Malorni mmalorni@gmail.com https://github.com/mmalorni
List of contributors:
- Vincent Coll vincentgagnnoncoll@gmail.com https://github.com/vincentcoll
- Stéphane Le Cornec stephane.lecornec@gmail.com https://github.com/coleste
- Eric Lemire elemire@users.noreply.github.com https://github.com/elemire
- Martin Malorni mmalorni@gmail.com https://github.com/mmalorni
- Gregory Savoie gs.microcom@gmail.com https://github.com/gregsavoie
- Noreddine Ben Jillali nbj.microcom@gmail.com https://github.com/nbj-microcom
+15
View File
@@ -0,0 +1,15 @@
Germany, 2016-05-02
RIDING BYTES agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Ramon Bartl, rb@ridingbytes.com https://github.com/ridingbytes
List of contributors:
Ramon Bartl, rb@ridingbytes.com https://github.com/ramonski
+16
View File
@@ -0,0 +1,16 @@
Italy, 2016-02-23
Telnet Servizi Srl agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Telnet Data github@telnetservizi.it https://github.com/telnetdata
List of contributors:
Telnet Data github@telnetservizi.it https://github.com/telnetdata
Matteo Cantarutti cantarutti.matteo@live.it https://github.com/mteok
+11
View File
@@ -0,0 +1,11 @@
Finland, 2016-02-18
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Jarmo Kortetjärvi jarmo.kortetjarvi@gmail.com https://github.com/jarmokortetjarvi
+11
View File
@@ -0,0 +1,11 @@
Thailand, 2016-03-26
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Ross Golder ross@golder.org https://github.com/rossigee
+11
View File
@@ -0,0 +1,11 @@
Pakistan, 2016-05-05
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Sharjeel Ali Shaukat as_ns2002@hotmail.com https://github.com/salishaukat
+5
View File
@@ -102,6 +102,11 @@ def exp_duplicate_database(db_original_name, db_name):
_drop_conn(cr, db_original_name)
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
registry = openerp.modules.registry.RegistryManager.new(db_name)
with registry.cursor() as cr:
# if it's a copy of a database, force generation of a new dbuuid
registry['ir.config_parameter'].init(cr, force=True)
from_fs = openerp.tools.config.filestore(db_original_name)
to_fs = openerp.tools.config.filestore(db_name)
if os.path.exists(from_fs) and not os.path.exists(to_fs):
+9
View File
@@ -502,6 +502,15 @@ def mod10r(number):
report = codec[ (int(digit) + report) % 10 ]
return result + str((10 - report) % 10)
def str2bool(s, default=None):
s = ustr(s).lower()
y = 'y yes 1 true t on'.split()
n = 'n no 0 false f off'.split()
if s not in (y + n):
if default is None:
raise ValueError('Use 0/1/yes/no/true/false/on/off')
return bool(default)
return s in y
def human_size(sz):
"""