[MERGE] forward port branch 11.0 up to 984a99611e
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
@@ -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 || []);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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}'/>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user