[MERGE] forward port branch saas-11.2 up to 58e3552246

This commit is contained in:
Christophe Simonis
2018-05-25 14:34:18 +02:00
67 changed files with 1406 additions and 144 deletions
+3
View File
@@ -9362,6 +9362,8 @@ msgstr "La referencia de asociado para esta factura."
msgid ""
"The partners of the journal's company and the related bank account mismatch."
msgstr ""
"La empresa del diario de la compañía y el de la cuenta del banco "
"relacionada, no coinciden"
#. module: account
#: code:addons/account/models/account_payment.py:61
@@ -10665,6 +10667,7 @@ msgid ""
"You cannot change the currency of the company since some journal items "
"already exist"
msgstr ""
"No puede cambiar la moneda de la compañía ya que contiene asientos contables"
#. module: account
#: code:addons/account/models/account.py:271
+6 -1
View File
@@ -65,13 +65,14 @@
# Fabri Yohann <psn@fabri.pw>, 2017
# Jérôme Tanché <jerome.tanche@ouest-dsi.fr>, 2017
# e2f <projects@e2f.com>, 2018
# Schreiner Grégory <schreiner.gregory@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.2\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-22 14:10+0000\n"
"PO-Revision-Date: 2018-03-22 14:10+0000\n"
"Last-Translator: e2f <projects@e2f.com>, 2018\n"
"Last-Translator: Schreiner Grégory <schreiner.gregory@gmail.com>, 2018\n"
"Language-Team: French (https://www.transifex.com/odoo/teams/41243/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -9405,6 +9406,8 @@ msgstr "La référence donnée par le partenaire pour cette facture."
msgid ""
"The partners of the journal's company and the related bank account mismatch."
msgstr ""
"Les partenaires du journal de la société et le compte bancaire lié ne "
"correspondent pas"
#. module: account
#: code:addons/account/models/account_payment.py:61
@@ -10707,6 +10710,8 @@ msgid ""
"You cannot change the currency of the company since some journal items "
"already exist"
msgstr ""
"Vous ne pouvez pas modifier la devise de l'entreprise car certains articles "
"de journal existent déjà"
#. module: account
#: code:addons/account/models/account.py:271
+4
View File
@@ -30,6 +30,10 @@ msgid ""
" * The 'Paid' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled.\n"
" * The 'Cancelled' status is used when user cancel invoice."
msgstr ""
" * 'Taslak' durumu, bir kullanıcı yeni yada onaylanmamış bir fatura hazırlıyorsa kullanılır.\n"
" * 'Açık' durumu, kullanıcı bir fatura oluştururken kullanılır, fatura numarası oluşturulur. Kullanıcı faturayı ödeyinceye kadar açık durumdadır.\n"
" * 'Ödendi' durumu, fatura ödenince otomatik olarak ayarlanır. Yevmiye girişlerinin uzlaştırılımış yada uzlaştırılmamış olmasıyla ilgilidir.\n"
" * 'İptal edildi' durumu kullanıcı bir faturayı iptal ettiğinde kullanılır."
#. module: account
#: model:ir.model.fields,field_description:account.field_account_chart_template__code_digits
+5
View File
@@ -441,12 +441,17 @@ class account_payment(models.Model):
@api.multi
def button_invoices(self):
if self.partner_type == 'supplier':
views = [(self.env.ref('account.invoice_supplier_tree').id, 'tree'), (self.env.ref('account.invoice_supplier_form').id, 'form')]
else:
views = [(self.env.ref('account.invoice_tree').id, 'tree'), (self.env.ref('account.invoice_form').id, 'form')]
return {
'name': _('Paid Invoices'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.invoice',
'view_id': False,
'views': views,
'type': 'ir.actions.act_window',
'domain': [('id', 'in', [x.id for x in self.invoice_ids])],
}
+5 -2
View File
@@ -11,13 +11,14 @@
# Ahmet Altinisik <aaltinisik@altinkaya.com.tr>, 2017
# Martin Trigaux, 2017
# Ayhan KIZILTAN <akiziltan76@hotmail.com>, 2017
# Umur Akın <umura@projetgrup.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.2\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-22 14:10+0000\n"
"PO-Revision-Date: 2018-03-22 14:10+0000\n"
"Last-Translator: Ayhan KIZILTAN <akiziltan76@hotmail.com>, 2017\n"
"Last-Translator: Umur Akın <umura@projetgrup.com>, 2018\n"
"Language-Team: Turkish (https://www.transifex.com/odoo/teams/41243/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -148,7 +149,7 @@ msgstr "Bakiye"
#. module: analytic
#: model:ir.ui.view,arch_db:analytic.view_account_analytic_account_kanban
msgid "Balance:"
msgstr ""
msgstr "Bakiye:"
#. module: analytic
#: model:ir.actions.act_window,name:analytic.action_analytic_account_form
@@ -412,6 +413,8 @@ msgstr "Analitik Satırları Ara"
#: model:ir.model.fields,help:analytic.field_account_analytic_tag__active
msgid "Set active to false to hide the Analytic Tag without removing it."
msgstr ""
"Analitik Etiketi kaldırmadan gizlemek için etkin seçeneğini yanlış olarak "
"ayarla."
#. module: analytic
#: model:ir.model.fields,field_description:analytic.field_account_analytic_line__tag_ids
+20 -13
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.exceptions import ValidationError
@@ -78,9 +79,9 @@ class AccountAnalyticAccount(models.Model):
@api.multi
def _compute_debit_credit_balance(self):
res_currency_obj = self.env['res.currency']
Curr = self.env['res.currency']
analytic_line_obj = self.env['account.analytic.line']
domain = [('account_id', 'in', self.mapped('id'))]
domain = [('account_id', 'in', self.ids)]
if self._context.get('from_date', False):
domain.append(('date', '>=', self._context['from_date']))
if self._context.get('to_date', False):
@@ -92,17 +93,23 @@ class AccountAnalyticAccount(models.Model):
domain.append(('company_id', 'in', self._context['company_ids']))
user_currency = self.env.user.company_id.currency_id
account_amounts = analytic_line_obj.search_read(domain, ['account_id', 'amount', 'currency_id'])
account_ids = set([line['account_id'][0] for line in account_amounts])
data_debit = {account_id: 0.0 for account_id in account_ids}
data_credit = {account_id: 0.0 for account_id in account_ids}
for account_amount in account_amounts:
currency_id = account_amount['currency_id'][0]
amount = res_currency_obj.browse(currency_id).compute(account_amount['amount'], user_currency)
if amount < 0.0:
data_debit[account_amount['account_id'][0]] += amount
else:
data_credit[account_amount['account_id'][0]] += amount
credit_groups = analytic_line_obj.read_group(
domain=domain + [('amount', '>=', 0.0)],
fields=['account_id', 'currency_id', 'amount'],
groupby=['account_id', 'currency_id'],
)
data_credit = defaultdict(float)
for l in credit_groups:
data_credit[l['account_id'][0]] += Curr.browse(l['currency_id'][0]).compute(l['amount'], user_currency)
debit_groups = analytic_line_obj.read_group(
domain=domain + [('amount', '<', 0.0)],
fields=['account_id', 'currency_id', 'amount'],
groupby=['account_id', 'currency_id'],
)
data_debit = defaultdict(float)
for l in debit_groups:
data_debit[l['account_id'][0]] += Curr.browse(l['currency_id'][0]).compute(l['amount'], user_currency)
for account in self:
account.debit = abs(data_debit.get(account.id, 0.0))
+4 -1
View File
@@ -11,13 +11,14 @@
# Ramiz Deniz Öner <deniz@denizoner.com>, 2017
# Gökhan Erdoğdu <gokhan.erdogdu@mechsoft.com.tr>, 2017
# Özem Sökmem <ozensokmen@gmail.com>, 2017
# Umur Akın <umura@projetgrup.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.2\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-22 14:10+0000\n"
"PO-Revision-Date: 2018-03-22 14:10+0000\n"
"Last-Translator: Özem Sökmem <ozensokmen@gmail.com>, 2017\n"
"Last-Translator: Umur Akın <umura@projetgrup.com>, 2018\n"
"Language-Team: Turkish (https://www.transifex.com/odoo/teams/41243/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -396,6 +397,8 @@ msgid ""
"This is the file created by the anonymization process. It should have the "
"extension '.json' or '.pickle'."
msgstr ""
"Bu dosya anonimleştirme işlemi tarafından oluşturulmuştur. Bunun uzantısı "
"'.json' yada '.pickle' dır."
#. module: anonymization
#: selection:ir.model.fields.anonymize.wizard,state:0
+4 -2
View File
@@ -412,6 +412,8 @@ msgstr "Birim Ürün"
msgid ""
"Used to order rules such that rules with a smaller sequence match first"
msgstr ""
"ilk olarak küçük sıralarla eşleyen kurallar gibi kullanılan sipariş "
"kuralları"
#. module: barcodes
#: model:ir.model.fields,help:barcodes.field_barcodes_barcode_events_mixin___barcode_scanned
@@ -426,7 +428,7 @@ msgstr "Tartılmış Ürün"
#. module: barcodes
#: model:ir.model,name:barcodes.model_barcode_nomenclature
msgid "barcode.nomenclature"
msgstr ""
msgstr "barcode.nomenclature"
#. module: barcodes
#: model:ir.model,name:barcodes.model_barcode_rule
@@ -436,4 +438,4 @@ msgstr "barkod.kuralı"
#. module: barcodes
#: model:ir.model,name:barcodes.model_barcodes_barcode_events_mixin
msgid "barcodes.barcode_events_mixin"
msgstr ""
msgstr "barcodes.barcode_events_mixin"
+1 -1
View File
@@ -145,7 +145,7 @@ msgstr "Decimaal scheidingsteken:"
#: code:addons/base_import/static/src/xml/base_import.xml:76
#, python-format
msgid "Defer parent/child computation"
msgstr ""
msgstr "Uitstellen boven/onderliggende berekening"
#. module: base_import
#: model:ir.model.fields,field_description:base_import.field_base_import_import__display_name
+7 -1
View File
@@ -656,7 +656,13 @@ class Meeting(models.Model):
}[data['rrule_type']]
deadline = fields.Datetime.from_string(data['stop'])
return deadline + relativedelta(**{delay: count * mult})
computed_final_date = False
while not computed_final_date and count > 0:
try: # may crash if year > 9999 (in case of recurring events)
computed_final_date = deadline + relativedelta(**{delay: count * mult})
except ValueError:
count -= data['interval']
return computed_final_date or deadline
return final_date
@api.multi
+2 -2
View File
@@ -1022,7 +1022,7 @@ msgstr "Forceer toewijzing"
#. module: crm
#: model:ir.ui.view,arch_db:crm.res_config_settings_view_form
msgid "Format phone numbers based on national conventions"
msgstr ""
msgstr "Opmaak telefoonnummers gebaseerd op nationale afspraken."
#. module: crm
#: code:addons/crm/models/crm_lead.py:588
@@ -1878,7 +1878,7 @@ msgstr "Telefoon"
#. module: crm
#: model:ir.model.fields,field_description:crm.field_res_config_settings__module_crm_phone_validation
msgid "Phone Formatting"
msgstr ""
msgstr "Telefoonnummer opmaak"
#. module: crm
#: code:addons/crm/models/crm_team.py:165
@@ -2,6 +2,7 @@ odoo.define('hr_attendance.kiosk_mode', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var ajax = require('web.ajax');
var core = require('web.core');
var Session = require('web.session');
@@ -28,6 +29,8 @@ var KioskMode = AbstractAction.extend({
self.$el.html(QWeb.render("HrAttendanceKioskMode", {widget: self}));
self.start_clock();
});
// Make a RPC call every day to keep the session alive
self._interval = window.setInterval(this._callServer.bind(this), (60*60*1000*24));
return $.when(def, this._super.apply(this, arguments));
},
@@ -56,8 +59,15 @@ var KioskMode = AbstractAction.extend({
destroy: function () {
core.bus.off('barcode_scanned', this, this._onBarcodeScanned);
clearInterval(this.clock_start);
clearInterval(this._interval);
this._super.apply(this, arguments);
},
_callServer: function () {
// Make a call to the database to avoid the auto close of the session
return ajax.rpc("/web/webclient/version_info", {});
},
});
core.action_registry.add('hr_attendance_kiosk_mode', KioskMode);
@@ -33,8 +33,6 @@
<filter name="group_name" string="Description" context="{'group_by':'name'}"/>
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
<filter name="group_category" string="Category" context="{'group_by':'category_id'}"/>
<filter name="group_manager" string="Manager" context="{'group_by':'manager_id'}"/>
<filter name="group_second_approver_id" string="Validator" context="{'group_by':'second_approver_id'}"/>
<filter name="group_type" string="Type" context="{'group_by':'holiday_status_id'}"/>
<separator/>
<filter name="group_date_from" string="Start Month" context="{'group_by':'date_from'}"/>
@@ -118,7 +116,7 @@
<field name="res_model">hr.leave.report</field>
<field name="src_model">hr.employee</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban</field>
<field name="view_mode">tree,kanban,form</field>
<field name="view_id" eval="hr_leave_report_tree"/>
<field name="context">{
'search_default_group_type': 1,
+1 -1
View File
@@ -9,7 +9,7 @@
'description': """
This module provides standard tools (account model, context manager and helpers) to support In-App purchases inside Odoo.
""",
'depends': ['web'],
'depends': ['web', 'web_settings_dashboard'],
'data': [
'security/ir.model.access.csv',
'security/ir_rule.xml',
+4 -3
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) David Arnold (devCO).
# Author David Arnold (devCO), dar@devco.co
# Copyright (C) David Arnold (XOE Solutions).
# Author David Arnold (XOE Solutions), dar@xoe.solutions
# Co-Authors Juan Pablo Aries (devCO), jpa@devco.co
# Hector Ivan Valencia Muñoz (TIX SAS)
# Nhomar Hernandez (Vauxoo)
@@ -13,7 +13,8 @@
'version': '0.8',
'category': 'Localization',
'description': 'Colombian Accounting and Tax Preconfiguration',
'author': 'David Arnold BA HSG (devCO)',
'author': 'David Arnold (XOE Solutions)',
'website': 'https://xoe.solutions',
'depends': [
'account',
],
+1
View File
@@ -3,6 +3,7 @@
# List of contributors:
# Jordi Esteve <jesteve@zikzakmedia.com>
# Ignacio Ibeas <ignacio@acysos.com>
# Dpto. Consultoría Grupo Opentia <consultoria@opentia.es>
# Pedro M. Baeza <pedro.baeza@tecnativa.com>
# Carlos Liébana <carlos.liebana@factorlibre.com>
@@ -113,6 +113,16 @@
<field name="chart_template_id" ref="account_chart_template_common"/>
</record>
<record id="fp_reagyp_a" model="account.fiscal.position.template">
<field name="name">REAGYP - Agricultura</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
</record>
<record id="fp_reagyp_gp" model="account.fiscal.position.template">
<field name="name">REAGYP - Ganadería y pesca</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
</record>
<!-- ************************************************************* -->
<!-- Fiscal Position Tax Templates -->
<!-- ************************************************************* -->
@@ -2889,6 +2899,28 @@
<field name="tax_dest_id" ref="account_tax_template_s_iva0_isp"/>
</record>
<!-- Régimen especial de Agricultura, Ganaderia y Pesca -->
<record id="fptt_reagyp_a_4b_1" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fp_reagyp_a"/>
<field name="tax_src_id" ref="l10n_es.account_tax_template_p_iva4_bc"/>
<field name="tax_dest_id" ref="l10n_es.account_tax_template_p_iva12_agr"/>
</record>
<record id="fptt_reagyp_a_4b_2" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fp_reagyp_a"/>
<field name="tax_src_id" ref="l10n_es.account_tax_template_p_iva4_bc"/>
<field name="tax_dest_id" ref="l10n_es.account_tax_template_p_irpf2"/>
</record>
<record id="fptt_reagyp_gp_4b_1" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fp_reagyp_gp"/>
<field name="tax_src_id" ref="l10n_es.account_tax_template_p_iva4_bc"/>
<field name="tax_dest_id" ref="account_tax_template_p_iva105_gan"/>
</record>
<record id="fptt_reagyp_gp_4b_2" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fp_reagyp_gp"/>
<field name="tax_src_id" ref="l10n_es.account_tax_template_p_iva4_bc"/>
<field name="tax_dest_id" ref="l10n_es.account_tax_template_p_irpf2"/>
</record>
<!-- ************************************************************* -->
<!-- Fiscal Position Account Templates -->
<!-- ************************************************************* -->
+13
View File
@@ -1044,6 +1044,18 @@
<field name="tax_group_id" ref="tax_group_iva_12"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_42')])]"/>
</record>
<record id="account_tax_template_p_iva105_gan" model="account.tax.template">
<field name="description">P_IVA105_GAN</field>
<field name="type_tax_use">purchase</field>
<field name="account_id" ref="l10n_es.account_common_472"/>
<field name="name">10,5% IVA Soportado régimen ganadero o pesca</field>
<field name="refund_account_id" ref="l10n_es.account_common_472"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="amount" eval="10.5"/>
<field name="amount_type">percent</field>
<field name="include_base_amount" eval="1"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_42')])]"/>
</record>
<record id="account_tax_template_s_iva0_e" model="account.tax.template">
<field name="description">S_IVA0_E</field>
<field name="type_tax_use">sale</field>
@@ -1330,6 +1342,7 @@
<field name="refund_account_id" ref="l10n_es.account_common_4751"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="amount" eval="-2"/>
<field name="sequence" eval="2"/>
<field name="amount_type">percent</field>
<field name="tax_group_id" ref="tax_group_retenciones_2"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_111_08'), ref('mod_111_09')])]"/>
+10 -5
View File
@@ -120,7 +120,8 @@ class MailThread(models.AbstractModel):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('partner_id', operator, operand)])
return [('id', 'in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
@api.model
def _search_follower_channels(self, operator, operand):
@@ -133,7 +134,8 @@ class MailThread(models.AbstractModel):
followers = self.env['mail.followers'].sudo().search([
('res_model', '=', self._name),
('channel_id', operator, operand)])
return [('id', 'in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
@api.multi
@api.depends('message_follower_ids')
@@ -143,7 +145,8 @@ class MailThread(models.AbstractModel):
('res_id', 'in', self.ids),
('partner_id', '=', self.env.user.partner_id.id),
])
following_ids = followers.mapped('res_id')
# using read() below is much faster than followers.mapped('res_id')
following_ids = [res['res_id'] for res in followers.read(['res_id'])]
for record in self:
record.message_is_follower = record.id in following_ids
@@ -155,9 +158,11 @@ class MailThread(models.AbstractModel):
])
# Cases ('message_is_follower', '=', True) or ('message_is_follower', '!=', False)
if (operator == '=' and operand) or (operator == '!=' and not operand):
return [('id', 'in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'in', [res['res_id'] for res in followers.read(['res_id'])])]
else:
return [('id', 'not in', followers.mapped('res_id'))]
# using read() below is much faster than followers.mapped('res_id')
return [('id', 'not in', [res['res_id'] for res in followers.read(['res_id'])])]
@api.multi
def _get_message_unread(self):
+2
View File
@@ -32,6 +32,7 @@ function _readActivities(self, ids) {
model: 'mail.activity',
method: 'read',
args: [ids],
context: (self.record && self.record.getContext()) || self.getSession().user_context,
}).then(function (activities) {
// convert create_date and date_deadline to moments
_.each(activities, function (activity) {
@@ -108,6 +109,7 @@ var AbstractActivityField = AbstractField.extend({
method: 'action_feedback',
args: [[id]],
kwargs: {feedback: feedback},
context: this.record.getContext(),
});
},
_scheduleActivity: function (id, previous_activity_type_id, callback) {
+7 -1
View File
@@ -473,10 +473,16 @@ class MrpProduction(models.Model):
@api.multi
def _generate_workorders(self, exploded_boms):
workorders = self.env['mrp.workorder']
original_one = False
for bom, bom_data in exploded_boms:
# If the routing of the parent BoM and phantom BoM are the same, don't recreate work orders, but use one master routing
if bom.routing_id.id and (not bom_data['parent_line'] or bom_data['parent_line'].bom_id.routing_id.id != bom.routing_id.id):
workorders += self._workorders_create(bom, bom_data)
temp_workorders = self._workorders_create(bom, bom_data)
workorders += temp_workorders
if temp_workorders: # In order to avoid two "ending work orders"
if original_one:
temp_workorders[-1].next_work_order_id = original_one
original_one = temp_workorders[0]
return workorders
def _workorders_create(self, bom, bom_data):
+7 -1
View File
@@ -225,6 +225,7 @@ class MrpWorkorder(models.Model):
'done_wo': False,
'location_id': move.location_id.id,
'location_dest_id': move.location_dest_id.id,
'date': move.date,
})
qty_todo -= 1
elif float_compare(qty_todo, 0.0, precision_rounding=rounding) < 0:
@@ -416,7 +417,12 @@ class MrpWorkorder(models.Model):
@api.multi
def button_start(self):
# TDE CLEANME
self.ensure_one()
# As button_start is automatically called in the new view
if self.state in ('done', 'cancel'):
return True
# Need a loss in case of the real time exceeding the expected
timeline = self.env['mrp.workcenter.productivity']
if self.duration < self.duration_expected:
loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type','=','productive')], limit=1)
+2 -2
View File
@@ -92,7 +92,7 @@ class WebsitePayment(http.Controller):
'type': 'form_save' if acquirer.save_token != 'none' and partner_id else 'form',
}
tx = request.env['payment.transaction'].sudo().create(values)
tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
request.session['website_payment_tx_id'] = tx.id
render_values = {
@@ -122,7 +122,7 @@ class WebsitePayment(http.Controller):
'type': 'form_save' if token.acquirer_id.save_token != 'none' and partner_id else 'form',
}
tx = request.env['payment.transaction'].sudo().create(values)
tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
request.session['website_payment_tx_id'] = tx.id
try:
+1 -1
View File
@@ -357,7 +357,7 @@ class PaymentTxOgone(models.Model):
'AMOUNT': int(self.amount * 100),
'CURRENCY': self.currency_id.name,
'OPERATION': 'SAL',
'ECI': 2, # Recurring (from MOTO)
'ECI': 9, # Recurring (from eCommerce)
'ALIAS': self.payment_token_id.acquirer_ref,
'RTIMEOUT': 30,
'PARAMPLUS' : url_encode(param_plus)
@@ -449,6 +449,7 @@ PaymentScreenWidget.include({
order.selected_paymentline.paid = true;
order.selected_paymentline.mercury_swipe_pending = false;
order.selected_paymentline.mercury_amount = response.authorize;
order.selected_paymentline.set_amount(response.authorize);
order.selected_paymentline.mercury_card_number = decodedMagtek['number'];
order.selected_paymentline.mercury_card_brand = response.card_type;
order.selected_paymentline.mercury_card_owner_name = decodedMagtek['name'];
+6
View File
@@ -218,6 +218,9 @@ class Project(models.Model):
doc_count = fields.Integer(compute='_compute_attached_docs_count', string="Number of documents attached")
date_start = fields.Date(string='Start Date')
date = fields.Date(string='Expiration Date', index=True, track_visibility='onchange')
subtask_project_id = fields.Many2one('project.project', string='Sub-task Project', ondelete="restrict",
help="Choosing a sub-tasks project will both enable sub-tasks and set their default project (possibly the project itself)")
# rating fields
percentage_satisfaction_task = fields.Integer(
compute='_compute_percentage_satisfaction_task', string="Happy % on Task", store=True, default=-1)
@@ -295,6 +298,8 @@ class Project(models.Model):
# Prevent double project creation
self = self.with_context(mail_create_nosubscribe=True)
project = super(Project, self).create(vals)
if not vals.get('subtask_project_id'):
project.subtask_project_id = project.id
if project.privacy_visibility == 'portal' and project.partner_id:
project.message_subscribe(project.partner_id.ids)
return project
@@ -508,6 +513,7 @@ class Task(models.Model):
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True, related_sudo=False)
parent_id = fields.Many2one('project.task', string='Parent Task', index=True)
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks", context={'active_test': False})
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
subtask_count = fields.Integer("Sub-task count", compute='_compute_subtask_count')
email_from = fields.Char(string='Email', help="These people will receive email.", index=True)
email_cc = fields.Char(string='Watchers Emails', help="""These email addresses will be added to the CC field of all inbound
+3 -1
View File
@@ -139,6 +139,7 @@
</div>
<field name="rating_status_period" class="oe_inline" attrs="{'required': [('rating_status','=','periodic')], 'invisible': [('rating_status','!=','periodic')]}" groups="project.group_project_rating"/>
<field name="portal_show_rating" attrs="{'invisible': [('rating_status','==','no')]}" groups="project.group_project_rating"/>
<field name="subtask_project_id" groups="project.group_subtask_project"/>
</group>
<group name="misc">
<group string="Configuration" groups="base.group_no_one" name="configuration">
@@ -413,7 +414,7 @@
<button class="oe_stat_button" icon="fa-tasks" type="object" name="action_open_parent_task" string="Parent Task" attrs="{'invisible' : [('parent_id', '=', False)]}" groups="project.group_subtask_project"/>
<button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
attrs="{'invisible' : [('parent_id', '!=', False)]}"
context="{'project_id': project_id, 'name': name, 'parent_id': id}" groups="project.group_subtask_project">
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" groups="project.group_subtask_project">
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
</button>
<button name="%(rating_rating_action_task)d" type="action" attrs="{'invisible': [('rating_count', '=', 0)]}" class="oe_stat_button" icon="fa-smile-o" groups="project.group_project_rating">
@@ -461,6 +462,7 @@
<field name="email_cc" groups="base.group_no_one"/>
<field name="parent_id" attrs="{'invisible' : [('subtask_count', '>', 0)]}" groups="project.group_subtask_project"/>
<field name="child_ids" invisible="1" />
<field name="subtask_project_id" invisible="1" />
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="displayed_image_id" groups="base.group_no_one"/>
</group>
+1
View File
@@ -348,6 +348,7 @@ class PurchaseOrder(models.Model):
@api.multi
def print_quotation(self):
self.write({'state': "sent"})
return self.env.ref('purchase.report_purchase_quotation').report_action(self)
@api.multi
+1 -1
View File
@@ -88,7 +88,7 @@ class PurchaseReport(models.Model):
join res_partner partner on s.partner_id = partner.id
left join product_product p on (l.product_id=p.id)
left join product_template t on (p.product_tmpl_id=t.id)
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.template,',t.id) AND ip.company_id=s.company_id)
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.product,',p.id) AND ip.company_id=s.company_id)
left join uom_uom u on (u.id=l.product_uom)
left join uom_uom u2 on (u2.id=t.uom_id)
left join stock_picking_type spt on (spt.id=s.picking_type_id)
+4 -3
View File
@@ -9,13 +9,14 @@
# Ediz Duman <neps1192@gmail.com>, 2017
# Martin Trigaux, 2017
# Levent Karakaş <levent@mektup.at>, 2017
# Umur Akın <umura@projetgrup.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.2\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-22 14:10+0000\n"
"PO-Revision-Date: 2018-03-22 14:10+0000\n"
"Last-Translator: Levent Karakaş <levent@mektup.at>, 2017\n"
"Last-Translator: Umur Akın <umura@projetgrup.com>, 2018\n"
"Language-Team: Turkish (https://www.transifex.com/odoo/teams/41243/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -260,7 +261,7 @@ msgstr "Değerlendirme Ortalaması"
#: model:ir.model.fields,field_description:rating.field_project_task__rating_last_feedback
#: model:ir.model.fields,field_description:rating.field_rating_mixin__rating_last_feedback
msgid "Rating Last Feedback"
msgstr ""
msgstr "Son Geribildirimleri Derecelendirme "
#. module: rating
#: model:ir.model.fields,field_description:rating.field_mail_channel__rating_last_image
@@ -268,7 +269,7 @@ msgstr ""
#: model:ir.model.fields,field_description:rating.field_project_task__rating_last_image
#: model:ir.model.fields,field_description:rating.field_rating_mixin__rating_last_image
msgid "Rating Last Image"
msgstr ""
msgstr "Son Resmin Değerlendirilmesi"
#. module: rating
#: model:ir.model.fields,field_description:rating.field_mail_channel__rating_last_value
+4
View File
@@ -217,6 +217,8 @@ msgid ""
"<span class=\"label label-success hidden-xs\"><i class=\"fa fa-fw fa-"
"check\"/><span class=\"hidden-xs\"> Done</span></span>"
msgstr ""
"<span class=\"label label-success hidden-xs\"><i class=\"fa fa-fw fa-"
"check\"/><span class=\"hidden-xs\"> Realizado </span></span>"
#. module: sale
#: model:ir.ui.view,arch_db:sale.portal_order_page
@@ -233,6 +235,8 @@ msgid ""
"<span class=\"label label-success\"><i class=\"fa fa-fw fa-check\"/> "
"Done</span>"
msgstr ""
"<span class=\"label label-success\"><i class=\"fa fa-fw fa-check\"/> "
"Realizado </span>"
#. module: sale
#: model:ir.ui.view,arch_db:sale.report_saleorder_document
-5
View File
@@ -142,11 +142,6 @@
<field name="number_increment">1</field>
</record>
<record id="base.main_partner" model="res.partner">
<field name="property_stock_customer" model="stock.warehouse"
eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
</record>
<!-- create xml ids for demo data that are widely used in tests or in other codes, for more convenience -->
<function model="ir.model.data" name="_update">
<value>stock.location</value>
+2 -1
View File
@@ -27,6 +27,7 @@
# Lucas Deliege <lud@odoo.com>, 2016
# lucasdeliege <deliegelucas@gmail.com>, 2016
# ShevAbam, 2016
# Fred Gilson <fgi@odoo.com>, 2016
# Régis GEROMEGNACE <regis.geromegnace@gmail.com>, 2016
# Shark McGnark <peculiarcheese@gmail.com>, 2016
# Guilhaume Bordiau <github@guilhaume.fr>, 2016
@@ -6224,7 +6225,7 @@ msgstr "Mettre à jour la quantité d'articles"
#: model:ir.ui.view,arch_db:stock.product_product_view_form_easy_inherit_stock
#: model:ir.ui.view,arch_db:stock.product_template_form_view_procurement_button
msgid "Update Qty On Hand"
msgstr "Mettre à jour le stock disponibles"
msgstr "Mettre à jour le stock disponible"
#. module: stock
#: selection:stock.move,priority:0 selection:stock.picking,priority:0
+8 -2
View File
@@ -28,12 +28,18 @@ class Company(models.Model):
location.sudo().write({'company_id': company.id})
company.write({'internal_transit_location_id': location.id})
warehouses = self.env['stock.warehouse'].search([('partner_id', '=', self.partner_id.id)])
warehouses.mapped('partner_id').with_context(force_company=self.id).write({
'property_stock_customer': location.id,
'property_stock_supplier': location.id,
})
@api.model
def create(self, vals):
company = super(Company, self).create(vals)
# multi-company rules prevents creating warehouse and sub-locations
company.create_transit_location()
# mutli-company rules prevents creating warehouse and sub-locations
self.env['stock.warehouse'].check_access_rights('create')
self.env['stock.warehouse'].sudo().create({'name': company.name, 'code': company.name[:5], 'company_id': company.id, 'partner_id': company.partner_id.id})
company.create_transit_location()
return company
+23 -20
View File
@@ -79,12 +79,6 @@ class StockQuant(models.Model):
if float_compare(quant.quantity, 1, precision_rounding=quant.product_uom_id.rounding) > 0 and quant.lot_id and quant.product_id.tracking == 'serial':
raise ValidationError(_('A serial number should only be linked to a single product.'))
@api.constrains('in_date', 'lot_id')
def check_in_date(self):
for quant in self:
if quant.in_date and not quant.lot_id:
raise ValidationError(_('An incoming date cannot be set to an untracked product.'))
@api.constrains('location_id')
def check_location_id(self):
for quant in self:
@@ -109,9 +103,9 @@ class StockQuant(models.Model):
@api.model
def _get_removal_strategy_order(self, removal_strategy):
if removal_strategy == 'fifo':
return 'in_date, id'
return 'in_date ASC NULLS FIRST, id'
elif removal_strategy == 'lifo':
return 'in_date desc, id desc'
return 'in_date DESC NULLS LAST, id desc'
raise UserError(_('Removal strategy %s not implemented.') % (removal_strategy,))
def _gather(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False):
@@ -134,7 +128,17 @@ class StockQuant(models.Model):
domain = expression.AND([[('owner_id', '=', owner_id and owner_id.id or False)], domain])
domain = expression.AND([[('location_id', '=', location_id.id)], domain])
return self.search(domain, order=removal_strategy_order)
# Copy code of _search for special NULLS FIRST/LAST order
self.sudo(self._uid).check_access_rights('read')
query = self._where_calc(domain)
self._apply_ir_rules(query, 'read')
from_clause, where_clause, where_clause_params = query.get_sql()
where_str = where_clause and (" WHERE %s" % where_clause) or ''
query_str = 'SELECT "%s".id FROM ' % self._table + from_clause + where_str + " ORDER BY "+ removal_strategy_order
self._cr.execute(query_str, where_clause_params)
res = self._cr.fetchall()
# No uniquify list necessary as auto_join is not applied anyways...
return self.browse([x[0] for x in res])
@api.model
def _get_available_quantity(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False, allow_negative=False):
@@ -196,17 +200,16 @@ class StockQuant(models.Model):
quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
rounding = product_id.uom_id.rounding
if lot_id:
incoming_dates = quants.mapped('in_date') # `mapped` already filtered out falsy items
incoming_dates = [fields.Datetime.from_string(incoming_date) for incoming_date in incoming_dates]
if in_date:
incoming_dates += [in_date]
# If multiple incoming dates are available for a given lot_id/package_id/owner_id, we
# consider only the oldest one as being relevant.
if incoming_dates:
in_date = fields.Datetime.to_string(min(incoming_dates))
else:
in_date = fields.Datetime.now()
incoming_dates = [d for d in quants.mapped('in_date') if d]
incoming_dates = [fields.Datetime.from_string(incoming_date) for incoming_date in incoming_dates]
if in_date:
incoming_dates += [in_date]
# If multiple incoming dates are available for a given lot_id/package_id/owner_id, we
# consider only the oldest one as being relevant.
if incoming_dates:
in_date = fields.Datetime.to_string(min(incoming_dates))
else:
in_date = fields.Datetime.now()
for quant in quants:
try:
+18 -14
View File
@@ -21,24 +21,28 @@ class MrpStockReport(models.TransientModel):
@api.model
def _get_move_lines(self, move_lines):
res = self.env['stock.move.line']
for move_line in move_lines:
lines_seen = move_lines
lines_todo = list(move_lines)
while lines_todo:
move_line = lines_todo.pop(0)
# if MTO
if move_line.move_id.move_orig_ids:
res |= move_line.move_id.move_orig_ids.mapped('move_line_ids').filtered(
lambda m: m.lot_id.id == move_line.lot_id.id)
lines = move_line.move_id.move_orig_ids.mapped('move_line_ids').filtered(
lambda m: m.lot_id == move_line.lot_id
) - lines_seen
# if MTS
elif move_line:
lines = self.env['stock.move.line'].search([
('product_id', '=', move_line.product_id.id),
('lot_id', '=', move_line.lot_id.id),
('id', 'not in', lines_seen.ids),
('date', '<', move_line.date),
])
else:
if move_line:
res |= self.env['stock.move.line'].search([
('product_id', '=', move_line.product_id.id),
('lot_id', '=', move_line.lot_id.id),
('id', '!=', move_line.id),
('date', '<', move_line.date),
])
if res:
res |= self._get_move_lines(res)
return res
continue
lines_todo += list(lines)
lines_seen |= lines
return lines_seen - move_lines
@api.model
def get_lines(self, line_id=None, **kw):
+2 -1
View File
@@ -178,9 +178,10 @@ class Warehouse(models.Model):
ResCompany = self.env['res.company']
if company_id:
transit_loc = ResCompany.browse(company_id).internal_transit_location_id.id
self.env['res.partner'].browse(partner_id).with_context(force_company=company_id).write({'property_stock_customer': transit_loc, 'property_stock_supplier': transit_loc})
else:
transit_loc = ResCompany._company_default_get('stock.warehouse').internal_transit_location_id.id
self.env['res.partner'].browse(partner_id).write({'property_stock_customer': transit_loc, 'property_stock_supplier': transit_loc})
self.env['res.partner'].browse(partner_id).write({'property_stock_customer': transit_loc, 'property_stock_supplier': transit_loc})
def create_sequences_and_picking_types(self):
IrSequenceSudo = self.env['ir.sequence'].sudo()
@@ -96,8 +96,8 @@
<span t-field="ml.product_id.description_picking"/>
</td>
<td>
<span t-if="o.state != 'done'" t-esc="ml.product_uom_qty"/>
<span t-if="o.state == 'done'" t-esc="ml.qty_done"/>
<span t-if="o.state != 'done'" t-field="ml.product_uom_qty"/>
<span t-if="o.state == 'done'" t-field="ml.qty_done"/>
<span t-field="ml.product_uom_id" groups="uom.group_uom"/>
</td>
<td width="20%" t-if="o.picking_type_id.code != 'incoming'">
+46 -1
View File
@@ -628,7 +628,24 @@ class StockQuant(TransactionCase):
})
quantity, in_date = self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
self.assertEqual(quantity, 1)
self.assertEqual(in_date, None)
self.assertNotEqual(in_date, None)
def test_in_date_1b(self):
stock_location = self.env.ref('stock.stock_location_stock')
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
})
self.env['stock.quant'].create({
'product_id': product1.id,
'location_id': stock_location.id,
'quantity': 1.0,
})
quantity, in_date = self.env['stock.quant']._update_available_quantity(product1, stock_location, 2.0)
self.assertEqual(quantity, 3)
self.assertNotEqual(in_date, None)
def test_in_date_2(self):
""" Check that an incoming date is correctly set when updating the quantity of a tracked
@@ -706,6 +723,34 @@ class StockQuant(TransactionCase):
# Removal strategy is LIFO, so lot1 should be received as it was received later.
self.assertEqual(quants[0][0].lot_id.id, lot1.id)
def test_in_date_4b(self):
""" Check for LIFO and max with/without in_date that it handles the LIFO NULLS LAST well
"""
stock_location = self.env.ref('stock.stock_location_stock')
stock_location1 = self.env.ref('stock.stock_location_components')
stock_location2 = self.env.ref('stock.stock_location_14')
lifo_strategy = self.env['product.removal'].search([('method', '=', 'lifo')])
stock_location.removal_strategy_id = lifo_strategy
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
'tracking': 'serial',
})
self.env['stock.quant'].create({
'product_id': product1.id,
'location_id': stock_location1.id,
'quantity': 1.0,
})
in_date_location2 = datetime.now()
self.env['stock.quant']._update_available_quantity(product1, stock_location2, 1.0, in_date=in_date_location2)
quants = self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1)
# Removal strategy is LIFO, so the one with date is the most recent one and should be selected
self.assertEqual(quants[0][0].location_id.id, stock_location2.id)
def test_in_date_5(self):
""" Receive the same lot at different times, once they're in the same location, the quants
are merged and only the earliest incoming date is kept.
+6 -2
View File
@@ -1047,8 +1047,12 @@ class Binary(http.Controller):
elif status != 200 and download:
return request.not_found()
height = int(height or 0)
width = int(width or 0)
if headers and dict(headers).get('Content-Type', '') == 'image/svg+xml': # we shan't resize svg images
height = 0
width = 0
else:
height = int(height or 0)
width = int(width or 0)
if crop and (width or height):
content = crop_image(content, type='center', size=(width, height), ratio=(1, 1))
+14 -6
View File
@@ -260,12 +260,20 @@ function get_file(options) {
if (options.error) {
var body = this.contentDocument.body;
var nodes = body.children.length === 0 ? body.childNodes : body.children;
options.error({
message: nodes.length > 1 ? nodes[1].textContent : '',
data: {
title: nodes.length > 0 ? nodes[0].textContent : '',
},
});
var errorParams = {};
try { // Case of a serialized Odoo Exception: It is Json Parsable
var node = nodes[1] || nodes[0];
errorParams = JSON.parse(node.textContent);
} catch (e) { // Arbitrary uncaught python side exception
errorParams = {
message: nodes.length > 1 ? nodes[1].textContent : '',
data: {
title: nodes.length > 0 ? nodes[0].textContent : '',
}
}
}
options.error(errorParams);
}
} finally {
complete();
@@ -12,6 +12,7 @@ odoo.define('web.AbstractController', function (require) {
* reading localstorage, ...) has to go through the controller.
*/
var concurrency = require('web.concurrency');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
var AbstractAction = require('web.AbstractAction');
@@ -54,6 +55,8 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
this.controllerID = params.controllerID;
this.initialState = params.initialState;
// use a DropPrevious to correctly handle concurrent updates
this.dp = new concurrency.DropPrevious();
// those arguments are temporary, they won't be necessary as soon as the
// ControlPanel will be handled by the View
this.displayName = params.displayName;
@@ -232,11 +235,11 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
var self = this;
var shouldReload = (options && 'reload' in options) ? options.reload : true;
var def = shouldReload ? this.model.reload(this.handle, params) : $.when();
return def.then(function (handle) {
return this.dp.add(def).then(function (handle) {
self.handle = handle || self.handle; // update handle if we reloaded
var state = self.model.get(self.handle);
var localState = self.renderer.getLocalState();
return self.renderer.updateState(state, params).then(function () {
return self.dp.add(self.renderer.updateState(state, params)).then(function () {
self.renderer.setLocalState(localState);
self._update(state);
});
@@ -1559,8 +1559,10 @@ var BasicModel = AbstractModel.extend({
rec = self._makeDataPoint(params);
list._cache[rec.res_id] = rec.id;
}
rec._noAbandon = true;
// Do not abandon the record if it has been created
// from `default_get`. The list has a savepoint only
// after having fully executed `default_get`.
rec._noAbandon = !list._savePoint;
list._changes.push({operation: 'ADD', id: rec.id});
if (command[0] === 1) {
list._changes.push({operation: 'UPDATE', id: rec.id});
@@ -1979,6 +1981,9 @@ var BasicModel = AbstractModel.extend({
var records = [];
var ids = [];
list = this._applyX2ManyOperations(list);
if (_.isEmpty(list.data)) {
return $.when();
}
_.each(list.data, function (localId) {
var record = self.localData[localId];
var data = record._changes || record.data;
@@ -1992,13 +1997,16 @@ var BasicModel = AbstractModel.extend({
return this._rpc({
model: model,
method: 'name_get',
args: [ids],
args: [_.uniq(ids)],
context: list.context,
})
.then(function (name_gets) {
for (var i = 0; i < name_gets.length; i++) {
records[i].data.display_name = name_gets[i][1];
}
_.each(records, function (record) {
var nameGet = _.find(name_gets, function (nameGet) {
return nameGet[0] === record.data.id;
});
record.data.display_name = nameGet[1];
});
});
},
/**
@@ -2484,7 +2492,16 @@ var BasicModel = AbstractModel.extend({
var self = this;
var def;
if (list.static) {
def = this._readUngroupedList(list);
def = this._readUngroupedList(list).then(function () {
if (list.parentID && self.isNew(list.parentID)) {
// list from a default_get, so fetch display_name for many2one fields
var many2ones = self._getMany2OneFieldNames(list);
var defs = _.map(many2ones, function (name) {
return self._fetchNameGets(list, name);
});
return $.when.apply($, defs);
}
});
} else {
def = this._searchReadUngroupedList(list);
}
@@ -3052,6 +3069,23 @@ var BasicModel = AbstractModel.extend({
var viewType = options && options.viewType || element.viewType;
return Object.keys(fieldsInfo && fieldsInfo[viewType] || {});
},
/**
* Get many2one fields names in a datapoint. This is useful in order to
* fetch their names in the case of a default_get.
*
* @private
* @param {Object} datapoint a valid resource object
* @returns {string[]} list of field names that are many2one
*/
_getMany2OneFieldNames: function (datapoint) {
var many2ones = [];
_.each(datapoint.fields, function (field, name) {
if (field.type === 'many2one') {
many2ones.push(name);
}
});
return many2ones;
},
/**
* Evaluate the record evaluation context. This method is supposed to be
* called by _getEvalContext. It basically only generates a dictionary of
@@ -1237,8 +1237,167 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('list in form: discard newly added element with empty required field (onchange)', function (assert) {
assert.expect(8);
QUnit.test('list in form: name_get with unique ids (default_get)', function (assert) {
assert.expect(2);
this.data.partner.records[0].display_name = "MyTrululu";
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<field name="p">' +
'<tree editable="bottom">' +
'<field name="trululu"/>' +
'</tree>' +
'</field>' +
'</sheet>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'default_get') {
return $.when({
p: [
[0, 0, { trululu: 1 }],
[0, 0, { trululu: 1 }]
]
});
}
if (args.method === 'name_get') {
assert.deepEqual(args.args[0], _.uniq(args.args[0]),
"should not have duplicates in name_get rpc");
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.$('td.o_data_cell').text(), "MyTrululuMyTrululu",
"both records should have the correct display_name for trululu field");
form.destroy();
});
QUnit.test('list in form: show name of many2one fields in multi-page (default_get)', function (assert) {
assert.expect(4);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<sheet>' +
'<field name="p">' +
'<tree editable="bottom" limit="1">' +
'<field name="display_name"/>' +
'<field name="trululu"/>' +
'</tree>' +
'</field>' +
'</sheet>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'default_get') {
return $.when({
p: [
[0, 0, { display_name: 'record1', trululu: 1 }],
[0, 0, { display_name: 'record2', trululu: 2 }]
]
});
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.$('td.o_data_cell').first().text(),
"record1", "should show display_name of 1st record");
assert.strictEqual(form.$('td.o_data_cell').first().next().text(),
"first record", "should show display_name of trululu of 1st record");
form.$('button.o_pager_next').click();
assert.strictEqual(form.$('td.o_data_cell').first().text(),
"record2", "should show display_name of 2nd record");
assert.strictEqual(form.$('td.o_data_cell').first().next().text(),
"second record", "should show display_name of trululu of 2nd record");
form.destroy();
});
QUnit.test('list in form: discard newly added element with empty required field (onchange in default_get)', function (assert) {
// variant of the test "list in form: discard newly added element with
// empty required field (default_get)", in which the `default_get`
// performs an `onchange` at the same time. This `onchange` may create
// some records, which should not be abandoned on discard, similarly
// to records created directly by `default_get`
assert.expect(7);
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
this.data.partner.onchanges = {
product_id: function (obj) {
if (obj.product_id === 37) {
obj.p = [[0, 0, { display_name: "entry", trululu: false }]];
}
},
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<field name="product_id"/>' +
'<field name="p">' +
'<tree editable="bottom">' +
'<field name="display_name"/>' +
'<field name="trululu" required="1"/>' +
'</tree>' +
'</field>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'default_get') {
return $.when({
product_id: 37,
});
}
return this._super.apply(this, arguments);
},
});
// check that there is a record in the editable list with empty string as required field
assert.strictEqual(form.$('.o_data_row').length, 1,
"should have a row in the editable list");
assert.strictEqual($('td.o_data_cell').first().text(), "entry",
"should have the correct displayed name");
var requiredField = $('td.o_data_cell.o_required_modifier');
assert.strictEqual(requiredField.length, 1,
"should have a required field on this record");
assert.strictEqual(requiredField.text(), "",
"should have empty string in the required field on this record");
// click on empty required field in editable list record
requiredField.click();
// click off so that the required field still stay empty
$('body').click();
// record should not be dropped
assert.strictEqual(form.$('.o_data_row').length, 1,
"should not have dropped record in the editable list");
assert.strictEqual($('td.o_data_cell').first().text(), "entry",
"should still have the correct displayed name");
assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "",
"should still have empty string in the required field");
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
form.destroy();
});
QUnit.test('list in form: discard newly added element with empty required field (onchange after default_get)', function (assert) {
// discarding a record from an `onchange` in a `default_get` should not
// abandon the record. However, any `onchange` after `default_get`
// should be abandoned on discard
assert.expect(6);
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
@@ -1249,7 +1408,6 @@ QUnit.module('relational_fields', {
obj.p = [[0, 0, { display_name: "entry", trululu: false }]];
}
},
trululu: function () {},
};
var form = createView({
@@ -1291,13 +1449,9 @@ QUnit.module('relational_fields', {
// click off so that the required field still stay empty
$('body').click();
// record should not be dropped
assert.strictEqual(form.$('.o_data_row').length, 1,
"should not have droppped record in the editable list");
assert.strictEqual($('td.o_data_cell').first().text(), "entry",
"should still have the correct displayed name");
assert.strictEqual($('td.o_data_cell.o_required_modifier').text(), "",
"should still have empty string in the required field");
// record should be dropped
assert.strictEqual(form.$('.o_data_row').length, 0,
"should have dropped record in the editable list");
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
form.destroy();
@@ -3418,6 +3418,47 @@ QUnit.module('Views', {
testUtils.unpatch(mixins.ParentedMixin);
});
QUnit.test('concurrent reloads finishing in inverse order', function (assert) {
assert.expect(3);
var blockSearchRead = false;
var def = $.Deferred();
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree><field name="foo"/></tree>',
mockRPC: function (route) {
var result = this._super.apply(this, arguments);
if (route === '/web/dataset/search_read' && blockSearchRead) {
return $.when(def).then(_.constant(result));
}
return result;
},
});
assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
"list view should contain 4 records");
// reload with a domain (this request is blocked)
blockSearchRead = true;
list.reload({domain: [['foo', '=', 'yop']]});
assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
"list view should still contain 4 records (search_read being blocked)");
// reload without the domain
blockSearchRead = false;
list.reload({domain: []});
// unblock the RPC
def.resolve();
assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
"list view should still contain 4 records");
list.destroy();
});
QUnit.test('list view on a "noCache" model', function (assert) {
assert.expect(8);
@@ -373,13 +373,13 @@ var ImageWidget = MediaWidget.extend({
.sortBy(function (r) {
if (_.any(self.firstFilters, function (filter) {
var regex = new RegExp(filter, 'i');
return r.name.match(regex) || r.datas_fname.match(regex);
return r.name.match(regex) || r.datas_fname && r.datas_fname.match(regex);
})) {
return -1;
}
if (_.any(self.lastFilters, function (filter) {
var regex = new RegExp(filter, 'i');
return r.name.match(regex) || r.datas_fname.match(regex);
return r.name.match(regex) || r.datas_fname && r.datas_fname.match(regex);
})) {
return 1;
}
+1
View File
@@ -112,6 +112,7 @@ class WebsiteForm(http.Controller):
'record': {}, # Values to create record
'attachments': [], # Attached files
'custom': '', # Custom fields values
'meta': '', # Add metadata if enabled
}
authorized_fields = model.sudo()._get_form_writable_fields()
+1 -1
View File
@@ -7,7 +7,7 @@
'website': 'https://www.odoo.com/page/quote-builder',
'version': '1.0',
'description': "",
'depends': ['website', 'sale_management', 'mail', 'payment', 'website_mail'],
'depends': ['website', 'sale_management', 'mail', 'payment', 'website_mail', 'sale_payment'],
'data': [
'data/website_quote_data.xml',
'report/sale_order_reports.xml',
@@ -72,7 +72,7 @@ tour.register('shop_buy_product', {
content: "Pay Now",
//Either there are multiple payment methods, and one is checked, either there is only one, and therefore there are no radio inputs
extra_trigger: '#payment_method label:contains("Wire Transfer") input:checked,#payment_method:not(:has("input:radio:visible"))',
trigger: 'button[id="o_payment_form_pay"]:visible',
trigger: 'button[id="o_payment_form_pay"]:visible:not(:disabled)',
},
{
content: "finish",
+1 -1
View File
@@ -1152,7 +1152,7 @@
<t t-if="error" t-foreach="error.get('error_message', [])" t-as="err">
<h4 class="text-danger" t-esc="err" />
</t>
<form name="/shop/checkout" method="post" class="checkout_autoformat">
<form action="/shop/address" method="post" class="checkout_autoformat">
<div t-attf-class="form-group #{error.get('name') and 'has-error' or ''} col-md-12 div_name">
<label class="control-label" for="name">Name</label>
<input type="text" name="name" class="form-control" t-att-value="'name' in checkout and checkout['name']" />
@@ -4,7 +4,6 @@
from odoo import http, _
from odoo.http import request
from odoo.addons.website_sale.controllers.main import WebsiteSale
from odoo.tools import float_repr
class WebsiteSaleDelivery(WebsiteSale):
@@ -46,9 +45,6 @@ class WebsiteSaleDelivery(WebsiteSale):
has_stockable_products = any(line.product_id.type in ['consu', 'product'] for line in order.order_line)
if has_stockable_products:
if order.carrier_id and not order.delivery_rating_success:
values['errors'].append(
(_("Ouch, you cannot choose this carrier!"),
_("%s does not ship to your address, please choose another one.\n(Error: %s)" % (order.carrier_id.name, order.delivery_message))))
order._remove_delivery_line()
delivery_carriers = order._get_delivery_methods()
@@ -67,8 +63,15 @@ class WebsiteSaleDelivery(WebsiteSale):
return {'status': order.delivery_rating_success,
'error_message': order.delivery_message,
'carrier_id': carrier_id,
'new_amount_delivery': float_repr(currency.round(order.delivery_price), currency.decimal_places),
'new_amount_untaxed': order.amount_untaxed,
'new_amount_tax': order.amount_tax,
'new_amount_total': order.amount_total,
'new_amount_delivery': self._format_amount(order.delivery_price, currency),
'new_amount_untaxed': self._format_amount(order.amount_untaxed, currency),
'new_amount_tax': self._format_amount(order.amount_tax, currency),
'new_amount_total': self._format_amount(order.amount_total, currency),
}
def _format_amount(self, amount, currency):
fmt = "%.{0}f".format(currency.decimal_places)
lang = request.env['res.lang']._lang_get(request.env.context.get('lang') or 'en_US')
return lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\
.replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'\u2011')
@@ -65,12 +65,11 @@ class SaleOrder(models.Model):
carrier = delivery
break
self.write({'carrier_id': carrier.id})
self._remove_delivery_line()
if carrier:
self.get_delivery_price()
if self.delivery_rating_success:
self.set_delivery_line()
else:
self._remove_delivery_line()
return bool(carrier)
@@ -27,6 +27,10 @@ odoo.define('website_sale_delivery.checkout', function (require) {
else {
console.error(result.error_message);
$compute_badge.text(result.error_message);
$amount_delivery.text(result.new_amount_delivery);
$amount_untaxed.text(result.new_amount_untaxed);
$amount_tax.text(result.new_amount_tax);
$amount_total.text(result.new_amount_total);
}
};
@@ -40,6 +44,12 @@ odoo.define('website_sale_delivery.checkout', function (require) {
var $carriers = $("#delivery_carrier input[name='delivery_type']");
$carriers.click(_onCarrierClick);
// Workaround to:
// - update the amount/error on the label at first rendering
// - prevent clicking on 'Pay Now' if the shipper rating fails
if ($carriers.length > 0) {
$carriers.filter(':checked').click();
}
/* Handle stuff */
$(".oe_website_sale select[name='shipping_id']").on('change', function () {
@@ -3,7 +3,7 @@
<template id="cart_delivery" name="Delivery Costs" inherit_id="website_sale.total">
<xpath expr="//tr[@id='order_total_untaxed']" position="before">
<tr id="order_delivery" t-if="website_sale_order and website_sale_order.has_delivery">
<tr id="order_delivery" t-if="website_sale_order and website_sale_order.carrier_id">
<td class="text-right noborder text-muted" title="Delivery will be updated after choosing a new delivery method">Delivery:</td>
<td class="text-left-not-lg text-right-lg noborder text-muted" >
<span t-field="website_sale_order.amount_delivery" style="white-space: nowrap;" t-options='{
+16
View File
@@ -0,0 +1,16 @@
Spain, 2018-05-12
Acysos S.L. agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Ignacio Ibeas ignacio@acysos.com https://github.com/acysos
List of contributors:
Ignacio Ibeas ignacio@acysos.com https://github.com/acysos
Alex Ezquebo alexander@acysos.com https://github.com/acysos
+11
View File
@@ -0,0 +1,11 @@
Italy, 2018-05-17
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,
Samuele Carli <carlisamuele@csspace.net> https://github.com/WohthaN
@@ -397,6 +397,7 @@ state_it_sv,it,"Savona","SV"
state_it_si,it,"Siena","SI"
state_it_sr,it,"Siracusa","SR"
state_it_so,it,"Sondrio","SO"
state_it_su,it,"Sud Sardegna","SU"
state_it_ta,it,"Taranto","TA"
state_it_te,it,"Teramo","TE"
state_it_tr,it,"Terni","TR"
1 id country_id:id name code
397 state_it_si it Siena SI
398 state_it_sr it Siracusa SR
399 state_it_so it Sondrio SO
400 state_it_su it Sud Sardegna SU
401 state_it_ta it Taranto TA
402 state_it_te it Teramo TE
403 state_it_tr it Terni TR
@@ -1123,6 +1123,7 @@
<field file="base/static/img/country_flags/mx.png" name="image" type="base64" />
<field name="currency_id" ref="MXN" />
<field eval="52" name="phone_code" />
<field name="vat_label">RFC</field>
</record>
<record id="my" model="res.country">
<field name="name">Malaysia</field>
+1 -1
View File
@@ -7657,7 +7657,7 @@ msgstr "Aktionen"
#. module: base
#: model:ir.ui.view,arch_db:base.action_view_company_form_link_2_currencies
msgid "Activate more currencies"
msgstr ""
msgstr "Aktivieren von zusätzlichen Währungen"
#. module: base
#: model:ir.model.fields,field_description:base.field_ir_cron__active
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -177,7 +177,9 @@ class Partner(models.Model):
[('contact', 'Contact'),
('invoice', 'Invoice address'),
('delivery', 'Shipping address'),
('other', 'Other address')], string='Address Type',
('other', 'Other address'),
("private", "Private Address"),
], string='Address Type',
default='contact',
help="Used to select automatically the right address according to the context in sales and purchases documents.")
street = fields.Char()
@@ -1,5 +1,9 @@
<?xml version="1.0"?>
<odoo>
<record id="group_private_addresses" model="res.groups">
<field name="name">Access to Private Addresses</field>
</record>
<data noupdate="1">
<record model="ir.rule" id="res_users_log_rule">
<field name="name">res.users.log per user</field>
@@ -69,6 +73,37 @@
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<!-- Security restriction for private addresses -->
<record id="res_partner_rule_private_employee" model="ir.rule">
<field name="name">res.partner.rule.private.employee</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="domain_force">
[('type', '!=', 'private')]
</field>
<field name="groups" eval="[
(4, ref('base.group_user')),
]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- Relex previous rule for group_private_addresses -->
<record id="res_partner_rule_private_group" model="ir.rule">
<field name="name">res.partner.rule.private.group</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="domain_force">
[('type', '=', 'private')]
</field>
<field name="groups" eval="[
(4, ref('base.group_private_addresses'))
]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- Restrict modifications on ir.filters to owner only -->
<record id="ir_filters_admin_all_rights_rule" model="ir.rule">
<field name="name">ir.filters.admin.all.rights</field>
@@ -391,6 +391,50 @@
</field>
</record>
<!-- Special restricted view for private address, with limited "named" info that
can be traced back to the employee -->
<record id="res_partner_view_form_private" model="ir.ui.view">
<field name="name">res.partner.view.form.private</field>
<field name="model">res.partner</field>
<field name="priority" eval="300"/>
<field name="arch" type="xml">
<form string="Private Address Form">
<sheet>
<field name="type" invisible="1"/>
<label for="name" class="oe_editonly"/>
<field name="name" required="0"/>
<group>
<group>
<label for="street" string="Address"/>
<div>
<div class="o_address_format" name="div_address">
<field name="street" placeholder="Street..." class="o_address_street"/>
<field name="street2" placeholder="Street 2..." class="o_address_street"/>
<field name="city" placeholder="City" class="o_address_city"/>
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' context="{'country_id': country_id, 'zip': zip}"/>
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
</div>
</div>
</group>
<group>
<field name="phone" widget="phone"/>
<field name="mobile" widget="phone"/>
<field name="email"/>
</group>
</group>
<group string="Bank Accounts">
<field name="bank_ids">
<tree editable="bottom">
<field name="acc_number"/>
</tree>
</field>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_res_partner_filter" model="ir.ui.view">
<field name="name">res.partner.select</field>
<field name="model">res.partner</field>
+1 -1
View File
@@ -236,7 +236,7 @@
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="o_kanban_image">
<img t-att-src="kanban_image('res.users', 'image_medium', record.id.value)"/>
<img t-att-src="kanban_image('res.users', 'image_medium', record.id.raw_value)"/>
</div>
<div class="oe_kanban_details">
<ul>
+1 -1
View File
@@ -166,7 +166,7 @@ def redirect_with_hash(url, code=303):
# FIXME: decide whether urls should be bytes or text, apparently
# addons/website/controllers/main.py:91 calls this with a bytes url
# but addons/web/controllers/main.py:481 uses text... (blows up on login)
url = pycompat.to_text(url)
url = pycompat.to_text(url).strip()
if urls.url_parse(url, scheme='http').scheme not in ('http', 'https'):
url = u'http://' + url
url = url.replace("'", "%27").replace("<", "%3C")
+7 -2
View File
@@ -107,9 +107,11 @@ class ThreadedWSGIServerReloadable(LoggingBaseWSGIServerMixIn, werkzeug.serving.
if os.environ.get('LISTEN_FDS') == '1' and os.environ.get('LISTEN_PID') == str(os.getpid()):
self.reload_socket = True
self.socket = socket.fromfd(SD_LISTEN_FDS_START, socket.AF_INET, socket.SOCK_STREAM)
_logger.info('HTTP service (werkzeug) running through socket activation')
else:
self.reload_socket = False
super(ThreadedWSGIServerReloadable, self).server_bind()
_logger.info('HTTP service (werkzeug) running on %s:%s', self.server_name, self.server_port)
def server_activate(self):
if not self.reload_socket:
@@ -203,6 +205,9 @@ class ThreadedServer(CommonServer):
os._exit(0)
# interrupt run() to start shutdown
raise KeyboardInterrupt()
elif sig == signal.SIGXCPU:
sys.stderr.write("CPU time limit exceeded! Shutting down immediately\n")
os._exit(0)
elif sig == signal.SIGHUP:
# restart on kill -HUP
odoo.phoenix = True
@@ -253,7 +258,6 @@ class ThreadedServer(CommonServer):
t = threading.Thread(target=self.http_thread, name="odoo.service.httpd")
t.setDaemon(True)
t.start()
_logger.info('HTTP service (werkzeug) running on %s:%s', self.interface, self.port)
def start(self, stop=False):
_logger.debug("Setting signal handlers")
@@ -262,6 +266,7 @@ class ThreadedServer(CommonServer):
signal.signal(signal.SIGTERM, self.signal_handler)
signal.signal(signal.SIGCHLD, self.signal_handler)
signal.signal(signal.SIGHUP, self.signal_handler)
signal.signal(signal.SIGXCPU, self.signal_handler)
signal.signal(signal.SIGQUIT, dumpstacks)
signal.signal(signal.SIGUSR1, log_ormcache_stats)
elif os.name == 'nt':
@@ -369,7 +374,7 @@ class GeventServer(CommonServer):
signal.signal(signal.SIGQUIT, dumpstacks)
signal.signal(signal.SIGUSR1, log_ormcache_stats)
gevent.spawn(self.watchdog)
self.httpd = WSGIServer((self.interface, self.port), self.app)
_logger.info('Evented Service (longpolling) running on %s:%s', self.interface, self.port)
try:
+6 -3
View File
@@ -10,7 +10,7 @@ import socket
import threading
import time
from email.header import decode_header
from email.header import decode_header, Header
from email.utils import getaddresses, formataddr
from lxml import etree
@@ -521,11 +521,14 @@ def decode_smtp_header(smtp_header):
text. email.header decode_header method return a decoded string and its
charset for each decoded par of the header. This method unicodes the
decoded header and join them in a complete string. """
if isinstance(smtp_header, Header):
smtp_header = ustr(smtp_header)
if smtp_header:
text = decode_header(smtp_header.replace('\r', ''))
# The joining space will not be needed as of Python 3.3
# See https://hg.python.org/cpython/rev/8c03fe231877
return ' '.join([ustr(x[0], x[1]) for x in text])
# See https://github.com/python/cpython/commit/07ea53cb218812404cdbde820647ce6e4b2d0f8e
sep = ' ' if pycompat.PY2 else ''
return sep.join([ustr(x[0], x[1]) for x in text])
return u''
# was mail_thread.decode_header()