[MERGE] forward port branch saas-11.2 up to 58e3552246
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])],
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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 -->
|
||||
<!-- ************************************************************* -->
|
||||
|
||||
@@ -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')])]"/>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'">
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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='{
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
+706
-1
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user