[MERGE] forward port branch 11.0 up to 984a99611e

This commit is contained in:
Christophe Simonis
2019-01-23 18:15:18 +01:00
31 changed files with 389 additions and 131 deletions
+13 -4
View File
@@ -263,9 +263,6 @@ class AccountMove(models.Model):
journal_id=journal_id,
auto=auto)
reversed_moves |= reversed_move
#unreconcile all lines reversed
aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity')
aml.remove_move_reconcile()
#reconcile together the reconcilable (or the liquidity aml) and their newly created counterpart
for account in set([x.account_id for x in aml]):
to_rec = aml.filtered(lambda y: y.account_id == account)
@@ -283,6 +280,18 @@ class AccountMove(models.Model):
def open_reconcile_view(self):
return self.line_ids.open_reconcile_view()
# FIXME: Clarify me and change me in master
@api.multi
def action_duplicate(self):
self.ensure_one()
action = self.env.ref('account.action_move_journal_line').read()[0]
action['target'] = 'inline'
action['context'] = dict(self.env.context)
action['context']['view_no_maturity'] = False
action['views'] = [(self.env.ref('account.view_move_form').id, 'form')]
action['res_id'] = self.copy().id
return action
@api.model
def _run_reverses_entries(self):
''' This method is called from a cron job. '''
@@ -467,7 +476,7 @@ class AccountMoveLine(models.Model):
index=True, store=True, copy=False) # related is required
blocked = fields.Boolean(string='No Follow-up', default=False,
help="You can check this box to mark this journal item as a litigation with the associated partner")
date_maturity = fields.Date(string='Due date', index=True, required=True,
date_maturity = fields.Date(string='Due date', index=True, required=True, copy=False,
help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.")
date = fields.Date(related='move_id.date', string='Date', index=True, store=True, copy=False) # related is required
analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines")
+1 -1
View File
@@ -204,7 +204,7 @@ class ResPartner(models.Model):
@api.multi
def _credit_debit_get(self):
tables, where_clause, where_params = self.env['account.move.line']._query_get()
tables, where_clause, where_params = self.env['account.move.line'].with_context(company_id=self.env.user.company_id.id)._query_get()
where_params = [tuple(self.ids)] + where_params
if where_clause:
where_clause = 'AND ' + where_clause
@@ -159,4 +159,8 @@ var ShowPaymentLineWidget = AbstractField.extend({
field_registry.add('payment', ShowPaymentLineWidget);
return {
ShowPaymentLineWidget: ShowPaymentLineWidget
};
});
+2 -1
View File
@@ -1502,10 +1502,11 @@
<field name="name">account.move.form</field>
<field name="model">account.move</field>
<field name="arch" type="xml">
<form string="Account Entry">
<form string="Account Entry" duplicate="false">
<header>
<button name="post" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/>
<button name="%(action_view_account_move_reversal)d" states="posted" string="Reverse Entry" type="action" groups="account.group_account_invoice"/>
<button name="action_duplicate" string="Duplicate" type="object" groups="account.group_account_invoice"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
+3 -2
View File
@@ -15,13 +15,14 @@ class ResPartner(models.Model):
self.env.cr.execute("""
SELECT
U.partner_id as id,
CASE WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away'
CASE WHEN age(now() AT TIME ZONE 'UTC', max(B.last_poll)) > interval %s THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', max(B.last_presence)) > interval %s THEN 'away'
ELSE 'online'
END as status
FROM bus_presence B
JOIN res_users U ON B.user_id = U.id
WHERE U.partner_id IN %s AND U.active = 't'
GROUP BY U.partner_id
""", ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids)))
res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall()))
for partner in self:
+1 -1
View File
@@ -70,7 +70,7 @@ class DeliveryCarrier(models.Model):
def install_more_provider(self):
return {
'name': 'New Providers',
'view_mode': 'kanban',
'view_mode': 'kanban,form',
'res_model': 'ir.module.module',
'domain': [['name', 'ilike', 'delivery_']],
'type': 'ir.actions.act_window',
+123 -105
View File
@@ -103,35 +103,140 @@ class XmlDeclaration(models.TransientModel):
'res_id': self.id,
}
def _build_intrastat_line(self, numlgn, item, linekey, amounts, dispatchmode, extendedmode):
self._set_Dim(item, 'EXSEQCODE', text_type(numlgn))
self._set_Dim(item, 'EXTRF', text_type(linekey.EXTRF))
self._set_Dim(item, 'EXCNT', text_type(linekey.EXCNT))
self._set_Dim(item, 'EXTTA', text_type(linekey.EXTTA))
self._set_Dim(item, 'EXREG', text_type(linekey.EXREG))
self._set_Dim(item, 'EXTGO', text_type(linekey.EXGO))
if extendedmode:
self._set_Dim(item, 'EXTPC', text_type(linekey.EXTPC))
self._set_Dim(item, 'EXDELTRM', text_type(linekey.EXDELTRM))
self._set_Dim(item, 'EXTXVAL', text_type(round(amounts[0], 0)).replace(".", ","))
self._set_Dim(item, 'EXWEIGHT', text_type(round(amounts[1], 0)).replace(".", ","))
self._set_Dim(item, 'EXUNITS', text_type(round(amounts[2], 0)).replace(".", ","))
def _get_intrastat_linekey(self, declcode, inv_line, dispatchmode, extendedmode):
IntrastatRegion = self.env['l10n_be_intrastat.region']
company = self.company_id
#Check type of transaction
if inv_line.intrastat_transaction_id:
extta = inv_line.intrastat_transaction_id.code
else:
extta = "1"
#Check country
if inv_line.invoice_id.intrastat_country_id:
excnt = inv_line.invoice_id.intrastat_country_id.code
else:
excnt = inv_line.invoice_id.partner_shipping_id.country_id.code or inv_line.invoice_id.partner_id.country_id.code
#Check region
#If purchase, comes from purchase order, linked to a location,
#which is linked to the warehouse
#if sales, the sales order is linked to the warehouse
#if sales, from a delivery order, linked to a location,
#which is linked to the warehouse
#If none found, get the company one.
exreg = None
if inv_line.invoice_id.type in ('in_invoice', 'in_refund'):
#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):
return
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:
exreg = IntrastatRegion.browse(region_id).code
elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'):
#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):
return
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
if not exreg:
if company.region_id:
exreg = company.region_id.code
else:
self._company_warning(_('The Intrastat Region of the selected company is not set, '
'please make sure to configure it first.'))
#Check commodity codes
intrastat_id = inv_line.product_id.get_intrastat_recursively()
if intrastat_id:
exgo = self.env['report.intrastat.code'].browse(intrastat_id).name
else:
raise exceptions.Warning(
_('Product "%s" has no intrastat code, please configure it') % inv_line.product_id.display_name)
#In extended mode, 2 more fields required
if extendedmode:
#Check means of transport
if inv_line.invoice_id.transport_mode_id:
extpc = inv_line.invoice_id.transport_mode_id.code
elif company.transport_mode_id:
extpc = company.transport_mode_id.code
else:
self._company_warning(_('The default Intrastat transport mode of your company '
'is not set, please make sure to configure it first.'))
#Check incoterm
if inv_line.invoice_id.incoterm_id:
exdeltrm = inv_line.invoice_id.incoterm_id.code
elif company.incoterm_id:
exdeltrm = company.incoterm_id.code
else:
self._company_warning(_('The default Incoterm of your company is not set, '
'please make sure to configure it first.'))
else:
extpc = ""
exdeltrm = ""
intrastatkey = namedtuple("intrastatkey",
['EXTRF', 'EXCNT', 'EXTTA', 'EXREG',
'EXGO', 'EXTPC', 'EXDELTRM'])
return intrastatkey(EXTRF=declcode, EXCNT=excnt,
EXTTA=extta, EXREG=exreg, EXGO=exgo,
EXTPC=extpc, EXDELTRM=exdeltrm)
def _get_reception_code(self, extended):
return 'EX19E' if extended else 'EX19S'
def _get_reception_form(self, extended):
return 'EXF19E' if extended else 'EXF19S'
def _get_expedition_code(self, extended):
return 'EX29E' if extended else 'EX29S'
def _get_expedition_form(self, extended):
return 'EXF29E' if extended else 'EXF29S'
@api.multi
def _get_lines(self, dispatchmode=False, extendedmode=False):
company = self.company_id
IntrastatRegion = self.env['l10n_be_intrastat.region']
if dispatchmode:
mode1 = 'out_invoice'
mode2 = 'in_refund'
declcode = "29"
declcode = self._get_expedition_code(extendedmode)
declform = self._get_expedition_form(extendedmode)
else:
mode1 = 'in_invoice'
mode2 = 'out_refund'
declcode = "19"
declcode = self._get_reception_code(extendedmode)
declform = self._get_reception_form(extendedmode)
decl = ET.Element('Report')
if not extendedmode:
decl.set('code', 'EX%sS' % declcode)
else:
decl.set('code', 'EX%sE' % declcode)
decl.set('code', declcode)
decl.set('date', '%s-%s' % (self.year, self.month))
datas = ET.SubElement(decl, 'Data')
if not extendedmode:
datas.set('form', 'EXF%sS' % declcode)
else:
datas.set('form', 'EXF%sE' % declcode)
datas.set('form', declform)
datas.set('close', 'true')
intrastatkey = namedtuple("intrastatkey",
['EXTRF', 'EXCNT', 'EXTTA', 'EXREG',
'EXGO', 'EXTPC', 'EXDELTRM'])
entries = {}
query = """
@@ -167,86 +272,10 @@ class XmlDeclaration(models.TransientModel):
invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids)
for inv_line in invoicelines:
linekey = self._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode)
if linekey is None:
continue
#Check type of transaction
if inv_line.intrastat_transaction_id:
extta = inv_line.intrastat_transaction_id.code
else:
extta = "1"
#Check country
if inv_line.invoice_id.intrastat_country_id:
excnt = inv_line.invoice_id.intrastat_country_id.code
else:
excnt = inv_line.invoice_id.partner_shipping_id.country_id.code or inv_line.invoice_id.partner_id.country_id.code
#Check region
#If purchase, comes from purchase order, linked to a location,
#which is linked to the warehouse
#if sales, the sales order is linked to the warehouse
#if sales, from a delivery order, linked to a location,
#which is linked to the warehouse
#If none found, get the company one.
exreg = None
if inv_line.invoice_id.type in ('in_invoice', 'in_refund'):
#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:
exreg = IntrastatRegion.browse(region_id).code
elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'):
#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
if not exreg:
if company.region_id:
exreg = company.region_id.code
else:
self._company_warning(_('The Intrastat Region of the selected company is not set, '
'please make sure to configure it first.'))
#Check commodity codes
intrastat_id = inv_line.product_id.get_intrastat_recursively()
if intrastat_id:
exgo = self.env['report.intrastat.code'].browse(intrastat_id).name
else:
raise exceptions.Warning(
_('Product "%s" has no intrastat code, please configure it') % inv_line.product_id.display_name)
#In extended mode, 2 more fields required
if extendedmode:
#Check means of transport
if inv_line.invoice_id.transport_mode_id:
extpc = inv_line.invoice_id.transport_mode_id.code
elif company.transport_mode_id:
extpc = company.transport_mode_id.code
else:
self._company_warning(_('The default Intrastat transport mode of your company '
'is not set, please make sure to configure it first.'))
#Check incoterm
if inv_line.invoice_id.incoterm_id:
exdeltrm = inv_line.invoice_id.incoterm_id.code
elif company.incoterm_id:
exdeltrm = company.incoterm_id.code
else:
self._company_warning(_('The default Incoterm of your company is not set, '
'please make sure to configure it first.'))
else:
extpc = ""
exdeltrm = ""
linekey = intrastatkey(EXTRF=declcode, EXCNT=excnt,
EXTTA=extta, EXREG=exreg, EXGO=exgo,
EXTPC=extpc, EXDELTRM=exdeltrm)
#We have the key
#calculate amounts
if inv_line.price_unit and inv_line.quantity:
@@ -270,18 +299,7 @@ class XmlDeclaration(models.TransientModel):
continue
numlgn += 1
item = ET.SubElement(datas, 'Item')
self._set_Dim(item, 'EXSEQCODE', text_type(numlgn))
self._set_Dim(item, 'EXTRF', text_type(linekey.EXTRF))
self._set_Dim(item, 'EXCNT', text_type(linekey.EXCNT))
self._set_Dim(item, 'EXTTA', text_type(linekey.EXTTA))
self._set_Dim(item, 'EXREG', text_type(linekey.EXREG))
self._set_Dim(item, 'EXTGO', text_type(linekey.EXGO))
if extendedmode:
self._set_Dim(item, 'EXTPC', text_type(linekey.EXTPC))
self._set_Dim(item, 'EXDELTRM', text_type(linekey.EXDELTRM))
self._set_Dim(item, 'EXTXVAL', text_type(round(amounts[0], 0)).replace(".", ","))
self._set_Dim(item, 'EXWEIGHT', text_type(round(amounts[1], 0)).replace(".", ","))
self._set_Dim(item, 'EXUNITS', text_type(round(amounts[2], 0)).replace(".", ","))
self._build_intrastat_line(numlgn, item, linekey, amounts, dispatchmode, extendedmode)
if numlgn == 0:
#no datas
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Belgian Intrastat Declaration - Complement for 2019',
'category': 'Accounting',
'description': """
Adds the possibility to specify the origin country of goods and the partner VAT in the Intrastat XML report.
""",
'depends': ['l10n_be_intrastat'],
'data': [
'views/account_invoice_line_view.xml',
],
'auto_install': True,
}
@@ -0,0 +1,32 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_be_intrastat_2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-21 11:04+0000\n"
"PO-Revision-Date: 2019-01-21 11:04+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: l10n_be_intrastat_2019
#: model:ir.model,name:l10n_be_intrastat_2019.model_l10n_be_intrastat_xml_xml_decl
msgid "Intrastat XML Declaration"
msgstr ""
#. module: l10n_be_intrastat_2019
#: model:ir.model,name:l10n_be_intrastat_2019.model_account_invoice_line
msgid "Invoice Line"
msgstr ""
#. module: l10n_be_intrastat_2019
#: model:ir.model.fields,field_description:l10n_be_intrastat_2019.field_account_invoice_line_intrastat_product_origin_country_id
msgid "Origin Country of Product"
msgstr ""
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import account_intrastat_report
from . import account_invoice_line
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.tools.pycompat import text_type
from collections import namedtuple
class XmlDeclaration(models.TransientModel):
"""
Intrastat XML Declaration
"""
_inherit = "l10n_be_intrastat_xml.xml_decl"
def _build_intrastat_line(self, numlgn, item, linekey, amounts, dispatchmode, extendedmode):
super(XmlDeclaration, self)._build_intrastat_line(numlgn, item, linekey, amounts, dispatchmode, extendedmode)
if dispatchmode:
self._set_Dim(item, 'EXCNTORI', text_type(linekey.EXCNTORI))
self._set_Dim(item, 'PARTNERID', text_type(linekey.PARTNERID))
def _get_intrastat_linekey(self, declcode, inv_line, dispatchmode, extendedmode):
res = super(XmlDeclaration, self)._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode)
if res and dispatchmode:
res_dict = res._asdict()
res_dict['EXCNTORI'] = inv_line.intrastat_product_origin_country_id.code or 'QU'
res_dict['PARTNERID'] = inv_line.invoice_id.partner_id.vat or 'QV999999999999'
return namedtuple('intrastatkey', res_dict.keys())(**res_dict)
return res
def _get_expedition_code(self, extended):
return 'INTRASTAT_X_E' if extended else 'INTRASTAT_X_S'
def _get_expedition_form(self, extended):
return 'INTRASTAT_X_EF' if extended else 'INTRASTAT_X_SF'
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
intrastat_product_origin_country_id = fields.Many2one('res.country', string='Origin Country of Product')
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="invoice_line_be_intrastat_data_form" model="ir.ui.view">
<field name="name">account.invoice.form.inherit.account.be.intrastat</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='invoice_line_ids']//field[@name='quantity']" position="before">
<field name="intrastat_product_origin_country_id" options="{'no_create_edit': True}"/>
</xpath>
</field>
</record>
</odoo>
+1
View File
@@ -230,6 +230,7 @@ var Chatter = Widget.extend({
record_name: this.record_name,
default_body: old_composer && old_composer.$input && old_composer.$input.val(),
default_mention_selections: old_composer && old_composer.mention_get_listener_selections(),
attachment_ids: (old_composer && old_composer.get('attachment_ids')) || [],
});
this.composer.on('input_focused', this, function () {
this.composer.mention_set_prefetched_partners(this.mentionSuggestions || []);
+2 -1
View File
@@ -391,7 +391,7 @@ var BasicComposer = Widget.extend({
// Attachments
this.AttachmentDataSet = new data.DataSetSearch(this, 'ir.attachment', this.context);
this.fileupload_id = _.uniqueId('o_chat_fileupload');
this.set('attachment_ids', []);
this.set('attachment_ids', options.attachment_ids || []);
// Mention
this.mention_manager = new MentionManager(this);
@@ -451,6 +451,7 @@ var BasicComposer = Widget.extend({
dom.autoresize(this.$input, {parent: this, min_height: this.options.input_min_height});
// Attachments
this.render_attachments();
$(window).on(this.fileupload_id, this.on_attachment_loaded);
this.on("change:attachment_ids", this, this.render_attachments);
+18 -9
View File
@@ -53,22 +53,31 @@ class SaleOrder(models.Model):
for possible refunds created directly from existing invoices. This is necessary since such a
refund is not directly linked to the SO.
"""
# Ignore the status of the deposit product
deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id()
line_invoice_status_all = [(d['order_id'][0], d['invoice_status']) for d in self.env['sale.order.line'].read_group([('order_id', 'in', self.ids), ('product_id', '!=', deposit_product_id.id)], ['order_id', 'invoice_status'], ['order_id', 'invoice_status'], lazy=False)]
for order in self:
invoice_ids = order.order_line.mapped('invoice_lines').mapped('invoice_id').filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
# Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
# 'account.invoice.refund')
# use like as origin may contains multiple references (e.g. 'SO01, SO02')
refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id), ('type', 'in', ('out_invoice', 'out_refund'))])
invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')])
# Search for refunds as well
refund_ids = self.env['account.invoice'].browse()
if invoice_ids:
for inv in invoice_ids:
refund_ids += refund_ids.search([('type', '=', 'out_refund'), ('origin', '=', inv.number), ('origin', '!=', False), ('journal_id', '=', inv.journal_id.id)])
# Ignore the status of the deposit product
deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id()
line_invoice_status = [line.invoice_status for line in order.order_line if line.product_id != deposit_product_id]
# Search for refunds as well
domain_inv = expression.OR([
['&', ('origin', '=', inv.number), ('journal_id', '=', inv.journal_id.id)]
for inv in invoice_ids if inv.number
])
if domain_inv:
refund_ids = self.env['account.invoice'].search(expression.AND([
['&', ('type', '=', 'out_refund'), ('origin', '!=', False)],
domain_inv
]))
else:
refund_ids = self.env['account.invoice'].browse()
line_invoice_status = [d[1] for d in line_invoice_status_all if d[0] == order.id]
if order.state not in ('sale', 'done'):
invoice_status = 'no'
+2
View File
@@ -262,6 +262,7 @@
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'uom_qty_change':True, 'company_id': parent.company_id}"
name="product_uom_qty" class="oe_inline"/>
<field name="product_uom" groups="uom.group_uom" class="oe_inline oe_no_button"
force_save="1"
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"/>
</div>
<label for="qty_delivered" string="Delivered Quantity" attrs="{'invisible': [('parent.state', 'not in', ['sale', 'done'])]}"/>
@@ -322,6 +323,7 @@
attrs="{'column_invisible': [('parent.state', 'not in', ['sale', 'done'])]}"/>
<field name="qty_to_invoice" invisible="1"/>
<field name="product_uom"
force_save="1"
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
context="{'company_id': parent.company_id}"
groups="uom.group_uom" options='{"no_open": True}'/>
+1 -2
View File
@@ -381,7 +381,7 @@ class StockMove(models.Model):
product_tot_qty_available = move.product_id.qty_available + tmpl_dict[move.product_id.id]
rounding = move.product_id.uom_id.rounding
qty_done = 0.0
qty_done = move.product_uom._compute_quantity(move.quantity_done, move.product_id.uom_id)
if float_is_zero(product_tot_qty_available, precision_rounding=rounding):
new_std_price = move._get_price_unit()
elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding) or \
@@ -390,7 +390,6 @@ class StockMove(models.Model):
else:
# Get the standard price
amount_unit = std_price_update.get((move.company_id.id, move.product_id.id)) or move.product_id.standard_price
qty_done = move.product_uom._compute_quantity(move.quantity_done, move.product_id.uom_id)
qty = forced_qty or qty_done
new_std_price = ((amount_unit * product_tot_qty_available) + (move._get_price_unit() * qty)) / (product_tot_qty_available + qty_done)
@@ -2587,6 +2587,40 @@ class TestStockValuation(TransactionCase):
self.assertAlmostEqual(self.product1.qty_at_date, 0.0)
self.assertAlmostEqual(self.product1.stock_value, 0.0)
def test_average_perpetual_6(self):
self.product1.product_tmpl_id.cost_method = 'average'
move1 = self.env['stock.move'].create({
'name': 'Receive 1 unit at 10',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'price_unit': 10,
})
move1._action_confirm()
move1._action_assign()
move1.move_line_ids.qty_done = 1.0
move2 = self.env['stock.move'].create({
'name': 'Receive 1 units at 5',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'price_unit': 5,
})
move2._action_confirm()
move2._action_assign()
move2.move_line_ids.qty_done = 1.0
# Receive both at the same time
(move1 | move2)._action_done()
self.assertAlmostEqual(self.product1.standard_price, 7.5)
def test_average_negative_1(self):
""" Test edit in the past. Receive 10, send 20, edit the second move to only send 10.
"""
@@ -307,6 +307,9 @@ class AdjustmentLines(models.Model):
return False
accounts = self.product_id.product_tmpl_id.get_product_accounts()
debit_account_id = accounts.get('stock_valuation') and accounts['stock_valuation'].id or False
# If the stock move is dropshipped move we need to get the cost account instead the stock valuation account
if self.move_id._is_dropshipped():
debit_account_id = accounts.get('expense') and accounts['expense'].id or False
already_out_account_id = accounts['stock_output'].id
credit_account_id = self.cost_line_id.account_id.id or cost_product.property_account_expense_id.id or cost_product.categ_id.property_account_expense_categ_id.id
@@ -193,6 +193,12 @@ var FieldMany2One = AbstractField.extend({
*/
_bindAutoComplete: function () {
var self = this;
// avoid ignoring autocomplete="off" by obfuscating placeholder, see #30439
if (this.$input.attr('placeholder')) {
this.$input.attr('placeholder', function (index, val) {
return val.split('').join('\ufeff');
});
}
this.$input.autocomplete({
source: function (req, resp) {
self._search(req.term).then(function (result) {
+1 -1
View File
@@ -396,7 +396,7 @@ class Web_Editor(http.Controller):
(note: searching for the URL string is not enough as it could appear in a comment or an xpath expression.)
"""
return bool(etree.XML(view.arch).xpath("link[@href='{}']".format(url)))
return bool(etree.XML(view.arch).xpath("//link[@href='{}']".format(url)))
view_to_xpath = IrUiView.get_related_views(bundle_xmlid, bundles=True).filtered(views_linking_url)
@@ -44,7 +44,7 @@ class ProductWishlist(models.Model):
"&",
("partner_id", "=", False),
("session", "=", self.env.user.current_session),
]).filtered('product_id.product_tmpl_id.website_published')
]).filtered('product_id.product_tmpl_id.website_published').sudo(user=self.env.user)
@api.model
def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False, session=False):
+3
View File
@@ -9,9 +9,12 @@ declaration.
Signed,
Roberto Fichera roberto.fichera@levelprime.com https://github.com/robyf70
Filippo Iovine - filippo.iovine@levelprime.com - https://github.com/FilippoIOVINE
List of contributors:
Roberto Fichera roberto.fichera@levelprime.com https://github.com/robyf70
Roberto Fichera robyf@tekno-soft.it https://github.com/robyf70
Filippo Iovine - filippo.iovine@levelprime.com - https://github.com/FilippoIOVINE
Filippo Iovine - filippo.jovine@gmail.com - https://github.com/FilippoIOVINE
+11
View File
@@ -0,0 +1,11 @@
china, 2019-01-16
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,
liweijie 674416404@qq.com https://github.com/liweijie0812
+11
View File
@@ -0,0 +1,11 @@
Mexico, 18-01-2018
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,
José Francisco Martínez Salgado admin@jofranmtz.com https://github.com/xyvs
-2
View File
@@ -180,8 +180,6 @@ todo_include_todos = False
intersphinx_mapping = {
'python': ('https://docs.python.org/3/', None),
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
'sqlalchemy': ('http://docs.sqlalchemy.org/en/rel_0_9/', None),
'django': ('https://django.readthedocs.org/en/latest/', None),
}
github_user = 'odoo'
+5
View File
@@ -384,6 +384,11 @@ class IrModelFields(models.Model):
raise UserError(_("The Selection Options expression is not a valid Pythonic expression. "
"Please provide an expression in the [('key','Label'), ...] format."))
@api.constrains('domain')
def _check_domain(self):
for field in self:
safe_eval(field.domain or '[]')
@api.constrains('name', 'state')
def _check_name(self):
for field in self:
+4 -1
View File
@@ -379,7 +379,10 @@ actual arch.
def _compute_defaults(self, values):
if 'inherit_id' in values:
values.setdefault('mode', 'extension' if values['inherit_id'] else 'primary')
# Do not automatically change the mode if the view already has an inherit_id,
# and the user change it to another.
if not values['inherit_id'] or all(not view.inherit_id for view in self):
values.setdefault('mode', 'extension' if values['inherit_id'] else 'primary')
return values
@api.model
+28
View File
@@ -1392,6 +1392,12 @@ class ViewModeField(ViewCase):
})
self.assertEqual(view2.mode, 'extension')
view2.write({'inherit_id': None})
self.assertEqual(view2.mode, 'primary')
view2.write({'inherit_id': view.id})
self.assertEqual(view2.mode, 'extension')
@mute_logger('odoo.sql_db')
def testModeExplicit(self):
view = self.View.create({
@@ -1404,6 +1410,7 @@ class ViewModeField(ViewCase):
'arch': '<qweb/>'
})
self.assertEqual(view.mode, 'primary')
self.assertEqual(view2.mode, 'primary')
with self.assertRaises(IntegrityError):
self.View.create({
@@ -1455,6 +1462,27 @@ class ViewModeField(ViewCase):
view.write({'mode': 'primary'})
def testChangeInheritOfPrimary(self):
"""
A primary view with an inherit_id must remain primary when changing the inherit_id
"""
base1 = self.View.create({
'inherit_id': None,
'arch': '<qweb/>',
})
base2 = self.View.create({
'inherit_id': None,
'arch': '<qweb/>',
})
view = self.View.create({
'mode': 'primary',
'inherit_id': base1.id,
'arch': '<qweb/>',
})
self.assertEqual(view.mode, 'primary')
view.write({'inherit_id': base2.id})
self.assertEqual(view.mode, 'primary')
class TestDefaultView(ViewCase):
def testDefaultViewBase(self):