[MERGE] forward port branch saas-11.3 up to ff80efd71e

This commit is contained in:
Christophe Simonis
2018-08-06 19:59:53 +02:00
21 changed files with 105 additions and 41 deletions
+12 -3
View File
@@ -185,14 +185,15 @@ class account_abstract_payment(models.AbstractModel):
:param currency: If not specified, search a default currency on wizard/journal.
:return: The total amount to pay the invoices.
'''
# Get the payment currency
if not currency:
currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id
# Get the payment invoices
if not invoices:
invoices = self.invoice_ids
# Get the payment currency
if not currency:
currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id or invoices and invoices[0].currency_id
# Avoid currency rounding issues by summing the amounts according to the company_currency_id before
total = 0.0
groups = groupby(invoices, lambda i: i.currency_id)
@@ -210,6 +211,14 @@ class account_register_payments(models.TransientModel):
_inherit = 'account.abstract.payment'
_description = "Register payments on multiple invoices"
@api.onchange('journal_id')
def _onchange_journal(self):
res = super(account_register_payments, self)._onchange_journal()
active_ids = self._context.get('active_ids')
invoices = self.env['account.invoice'].browse(active_ids)
self.amount = abs(self._compute_payment_amount(invoices))
return res
@api.model
def default_get(self, fields):
rec = super(account_register_payments, self).default_get(fields)
+3 -1
View File
@@ -351,7 +351,9 @@ class AccountAssetAsset(models.Model):
def set_to_close(self):
move_ids = self._get_disposal_moves()
if move_ids:
self._return_disposal_view(move_ids)
return self._return_disposal_view(move_ids)
# Fallback, as if we just clicked on the smartbutton
return self.open_entries()
@api.multi
def set_to_draft(self):
+1 -1
View File
@@ -106,7 +106,7 @@ The purpose of this component is to listen to keypresses to detect barcodes, the
on core.bus inside a 'barcode_event'.
All keypress events are buffered until there is no more keypress during 50ms or a carriage return / tab is
inputted (because most barcode scanners use this as a suffix).
If the buffered keys looks like a barcode (match the the regexp /.{3,}[\n\r\t]*), an event is triggered :
If the buffered keys looks like a barcode, an event is triggered :
core.bus.trigger('barcode_scanned', barcode);
Otherwise, the keypresses are 'resent'. However, for security reasons, a keypress event programmatically
crafted doesn't trigger native browser behaviors. For this reason, BarcodeEvents doesn't intercept keypresses
+1 -1
View File
@@ -248,7 +248,7 @@ var BoardRenderer = FormRenderer.extend({
}
var view = _.find(action.views, function (descr) {
return descr[1] === params.viewType;
});
}) || [false, params.viewType];
return self.loadViews(action.res_model, context, [view])
.then(function (viewsInfo) {
var viewInfo = viewsInfo[params.viewType];
+1 -1
View File
@@ -24,7 +24,7 @@ class MailActivity(models.Model):
'default_res_id': self.env.context.get('default_res_id'),
'default_res_model': self.env.context.get('default_res_model'),
'default_name': self.summary or self.res_name,
'default_description': self.note and tools.html2plaintext(self.note) or '',
'default_description': self.note and tools.html2plaintext(self.note).strip() or '',
'default_activity_ids': [(6, 0, self.ids)],
}
return action
+16 -8
View File
@@ -149,14 +149,22 @@ class Team(models.Model):
tree_view_id = self.env.ref('crm.crm_case_tree_view_oppor').id
form_view_id = self.env.ref('crm.crm_case_form_view_oppor').id
kanb_view_id = self.env.ref('crm.crm_case_kanban_view_leads').id
action['views'] = [
[kanb_view_id, 'kanban'],
[tree_view_id, 'tree'],
[form_view_id, 'form'],
[False, 'graph'],
[False, 'calendar'],
[False, 'pivot']
]
mode_string = action.get('view_mode', '')
view_mode = mode_string.split(',') if mode_string else ['kanban', 'tree', 'form', 'graph', 'calendar', 'pivot']
dict_views = {
'kanban': kanb_view_id,
'tree': tree_view_id,
'form': form_view_id,
'graph': False,
'calendar': False,
'pivot': False,
}
# Sort the views according to the view mode of the action
# which can be modified via studio, or technical menu for that matter
action['views'] = [[dict_views.get(view_type, False), view_type] for view_type in view_mode]
action['context'] = action_context
return action
+1 -1
View File
@@ -81,7 +81,7 @@ class AccountAnalyticLine(models.Model):
vals['account_id'] = project.analytic_account_id.id
vals['company_id'] = project.analytic_account_id.company_id.id
if not project.analytic_account_id.active:
raise UserError(_('The project you are timesheeting on is not linked to a active analytic account. Please the project configuration.'))
raise UserError(_('The project you are timesheeting on is not linked to an active analytic account. Set one on the project configuration.'))
# employee implies user
if vals.get('employee_id') and not vals.get('user_id'):
employee = self.env['hr.employee'].browse(vals['employee_id'])
+2 -7
View File
@@ -48,7 +48,7 @@ class Project(models.Model):
allow_timesheets = values['allow_timesheets'] if 'allow_timesheets' in values else self.default_get(['allow_timesheets'])['allow_timesheets']
if allow_timesheets and not values.get('analytic_account_id'):
analytic_account = self.env['account.analytic.account'].create({
'name': values.get('name', _('Unkwon Analytic Account')),
'name': values.get('name', _('Unknown Analytic Account')),
'company_id': values.get('company_id', self.env.user.company_id.id),
'partner_id': values.get('partner_id'),
'active': True,
@@ -96,7 +96,7 @@ class Task(models.Model):
analytic_account_id = fields.Many2one('account.analytic.account', string="Analytic Account", related='project_id.analytic_account_id', readonly=True)
allow_timesheets = fields.Boolean("Allow timesheets", compute='_compute_allow_timesheets', help="Timesheets can be logged on this task.")
remaining_hours = fields.Float("Remaining Hours", compute='_compute_remaining_hours', inverse='_inverse_remaining_hours', help="Total remaining time, can be re-estimated periodically by the assignee of the task.")
remaining_hours = fields.Float("Remaining Hours", compute='_compute_remaining_hours', store=True, readonly=True, help="Total remaining time, can be re-estimated periodically by the assignee of the task.")
effective_hours = fields.Float("Hours Spent", compute='_compute_effective_hours', compute_sudo=True, store=True, help="Computed using the sum of the task work done.")
total_hours_spent = fields.Float("Total Hours", compute='_compute_total_hours_spent', store=True, help="Computed as: Time Spent + Sub-tasks Hours.")
progress = fields.Float("Progress", compute='_compute_progress_hours', store=True, group_operator="avg", help="Display progress of current task.")
@@ -130,11 +130,6 @@ class Task(models.Model):
for task in self:
task.remaining_hours = task.planned_hours - task.effective_hours - task.subtask_effective_hours
@api.onchange('remaining_hours')
def _inverse_remaining_hours(self):
for task in self:
task.planned_hours = task.remaining_hours + task.effective_hours + task.subtask_effective_hours
@api.depends('effective_hours', 'subtask_effective_hours')
def _compute_total_hours_spent(self):
for task in self:
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event
from . import mass_mailing
@@ -0,0 +1,12 @@
from odoo import models, api
class MassMailingCampaign(models.Model):
_inherit = "mail.mass_mailing"
@api.onchange('mailing_model_id', 'contact_list_ids')
def _onchange_model_and_list(self):
result = super(MassMailingCampaign, self)._onchange_model_and_list()
if self.mailing_model_name == 'event.registration' and self.mailing_domain == '[]':
self.mailing_domain = self.env.context.get('default_mailing_domain', '[]')
return result
+5 -5
View File
@@ -73,6 +73,11 @@
</div>
</group>
</group>
<group name="description_internal">
<group string="Description for Internal" colspan="4">
<field name="description" nolabel="1" placeholder="This note is only for internal purposes."/>
</group>
</group>
</page>
<page string="Sales" attrs="{'invisible':[('sale_ok','=',False)]}" name="sales">
<div name="pricelist_item" groups="product.group_product_pricelist">
@@ -109,11 +114,6 @@
<field name="packaging_ids" nolabel="1" context="{'default_product_id': product_variant_id,'tree_view_ref':'product.product_packaging_tree_view', 'form_view_ref':'product.product_packaging_form_view'}"/>
</group>
</page>
<group name="description_internal">
<group string="Description for Internal">
<field name="description" nolabel="1" placeholder="This note is only for internal purposes."/>
</group>
</group>
</notebook>
</sheet>
<div class="oe_chatter">
@@ -119,7 +119,7 @@ class PurchaseRequisition(models.Model):
def action_in_progress(self):
self.ensure_one()
if not all(obj.line_ids for obj in self):
raise UserError(_('You cannot confirm agreement because there is no product line.'))
raise UserError(_("You cannot confirm agreement '%s' because there is no product line.") % self.name)
if self.type_id.quantity_copy == 'none' and self.vendor_id:
for requisition_line in self.line_ids:
if requisition_line.price_unit <= 0.0:
+4
View File
@@ -78,6 +78,10 @@ class StockMove(models.Model):
vals['purchase_line_id'] = self.purchase_line_id.id
return vals
def _clean_merged(self):
super(StockMove, self)._clean_merged()
self.write({'created_purchase_line_id': False})
def _action_done(self):
res = super(StockMove, self)._action_done()
self.mapped('purchase_line_id').sudo()._update_received_qty()
+2 -2
View File
@@ -173,14 +173,14 @@ class TestSaleStock(TestSale):
# Check invoice
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" instead of "%s" after picking return' % self.so.invoice_status)
self.assertAlmostEqual(self.so.order_line[0].qty_delivered, 3.0, 'Sale Stock: delivered quantity should be 3.0 instead of "%s" after picking return' % self.so.order_line[0].qty_delivered)
self.assertAlmostEqual(self.so.order_line[0].qty_delivered, 3.0, msg='Sale Stock: delivered quantity should be 3.0 instead of "%s" after picking return' % self.so.order_line[0].qty_delivered)
# let's do an invoice with refunds
adv_wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=[self.so.id]).create({
'advance_payment_method': 'all',
})
adv_wiz.with_context(open_invoices=True).create_invoices()
self.inv_2 = self.so.invoice_ids.filtered(lambda r: r.state == 'draft')
self.assertAlmostEqual(self.inv_2.invoice_line_ids[0].quantity, 2.0, 'Sale Stock: refund quantity on the invoice should be 2.0 instead of "%s".' % self.inv_2.invoice_line_ids[0].quantity)
self.assertAlmostEqual(self.inv_2.invoice_line_ids[0].quantity, 2.0, msg='Sale Stock: refund quantity on the invoice should be 2.0 instead of "%s".' % self.inv_2.invoice_line_ids[0].quantity)
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" instead of "%s" after invoicing the return' % self.so.invoice_status)
def test_03_sale_stock_delivery_partial(self):
+11 -2
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from collections import OrderedDict
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo.tools.misc import split_every
@@ -318,9 +318,18 @@ class ProcurementGroup(models.Model):
orderpoints_noprefetch = orderpoints_noprefetch[1000:]
# Calculate groups that can be executed together
location_data = defaultdict(lambda: dict(products=self.env['product.product'], orderpoints=self.env['stock.warehouse.orderpoint'], groups=list()))
location_data = OrderedDict()
def makedefault():
return {
'products': self.env['product.product'],
'orderpoints': self.env['stock.warehouse.orderpoint'],
'groups': []
}
for orderpoint in orderpoints:
key = self._procurement_from_orderpoint_get_grouping_key([orderpoint.id])
location_data[key] = makedefault()
location_data[key]['products'] += orderpoint.product_id
location_data[key]['orderpoints'] += orderpoint
location_data[key]['groups'] = self._procurement_from_orderpoint_get_groups([orderpoint.id])
+5 -1
View File
@@ -565,6 +565,10 @@ class StockMove(models.Model):
move.package_level_id.id
]
def _clean_merged(self):
"""Cleanup hook used when merging moves"""
self.write({'propagate': False})
def _merge_moves(self, merge_into=False):
""" This method will, for each move in `self`, go up in their linked picking and try to
find in their existing moves a candidate into which we can merge the move.
@@ -602,7 +606,7 @@ class StockMove(models.Model):
if moves_to_unlink:
# We are using propagate to False in order to not cancel destination moves merged in moves[0]
moves_to_unlink.write({'propagate': False})
moves_to_unlink._clean_merged()
moves_to_unlink._action_cancel()
moves_to_unlink.sudo().unlink()
return (self | self.env['stock.move'].concat(*moves_to_merge)) - moves_to_unlink
@@ -142,6 +142,7 @@ var KanbanColumnProgressBar = Widget.extend({
// Display and animate the progress bars
var barNumber = 0;
var barMinWidth = 6; // In %
_.each(self.colors, function (val, key) {
var $bar = self.$bars[val];
var count = self.subgroupCounts && self.subgroupCounts[key] || 0;
@@ -168,16 +169,15 @@ var KanbanColumnProgressBar = Widget.extend({
$bar.addClass('o_bar_has_records');
// Make sure every bar that has records has some space
// and that everything adds up to 100%
var minWidth = 6*barNumber;
var maxWidth = 100 - minWidth;
var maxWidth = 100 - barMinWidth * barNumber;
self.$('.progress-bar.o_bar_has_records').css('max-width', maxWidth + '%');
self.$('.progress-bar.o_bar_has_records').css('min-width', minWidth + '%');
$bar.css('width', (count * 100 / self.groupCount) + '%');
barNumber++;
} else {
$bar.css('width', '');
}
});
this.$('.progress-bar.o_bar_has_records').css('min-width', barMinWidth + '%');
// Display and animate the counter number
var start = this.prevTotalCounterValue;
@@ -541,7 +541,7 @@ var PivotModel = AbstractModel.extend({
_loadData: function () {
var self = this;
var groupBys = [];
var rowGroupBys = this.data.groupedBy.length ? this.data.groupedBy : this.initialRowGroupBys;
var rowGroupBys = !_.isEmpty(this.data.groupedBy) ? this.data.groupedBy : this.initialRowGroupBys;
var colGroupBys = this.data.colGroupBys;
var measures = _.map(this.data.measures, function(measure) {
if (self.fields[measure].type === 'many2one') {
@@ -624,7 +624,7 @@ var PivotModel = AbstractModel.extend({
});
var index = 0;
var rowGroupBys = this.data.groupedBy.length ? this.data.groupedBy : this.initialRowGroupBys;
var rowGroupBys = !_.isEmpty(this.data.groupedBy) ? this.data.groupedBy : this.initialRowGroupBys;
var colGroupBys = this.data.colGroupBys;
var datapt, row, col, attrs, cell_value;
var main_row_header, main_col_header;
@@ -44,7 +44,7 @@ class WebsiteSaleDigital(CustomerPortal):
product_id = product.id
template = product.product_tmpl_id
att = Attachment.search_read(
domain=['|', '&', ('res_model', '=', product._name), ('res_id', '=', product_id), '&', ('res_model', '=', template._name), '&', ('res_id', '=', template.id), ('product_downloadable', '=', True)],
domain=['|', '&', ('res_model', '=', product._name), ('res_id', '=', product_id), '&', ('res_model', '=', template._name), ('res_id', '=', template.id), ('product_downloadable', '=', True)],
fields=['name', 'write_date'],
order='write_date desc',
)
@@ -0,0 +1,17 @@
Switzerland, 2018-07-31
International Business Hub 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,
Sandro Pignataro pignatarosandro@gmail.com https://github.com/Pigna74
List of contributors:
Guido Notari guido.notari@gmail.com https://github.com/gnotari
Matteo Bilotta byloth@gmail.com https://github.com/Byloth
+4 -1
View File
@@ -71,7 +71,10 @@ def _initialize_db(id, db_name, demo, lang, user_password, login='admin', countr
if country_code:
countries = env['res.country'].search([('code', 'ilike', country_code)])
if countries:
env['res.company'].browse(1).country_id = countries[0]
comp_local = {'country_id': countries[0].id}
if countries[0].currency_id:
comp_local['currency_id'] = countries[0].currency_id.id
env['res.company'].browse(1).write(comp_local)
# update admin's password and lang and login
values = {'password': user_password, 'lang': lang}