[MERGE] forward port branch 10.0 up to 849d8ffd21

This commit is contained in:
Christophe Simonis
2018-01-09 15:12:51 +01:00
17 changed files with 116 additions and 23 deletions
+4 -1
View File
@@ -283,7 +283,8 @@ class AccountChartTemplate(models.Model):
tmp1, tmp2 = self.parent_id._install_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, acc_ref=acc_ref, taxes_ref=taxes_ref) tmp1, tmp2 = self.parent_id._install_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, acc_ref=acc_ref, taxes_ref=taxes_ref)
acc_ref.update(tmp1) acc_ref.update(tmp1)
taxes_ref.update(tmp2) taxes_ref.update(tmp2)
tmp1, tmp2 = self._load_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, account_ref=acc_ref, taxes_ref=taxes_ref) # Ensure, even if individually, that everything is translated according to the company's language.
tmp1, tmp2 = self.with_context(lang=company.partner_id.lang)._load_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, account_ref=acc_ref, taxes_ref=taxes_ref)
acc_ref.update(tmp1) acc_ref.update(tmp1)
taxes_ref.update(tmp2) taxes_ref.update(tmp2)
return acc_ref, taxes_ref return acc_ref, taxes_ref
@@ -805,6 +806,8 @@ class WizardMultiChartsAccounts(models.TransientModel):
all the provided information to create the accounts, the banks, the journals, the taxes, the all the provided information to create the accounts, the banks, the journals, the taxes, the
accounting properties... accordingly for the chosen company. accounting properties... accordingly for the chosen company.
''' '''
# Ensure everything is translated consitingly to the company's language, not the user's one.
self = self.with_context(lang=self.company_id.partner_id.lang)
if len(self.env['account.account'].search([('company_id', '=', self.company_id.id)])) > 0: if len(self.env['account.account'].search([('company_id', '=', self.company_id.id)])) > 0:
# We are in a case where we already have some accounts existing, meaning that user has probably # We are in a case where we already have some accounts existing, meaning that user has probably
# created its own accounts and does not need a coa, so skip installation of coa. # created its own accounts and does not need a coa, so skip installation of coa.
@@ -68,8 +68,8 @@ class HrHolidaySummaryReport(models.AbstractModel):
for index in range(0, ((date_to - date_from).days + 1)): for index in range(0, ((date_to - date_from).days + 1)):
if date_from >= start_date and date_from <= end_date: if date_from >= start_date and date_from <= end_date:
res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name
count+=1
date_from += timedelta(1) date_from += timedelta(1)
count += abs(holiday.number_of_days)
self.sum = count self.sum = count
return res return res
@@ -164,6 +164,7 @@ class XmlDeclaration(models.TransientModel):
lines = self.env.cr.fetchall() lines = self.env.cr.fetchall()
invoicelines_ids = [rec[0] for rec in lines] invoicelines_ids = [rec[0] for rec in lines]
invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids) invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids)
for inv_line in invoicelines: for inv_line in invoicelines:
#Check type of transaction #Check type of transaction
@@ -189,6 +190,8 @@ class XmlDeclaration(models.TransientModel):
#comes from purchase #comes from purchase
po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
if po_lines: if po_lines:
if self._is_situation_triangular(company, po_line=po_lines):
continue
location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location()) location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location())
region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location) region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location)
if region_id: if region_id:
@@ -197,6 +200,8 @@ class XmlDeclaration(models.TransientModel):
#comes from sales #comes from sales
so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
if so_lines: if so_lines:
if self._is_situation_triangular(company, so_line=so_lines):
continue
saleorder = so_lines.order_id saleorder = so_lines.order_id
if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id: if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id:
exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code
@@ -286,3 +291,24 @@ class XmlDeclaration(models.TransientModel):
dim = ET.SubElement(item, 'Dim') dim = ET.SubElement(item, 'Dim')
dim.set('prop', prop) dim.set('prop', prop)
dim.text = value dim.text = value
def _is_situation_triangular(self, company, po_line=False, so_line=False):
# Ignoring what is purchased and sold by us with a dropshipping route
# outside of our country, or completely within it
# https://www.nbb.be/doc/dq/f_pdf_ex/intra2017fr.pdf (§ 4.x)
dropship_pick_type = self.env.ref('stock_dropshipping.picking_type_dropship', raise_if_not_found=False)
if not dropship_pick_type:
return False
stock_move_domain = [('picking_type_id', '=', dropship_pick_type.id)]
if po_line:
stock_move_domain.append(('purchase_line_id', '=', po_line.id))
if so_line:
stock_move_domain.append(('procurement_id.sale_line_id', '=', so_line.id))
stock_move = self.env['stock.move'].search(stock_move_domain, limit=1)
return stock_move and (
(stock_move.partner_id.country_id.code != company.country_id.code and
stock_move.picking_partner_id.country_id.code != company.country_id.code) or
(stock_move.partner_id.country_id.code == company.country_id.code and
stock_move.picking_partner_id.country_id.code == company.country_id.code))
+9 -1
View File
@@ -263,6 +263,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
var def3 = this.extended_composer.appendTo(this.$('.o_mail_chat_content')); var def3 = this.extended_composer.appendTo(this.$('.o_mail_chat_content'));
var def4 = this.searchview.appendTo($("<div>")).then(function () { var def4 = this.searchview.appendTo($("<div>")).then(function () {
self.$searchview_buttons = self.searchview.$buttons.contents(); self.$searchview_buttons = self.searchview.$buttons.contents();
// manually call do_search to generate the initial domain and filter
// the messages in the default channel
self.searchview.do_search();
}); });
this.render_sidebar(); this.render_sidebar();
@@ -583,7 +586,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
}); });
this.domain = result.domain; this.domain = result.domain;
this.fetch_and_render_thread(); if (this.channel) {
// initially (when do_search is called manually), there is no
// channel set yet, so don't try to fetch and render the thread as
// this will be done as soon as the default channel is set
this.fetch_and_render_thread();
}
}, },
on_post_message: function (message) { on_post_message: function (message) {
+2 -2
View File
@@ -282,7 +282,7 @@ class StockMove(models.Model):
preferred_domain_list = [preferred_domain] + [fallback_domain] + [fallback_domain2] preferred_domain_list = [preferred_domain] + [fallback_domain] + [fallback_domain2]
if move.has_tracking == 'none': if move.has_tracking == 'none':
quants = quant_obj.quants_get_preferred_domain(move.product_qty, move, domain=main_domain, preferred_domain_list=preferred_domain_list) quants = quant_obj.quants_get_preferred_domain(move.product_qty, move, domain=main_domain, preferred_domain_list=preferred_domain_list)
self.env['stock.quant'].quants_move(quants, move, move.location_dest_id) self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, owner_id=move.restrict_partner_id.id)
else: else:
for movelot in move.active_move_lot_ids: for movelot in move.active_move_lot_ids:
if float_compare(movelot.quantity_done, 0, precision_rounding=rounding) > 0: if float_compare(movelot.quantity_done, 0, precision_rounding=rounding) > 0:
@@ -290,7 +290,7 @@ class StockMove(models.Model):
raise UserError(_('You need to supply a lot/serial number.')) raise UserError(_('You need to supply a lot/serial number.'))
qty = move.product_uom._compute_quantity(movelot.quantity_done, move.product_id.uom_id) qty = move.product_uom._compute_quantity(movelot.quantity_done, move.product_id.uom_id)
quants = quant_obj.quants_get_preferred_domain(qty, move, lot_id=movelot.lot_id.id, domain=main_domain, preferred_domain_list=preferred_domain_list) quants = quant_obj.quants_get_preferred_domain(qty, move, lot_id=movelot.lot_id.id, domain=main_domain, preferred_domain_list=preferred_domain_list)
self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, lot_id = movelot.lot_id.id) self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, lot_id = movelot.lot_id.id, owner_id=move.restrict_partner_id.id)
moves_to_unreserve |= move moves_to_unreserve |= move
# Next move in production order # Next move in production order
if move.move_dest_id and move.move_dest_id.state not in ('done', 'cancel'): if move.move_dest_id and move.move_dest_id.state not in ('done', 'cancel'):
+8 -4
View File
@@ -1159,11 +1159,15 @@ class ProductCategory(models.Model):
class MailComposeMessage(models.TransientModel): class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message' _inherit = 'mail.compose.message'
@api.multi
def mail_purchase_order_on_send(self):
if not self.filtered('subtype_id.internal'):
order = self.env['purchase.order'].browse(self._context['default_res_id'])
if order.state == 'draft':
order.state = 'sent'
@api.multi @api.multi
def send_mail(self, auto_commit=False): def send_mail(self, auto_commit=False):
if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'): if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'):
if not self.filtered('subtype_id.internal'): self.mail_purchase_order_on_send()
order = self.env['purchase.order'].browse([self._context['default_res_id']])
if order.state == 'draft':
order.state = 'sent'
return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit) return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit)
@@ -59,7 +59,7 @@
<field name="inherit_id" ref="account.invoice_supplier_form"/> <field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="reference" position="after" > <field name="reference" position="after" >
<field name="purchase_id" attrs="{'invisible': [('state', '=', 'purchase')]}" class="oe_edit_only" <field name="purchase_id" attrs="{'readonly': [('state','not in',['draft'])], 'invisible': [('state', '=', 'purchase')]}" class="oe_edit_only"
options="{'no_create': True}"/> options="{'no_create': True}"/>
</field> </field>
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='company_id']" position="after"> <xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='company_id']" position="after">
+18
View File
@@ -733,6 +733,18 @@ class SaleOrderLine(models.Model):
msg += _("Invoiced Quantity") + ": %s <br/>" % (line.qty_invoiced,) msg += _("Invoiced Quantity") + ": %s <br/>" % (line.qty_invoiced,)
msg += "</ul>" msg += "</ul>"
order.message_post(body=msg) order.message_post(body=msg)
# Prevent writing on a locked SO.
protected_fields = self._get_protected_fields()
if 'done' in self.mapped('order_id.state') and any(f in values.keys() for f in protected_fields):
fields = self.env['ir.model.fields'].search([
('name', 'in', protected_fields), ('model', '=', self._name)
])
raise UserError(
_('It is forbidden to modify the following fields in a locked order:\n%s')
% '\n'.join(fields.mapped('field_description'))
)
result = super(SaleOrderLine, self).write(values) result = super(SaleOrderLine, self).write(values)
if lines: if lines:
lines._action_procurement_create() lines._action_procurement_create()
@@ -991,6 +1003,12 @@ class SaleOrderLine(models.Model):
return product[field_name] * uom_factor * cur_factor, currency_id.id return product[field_name] * uom_factor * cur_factor, currency_id.id
def _get_protected_fields(self):
return [
'product_id', 'name', 'price_unit', 'product_uom', 'product_uom_qty',
'tax_id', 'analytic_tag_ids'
]
@api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id') @api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id')
def _onchange_discount(self): def _onchange_discount(self):
self.discount = 0.0 self.discount = 0.0
+16
View File
@@ -15,6 +15,22 @@ msgstr ""
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#. module: stock
#: code:addons/stock/models/stock_move.py:230
#, python-format
msgid "\n"
"\n"
"%s --> Product UoM is %s (%s) - Move UoM is %s (%s)"
msgstr ""
#. module: stock
#: code:addons/stock/models/stock_move.py:231
#, python-format
msgid "\n"
"\n"
"Blocking: %s"
msgstr ""
#. module: stock #. module: stock
#: model:ir.model.fields,help:stock.field_stock_config_settings_module_stock_dropshipping #: model:ir.model.fields,help:stock.field_stock_config_settings_module_stock_dropshipping
msgid "\n" msgid "\n"
+4 -2
View File
@@ -223,10 +223,12 @@ class StockMove(models.Model):
@api.constrains('product_uom') @api.constrains('product_uom')
def _check_uom(self): def _check_uom(self):
moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id.id != move.product_uom.category_id.id) moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id != move.product_uom.category_id)
if moves_error: if moves_error:
user_warning = _('You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.') user_warning = _('You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.')
user_warning += '\n\nBlocking: %s' % ' ,'.join(moves_error.mapped('name')) for move in moves_error:
user_warning += _('\n\n%s --> Product UoM is %s (%s) - Move UoM is %s (%s)') % (move.product_id.display_name, move.product_id.uom_id.name, move.product_id.uom_id.category_id.name, move.product_uom.name, move.product_uom.category_id.name)
user_warning += _('\n\nBlocking: %s') % ' ,'.join(moves_error.mapped('name'))
raise UserError(user_warning) raise UserError(user_warning)
@api.model_cr @api.model_cr
+6 -2
View File
@@ -427,6 +427,7 @@ class Warehouse(models.Model):
self.Routing(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id), self.Routing(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id),
self.Routing(warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id), self.Routing(warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id),
self.Routing(warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id)], self.Routing(warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id)],
'company_id': warehouse.company_id.id,
}) for warehouse in self) }) for warehouse in self)
@api.multi @api.multi
@@ -436,6 +437,7 @@ class Warehouse(models.Model):
'product_categ_selectable': True, 'product_categ_selectable': True,
'product_selectable': False, 'product_selectable': False,
'sequence': 10, 'sequence': 10,
'company_id': self.company_id.id,
} }
@api.model @api.model
@@ -455,7 +457,8 @@ class Warehouse(models.Model):
'product_selectable': True, 'product_selectable': True,
'product_categ_selectable': True, 'product_categ_selectable': True,
'supplied_wh_id': self.id, 'supplied_wh_id': self.id,
'supplier_wh_id': supplier_warehouse.id} 'supplier_wh_id': supplier_warehouse.id,
'company_id': self.company_id.id}
def _get_crossdock_route_values(self): def _get_crossdock_route_values(self):
return { return {
@@ -464,7 +467,8 @@ class Warehouse(models.Model):
'product_selectable': True, 'product_selectable': True,
'product_categ_selectable': True, 'product_categ_selectable': True,
'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step', 'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step',
'sequence': 20} 'sequence': 20,
'company_id': self.company_id.id}
# Pull / Push tools # Pull / Push tools
# ------------------------------------------------------------ # ------------------------------------------------------------
+1
View File
@@ -116,6 +116,7 @@
<pivot string="Inventory"> <pivot string="Inventory">
<field name="location_id" type="row"/> <field name="location_id" type="row"/>
<field name="qty" type="measure"/> <field name="qty" type="measure"/>
<field name="inventory_value" type="measure"/>
</pivot> </pivot>
</field> </field>
</record> </record>
+3 -1
View File
@@ -97,7 +97,9 @@ class ProductProduct(models.Model):
""" Changes the Standard Price of Product and creates an account move accordingly.""" """ Changes the Standard Price of Product and creates an account move accordingly."""
AccountMove = self.env['account.move'] AccountMove = self.env['account.move']
locations = self.env['stock.location'].search([('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id)]) quant_locs = self.env['stock.quant'].sudo().read_group([('product_id', 'in', self.ids)], ['location_id'], ['location_id'])
quant_loc_ids = [loc['location_id'][0] for loc in quant_locs]
locations = self.env['stock.location'].search([('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id), ('id', 'in', quant_loc_ids)])
product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self} product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self}
+11 -4
View File
@@ -442,7 +442,7 @@ class Post(models.Model):
elif post.parent_id and not post.can_answer: elif post.parent_id and not post.can_answer:
raise KarmaError('Not enough karma to answer to a question') raise KarmaError('Not enough karma to answer to a question')
if not post.parent_id and not post.can_post: if not post.parent_id and not post.can_post:
post.state = 'pending' post.sudo().state = 'pending'
# add karma for posting new questions # add karma for posting new questions
if not post.parent_id and post.state == 'active': if not post.parent_id and post.state == 'active':
@@ -471,11 +471,18 @@ class Post(models.Model):
@api.multi @api.multi
def write(self, vals): def write(self, vals):
trusted_keys = ['active', 'is_correct', 'tag_ids'] # fields where security is checked manually
if 'content' in vals: if 'content' in vals:
vals['content'] = self._update_content(vals['content'], self.forum_id.id) vals['content'] = self._update_content(vals['content'], self.forum_id.id)
if 'state' in vals: if 'state' in vals:
if vals['state'] in ['active', 'close'] and any(not post.can_close for post in self): if vals['state'] in ['active', 'close']:
raise KarmaError('Not enough karma to close or reopen a post.') if any(not post.can_close for post in self):
raise KarmaError('Not enough karma to close or reopen a post.')
trusted_keys += ['state', 'closed_uid', 'closed_date', 'closed_reason_id']
elif vals['state'] == 'flagged':
if any(not post.can_flag for post in self):
raise KarmaError('Not enough karma to flag a post.')
trusted_keys += ['state', 'flag_user_id']
if 'active' in vals: if 'active' in vals:
if any(not post.can_unlink for post in self): if any(not post.can_unlink for post in self):
raise KarmaError('Not enough karma to delete or reactivate a post') raise KarmaError('Not enough karma to delete or reactivate a post')
@@ -492,7 +499,7 @@ class Post(models.Model):
tag_ids = set(tag.get('id') for tag in self.resolve_2many_commands('tag_ids', vals['tag_ids'])) tag_ids = set(tag.get('id') for tag in self.resolve_2many_commands('tag_ids', vals['tag_ids']))
if any(set(post.tag_ids) != tag_ids for post in self) and any(self.env.user.karma < post.forum_id.karma_edit_retag for post in self): if any(set(post.tag_ids) != tag_ids for post in self) and any(self.env.user.karma < post.forum_id.karma_edit_retag for post in self):
raise KarmaError(_('Not enough karma to retag.')) raise KarmaError(_('Not enough karma to retag.'))
if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id', 'tag_ids'] for key in vals.keys()) and any(not post.can_edit for post in self): if any(key not in trusted_keys for key in vals) and any(not post.can_edit for post in self):
raise KarmaError('Not enough karma to edit a post.') raise KarmaError('Not enough karma to edit a post.')
res = super(Post, self).write(vals) res = super(Post, self).write(vals)
+3 -3
View File
@@ -5,9 +5,9 @@
<div> <div>
<div t-if="'karma' in fields" class='css_editable_mode_hidden'> <div t-if="'karma' in fields" class='css_editable_mode_hidden'>
<div t-if="options.get('UserBio')"> <div t-if="options.get('UserBio')">
<span t-field="object.company_id.name" class="o_forum_tooltip_line"/><br/> <span t-field="object.company_name" class="o_forum_tooltip_line"/><br/>
<a t-att-href="object.company_id.website" t-if="object.company_id.website"> <a t-att-href="object.website" t-if="object.website">
<span t-field="object.company_id.website" class="o_forum_tooltip_line"/> <span t-field="object.website" class="o_forum_tooltip_line"/>
</a> </a>
</div> </div>
<b><t t-esc="object.karma"/></b> <b><t t-esc="object.karma"/></b>
@@ -12,6 +12,7 @@ class PaymentTransaction(models.Model):
# link with the sales order # link with the sales order
sale_order_id = fields.Many2one('sale.order', string='Sales Order', auto_join=True) sale_order_id = fields.Many2one('sale.order', string='Sales Order', auto_join=True)
so_state = fields.Selection('sale.order', string='Sale Order State', related='sale_order_id.state')
def _generate_and_pay_invoice(self, tx, acquirer_name): def _generate_and_pay_invoice(self, tx, acquirer_name):
tx.sale_order_id._force_lines_to_invoice_policy_order() tx.sale_order_id._force_lines_to_invoice_policy_order()
+2 -1
View File
@@ -252,7 +252,8 @@ class SaleOrderOption(models.Model):
order_line = order.order_line.filtered(lambda line: line.product_id == self.product_id) order_line = order.order_line.filtered(lambda line: line.product_id == self.product_id)
if order_line: if order_line:
order_line[0].product_uom_qty += 1 order_line = order_line[0]
order_line.product_uom_qty += 1
else: else:
vals = { vals = {
'price_unit': self.price_unit, 'price_unit': self.price_unit,