[MERGE] forward port branch saas-15 up to 38b201abce
This commit is contained in:
@@ -286,7 +286,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)
|
||||
acc_ref.update(tmp1)
|
||||
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)
|
||||
taxes_ref.update(tmp2)
|
||||
return acc_ref, taxes_ref
|
||||
@@ -835,6 +836,8 @@ class WizardMultiChartsAccounts(models.TransientModel):
|
||||
all the provided information to create the accounts, the banks, the journals, the taxes, the
|
||||
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:
|
||||
# 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.
|
||||
|
||||
@@ -68,8 +68,8 @@ class HrHolidaySummaryReport(models.AbstractModel):
|
||||
for index in range(0, ((date_to - date_from).days + 1)):
|
||||
if date_from >= start_date and date_from <= end_date:
|
||||
res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name
|
||||
count+=1
|
||||
date_from += timedelta(1)
|
||||
count += abs(holiday.number_of_days)
|
||||
self.sum = count
|
||||
return res
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ class XmlDeclaration(models.TransientModel):
|
||||
lines = self.env.cr.fetchall()
|
||||
invoicelines_ids = [rec[0] for rec in lines]
|
||||
invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids)
|
||||
|
||||
for inv_line in invoicelines:
|
||||
|
||||
#Check type of transaction
|
||||
@@ -189,6 +190,8 @@ class XmlDeclaration(models.TransientModel):
|
||||
#comes from purchase
|
||||
po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
|
||||
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())
|
||||
region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location)
|
||||
if region_id:
|
||||
@@ -197,6 +200,8 @@ class XmlDeclaration(models.TransientModel):
|
||||
#comes from sales
|
||||
so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
|
||||
if so_lines:
|
||||
if self._is_situation_triangular(company, so_line=so_lines):
|
||||
continue
|
||||
saleorder = so_lines.order_id
|
||||
if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id:
|
||||
exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code
|
||||
@@ -286,3 +291,24 @@ class XmlDeclaration(models.TransientModel):
|
||||
dim = ET.SubElement(item, 'Dim')
|
||||
dim.set('prop', prop)
|
||||
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))
|
||||
|
||||
@@ -251,6 +251,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
||||
var def3 = this.extended_composer.appendTo(this.$('.o_mail_chat_content'));
|
||||
var def4 = this.searchview.appendTo($("<div>")).then(function () {
|
||||
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.renderSidebar();
|
||||
@@ -681,7 +684,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
||||
contexts: [session.user_context],
|
||||
});
|
||||
this.domain = result.domain;
|
||||
this.fetch_and_render_thread();
|
||||
if (this.channel) {
|
||||
// initially (when _onSearch 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();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -21,10 +21,7 @@
|
||||
<strong>
|
||||
<span t-attf-class="o_activity_date o_activity_color_#{activity.state}"><t t-esc="activity.label_delay" /> (<t t-esc="activity.date_deadline"/>) </span>
|
||||
</strong>
|
||||
planned by <t t-esc="activity.create_uid[1]" />
|
||||
<t t-if="activity.create_uid[0] != activity.user_id[0]">
|
||||
for <t t-esc="activity.user_id[1]" />
|
||||
</t>
|
||||
planned by <t t-esc="activity.user_id[1]" />
|
||||
- <t t-esc="activity.time_ago"/>
|
||||
<div class="pull-right">
|
||||
<a href="#" class="o_activity_edit o_activity_link" t-att-data-activity-id="activity.id">Edit</a> |
|
||||
|
||||
@@ -282,7 +282,7 @@ class StockMove(models.Model):
|
||||
preferred_domain_list = [preferred_domain] + [fallback_domain] + [fallback_domain2]
|
||||
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)
|
||||
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:
|
||||
for movelot in move.active_move_lot_ids:
|
||||
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.'))
|
||||
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)
|
||||
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
|
||||
# Next move in production order
|
||||
if move.move_dest_id and move.move_dest_id.state not in ('done', 'cancel'):
|
||||
|
||||
@@ -1168,11 +1168,15 @@ class ProductCategory(models.Model):
|
||||
class MailComposeMessage(models.TransientModel):
|
||||
_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
|
||||
def send_mail(self, auto_commit=False):
|
||||
if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'):
|
||||
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'
|
||||
self.mail_purchase_order_on_send()
|
||||
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="arch" type="xml">
|
||||
<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}"/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='company_id']" position="after">
|
||||
|
||||
@@ -737,6 +737,18 @@ class SaleOrderLine(models.Model):
|
||||
msg += _("Invoiced Quantity") + ": %s <br/>" % (line.qty_invoiced,)
|
||||
msg += "</ul>"
|
||||
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)
|
||||
if lines:
|
||||
lines._action_procurement_create()
|
||||
@@ -995,6 +1007,12 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
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')
|
||||
def _onchange_discount(self):
|
||||
self.discount = 0.0
|
||||
|
||||
@@ -76,12 +76,14 @@ class SaleAdvancePaymentInv(models.TransientModel):
|
||||
|
||||
if self.amount <= 0.00:
|
||||
raise UserError(_('The value of the down payment amount must be positive.'))
|
||||
context = {'lang': order.partner_id.lang}
|
||||
if self.advance_payment_method == 'percentage':
|
||||
amount = order.amount_untaxed * self.amount / 100
|
||||
name = _("Down payment of %s%%") % (self.amount,)
|
||||
else:
|
||||
amount = self.amount
|
||||
name = _('Down Payment')
|
||||
del context
|
||||
taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
|
||||
if order.fiscal_position_id and taxes:
|
||||
tax_ids = order.fiscal_position_id.map_tax(taxes).ids
|
||||
@@ -152,6 +154,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
|
||||
tax_ids = order.fiscal_position_id.map_tax(taxes).ids
|
||||
else:
|
||||
tax_ids = taxes.ids
|
||||
context = {'lang': order.partner_id.lang}
|
||||
so_line = sale_line_obj.create({
|
||||
'name': _('Advance: %s') % (time.strftime('%m %Y'),),
|
||||
'price_unit': amount,
|
||||
@@ -163,6 +166,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
|
||||
'tax_id': [(6, 0, tax_ids)],
|
||||
'is_downpayment': True,
|
||||
})
|
||||
del context
|
||||
self._create_invoice(order, so_line, amount)
|
||||
if self._context.get('open_invoices', False):
|
||||
return sale_orders.action_view_invoice()
|
||||
|
||||
@@ -15,6 +15,22 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: \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
|
||||
#: code:addons/stock/models/stock_move.py:214
|
||||
#, python-format
|
||||
|
||||
@@ -223,10 +223,12 @@ class StockMove(models.Model):
|
||||
|
||||
@api.constrains('product_uom')
|
||||
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:
|
||||
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)
|
||||
|
||||
@api.model_cr
|
||||
|
||||
@@ -457,6 +457,7 @@ class Warehouse(models.Model):
|
||||
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_output_stock_loc_id, customer_loc, warehouse.out_type_id)],
|
||||
'company_id': warehouse.company_id.id,
|
||||
}) for warehouse in self)
|
||||
|
||||
@api.multi
|
||||
@@ -466,6 +467,7 @@ class Warehouse(models.Model):
|
||||
'product_categ_selectable': True,
|
||||
'product_selectable': False,
|
||||
'sequence': 10,
|
||||
'company_id': self.company_id.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
@@ -485,7 +487,8 @@ class Warehouse(models.Model):
|
||||
'product_selectable': True,
|
||||
'product_categ_selectable': True,
|
||||
'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):
|
||||
return {
|
||||
@@ -494,7 +497,8 @@ class Warehouse(models.Model):
|
||||
'product_selectable': True,
|
||||
'product_categ_selectable': True,
|
||||
'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
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
<pivot string="Inventory">
|
||||
<field name="location_id" type="row"/>
|
||||
<field name="qty" type="measure"/>
|
||||
<field name="inventory_value" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -97,7 +97,9 @@ class ProductProduct(models.Model):
|
||||
""" Changes the Standard Price of Product and creates an account move accordingly."""
|
||||
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}
|
||||
|
||||
|
||||
@@ -442,7 +442,7 @@ class Post(models.Model):
|
||||
elif post.parent_id and not post.can_answer:
|
||||
raise KarmaError('Not enough karma to answer to a question')
|
||||
if not post.parent_id and not post.can_post:
|
||||
post.state = 'pending'
|
||||
post.sudo().state = 'pending'
|
||||
|
||||
# add karma for posting new questions
|
||||
if not post.parent_id and post.state == 'active':
|
||||
@@ -471,11 +471,18 @@ class Post(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
trusted_keys = ['active', 'is_correct', 'tag_ids'] # fields where security is checked manually
|
||||
if 'content' in vals:
|
||||
vals['content'] = self._update_content(vals['content'], self.forum_id.id)
|
||||
if 'state' in vals:
|
||||
if vals['state'] in ['active', 'close'] and any(not post.can_close for post in self):
|
||||
raise KarmaError('Not enough karma to close or reopen a post.')
|
||||
if vals['state'] in ['active', 'close']:
|
||||
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 any(not post.can_unlink for post in self):
|
||||
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']))
|
||||
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.'))
|
||||
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.')
|
||||
|
||||
res = super(Post, self).write(vals)
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<div>
|
||||
<div t-if="'karma' in fields" class='css_editable_mode_hidden'>
|
||||
<div t-if="options.get('UserBio')">
|
||||
<span t-field="object.company_id.name" class="o_forum_tooltip_line"/><br/>
|
||||
<a t-att-href="object.company_id.website" t-if="object.company_id.website">
|
||||
<span t-field="object.company_id.website" class="o_forum_tooltip_line"/>
|
||||
<span t-field="object.company_name" class="o_forum_tooltip_line"/><br/>
|
||||
<a t-att-href="object.website" t-if="object.website">
|
||||
<span t-field="object.website" class="o_forum_tooltip_line"/>
|
||||
</a>
|
||||
</div>
|
||||
<b><t t-esc="object.karma"/></b>
|
||||
|
||||
@@ -12,6 +12,7 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
# link with the sales order
|
||||
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):
|
||||
tx.sale_order_id._force_lines_to_invoice_policy_order()
|
||||
|
||||
@@ -263,7 +263,8 @@ class SaleOrderOption(models.Model):
|
||||
|
||||
order_line = order.order_line.filtered(lambda line: line.product_id == self.product_id)
|
||||
if order_line:
|
||||
order_line[0].product_uom_qty += 1
|
||||
order_line = order_line[0]
|
||||
order_line.product_uom_qty += 1
|
||||
else:
|
||||
vals = {
|
||||
'price_unit': self.price_unit,
|
||||
|
||||
Reference in New Issue
Block a user