diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-06-15 13:27:17 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-06-15 13:27:27 +0200 |
| commit | 73652a0b196b51ec1f9a27880d0b398fb79bfca3 (patch) | |
| tree | 04e741fefdbb859dffa66a4037da7777ea980476 /addons | |
| parent | 8a03563269c3dc55c9c7157494af51e2485f1a14 (diff) | |
| parent | 50860317ccd2c1b44f873598858a3cff87fa7ea6 (diff) | |
[MERGE] forward port branch saas-11.3 up to 50860317ccd
Note: 1aacc962620982528a3dfc90d2c1dd0027359375 has been ignored and will
be forward-ported later
Diffstat (limited to 'addons')
96 files changed, 666 insertions, 275 deletions
diff --git a/addons/account/i18n/ru.po b/addons/account/i18n/ru.po index a3f724f574f..71955ffdde2 100644 --- a/addons/account/i18n/ru.po +++ b/addons/account/i18n/ru.po @@ -35,13 +35,14 @@ # Yuriy Ney <k_wizard@mail.ru>, 2017 # Yuriy Ney <kwizardn@gmail.com>, 2018 # sergeiruzkiicode <sergei.ruzki@icode.by>, 2018 +# Илья Пономарев <horochiy92@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: sergeiruzkiicode <sergei.ruzki@icode.by>, 2018\n" +"Last-Translator: Илья Пономарев <horochiy92@gmail.com>, 2018\n" "Language-Team: Russian (https://www.transifex.com/odoo/teams/41243/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -4790,12 +4791,12 @@ msgstr "" #. module: account #: model:ir.ui.view,arch_db:account.res_config_settings_view_form msgid "Import your bank statements in CAMT.053" -msgstr "" +msgstr "Импорт банковской выписки в формате CAMT.053" #. module: account #: model:ir.ui.view,arch_db:account.res_config_settings_view_form msgid "Import your bank statements in CSV" -msgstr "" +msgstr "Импорт Банковской Выписки в формате CSV" #. module: account #: model:ir.ui.view,arch_db:account.res_config_settings_view_form diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index ebc430f0603..d2555241023 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -427,7 +427,7 @@ class AccountInvoice(models.Model): """ computes the prefix of the number that will be assigned to the first invoice/bill/refund of a journal, in order to let the user manually change it. """ - if not self.env.user._is_admin(): + if not self.env.user._is_system(): for invoice in self: invoice.sequence_number_next_prefix = False invoice.sequence_number_next = '' @@ -1629,6 +1629,13 @@ class AccountInvoiceLine(models.Model): return accounts['income'] return accounts['expense'] + def _set_currency(self): + company = self.invoice_id.company_id + currency = self.invoice_id.currency_id + if company and currency: + if company.currency_id != currency: + self.price_unit = self.price_unit * currency.with_context(dict(self._context or {}, date=self.invoice_id.date_invoice)).rate + def _set_taxes(self): """ Used in on_change to set taxes and price.""" if self.invoice_id.type in ('out_invoice', 'out_refund'): @@ -1647,8 +1654,10 @@ class AccountInvoiceLine(models.Model): prec = self.env['decimal.precision'].precision_get('Product Price') if not self.price_unit or float_compare(self.price_unit, self.product_id.standard_price, precision_digits=prec) == 0: self.price_unit = fix_price(self.product_id.standard_price, taxes, fp_taxes) + self._set_currency() else: self.price_unit = fix_price(self.product_id.lst_price, taxes, fp_taxes) + self._set_currency() @api.onchange('product_id') def _onchange_product_id(self): @@ -1697,8 +1706,6 @@ class AccountInvoiceLine(models.Model): domain['uom_id'] = [('category_id', '=', product.uom_id.category_id.id)] if company and currency: - if company.currency_id != currency: - self.price_unit = self.price_unit * currency.with_context(dict(self._context or {}, date=self.invoice_id.date_invoice)).rate if self.uom_id and self.uom_id.id != product.uom_id.id: self.price_unit = product.uom_id._compute_price(self.price_unit, self.uom_id) diff --git a/addons/account/models/res_config_settings.py b/addons/account/models/res_config_settings.py index 601bda4541e..7282714202b 100644 --- a/addons/account/models/res_config_settings.py +++ b/addons/account/models/res_config_settings.py @@ -14,7 +14,7 @@ class ResConfigSettings(models.TransientModel): 'account.journal', related='company_id.currency_exchange_journal_id', string="Exchange Gain or Loss Journal", - domain=[('type', '=', 'general')], + domain="[('company_id', '=', company_id), ('type', '=', 'general')]", help='The accounting journal where automatic exchange differences will be registered') has_chart_of_accounts = fields.Boolean(compute='_compute_has_chart_of_accounts', string='Company has a chart of accounts') chart_template_id = fields.Many2one('account.chart.template', string='Template', diff --git a/addons/account/views/account_journal_dashboard_view.xml b/addons/account/views/account_journal_dashboard_view.xml index fe5d69d5eb9..0c27c843252 100644 --- a/addons/account/views/account_journal_dashboard_view.xml +++ b/addons/account/views/account_journal_dashboard_view.xml @@ -248,14 +248,16 @@ <span><t t-esc="dashboard.account_balance"/></span> </div> </div> - <div class="row" name="latest_statement" t-if="dashboard.last_balance != dashboard.account_balance"> - <div class="col-xs-7"> + <t t-if="dashboard.last_balance != dashboard.account_balance"> + <div class="row" name="latest_statement"> + <div class="col-xs-7"> <span title="Latest Statement">Latest Statement</span> - </div> - <div class="col-xs-5 text-right"> + </div> + <div class="col-xs-5 text-right"> <span><t t-esc="dashboard.last_balance"/></span> + </div> </div> - </div> + </t> </div> </t> <t t-name="JournalBodySalePurchase" id="account.JournalBodySalePurchase"> diff --git a/addons/account/views/report_journal.xml b/addons/account/views/report_journal.xml index 06dbab99ae4..c642b89347a 100644 --- a/addons/account/views/report_journal.xml +++ b/addons/account/views/report_journal.xml @@ -50,7 +50,7 @@ <td><span t-field="aml.date"/></td> <td><span t-field="aml.account_id.code"/></td> <td><span t-esc="aml.sudo().partner_id and aml.sudo().partner_id.name and aml.sudo().partner_id.name[:23] or ''"/></td> - <td><span t-esc="aml.name[:35]"/></td> + <td><span t-esc="aml.name and aml.name[:35]"/></td> <td><span t-esc="aml.debit" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td> <td><span t-esc="aml.credit" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td> <td t-if="data['form']['amount_currency'] and aml.amount_currency"> diff --git a/addons/account_bank_statement_import/account_bank_statement_import.py b/addons/account_bank_statement_import/account_bank_statement_import.py index 45c285a5d51..6fb102104c5 100644 --- a/addons/account_bank_statement_import/account_bank_statement_import.py +++ b/addons/account_bank_statement_import/account_bank_statement_import.py @@ -50,7 +50,11 @@ class AccountBankStatementImport(models.TransientModel): # Create the bank statements statement_ids, notifications = self._create_bank_statements(stmts_vals) # Now that the import worked out, set it as the bank_statements_source of the journal - journal.bank_statements_source = 'file_import' + if journal.bank_statements_source != 'file_import': + # Use sudo() because only 'account.group_account_manager' + # has write access on 'account.journal', but 'account.group_account_user' + # must be able to import bank statement files + journal.sudo().bank_statements_source = 'file_import' # Finally dispatch to reconciliation interface action = self.env.ref('account.action_bank_reconcile_bank_statements') return { diff --git a/addons/account_check_printing/i18n/tr.po b/addons/account_check_printing/i18n/tr.po index a57d2d5029c..f22ebc6235d 100644 --- a/addons/account_check_printing/i18n/tr.po +++ b/addons/account_check_printing/i18n/tr.po @@ -8,13 +8,14 @@ # Ayhan KIZILTAN <akiziltan76@hotmail.com>, 2018 # Martin Trigaux, 2018 # Alexander B. <road2monstercat@gmail.com>, 2018 +# 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: Alexander B. <road2monstercat@gmail.com>, 2018\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" @@ -333,6 +334,8 @@ msgid "" "The ISO country code in two chars. \n" "You can use this field for quick search." msgstr "" +"İki karakterde ISO ülke kodu.\n" +"Hızlı arama için bu alanı kullanabilirsiniz." #. module: account_check_printing #: code:addons/account_check_printing/models/account_journal.py:26 diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py index c35430d1f92..07c42694ef1 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -10,7 +10,7 @@ from odoo.tools.translate import _ class Partner(models.Model): _inherit = 'res.partner' - country_enforce_cities = fields.Boolean(related='country_id.enforce_cities') + country_enforce_cities = fields.Boolean(related='country_id.enforce_cities', readonly=True) city_id = fields.Many2one('res.city', string='City of Address') @api.onchange('city_id') diff --git a/addons/board/static/src/js/favorite_menu.js b/addons/board/static/src/js/favorite_menu.js index 701ebef2801..112b4d634fa 100644 --- a/addons/board/static/src/js/favorite_menu.js +++ b/addons/board/static/src/js/favorite_menu.js @@ -99,7 +99,10 @@ FavoriteMenu.include({ }) .then(function (r) { if (r) { - self.do_notify(_.str.sprintf(_t("'%s' added to dashboard"), name), ''); + self.do_notify( + _.str.sprintf(_t("'%s' added to dashboard"), name), + _t('Please refresh your browser for the changes to take effect.') + ); } else { self.do_warn(_t("Could not add filter to dashboard")); } diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 26f6629158d..3ec21c7eb1e 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -243,6 +243,8 @@ class Lead(models.Model): @api.model def _onchange_user_values(self, user_id): """ returns new values when user_id has changed """ + if not user_id: + return {} if user_id and self._context.get('team_id'): team = self.env['crm.team'].browse(self._context['team_id']) if user_id in team.member_ids.ids: diff --git a/addons/event/i18n/tr.po b/addons/event/i18n/tr.po index 67b6aeb2e6f..90c8084cbdc 100644 --- a/addons/event/i18n/tr.po +++ b/addons/event/i18n/tr.po @@ -1551,7 +1551,7 @@ msgstr "" #. module: event #: model:ir.model,name:event.model_event_confirm msgid "event.confirm" -msgstr "" +msgstr "event.confirm" #. module: event #: code:addons/event/models/event.py:503 diff --git a/addons/event/models/event.py b/addons/event/models/event.py index 491459b135a..1c2189a2d12 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -444,9 +444,14 @@ class EventRegistration(models.Model): @api.multi def message_get_suggested_recipients(self): recipients = super(EventRegistration, self).message_get_suggested_recipients() + public_users = self.env['res.users'].sudo() + public_groups = self.env.ref("base.group_public", raise_if_not_found=False) + if public_groups: + public_users = public_groups.sudo().with_context(active_test=False).mapped("users") try: for attendee in self: - if attendee.partner_id: + is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False + if attendee.partner_id and not is_public: attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer')) elif attendee.email: attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email')) diff --git a/addons/event/models/res_partner.py b/addons/event/models/res_partner.py index e91773c8fee..03934562824 100644 --- a/addons/event/models/res_partner.py +++ b/addons/event/models/res_partner.py @@ -10,6 +10,8 @@ class ResPartner(models.Model): event_count = fields.Integer("Events Count", compute='_compute_event_count', help="Number of events the partner has participated.") def _compute_event_count(self): + if not self.user_has_groups('event.group_event_user'): + return for partner in self: partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)]) diff --git a/addons/event/report/event_event_templates.xml b/addons/event/report/event_event_templates.xml index 76b64138f44..8490dc4aaca 100644 --- a/addons/event/report/event_event_templates.xml +++ b/addons/event/report/event_event_templates.xml @@ -18,7 +18,7 @@ <img t-att-src="'data:image/png;base64,%s' % to_text(o.event_id.organizer_id.company_id.logo_web)" style="max-height:1cm; max-width:4cm;"/> </span> <h5 t-field="o.event_id.name"/> - <h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.date_begin" t-options='{"hide_seconds": True}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.date_end" t-options='{"hide_seconds": True}'> </span> )</h5> + <h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.with_context(tz=o.event_id.date_tz).date_begin" t-options='{"hide_seconds": True}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.with_context(tz=o.event_id.date_tz).date_end" t-options='{"hide_seconds": True}'> </span> )</h5> </div> <div class="row"> <div class="col-xs-12 text-center" id="o_event_name"> diff --git a/addons/event/views/res_partner_views.xml b/addons/event/views/res_partner_views.xml index 59d096efcd1..22aeba8686a 100644 --- a/addons/event/views/res_partner_views.xml +++ b/addons/event/views/res_partner_views.xml @@ -11,6 +11,7 @@ <button class="oe_stat_button" type="object" icon="fa-ticket" + groups="event.group_event_user" name="action_event_view" attrs="{'invisible': [('event_count','=', 0)]}"> <field string="Events" name="event_count" widget="statinfo"/> </button> diff --git a/addons/event_sale/i18n/tr.po b/addons/event_sale/i18n/tr.po index 1cf54f0f8c6..ab1b8afb73f 100644 --- a/addons/event_sale/i18n/tr.po +++ b/addons/event_sale/i18n/tr.po @@ -455,7 +455,7 @@ msgstr "Lütfen kayıtlarla ilgili ayrıntıları veriniz." #. module: event_sale #: model:ir.model,name:event_sale.model_registration_editor msgid "registration.editor" -msgstr "" +msgstr "registration.editor" #. module: event_sale #: model:ir.model,name:event_sale.model_registration_editor_line diff --git a/addons/fleet/i18n/tr.po b/addons/fleet/i18n/tr.po index 6986a11fd02..d707676ed82 100644 --- a/addons/fleet/i18n/tr.po +++ b/addons/fleet/i18n/tr.po @@ -158,6 +158,8 @@ msgstr "Sözleşmenin hala geçerli olup olmadığını seçin" msgid "" "Choose whether the service refer to contracts, vehicle services or both" msgstr "" +"Hizmetin sözleşmelerle mi, araç hizmetleriyle mi ya da her ikisiyle de mi " +"ilgili olduğunu seçin" #. module: fleet #: model:ir.ui.view,arch_db:fleet.fleet_vehicle_log_contract_view_form diff --git a/addons/google_calendar/i18n/tr.po b/addons/google_calendar/i18n/tr.po index 13557b095b7..ebf3cda9fcf 100644 --- a/addons/google_calendar/i18n/tr.po +++ b/addons/google_calendar/i18n/tr.po @@ -292,7 +292,7 @@ msgstr "Tokenınız geçersiz ya da iptal edilmiş!" #. module: google_calendar #: model:ir.model,name:google_calendar.model_google_calendar msgid "google.calendar" -msgstr "" +msgstr "google.calendar" #. module: google_calendar #: model:ir.model,name:google_calendar.model_res_config_settings diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 995a7252a92..1a97029e4e0 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -92,7 +92,7 @@ class HolidaysRequest(models.Model): help='Number of hours of the leave request according to your working schedule.') meeting_id = fields.Many2one('calendar.event', string='Meeting') - parent_id = fields.Many2one('hr.leave', string='Parent') + parent_id = fields.Many2one('hr.leave', string='Parent', copy=False) linked_request_ids = fields.One2many('hr.leave', 'parent_id', string='Linked Requests') department_id = fields.Many2one('hr.department', string='Department', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True, diff --git a/addons/hr_recruitment/i18n/pt_BR.po b/addons/hr_recruitment/i18n/pt_BR.po index f313ca3b50e..1e3d3e4a631 100644 --- a/addons/hr_recruitment/i18n/pt_BR.po +++ b/addons/hr_recruitment/i18n/pt_BR.po @@ -12,13 +12,14 @@ # danimaribeiro <danimaribeiro@gmail.com>, 2016 # Adriel Kotviski <kotviski@gmail.com>, 2016 # Silmar <pinheirosilmar@gmail.com>, 2017 +# Thiago Alves Cavalcante <tkramer.tk@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: Silmar <pinheirosilmar@gmail.com>, 2017\n" +"Last-Translator: Thiago Alves Cavalcante <tkramer.tk@gmail.com>, 2018\n" "Language-Team: Portuguese (Brazil) (https://www.transifex.com/odoo/teams/41243/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -414,7 +415,7 @@ msgstr "<span class=\"o_stat_text\">Rastreadores</span>" #. module: hr_recruitment #: model:ir.ui.view,arch_db:hr_recruitment.view_hr_recruitment_stage_kanban msgid "<span>Folded in Recruitment Pipe: </span>" -msgstr "" +msgstr "Dobrado na Lista de Recrutamento" #. module: hr_recruitment #: model:ir.model.fields,help:hr_recruitment.field_hr_job__alias_defaults @@ -484,7 +485,7 @@ msgstr "Modelo Apelidado" #. module: hr_recruitment #: model:ir.ui.menu,name:hr_recruitment.menu_crm_case_categ_all_app msgid "All Applications" -msgstr "" +msgstr "Todas as aplicações" #. module: hr_recruitment #: model:ir.model,name:hr_recruitment.model_hr_applicant @@ -608,7 +609,7 @@ msgstr "Anexos" #. module: hr_recruitment #: model:ir.model.fields,field_description:hr_recruitment.field_hr_recruitment_stage__template_id msgid "Automated Email" -msgstr "" +msgstr "E-mail Automatizado" #. module: hr_recruitment #: model:ir.model.fields,field_description:hr_recruitment.field_hr_applicant__availability @@ -650,7 +651,7 @@ msgstr "Categoria de candidato" #: code:addons/hr_recruitment/static/src/js/tour.js:16 #, python-format msgid "Click here to create a new job position." -msgstr "" +msgstr "Clique aqui para criar um novo cargo." #. module: hr_recruitment #: model:ir.model.fields,field_description:hr_recruitment.field_hr_applicant__date_closed @@ -722,7 +723,7 @@ msgstr "Criar Funcionário" #: model:ir.actions.act_window,name:hr_recruitment.create_job_simple #: model:ir.ui.view,arch_db:hr_recruitment.hr_job_simple_form msgid "Create a Job Position" -msgstr "" +msgstr "Criar um Cargo" #. module: hr_recruitment #: model:ir.ui.view,arch_db:hr_recruitment.hr_recruitment_source_tree @@ -915,7 +916,7 @@ msgstr "Apelido de E-mail" #. module: hr_recruitment #: model:ir.ui.view,arch_db:hr_recruitment.hr_job_survey msgid "Email alias" -msgstr "" +msgstr "Apelido de email" #. module: hr_recruitment #: model:ir.model.fields,help:hr_recruitment.field_hr_job__alias_id @@ -1039,7 +1040,7 @@ msgstr "Departamento de RH" #. module: hr_recruitment #: model:ir.model.fields,field_description:hr_recruitment.field_hr_job__hr_responsible_id msgid "HR Responsible" -msgstr "" +msgstr "Responsável do RH" #. module: hr_recruitment #: model:ir.ui.view,arch_db:hr_recruitment.view_hr_job_kanban @@ -1091,7 +1092,7 @@ msgstr "Qualificação Inicial" #: model:ir.model.fields,field_description:hr_recruitment.field_res_config_settings__module_hr_recruitment_survey #: model:ir.ui.view,arch_db:hr_recruitment.res_config_settings_view_form msgid "Interview Forms" -msgstr "" +msgstr "Formulários de Entrevista" #. module: hr_recruitment #: code:addons/hr_recruitment/models/hr_job.py:94 @@ -1104,12 +1105,12 @@ msgstr "Trabalho" #. module: hr_recruitment #: model:mail.template,subject:hr_recruitment.email_template_data_applicant_congratulations msgid "Job Application Confirmation: ${object.job_id.name | safe}" -msgstr "" +msgstr "Confirmação de pedido de emprego: ${object.job_id.name | safe}" #. module: hr_recruitment #: model:ir.ui.view,arch_db:hr_recruitment.crm_case_pivot_view_job msgid "Job Applications" -msgstr "" +msgstr "Solicitações de emprego" #. module: hr_recruitment #: model:utm.campaign,name:hr_recruitment.utm_campaign_job @@ -1152,12 +1153,12 @@ msgstr "Cargos" #. module: hr_recruitment #: model:ir.ui.view,arch_db:hr_recruitment.res_config_settings_view_form msgid "Job Posting" -msgstr "" +msgstr "Anúncio de emprego" #. module: hr_recruitment #: model:ir.model.fields,field_description:hr_recruitment.field_hr_recruitment_stage__job_id msgid "Job Specific" -msgstr "" +msgstr "Especificação de trabalho" #. module: hr_recruitment #: model:ir.ui.view,arch_db:hr_recruitment.hr_applicant_view_search @@ -1228,7 +1229,7 @@ msgstr "" #, python-format msgid "" "Let's have a look at the <b>applications pipeline</b> for this job position." -msgstr "" +msgstr "Vamos dar uma olhada na <b>lista de exigências</b> para este cargo. " #. module: hr_recruitment #: model:res.groups,name:hr_recruitment.group_hr_recruitment_manager diff --git a/addons/hr_recruitment/views/hr_job_views.xml b/addons/hr_recruitment/views/hr_job_views.xml index 2311188baed..201d58970cf 100644 --- a/addons/hr_recruitment/views/hr_job_views.xml +++ b/addons/hr_recruitment/views/hr_job_views.xml @@ -23,7 +23,6 @@ <field name="manager_id"/> <field name="state"/> <field name="user_id"/> - <field name="documents_count"/> <templates> <t t-name="kanban-box"> <div t-attf-class="#{kanban_color(record.color.raw_value)}"> @@ -49,7 +48,6 @@ <div class="o_recruitment_kanban_box o_kanban_primary_bottom bottom_block"> <a name="action_get_attachment_tree_view" type="object"> <span title='Documents'><i class='fa fa-book'/> - <t t-esc="record.documents_count.raw_value"/> </span> </a> </div> diff --git a/addons/hr_recruitment_survey/i18n/pt_BR.po b/addons/hr_recruitment_survey/i18n/pt_BR.po index 270f1dc87bf..f12f2ac4c2b 100644 --- a/addons/hr_recruitment_survey/i18n/pt_BR.po +++ b/addons/hr_recruitment_survey/i18n/pt_BR.po @@ -5,17 +5,18 @@ # Translators: # grazziano <gra.negocia@gmail.com>, 2016 # Clemilton Clementino <clemylton@hotmail.com>, 2016 -# Martin Trigaux <mat@odoo.com>, 2016 +# Martin Trigaux, 2016 # Mateus Lopes <mateus1@gmail.com>, 2016 # danimaribeiro <danimaribeiro@gmail.com>, 2016 # Rodrigo de Almeida Sottomaior Macedo <rmsolucoeseminformatic4@gmail.com>, 2016 +# Thiago Alves Cavalcante <tkramer.tk@gmail.com>, 2018 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.saas~18\n" +"Project-Id-Version: Odoo Server saas~11.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-20 09:53+0000\n" -"PO-Revision-Date: 2017-09-20 09:53+0000\n" -"Last-Translator: Rodrigo de Almeida Sottomaior Macedo <rmsolucoeseminformatic4@gmail.com>, 2016\n" +"POT-Creation-Date: 2018-03-22 14:10+0000\n" +"PO-Revision-Date: 2018-03-22 14:10+0000\n" +"Last-Translator: Thiago Alves Cavalcante <tkramer.tk@gmail.com>, 2018\n" "Language-Team: Portuguese (Brazil) (https://www.transifex.com/odoo/teams/41243/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -102,8 +103,8 @@ msgid "Basic information" msgstr "Informações básicas" #. module: hr_recruitment_survey -#: model:ir.model.fields,help:hr_recruitment_survey.field_hr_applicant_survey_id -#: model:ir.model.fields,help:hr_recruitment_survey.field_hr_job_survey_id +#: model:ir.model.fields,help:hr_recruitment_survey.field_hr_applicant__survey_id +#: model:ir.model.fields,help:hr_recruitment_survey.field_hr_job__survey_id msgid "" "Choose an interview form for this job position and you will be able to " "print/answer this interview from all applicants who apply for this job" @@ -201,7 +202,7 @@ msgid "Important" msgstr "Importante" #. module: hr_recruitment_survey -#: model:ir.model.fields,field_description:hr_recruitment_survey.field_hr_job_survey_id +#: model:ir.model.fields,field_description:hr_recruitment_survey.field_hr_job__survey_id #: model:ir.ui.view,arch_db:hr_recruitment_survey.view_hr_job_kanban_inherit msgid "Interview Form" msgstr "Formulário de Entrevista" @@ -209,7 +210,7 @@ msgstr "Formulário de Entrevista" #. module: hr_recruitment_survey #: model:ir.ui.view,arch_db:hr_recruitment_survey.res_config_settings_view_form msgid "Interview Forms" -msgstr "" +msgstr "Formulários de Entrevista" #. module: hr_recruitment_survey #: model:ir.model,name:hr_recruitment_survey.model_hr_job @@ -282,7 +283,7 @@ msgid "Regular meetings" msgstr "Reuniões regulares" #. module: hr_recruitment_survey -#: model:ir.model.fields,field_description:hr_recruitment_survey.field_hr_applicant_response_id +#: model:ir.model.fields,field_description:hr_recruitment_survey.field_hr_applicant__response_id msgid "Response" msgstr "Resposta" @@ -297,7 +298,7 @@ msgid "State of the art technology" msgstr "Estado da Arte da Tecnologia" #. module: hr_recruitment_survey -#: model:ir.model.fields,field_description:hr_recruitment_survey.field_hr_applicant_survey_id +#: model:ir.model.fields,field_description:hr_recruitment_survey.field_hr_applicant__survey_id msgid "Survey" msgstr "Pesquisa" diff --git a/addons/hr_timesheet_attendance/i18n/tr.po b/addons/hr_timesheet_attendance/i18n/tr.po index 5588db3e311..8865465e6d4 100644 --- a/addons/hr_timesheet_attendance/i18n/tr.po +++ b/addons/hr_timesheet_attendance/i18n/tr.po @@ -86,7 +86,7 @@ msgstr "Kullanıcı" #. module: hr_timesheet_attendance #: model:ir.model,name:hr_timesheet_attendance.model_hr_timesheet_attendance_report msgid "hr.timesheet.attendance.report" -msgstr "" +msgstr "hr.timesheet.attendance.report" #. module: hr_timesheet_attendance #: model:ir.ui.view,arch_db:hr_timesheet_attendance.view_hr_timesheet_attendance_report_pivot diff --git a/addons/iap/i18n/tr.po b/addons/iap/i18n/tr.po index 0a841f08079..7a9b5661000 100644 --- a/addons/iap/i18n/tr.po +++ b/addons/iap/i18n/tr.po @@ -130,4 +130,4 @@ msgstr "Hizmet Adı" #. module: iap #: model:ir.model,name:iap.model_iap_account msgid "iap.account" -msgstr "" +msgstr "iap.account" diff --git a/addons/im_livechat/i18n/tr.po b/addons/im_livechat/i18n/tr.po index 996f2a4e8a3..7b440f587db 100644 --- a/addons/im_livechat/i18n/tr.po +++ b/addons/im_livechat/i18n/tr.po @@ -64,6 +64,11 @@ msgid "" "country, GeoIP must be installed on your server, otherwise, the countries of" " the rule will not be taken into account.</span>" msgstr "" +"<span>Canlı destek kanalınız için kuralları tanımlayın. Belirtilen URL ve " +"ülke için aksiyon tanımlayabilir (desteği gizle, desteği otomatik aç, veya " +"sadece butonu göster)<br/>Ülkeyi tanımlayabilmek için, GeoIP'nin sunucuda " +"yüklenmiş olması gerekmektedir, aksi durumda ülke ile ilgili kurallar " +"geçersiz olacaktır.</span>" #. module: im_livechat #: model:ir.model.fields,field_description:im_livechat.field_im_livechat_channel_rule__action diff --git a/addons/l10n_nl/data/account_tax_template.xml b/addons/l10n_nl/data/account_tax_template.xml index c26dc9a8345..e9b937b98f5 100644 --- a/addons/l10n_nl/data/account_tax_template.xml +++ b/addons/l10n_nl/data/account_tax_template.xml @@ -593,7 +593,7 @@ <field name="refund_account_id" ref="vat_refund_h_non_eu"/> <field name="account_id" ref="vat_refund_h_non_eu"/> <field name="type_tax_use">none</field> - <field name="tag_ids" eval="[(6,0,[ref('tag_nl_34')])]"/> + <field name="tag_ids" eval="[(6,0,[ref('tag_nl_33')])]"/> <field name="tax_group_id" ref="tax_group_21_niet_eu"/> </record> <record id="btw_E2" model="account.tax.template"> @@ -735,7 +735,7 @@ <field name="refund_account_id" ref="vat_refund_h_d_non_eu"/> <field name="account_id" ref="vat_refund_h_d_non_eu"/> <field name="type_tax_use">none</field> - <field name="tag_ids" eval="[(6,0,[ref('tag_nl_34')])]"/> + <field name="tag_ids" eval="[(6,0,[ref('tag_nl_33')])]"/> <field name="tax_group_id" ref="tax_group_21_niet_eu"/> </record> <record id="btw_E2_d" model="account.tax.template"> diff --git a/addons/link_tracker/i18n/tr.po b/addons/link_tracker/i18n/tr.po index 21f4defb143..325781ee040 100644 --- a/addons/link_tracker/i18n/tr.po +++ b/addons/link_tracker/i18n/tr.po @@ -228,19 +228,19 @@ msgstr "Website Link Tıklamaları" #. module: link_tracker #: model:ir.model,name:link_tracker.model_link_tracker msgid "link.tracker" -msgstr "" +msgstr "link.tracker" #. module: link_tracker #: model:ir.model,name:link_tracker.model_link_tracker_click msgid "link.tracker.click" -msgstr "" +msgstr "link.tracker.click" #. module: link_tracker #: model:ir.model,name:link_tracker.model_link_tracker_code msgid "link.tracker.code" -msgstr "" +msgstr "link.tracker.code" #. module: link_tracker #: model:ir.actions.act_window,name:link_tracker.action_link_tracker_stats msgid "link.tracker.form.graph.action" -msgstr "" +msgstr "link.tracker.form.graph.action" diff --git a/addons/lunch/i18n/tr.po b/addons/lunch/i18n/tr.po index 6fd066e863d..4bb623040f0 100644 --- a/addons/lunch/i18n/tr.po +++ b/addons/lunch/i18n/tr.po @@ -986,4 +986,4 @@ msgstr "öğle yemeği ürün kategorisi" #. module: lunch #: model:ir.model,name:lunch.model_lunch_order_line_lucky msgid "lunch.order.line.lucky" -msgstr "" +msgstr "lunch.order.line.lucky" diff --git a/addons/mail/i18n/tr.po b/addons/mail/i18n/tr.po index 10f755b2fec..1aabb67b1ff 100644 --- a/addons/mail/i18n/tr.po +++ b/addons/mail/i18n/tr.po @@ -329,6 +329,8 @@ msgid "" "A shortcode is a keyboard shortcut. For instance, you type #gm and it will " "be transformed into \"Good Morning\"." msgstr "" +"Bir kısa kod, bir klavye kısayoludır. Örneğin, # gm yazarsınız ve " +"\"Günaydın\" a dönüşür." #. module: mail #: model:ir.model,name:mail.model_res_groups @@ -1518,6 +1520,8 @@ msgid "" "Email address of the sender. This field is set when no matching partner is " "found and replaces the author_id field in the chatter." msgstr "" +"Gönderenin e-posta adresi. Eşleşen bir ortak bulunmadığında ve sohbetteki " +"author_id alanının yerini aldığında bu alan ayarlanır." #. module: mail #: model:ir.ui.view,arch_db:mail.email_compose_message_wizard_form @@ -3123,7 +3127,7 @@ msgstr "" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__new_value_char msgid "New Value Char" -msgstr "" +msgstr "Yeni Karakter Değeri" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__new_value_datetime @@ -3143,12 +3147,12 @@ msgstr "Yeni Tam Sayı Değeri" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__new_value_monetary msgid "New Value Monetary" -msgstr "" +msgstr "Yeni Parasal Değer" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__new_value_text msgid "New Value Text" -msgstr "" +msgstr "Yeni Metin Değeri" #. module: mail #. openerp-web @@ -3275,7 +3279,7 @@ msgstr "" #: code:addons/mail/static/src/xml/web_kanban_activity.xml:22 #, python-format msgid "No activities planned." -msgstr "" +msgstr "Hiçbir etkinlik planlanmadı." #. module: mail #. openerp-web @@ -3591,7 +3595,7 @@ msgstr "Odoo" #, python-format msgid "" "Odoo has now the permission to send you native notifications on this device." -msgstr "" +msgstr "Odoo, artık bu cihazda yerel bildirimler göndermek için izin aldı." #. module: mail #. openerp-web @@ -3607,7 +3611,7 @@ msgstr "Odoo için izniniz gerekiyor" msgid "" "Odoo will not have the permission to send native notifications on this " "device." -msgstr "" +msgstr "Odoo, bu cihazda yerel bildirimler gönderme iznine sahip olmayacak." #. module: mail #. openerp-web @@ -3619,32 +3623,32 @@ msgstr "Çevrimdışı" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__old_value_char msgid "Old Value Char" -msgstr "" +msgstr "Eski Karakter Değeri" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__old_value_datetime msgid "Old Value DateTime" -msgstr "" +msgstr "Eski Tarih Saat Değeri" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__old_value_float msgid "Old Value Float" -msgstr "" +msgstr "Eski Sayısal Değer" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__old_value_integer msgid "Old Value Integer" -msgstr "" +msgstr "Eski Tam Sayı Değeri" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__old_value_monetary msgid "Old Value Monetary" -msgstr "" +msgstr "Eski Parasal Değer" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_tracking_value__old_value_text msgid "Old Value Text" -msgstr "" +msgstr "Eski Metin Değeri" #. module: mail #: selection:ir.model.fields,track_visibility:0 @@ -3701,21 +3705,21 @@ msgstr "Üst Belgeyi Aç" #: code:addons/mail/static/src/xml/discuss.xml:139 #, python-format msgid "Open channel settings" -msgstr "" +msgstr "Açık kanal ayarları" #. module: mail #. openerp-web #: code:addons/mail/static/src/xml/discuss.xml:187 #, python-format msgid "Open chat" -msgstr "" +msgstr "Konuşmayı aç" #. module: mail #. openerp-web #: code:addons/mail/static/src/xml/extended_chat_window.xml:15 #, python-format msgid "Open in Discuss" -msgstr "" +msgstr "Tartışmayı Aç" #. module: mail #: model:ir.model.fields,field_description:mail.field_res_partner__opt_out @@ -3881,6 +3885,9 @@ msgid "" " named. For example on a project, the parent_id of project subtypes refers " "to task-related subtypes." msgstr "" +"Otomatik abonelik için kullanılan alt alt türü. Bu alan doğru bir şekilde " +"adlandırılmamış. Örneğin, bir projede, proje alt türlerinin parent_id'i, " +"görevle ilgili alt türlere başvurur." #. module: mail #: model:ir.actions.act_window,name:mail.action_partner_mass_mail @@ -3905,7 +3912,7 @@ msgstr "" #: model:ir.model.fields,field_description:mail.field_mail_message__needaction_partner_ids #: model:ir.model.fields,field_description:mail.field_survey_mail_compose_message__needaction_partner_ids msgid "Partners with Need Action" -msgstr "" +msgstr "Eylem İhtiyacı Olan İş Ortakları" #. module: mail #: model:ir.model.fields,help:mail.field_email_template_preview__auto_delete @@ -4312,7 +4319,7 @@ msgstr "Yeni Şablon olarak kaydet" #: code:addons/mail/static/src/js/chat_window.js:40 #, python-format msgid "Say something" -msgstr "" +msgstr "Bir şey söyle" #. module: mail #: model:ir.ui.view,arch_db:mail.mail_activity_view_form_popup @@ -4436,7 +4443,7 @@ msgstr "" #: code:addons/mail/static/src/js/thread_field.js:92 #, python-format msgid "Sending Error" -msgstr "" +msgstr "Hata Gönderiliyor" #. module: mail #. openerp-web @@ -4483,7 +4490,7 @@ msgstr "" #: model:ir.ui.view,arch_db:mail.mail_shortcode_view_form #: model:ir.ui.view,arch_db:mail.mail_shortcode_view_tree msgid "Shortcodes" -msgstr "" +msgstr "Kısa kodlar" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_shortcode__source @@ -4500,7 +4507,7 @@ msgstr "Bir sonraki eylem tarihi bugünden önce olan tüm kayıtları göster" #: code:addons/mail/models/mail_channel.py:673 #, python-format msgid "Show an helper message" -msgstr "" +msgstr "Bir yardımcı mesajı göster" #. module: mail #: model:ir.model.fields,field_description:mail.field_email_template_preview__ref_ir_act_window @@ -4557,6 +4564,8 @@ msgid "" "Specify a model if the activity should be specific to a model and not " "available when managing activities for other models." msgstr "" +"Etkinliğin bir modele özgü olması ve diğer modellerin etkinliklerini " +"yönetirken kullanılamaması durumunda bir model belirtin." #. module: mail #. openerp-web @@ -4821,6 +4830,8 @@ msgid "" "This group is visible by non members. Invisible groups can add members " "through the invite button." msgstr "" +"Bu grup üye olmayanlar tarafından görülebilir. Görünmez gruplar, davet " +"butonuyla üye ekleyebilir." #. module: mail #: model:ir.ui.view,arch_db:mail.view_mail_search @@ -4924,13 +4935,13 @@ msgstr "İzleme" #: model:ir.ui.view,arch_db:mail.view_mail_tracking_value_form #: model:ir.ui.view,arch_db:mail.view_mail_tracking_value_tree msgid "Tracking Value" -msgstr "" +msgstr "İzleme Değeri" #. module: mail #: model:ir.actions.act_window,name:mail.action_view_mail_tracking_value #: model:ir.ui.menu,name:mail.menu_mail_tracking_value msgid "Tracking Values" -msgstr "" +msgstr "İzleme Değerleri" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_compose_message__tracking_value_ids @@ -4938,7 +4949,7 @@ msgstr "" #: model:ir.model.fields,field_description:mail.field_mail_message__tracking_value_ids #: model:ir.model.fields,field_description:mail.field_survey_mail_compose_message__tracking_value_ids msgid "Tracking values" -msgstr "" +msgstr "İzleme Değerleri" #. module: mail #: model:ir.model.fields,field_description:mail.field_mail_compose_message__message_type @@ -5191,7 +5202,7 @@ msgstr "Şablonu Kullan" #. module: mail #: model:ir.ui.view,arch_db:mail.res_config_settings_view_form msgid "Use your own email servers" -msgstr "" +msgstr "Kendi e-mail sunucusunu kullanın" #. module: mail #: model:ir.model.fields,help:mail.field_mail_message_subtype__sequence @@ -5299,7 +5310,7 @@ msgstr "Bu modelin ileti ve bildirimleri destekleyip desteklemediğidir." #. module: mail #: model:ir.ui.view,arch_db:mail.mail_channel_view_form msgid "Who can follow the group's activities?" -msgstr "" +msgstr "Grubun faaliyetlerini kim takip edebilir?" #. module: mail #. openerp-web @@ -5433,13 +5444,13 @@ msgstr "" #: code:addons/mail/static/src/js/thread_field.js:92 #, python-format msgid "Your message has not been sent." -msgstr "" +msgstr "Mesajınız gönderilemedi." #. module: mail #: code:addons/mail/models/ir_actions.py:30 #, python-format msgid "Your template should define email_from" -msgstr "" +msgstr "Şablonunuz email_from tanımlanabilir" #. module: mail #: code:addons/mail/models/mail_thread.py:948 @@ -5533,7 +5544,7 @@ msgstr "" #: code:addons/mail/models/mail_alias.py:267 #, python-format msgid "incorrectly configured alias (unknown reference record)" -msgstr "" +msgstr "yanlış yapılandırılmış takma ad (bilinmeyen referans kaydı)" #. module: mail #: model:ir.model,name:mail.model_ir_actions_server diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index a70e4d65205..d6b696c8124 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -500,11 +500,11 @@ class MailThread(models.AbstractModel): msg_comment = MailMessage.search([ ('model', '=', self._name), ('res_id', '=', self.id), - ('subtype_id', '=', subtype_comment.id)]) + ('subtype_id', '=', subtype_comment)]) msg_not_comment = MailMessage.search([ ('model', '=', self._name), ('res_id', '=', self.id), - ('subtype_id', '!=', subtype_comment.id)]) + ('subtype_id', '!=', subtype_comment)]) # update the messages msg_comment.write({"res_id": new_thread.id, "model": new_thread._name}) diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js index f11531b6eab..30191cd3845 100644 --- a/addons/mail/static/src/js/systray.js +++ b/addons/mail/static/src/js/systray.js @@ -156,7 +156,7 @@ var MessagingMenu = Widget.extend({ if (channelID === 'channel_inbox' || channelID === 'mail_failure') { var resID = $(event.currentTarget).data('res_id'); var resModel = $(event.currentTarget).data('res_model'); - if (resModel && resID) { + if (resModel && resModel !== 'mail.channel' && resID) { this.do_action({ type: 'ir.actions.act_window', res_model: resModel, @@ -174,7 +174,11 @@ var MessagingMenu = Widget.extend({ domain: [['message_has_error', '=', true]], }); } else { - this.do_action('mail.mail_channel_action_client_chat', {clear_breadcrumbs: true}) + var clientChatOptions = {clear_breadcrumbs: true}; + if (resModel && resModel === 'mail.channel' && resID) { + clientChatOptions.active_id = resID; + } + this.do_action('mail.mail_channel_action_client_chat', clientChatOptions) .then(function () { self.trigger_up('hide_home_menu'); // we cannot 'go back to previous page' otherwise core.bus.trigger('change_menu_section', self.call('chat_manager', 'getDiscussMenuID')); diff --git a/addons/mail/views/res_partner_views.xml b/addons/mail/views/res_partner_views.xml index 0fc6007b449..0264859b615 100644 --- a/addons/mail/views/res_partner_views.xml +++ b/addons/mail/views/res_partner_views.xml @@ -7,7 +7,7 @@ <field name="model">res.partner</field> <field name="inherit_id" ref="base.view_partner_form"/> <field name="arch" type="xml"> - <xpath expr="//field[@name='user_id']" position="after"> + <xpath expr="//page[@name='sales_purchases']//field[@name='user_id']" position="after"> <field name="opt_out" string="Mailing Opt-Out" groups="base.group_no_one"/> <field name="message_bounce" groups="base.group_no_one"/> </xpath> diff --git a/addons/maintenance/i18n/tr.po b/addons/maintenance/i18n/tr.po index bbac3f16eee..fe2712e0e13 100644 --- a/addons/maintenance/i18n/tr.po +++ b/addons/maintenance/i18n/tr.po @@ -982,7 +982,7 @@ msgstr "Durumu değişti" #. module: maintenance #: model:ir.model.fields,field_description:maintenance.field_maintenance_request__name msgid "Subjects" -msgstr "" +msgstr "Konular" #. module: maintenance #: model:ir.model.fields,field_description:maintenance.field_maintenance_request__maintenance_team_id diff --git a/addons/mass_mailing/i18n/tr.po b/addons/mass_mailing/i18n/tr.po index 03ea4cd2a45..88b89e36056 100644 --- a/addons/mass_mailing/i18n/tr.po +++ b/addons/mass_mailing/i18n/tr.po @@ -1911,7 +1911,7 @@ msgstr "Ve bir sonraki siparişinizde20 TL tasarruf edin!" #. module: mass_mailing #: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_campaign__campaign_id msgid "campaign_id" -msgstr "" +msgstr "campaign_id" #. module: mass_mailing #: model:ir.ui.view,arch_db:mass_mailing.view_mail_mass_mailing_list_form @@ -1937,12 +1937,12 @@ msgstr "" #. module: mass_mailing #: model:ir.model,name:mass_mailing.model_link_tracker msgid "link.tracker" -msgstr "" +msgstr "link.tracker" #. module: mass_mailing #: model:ir.model,name:mass_mailing.model_link_tracker_click msgid "link.tracker.click" -msgstr "" +msgstr "link.tracker.click" #. module: mass_mailing #: model:ir.model,name:mass_mailing.model_res_config_settings diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js index e611d9dbe26..e1689b69e5b 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing.js +++ b/addons/mass_mailing/static/src/js/mass_mailing.js @@ -41,7 +41,7 @@ FieldTextHtml.include({ var datarecord = this._super(); if (this.model === 'mail.mass_mailing') { // these fields can potentially get very long, let's remove them - datarecord = _.omit(datarecord, ['mailing_domain', 'contact_list_ids', 'body_html']); + datarecord = _.omit(datarecord, ['mailing_domain', 'contact_list_ids', 'body_html', 'attachment_ids']); } return datarecord; }, diff --git a/addons/mass_mailing_event_track/models/event.py b/addons/mass_mailing_event_track/models/event.py index 156625391c0..e5747875f21 100644 --- a/addons/mass_mailing_event_track/models/event.py +++ b/addons/mass_mailing_event_track/models/event.py @@ -16,7 +16,7 @@ class Event(models.Model): view_mode='form', target='current', context=dict( - default_mailing_model=self.env.ref('website_event_track.model_event_track').id, + default_mailing_model_id=self.env.ref('website_event_track.model_event_track').id, default_mailing_domain="[('event_id', 'in', %s), ('stage_id.is_cancel', '!=', True)]" % self.ids, ), ) diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index b1892c4db2a..922802f9659 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -80,6 +80,8 @@ class MrpBom(models.Model): def onchange_product_tmpl_id(self): if self.product_tmpl_id: self.product_uom_id = self.product_tmpl_id.uom_id.id + if self.product_id.product_tmpl_id != self.product_tmpl_id: + self.product_id = False @api.onchange('routing_id') def onchange_routing_id(self): diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 17914b1b9dd..cc7a98635ea 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -364,6 +364,7 @@ class MrpWorkorder(models.Model): move_line = production_move.move_line_ids.filtered(lambda x: x.lot_id.id == self.final_lot_id.id) if move_line: move_line.product_uom_qty += self.qty_producing + move_line.qty_done += self.qty_producing else: move_line.create({'move_id': production_move.id, 'product_id': production_move.product_id.id, diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 1099d8b549e..befe9ceb9d1 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -73,6 +73,13 @@ class StockMove(models.Model): order_finished_lot_ids = fields.Many2many('stock.production.lot', compute='_compute_order_finished_lot_ids') finished_lots_exist = fields.Boolean('Finished Lots Exist', compute='_compute_order_finished_lot_ids') + def _unreserve_initial_demand(self, new_move): + # If you were already putting stock.move.lots on the next one in the work order, transfer those to the new move + self.filtered(lambda m: m.production_id or m.raw_material_production_id)\ + .mapped('move_line_ids')\ + .filtered(lambda ml: ml.qty_done == 0.0)\ + .write({'move_id': new_move, 'product_uom_qty': 0}) + @api.depends('active_move_line_ids.qty_done', 'active_move_line_ids.product_uom_id') def _compute_done_quantity(self): super(StockMove, self)._compute_done_quantity() diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml index 0aee35f12b1..16757f1acd4 100644 --- a/addons/mrp/views/mrp_workorder_views.xml +++ b/addons/mrp/views/mrp_workorder_views.xml @@ -196,18 +196,19 @@ <label for="duration"/> <div> <button style="pointer-events: none;" class="oe_inline label label-default"> - <field name="duration" widget="mrp_time_counter"/> + <field name="duration" widget="mrp_time_counter" help="Time the currently logged user spent on this workorder."/> </button> </div> </group> </group> <group> - <field name="time_ids" nolabel="1"> + <field name="time_ids" nolabel="1" context="{'default_workcenter_id': workcenter_id}"> <tree> <field name="date_start"/> <field name="date_end"/> <field name="duration" widget="float_time" sum="Total duration"/> <field name="user_id"/> + <field name="workcenter_id" invisible="1"/> <field name="loss_id" string="Efficiency"/> </tree> <form> @@ -219,6 +220,7 @@ </group> <group> <field name="user_id"/> + <field name="workcenter_id"/> <field name="loss_id"/> </group> </group> diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index 2896e566f43..2cb98f74b24 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -60,6 +60,15 @@ class PaymentAcquirerAuthorize(models.Model): @api.multi def authorize_form_generate_values(self, values): self.ensure_one() + # State code is only supported in US, use state name by default + # See https://developer.authorize.net/api/reference/ + state = values['partner_state'].name if values.get('partner_state') else '' + if values.get('partner_country') and values.get('partner_country') == self.env.ref('base.us', False): + state = values['partner_state'].code if values.get('partner_state') else '' + billing_state = values['billing_partner_state'].name if values.get('billing_partner_state') else '' + if values.get('billing_partner_country') and values.get('billing_partner_country') == self.env.ref('base.us', False): + billing_state = values['billing_partner_state'].code if values.get('billing_partner_state') else '' + base_url = self.env['ir.config_parameter'].get_param('web.base.url') authorize_tx_values = dict(values) temp_authorize_tx_values = { @@ -84,7 +93,7 @@ class PaymentAcquirerAuthorize(models.Model): 'first_name': values.get('partner_first_name'), 'last_name': values.get('partner_last_name'), 'phone': values.get('partner_phone'), - 'state': values.get('partner_state') and values['partner_state'].code or '', + 'state': state, 'billing_address': values.get('billing_partner_address'), 'billing_city': values.get('billing_partner_city'), 'billing_country': values.get('billing_partner_country') and values.get('billing_partner_country').name or '', @@ -93,7 +102,7 @@ class PaymentAcquirerAuthorize(models.Model): 'billing_first_name': values.get('billing_partner_first_name'), 'billing_last_name': values.get('billing_partner_last_name'), 'billing_phone': values.get('billing_partner_phone'), - 'billing_state': values.get('billing_partner_state') and values['billing_partner_state'].code or '', + 'billing_state': billing_state, } temp_authorize_tx_values['returndata'] = authorize_tx_values.pop('return_url', '') temp_authorize_tx_values['x_fp_hash'] = self._authorize_generate_hashing(temp_authorize_tx_values) diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py index 1d03a688630..409e09b8a9e 100644 --- a/addons/payment_ogone/controllers/main.py +++ b/addons/payment_ogone/controllers/main.py @@ -110,7 +110,7 @@ class OgoneController(http.Controller): def feedback(self, **kwargs): try: tx = request.env['payment.transaction'].sudo()._ogone_form_get_tx_from_data(kwargs) - tx._ogone_s2s_validate() + tx._ogone_s2s_validate_tree(kwargs) except ValidationError: return 'ko' return 'ok' diff --git a/addons/payment_stripe/static/src/js/stripe.js b/addons/payment_stripe/static/src/js/stripe.js index e4474fe72ce..956765e8042 100644 --- a/addons/payment_stripe/static/src/js/stripe.js +++ b/addons/payment_stripe/static/src/js/stripe.js @@ -87,74 +87,30 @@ odoo.define('payment_stripe.stripe', function(require) { payment_form.append('<i class="fa fa-spinner fa-spin"/>'); payment_form.attr('disabled','disabled'); - var acquirer_id = payment_form.find('input[type="radio"][data-provider="stripe"]:checked').data('acquirer-id'); - if (! acquirer_id) { - return false; - } - + var payment_tx_url = payment_form.find('input[name="prepare_tx_url"]').val(); var access_token = $("input[name='access_token']").val() || $("input[name='token']").val() || ''; - var so_id = $("input[name='return_url']").val().match(/quote\/([0-9]+)/) || undefined; - if (so_id) { - so_id = parseInt(so_id[1]); - } - var invoice_id = $("input[name='return_url']").val().match(/invoices\/([0-9]+)/) || undefined; - if (invoice_id) { - invoice_id = parseInt(invoice_id[1]); - } - - var currency = $("input[name='currency']").val(); - var currency_id = $("input[name='currency_id']").val(); - var amount = parseFloat($("input[name='amount']").val() || '0.0'); + var get_input_value = function(name) { + return provider_form.find('input[name="' + name + '"]').val(); + } - if ($('.o_website_payment').length !== 0) { - var invoice_num = $("input[name='invoice_num']").val(); - var url = _.str.sprintf("/website_payment/transaction/v2/%f/%s/%s", - amount, currency_id, invoice_num); + var acquirer_id = parseInt(provider_form.find('#acquirer_stripe').val()); + var amount = parseFloat(get_input_value("amount") || '0.0'); + var currency = get_input_value("currency"); + var email = get_input_value("email"); + var invoice_num = get_input_value("invoice_num"); + var merchant = get_input_value("merchant"); - var create_tx = ajax.jsonRpc(url, 'call', { - acquirer_id: acquirer_id - }).then(function (data) { - try { provider_form[0].innerHTML = data; } catch (e) {} - }); - } - else if ($('.o_website_quote').length !== 0) { - var url = _.str.sprintf("/quote/%s/transaction/", so_id); - var create_tx = ajax.jsonRpc(url, 'call', { - access_token: access_token, - acquirer_id: acquirer_id - }).then(function (data) { - try { provider_form[0].innerHTML = data; } catch (e) {}; - }); - } else if (window.location.href.includes("/my/orders/")) { - var create_tx = ajax.jsonRpc('/pay/sale/' + so_id + '/form_tx/', 'call', { - access_token: access_token, - acquirer_id: acquirer_id - }).then(function (data) { - try { provider_form.innerHTML = data; } catch (e) {}; - }); - } else if (window.location.href.includes("/my/invoices/")) { - var create_tx = ajax.jsonRpc('/invoice/pay/' + invoice_id + '/form_tx/', 'call', { - access_token: access_token, - acquirer_id: acquirer_id - }).then(function (data) { - try { provider_form.innerHTML = data; } catch (e) {}; - }); - } - else { - var create_tx = ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', { - so_id: so_id, - access_token: access_token, - acquirer_id: acquirer_id - }).then(function (data) { - try { provider_form.innerHTML = data; } catch (e) {}; - }); - } - create_tx.done(function () { + ajax.jsonRpc(payment_tx_url, 'call', { + acquirer_id: acquirer_id, + access_token: access_token, + }).then(function(data) { + try { provider_form[0].innerHTML = data; } catch (e) {}; + }).done(function () { getStripeHandler().open({ - name: $("input[name='merchant']").val(), - description: $("input[name='invoice_num']").val(), - email: $("input[name='email']").val(), + name: merchant, + description: invoice_num, + email: email, currency: currency, amount: _.contains(int_currencies, currency) ? amount : amount * 100, }); diff --git a/addons/phone_validation/i18n/tr.po b/addons/phone_validation/i18n/tr.po index d154d793e22..b86582c48e2 100644 --- a/addons/phone_validation/i18n/tr.po +++ b/addons/phone_validation/i18n/tr.po @@ -95,4 +95,4 @@ msgstr "" #. module: phone_validation #: model:ir.model,name:phone_validation.model_phone_validation_mixin msgid "phone.validation.mixin" -msgstr "" +msgstr "phone.validation.mixin" diff --git a/addons/point_of_sale/i18n/tr.po b/addons/point_of_sale/i18n/tr.po index 4b4e51b656a..fd552931a40 100644 --- a/addons/point_of_sale/i18n/tr.po +++ b/addons/point_of_sale/i18n/tr.po @@ -4132,6 +4132,9 @@ msgid "" "You cannot confirm all orders of this session, because they have not the 'paid' status.\n" "{reference} is in state {state}, total amount: {total}, paid: {paid}" msgstr "" +"Bu oturumun tüm siparişlerini onaylayamazsınız, çünkü \"ödendi\" durumları " +"yoktur. {Reference} durum {state} durumundadır, toplam tutar: {total}, " +"ödendi: {paid}" #. module: point_of_sale #: code:addons/point_of_sale/models/pos_session.py:151 diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index 8d1dcf69074..74da78ddf51 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -38,8 +38,8 @@ class PosSession(models.Model): paid=order.amount_paid, )) order.action_pos_order_done() - orders = session.order_ids.filtered(lambda order: order.state in ['invoiced', 'done']) - orders.sudo()._reconcile_payments() + orders_to_reconcile = session.order_ids.filtered(lambda order: order.state in ['invoiced', 'done'] and order.partner_id) + orders_to_reconcile.sudo()._reconcile_payments() config_id = fields.Many2one( 'pos.config', string='Point of Sale', diff --git a/addons/point_of_sale/models/product.py b/addons/point_of_sale/models/product.py index 43a959bcfa4..ed9f2dce8cd 100644 --- a/addons/point_of_sale/models/product.py +++ b/addons/point_of_sale/models/product.py @@ -27,6 +27,18 @@ class ProductTemplate(models.Model): self.available_in_pos = False +class ProductProduct(models.Model): + _inherit = 'product.product' + + @api.multi + def unlink(self): + product_ctx = dict(self.env.context or {}, active_test=False) + if self.env['pos.session'].search_count([('state', '!=', 'closed')]): + if self.with_context(product_ctx).search_count([('id', 'in', self.ids), ('product_tmpl_id.available_in_pos', '=', True)]): + raise UserError(_('You cannot delete a product saleable in point of sale while a session is still opened.')) + return super(ProductProduct, self).unlink() + + class UomCateg(models.Model): _inherit = 'uom.category' diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 63c9e1c0cb0..c73c7f75165 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1406,6 +1406,7 @@ exports.Orderline = Backbone.Model.extend({ this.set_unit_price(this.product.get_price(this.order.pricelist, this.get_quantity())); this.order.fix_tax_included_price(this); } + this.trigger('change', this); }, // return the quantity of product get_quantity: function(){ @@ -2124,7 +2125,7 @@ exports.Order = Backbone.Model.extend({ company_registry: company.company_registry, contact_address: company.partner_id[1], vat: company.vat, - vat_label: company.country.vat_label, + vat_label: company.country && company.country.vat_label || '', name: company.name, phone: company.phone, logo: this.pos.company_logo_base64, diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py index 9fb2c6b94ac..89dbded3676 100644 --- a/addons/portal/controllers/mail.py +++ b/addons/portal/controllers/mail.py @@ -100,7 +100,7 @@ class PortalChatter(http.Controller): raise Forbidden() # Non-employee see only messages with not internal subtype (aka, no internal logs) if not request.env['res.users'].has_group('base.group_user'): - domain = expression.AND([['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)], domain]) + domain = expression.AND([['&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)], domain]) Message = request.env['mail.message'].sudo() return { 'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(), diff --git a/addons/portal/i18n/tr.po b/addons/portal/i18n/tr.po index 6ea7eb0fe95..2aac19a95f8 100644 --- a/addons/portal/i18n/tr.po +++ b/addons/portal/i18n/tr.po @@ -626,7 +626,7 @@ msgstr "oturum açıldı" #. module: portal #: model:ir.model,name:portal.model_portal_mixin msgid "portal.mixin" -msgstr "" +msgstr "portal.mixin" #. module: portal #: model:ir.ui.view,arch_db:portal.portal_my_details diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py index 22d6765aac7..44eb1637619 100644 --- a/addons/portal/wizard/portal_wizard.py +++ b/addons/portal/wizard/portal_wizard.py @@ -134,7 +134,7 @@ class PortalWizardUser(models.TransientModel): if wizard_user.partner_id.company_id: company_id = wizard_user.partner_id.company_id.id else: - company_id = self.env['res.company']._company_default_get('res.users') + company_id = self.env['res.company']._company_default_get('res.users').id user_portal = wizard_user.sudo().with_context(company_id=company_id)._create_user() else: user_portal = user diff --git a/addons/pos_cache/i18n/tr.po b/addons/pos_cache/i18n/tr.po index 85565d72609..22c95880db7 100644 --- a/addons/pos_cache/i18n/tr.po +++ b/addons/pos_cache/i18n/tr.po @@ -110,7 +110,7 @@ msgstr "Ürün Alanı" #. module: pos_cache #: model:ir.model,name:pos_cache.model_pos_cache msgid "pos.cache" -msgstr "" +msgstr "pos.cache" #. module: pos_cache #: model:ir.model,name:pos_cache.model_pos_config diff --git a/addons/pos_restaurant/i18n/tr.po b/addons/pos_restaurant/i18n/tr.po index f403a4cc836..fef517cdeba 100644 --- a/addons/pos_restaurant/i18n/tr.po +++ b/addons/pos_restaurant/i18n/tr.po @@ -752,14 +752,14 @@ msgstr "pos.config" #. module: pos_restaurant #: model:ir.model,name:pos_restaurant.model_restaurant_floor msgid "restaurant.floor" -msgstr "" +msgstr "restaurant.floor" #. module: pos_restaurant #: model:ir.model,name:pos_restaurant.model_restaurant_printer msgid "restaurant.printer" -msgstr "" +msgstr "restaurant.printer" #. module: pos_restaurant #: model:ir.model,name:pos_restaurant.model_restaurant_table msgid "restaurant.table" -msgstr "" +msgstr "restaurant.table" diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 5470ba084c5..51b450cba2b 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -220,6 +220,7 @@ class ProductProduct(models.Model): for supplier_info in self.seller_ids: if supplier_info.name.id == self._context.get('partner_id'): self.code = supplier_info.product_code or self.default_code + break else: self.code = self.default_code @@ -229,6 +230,7 @@ class ProductProduct(models.Model): if supplier_info.name.id == self._context.get('partner_id'): product_name = supplier_info.product_name or self.default_code self.partner_ref = '%s%s' % (self.code and '[%s] ' % self.code or '', product_name) + break else: self.partner_ref = self.name_get()[0][1] diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py index 8060468b2ab..b7883a6c82f 100644 --- a/addons/product/models/product_attribute.py +++ b/addons/product/models/product_attribute.py @@ -4,6 +4,7 @@ from odoo import api, fields, models, _ from odoo.addons import decimal_precision as dp from odoo.exceptions import UserError, ValidationError +from odoo.osv import expression class ProductAttribute(models.Model): @@ -107,7 +108,7 @@ class ProductAttributeLine(models.Model): # search on a m2o and one on a m2m, probably this will quickly become # difficult to compute - check if performance optimization is required if name and operator in ('=', 'ilike', '=ilike', 'like', '=like'): - args = ['|', ('attribute_id', operator, name), ('value_ids', operator, name)] + args = expression.AND([['|', ('attribute_id', operator, name), ('value_ids', operator, name)], args]) attribute_ids = self._search(args, limit=limit, access_rights_uid=name_get_uid) return self.browse(attribute_ids).name_get() return super(ProductAttributeLine, self)._name_search(name=name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid) diff --git a/addons/product/tests/__init__.py b/addons/product/tests/__init__.py index b0122192ff8..96caad34928 100644 --- a/addons/product/tests/__init__.py +++ b/addons/product/tests/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_variants, test_pricelist, test_product_pricelist +from . import test_variants, test_pricelist, test_product_pricelist, test_seller diff --git a/addons/product/tests/test_seller.py b/addons/product/tests/test_seller.py new file mode 100644 index 00000000000..36693317c34 --- /dev/null +++ b/addons/product/tests/test_seller.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import TransactionCase + + +class TestSeller(TransactionCase): + + def setUp(self): + super(TestSeller, self).setUp() + self.product_service = self.env.ref('product.product_product_2') + self.product_service.default_code = 'DEFCODE' + self.asustec = self.env.ref('base.res_partner_1') + self.camptocamp = self.env.ref('base.res_partner_12') + + def test_10_sellers(self): + self.product_service.write({'seller_ids': [ + (0, 0, {'name': self.asustec.id, 'product_code': 'ASUCODE'}), + (0, 0, {'name': self.camptocamp.id, 'product_code': 'C2CCODE'}), + ]}) + + default_code = self.product_service.code + self.assertEqual("DEFCODE", default_code, "Default code not used in product name") + + context_code = self.product_service\ + .with_context(partner_id=self.camptocamp.id)\ + .code + self.assertEqual('C2CCODE', context_code, "Partner's code not used in product name with context set") diff --git a/addons/product_expiry/i18n/tr.po b/addons/product_expiry/i18n/tr.po index 450843980a7..af48243a9f9 100644 --- a/addons/product_expiry/i18n/tr.po +++ b/addons/product_expiry/i18n/tr.po @@ -107,6 +107,8 @@ msgid "" "Number of days before the goods should be removed from the stock. It will be" " computed on the lot/serial number." msgstr "" +"Malların stoktan çıkarılmadan önce gereken gün sayısı. Bu lot / seri " +"numarası üzerinde hesaplanacaktır.." #. module: product_expiry #: model:ir.model.fields,help:product_expiry.field_product_product__use_time diff --git a/addons/project/controllers/portal.py b/addons/project/controllers/portal.py index 96d845105e2..27ecc3c6a7f 100644 --- a/addons/project/controllers/portal.py +++ b/addons/project/controllers/portal.py @@ -142,7 +142,7 @@ class CustomerPortal(CustomerPortal): # pager pager = portal_pager( url="/my/tasks", - url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby}, + url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby, 'search_in': search_in, 'search': search}, total=task_count, page=page, step=self._items_per_page diff --git a/addons/project/i18n/tr.po b/addons/project/i18n/tr.po index 4e4795b6648..aa60fd117ae 100644 --- a/addons/project/i18n/tr.po +++ b/addons/project/i18n/tr.po @@ -1523,6 +1523,8 @@ msgid "" "Lets the company customize which Pad installation should be used to link to " "new pads (for example: http://etherpad.com/)." msgstr "" +"Şirketin yeni pedlere bağlantı kurmak için hangi Pad kurulumunun " +"kullanılması gerektiğini özelleştirir (örneğin: http://etherpad.com/)." #. module: project #: model:ir.model.fields,help:project.field_project_project__analytic_account_id diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 4d486326217..c469614e1f0 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -872,7 +872,7 @@ class Task(models.Model): groups = [new_group] + groups for group_name, group_method, group_data in groups: - if group_name in ('customer'): + if group_name == 'customer': continue group_data['has_button_access'] = True diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py index c24f2f75664..401bcdfa8b3 100644 --- a/addons/purchase/models/account_invoice.py +++ b/addons/purchase/models/account_invoice.py @@ -57,7 +57,7 @@ class AccountInvoice(models.Model): taxes = line.taxes_id invoice_line_tax_ids = line.order_id.fiscal_position_id.map_tax(taxes, line.product_id, line.order_id.partner_id) invoice_line = self.env['account.invoice.line'] - date = self.date + date = self.date or self.date_invoice data = { 'purchase_line_id': line.id, 'name': line.order_id.name + ': ' + line.name, @@ -113,7 +113,7 @@ class AccountInvoice(models.Model): def _onchange_currency_id(self): if self.currency_id: for line in self.invoice_line_ids.filtered(lambda r: r.purchase_line_id): - date = self.date or fields.Date.today() + date = self.date or self.date_invoice or fields.Date.today() company = self.company_id line.price_unit = line.purchase_id.currency_id._convert( line.purchase_line_id.price_unit, self.currency_id, company, date, round=False) diff --git a/addons/purchase/views/purchase_template.xml b/addons/purchase/views/purchase_template.xml index 9c5d5c5975c..d844cb9ec77 100644 --- a/addons/purchase/views/purchase_template.xml +++ b/addons/purchase/views/purchase_template.xml @@ -5,7 +5,7 @@ <div> <strong>The ordered quantity has been updated.</strong> <ul> - <li><t t-esc="line.order_id.product_id.display_name"/>:</li> + <li><t t-esc="line.product_id.display_name"/>:</li> Ordered Quantity: <t t-esc="line.product_qty" /> -> <t t-esc="float(product_qty)"/><br/> <t t-if='line.order_id.product_id.type in ("consu", "product")'> Received Quantity: <t t-esc="line.qty_received" /><br/> diff --git a/addons/purchase_stock/models/account_invoice.py b/addons/purchase_stock/models/account_invoice.py index 28b7bf8bd0b..87431c00ef7 100644 --- a/addons/purchase_stock/models/account_invoice.py +++ b/addons/purchase_stock/models/account_invoice.py @@ -57,7 +57,7 @@ class AccountInvoice(models.Model): valuation_price_unit = po_currency._convert( i_line.purchase_line_id.product_uom._compute_price(i_line.purchase_line_id.price_unit, i_line.uom_id), inv.currency_id, - company=po_company, date=inv.date_invoice, round=False, + company=po_company, date=inv.date or inv.date_invoice, round=False, ) stock_move_obj = self.env['stock.move'] valuation_stock_move = stock_move_obj.search([ @@ -90,7 +90,7 @@ class AccountInvoice(models.Model): # In this condition, we have a real price-valuated product which has not yet been received valuation_price_unit = po_currency._convert( i_line.purchase_line_id.price_unit, inv.currency_id, - company=po_company, date=inv.date_invoice, round=False, + company=po_company, date=inv.date or inv.date_invoice, round=False, ) interim_account_price = valuation_price_unit * line_quantity diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index 270812d5f7e..6524c13056e 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -117,7 +117,7 @@ class PurchaseOrder(models.Model): result['context'] = {} pick_ids = self.mapped('picking_ids') # choose the view_mode accordingly - if len(pick_ids) > 1: + if not pick_ids or len(pick_ids) > 1: result['domain'] = "[('id','in',%s)]" % (pick_ids.ids) elif len(pick_ids) == 1: res = self.env.ref('stock.view_picking_form', False) diff --git a/addons/repair/i18n/tr.po b/addons/repair/i18n/tr.po index 88357d2a8f6..d93d5460fb6 100644 --- a/addons/repair/i18n/tr.po +++ b/addons/repair/i18n/tr.po @@ -990,4 +990,4 @@ msgstr "stock.traceability.report" #. module: repair #: model:ir.model,name:repair.model_stock_warn_insufficient_qty_repair msgid "stock.warn.insufficient.qty.repair" -msgstr "" +msgstr "stock.warn.insufficient.qty.repair" diff --git a/addons/repair/models/repair.py b/addons/repair/models/repair.py index 53e6887b5df..be10a0c1ccd 100644 --- a/addons/repair/models/repair.py +++ b/addons/repair/models/repair.py @@ -200,9 +200,11 @@ class Repair(models.Model): def action_validate(self): self.ensure_one() precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') - available_qty = self.env['stock.quant']._get_available_quantity(self.product_id, self.location_id, self.lot_id, strict=True) - if float_compare(available_qty, self.product_qty, precision_digits=precision) >= 0: - return self.action_repair_confirm() + available_qty_owner = self.env['stock.quant']._get_available_quantity(self.product_id, self.location_id, self.lot_id, owner_id=self.partner_id, strict=True) + available_qty_noown = self.env['stock.quant']._get_available_quantity(self.product_id, self.location_id, self.lot_id, strict=True) + for available_qty in [available_qty_owner, available_qty_noown]: + if float_compare(available_qty, self.product_qty, precision_digits=precision) >= 0: + return self.action_repair_confirm() else: return { 'name': _('Insufficient Quantity'), @@ -428,8 +430,15 @@ class Repair(models.Model): if self.filtered(lambda repair: not repair.repaired): raise UserError(_("Repair must be repaired in order to make the product moves.")) res = {} + precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') Move = self.env['stock.move'] for repair in self: + # Try to create move with the appropriate owner + owner_id = False + available_qty_owner = self.env['stock.quant']._get_available_quantity(repair.product_id, repair.location_id, repair.lot_id, owner_id=repair.partner_id, strict=True) + if float_compare(available_qty_owner, repair.product_qty, precision_digits=precision) >= 0: + owner_id = repair.partner_id.id + moves = self.env['stock.move'] for operation in repair.operations: move = Move.create({ @@ -447,6 +456,7 @@ class Repair(models.Model): 'qty_done': operation.product_uom_qty, 'package_id': False, 'result_package_id': False, + 'owner_id': owner_id, 'location_id': operation.location_id.id, #TODO: owner stuff 'location_dest_id': operation.location_dest_id.id,})], 'repair_id': repair.id, @@ -469,6 +479,7 @@ class Repair(models.Model): 'qty_done': repair.product_qty, 'package_id': False, 'result_package_id': False, + 'owner_id': owner_id, 'location_id': repair.location_id.id, #TODO: owner stuff 'location_dest_id': repair.location_id.id,})], 'repair_id': repair.id, diff --git a/addons/repair/report/repair_templates_repair_order.xml b/addons/repair/report/repair_templates_repair_order.xml index 0e255cbc69d..ccd7dcfa53c 100644 --- a/addons/repair/report/repair_templates_repair_order.xml +++ b/addons/repair/report/repair_templates_repair_order.xml @@ -2,7 +2,7 @@ <odoo> <data> <template id="report_repairorder"> - <t t-foreach="docs" t-as="o"> + <t t-set="o" t-value="doc"/> <t t-call="web.external_layout"> <t t-set="o" t-value="o.with_context({'lang': o.partner_id.lang})" /> <div class="page"> @@ -155,7 +155,6 @@ <div class="oe_structure"/> </div> </t> - </t> </template> <template id="report_repairorder2"> diff --git a/addons/sale/report/report_all_channels_sales_views.xml b/addons/sale/report/report_all_channels_sales_views.xml index 793b568528e..201e229a186 100644 --- a/addons/sale/report/report_all_channels_sales_views.xml +++ b/addons/sale/report/report_all_channels_sales_views.xml @@ -4,7 +4,7 @@ <field name="name">report.all.channels.sales.pivot</field> <field name="model">report.all.channels.sales</field> <field name="arch" type="xml"> - <pivot string="Sales by Channel Analysis"> + <pivot string="Sales by Channel Analysis" disable_linking="True"> <field name="name" type="row"/> <field name="price_total" string="Total Price" type="measure"/> </pivot> diff --git a/addons/sale_payment/i18n/tr.po b/addons/sale_payment/i18n/tr.po index e5930a93beb..8a6ddb72af1 100644 --- a/addons/sale_payment/i18n/tr.po +++ b/addons/sale_payment/i18n/tr.po @@ -176,7 +176,7 @@ msgstr "Ödeme İşlemi" #. module: sale_payment #: model:ir.ui.view,arch_db:sale_payment.crm_team_salesteams_view_kanban_inherit_website_portal_sale msgid "Payment to Capture" -msgstr "" +msgstr "Yakalanan Ödeme" #. module: sale_payment #: model:ir.ui.view,arch_db:sale_payment.crm_team_salesteams_view_kanban_inherit_website_portal_sale diff --git a/addons/sale_timesheet/__init__.py b/addons/sale_timesheet/__init__.py index 4480f53802e..eca4161450d 100644 --- a/addons/sale_timesheet/__init__.py +++ b/addons/sale_timesheet/__init__.py @@ -1,7 +1,18 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import api, SUPERUSER_ID + from . import models from . import wizard from . import controllers from . import report + +def uninstall_hook(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + env['product.template'].search([ + ('service_type', '=', 'timesheet') + ]).write({'service_type': 'manual'}) + env['product.product'].search([ + ('service_type', '=', 'timesheet') + ]).write({'service_type': 'manual'}) diff --git a/addons/sale_timesheet/__manifest__.py b/addons/sale_timesheet/__manifest__.py index c3c44ab8e48..57532d279d6 100644 --- a/addons/sale_timesheet/__manifest__.py +++ b/addons/sale_timesheet/__manifest__.py @@ -34,8 +34,9 @@ have real delivered quantities in sales orders. 'demo': [ 'data/sale_service_demo.xml', ], - 'auto_install': True, 'qweb': [ 'static/src/xml/timesheet_plan.xml', ], + 'auto_install': True, + 'uninstall_hook': 'uninstall_hook', } diff --git a/addons/stock/i18n/tr.po b/addons/stock/i18n/tr.po index 5eb3786cd5d..cd3c1a68696 100644 --- a/addons/stock/i18n/tr.po +++ b/addons/stock/i18n/tr.po @@ -6851,7 +6851,7 @@ msgstr "stock.fixed.putaway.strat" #. module: stock #: model:ir.model,name:stock.model_stock_overprocessed_transfer msgid "stock.overprocessed.transfer" -msgstr "" +msgstr "stock.overprocessed.transfer" #. module: stock #: model:ir.model,name:stock.model_stock_return_picking_line @@ -6871,12 +6871,12 @@ msgstr "stock.traceability.report" #. module: stock #: model:ir.model,name:stock.model_stock_warn_insufficient_qty msgid "stock.warn.insufficient.qty" -msgstr "" +msgstr "stock.warn.insufficient.qty" #. module: stock #: model:ir.model,name:stock.model_stock_warn_insufficient_qty_scrap msgid "stock.warn.insufficient.qty.scrap" -msgstr "" +msgstr "stock.warn.insufficient.qty.scrap" #. module: stock #: model:ir.ui.view,arch_db:stock.view_inventory_form diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 75223abea24..ae8cc4d98fd 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -98,13 +98,13 @@ class Product(models.Model): domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc domain_move_out = [('product_id', 'in', self.ids)] + domain_move_out_loc - if lot_id: + if lot_id is not None: domain_quant += [('lot_id', '=', lot_id)] - if owner_id: + if owner_id is not None: domain_quant += [('owner_id', '=', owner_id)] domain_move_in += [('restrict_partner_id', '=', owner_id)] domain_move_out += [('restrict_partner_id', '=', owner_id)] - if package_id: + if package_id is not None: domain_quant += [('package_id', '=', package_id)] if dates_in_the_past: domain_move_in_done = list(domain_move_in) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 443607f7b2a..0dccd0a29a6 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -665,7 +665,7 @@ class StockMove(models.Model): self.product_uom = product.uom_id.id return {'domain': {'product_uom': [('category_id', '=', product.uom_id.category_id.id)]}} - @api.onchange('date') + @api.onchange('date_expected') def onchange_date(self): if self.date_expected: self.date = self.date_expected @@ -874,11 +874,6 @@ class StockMove(models.Model): package_id=package_id, owner_id=owner_id, strict=strict ) except UserError: - # If it raises here, it means that the `available_quantity` brought by a done move line - # is not available on the quants itself. This could be the result of an inventory - # adjustment that removed totally of partially `available_quantity`. When this happens, we - # chose to do nothing. This situation could not happen on MTS move, because in this case - # `available_quantity` is directly the quantity on the quants themselves. taken_quantity = 0 # Find a candidate move line to update or create a new one. @@ -995,7 +990,17 @@ class StockMove(models.Model): available_move_lines[(move_line.location_id, move_line.lot_id, move_line.result_package_id, move_line.owner_id)] -= move_line.product_qty for (location_id, lot_id, package_id, owner_id), quantity in available_move_lines.items(): need = move.product_qty - sum(move.move_line_ids.mapped('product_qty')) - taken_quantity = move._update_reserved_quantity(need, quantity, location_id, lot_id, package_id, owner_id) + # `quantity` is what is brought by chained done move lines. We double check + # here this quantity is available on the quants themselves. If not, this + # could be the result of an inventory adjustment that removed totally of + # partially `quantity`. When this happens, we chose to reserve the maximum + # still available. This situation could not happen on MTS move, because in + # this case `quantity` is directly the quantity on the quants themselves. + available_quantity = self.env['stock.quant']._get_available_quantity( + move.product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True) + if float_is_zero(available_quantity, precision_rounding=move.product_id.uom_id.rounding): + continue + taken_quantity = move._update_reserved_quantity(need, min(quantity, available_quantity), location_id, lot_id, package_id, owner_id) if float_is_zero(taken_quantity, precision_rounding=move.product_id.uom_id.rounding): continue if need - taken_quantity == 0.0: @@ -1077,6 +1082,9 @@ class StockMove(models.Model): break return extra_move + def _unreserve_initial_demand(self, new_move): + pass + def _action_done(self): self.filtered(lambda move: move.state == 'draft')._action_confirm() # MRP allows scrapping draft moves @@ -1117,9 +1125,7 @@ class StockMove(models.Model): move_line.write({'product_uom_qty': move_line.qty_done}) except UserError: pass - - # If you were already putting stock.move.lots on the next one in the work order, transfer those to the new move - move.move_line_ids.filtered(lambda x: x.qty_done == 0.0).write({'move_id': new_move, 'product_uom_qty': 0}) + move._unreserve_initial_demand(new_move) move.move_line_ids._action_done() # Check the consistency of the result packages; there should be an unique location across # the contained quants. diff --git a/addons/stock/report/report_stockpicking_operations.xml b/addons/stock/report/report_stockpicking_operations.xml index 6120e95adcc..f1259409334 100644 --- a/addons/stock/report/report_stockpicking_operations.xml +++ b/addons/stock/report/report_stockpicking_operations.xml @@ -99,6 +99,7 @@ <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'"> <span t-esc="ml.location_id.display_name"/> @@ -115,12 +116,16 @@ </div> </td> <td width="15%" class=" text-center h6" t-if="has_serial_number"> - <img t-if="has_serial_number and (ml.lot_id or ml.lot_name)" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', ml.lot_id.name, 600, 100)" style="width:100%;height:35px"/> + <img t-if="has_serial_number and (ml.lot_id or ml.lot_name)" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s&humanreadable=1' % ('Code128', ml.lot_id.name, 600, 100)" style="width:100%;height:35px;"/> + </td> <td width="15%" class="text-center" t-if="has_barcode"> <t t-if="product_barcode != move.product_id.barcode"> <span t-if="move.product_id and move.product_id.barcode"> - <img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', move.product_id.barcode, 600, 100)" style="width:100%;height:35px"/> + <img t-if="len(move.product_id.barcode) == 13" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('EAN13', move.product_id.barcode, 600, 100)" style="width:100%;height:35px"/> + <img t-elif="len(move.product_id.barcode) == 8" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('EAN8', move.product_id.barcode, 600, 100)" style="width:100%;height:35px"/> + <img t-else="" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', move.product_id.barcode, 600, 100)" style="width:100%;height:35px"/> + </span> <t t-set="product_barcode" t-value="move.product_id.barcode"/> </t> diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 28861355303..afa5560a298 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -1901,18 +1901,29 @@ class StockMove(TransactionCase): ml.qty_done = 1 res_dict_for_back_order = picking_pack_cust.button_validate() backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')) - backorder_wizard.with_context(debug=True).process() + backorder_wizard.process() backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_pack_cust.id)]) - backordered_ml = backorder.move_line_ids - quant_lot3 = self.env['stock.quant'].search([('lot_id', '=', backordered_ml.lot_id.id)]) + backordered_move = backorder.move_lines # due to the rounding, the backordered quantity is 0.999 ; we shoudln't be able to reserve # 0.999 on a tracked by serial number quant - self.assertEqual(backordered_ml.product_uom_qty, 0) - self.assertTrue(backordered_ml.lot_id) + backordered_move._action_assign() + self.assertEqual(backordered_move.reserved_availability, 0) # force the serial number and validate - backordered_ml.qty_done = 1 + lot3 = self.env['stock.production.lot'].search([('name', '=', "lot3")]) + backorder.write({'move_line_ids': [(0, 0, { + 'product_id': self.product2.id, + 'product_uom_id': self.uom_unit.id, + 'qty_done': 1, + 'product_uom_qty': 0, + 'lot_id': lot3.id, + 'package_id': False, + 'result_package_id': False, + 'location_id': backordered_move.location_id.id, + 'location_dest_id': backordered_move.location_dest_id.id, + 'move_id': backordered_move.id, + })]}) overprocessed_wizard = backorder.button_validate() overprocessed_wizard = self.env['stock.overprocessed.transfer'].browse(overprocessed_wizard['res_id']) @@ -1921,6 +1932,57 @@ class StockMove(TransactionCase): self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.customer_location), 3) self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.pack_location), 0) + def test_link_assign_10(self): + """ Test the assignment mechanism with partial availability. + """ + # make some stock: + # stock location: 2.0 + # pack location: -1.0 + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0) + self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0) + + move_out = self.env['stock.move'].create({ + 'name': 'test_link_assign_out', + 'location_id': self.pack_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + }) + move_out._action_confirm() + move_out._action_assign() + move_out.quantity_done = 1.0 + move_out._action_done() + self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.pack_location)), 1.0) + + move_stock_pack = self.env['stock.move'].create({ + 'name': 'test_link_assign_1_1', + 'location_id': self.stock_location.id, + 'location_dest_id': self.pack_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 2.0, + }) + move_pack_cust = self.env['stock.move'].create({ + 'name': 'test_link_assign_1_2', + 'location_id': self.pack_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 2.0, + }) + move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]}) + move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]}) + + (move_stock_pack + move_pack_cust)._action_confirm() + move_stock_pack._action_assign() + move_stock_pack.quantity_done = 2.0 + move_stock_pack._action_done() + self.assertEqual(len(move_pack_cust.move_line_ids), 1) + + self.assertAlmostEqual(move_pack_cust.reserved_availability, 1.0) + self.assertEqual(move_pack_cust.state, 'partially_available') + def test_use_unreserved_move_line_1(self): """ Test that validating a stock move linked to an untracked product reserved by another one correctly unreserves the other one. diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index 7d4ac1ac2a9..14807df4fc2 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -201,39 +201,45 @@ class ProductProduct(models.Model): fifo_automated_values[(row[0], row[1])] = (row[2], row[3], list(row[4])) for product in self: - if product.cost_method in ['standard', 'average']: - qty_available = product.with_context(company_owned=True).qty_available - price_used = product.standard_price - if to_date: - price_used = product.get_history_price( - self.env.user.company_id.id, - date=to_date, - ) - product.stock_value = price_used * qty_available - product.qty_at_date = qty_available - elif product.cost_method == 'fifo': - if to_date: - if product.product_tmpl_id.valuation == 'manual_periodic': - domain = [('product_id', '=', product.id), ('date', '<=', to_date)] + StockMove._get_all_base_domain() - moves = StockMove.search(domain) + if to_date: + price_used = product.get_history_price( + self.env.user.company_id.id, + date=to_date, + ) if product.cost_method in ['standard', 'average'] else 0.0 + if product.product_tmpl_id.valuation == 'manual_periodic': + domain = [('product_id', '=', product.id), ('date', '<=', to_date)] + StockMove._get_all_base_domain() + moves = StockMove.search(domain) + product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available + if product.cost_method == 'fifo': product.stock_value = sum(moves.mapped('value')) - product.qty_at_date = product.with_context(company_owned=True).qty_available product.stock_fifo_manual_move_ids = StockMove.browse(moves.ids) - elif product.product_tmpl_id.valuation == 'real_time': - valuation_account_id = product.categ_id.property_stock_valuation_account_id.id - value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, []) + elif product.cost_method in ['standard', 'average']: + product.stock_value = product.qty_at_date * price_used + elif product.product_tmpl_id.valuation == 'real_time': + valuation_account_id = product.categ_id.property_stock_valuation_account_id.id + value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, []) + product.qty_at_date = quantity + if product.cost_method == 'fifo': product.stock_value = value - product.qty_at_date = quantity product.stock_fifo_real_time_aml_ids = self.env['account.move.line'].browse(aml_ids) - else: - product.stock_value, moves = product._sum_remaining_values() - product.qty_at_date = product.with_context(company_owned=True).qty_available - if product.product_tmpl_id.valuation == 'manual_periodic': - product.stock_fifo_manual_move_ids = moves - elif product.product_tmpl_id.valuation == 'real_time': - valuation_account_id = product.categ_id.property_stock_valuation_account_id.id - value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, []) + elif product.cost_method in ['standard', 'average']: + product.stock_value = quantity * price_used + else: + if product.product_tmpl_id.valuation == 'manual_periodic': + product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available + if product.cost_method == 'fifo': + product.stock_value, product.stock_fifo_manual_move_ids = product._sum_remaining_values() + elif product.cost_method in ['standard', 'average']: + product.stock_value = product.qty_at_date * product.standard_price + elif product.product_tmpl_id.valuation == 'real_time': + valuation_account_id = product.categ_id.property_stock_valuation_account_id.id + value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, []) + product.qty_at_date = quantity + if product.cost_method == 'fifo': + product.stock_value = value product.stock_fifo_real_time_aml_ids = self.env['account.move.line'].browse(aml_ids) + elif product.cost_method in ['standard', 'average']: + product.stock_value = quantity * product.standard_price def action_valuation_at_date_details(self): """ Returns an action with either a list view of all the valued stock moves of `self` if the @@ -404,4 +410,3 @@ class ProductCategory(models.Model): 'message': _("Changing your cost method is an important change that will impact your inventory valuation. Are you sure you want to make that change?"), } } - diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index f7bdf08ffea..07cb19163d7 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -596,7 +596,8 @@ class StockMove(models.Model): def _create_account_move_line(self, credit_account_id, debit_account_id, journal_id): self.ensure_one() AccountMove = self.env['account.move'] - quantity = self.env.context.get('forced_quantity', self.product_qty if self._is_in() else -1 * self.product_qty) + quantity = self.env.context.get('forced_quantity', self.product_qty) + quantity = quantity if self._is_in() else -1 * quantity # Make an informative `ref` on the created account move to differentiate between classic # movements, vacuum and edition of past moves. diff --git a/addons/stock_account/tests/test_stockvaluation.py b/addons/stock_account/tests/test_stockvaluation.py index 5bba1036eab..200e91af00b 100644 --- a/addons/stock_account/tests/test_stockvaluation.py +++ b/addons/stock_account/tests/test_stockvaluation.py @@ -2079,6 +2079,8 @@ class TestStockValuation(TransactionCase): self.assertEqual(len(move1.account_move_ids), 1) + self.assertAlmostEqual(self.product1.qty_available, 10.0) + self.assertAlmostEqual(self.product1.qty_at_date, 10.0) self.assertEqual(self.product1.stock_value, 100) # --------------------------------------------------------------------- @@ -2121,6 +2123,8 @@ class TestStockValuation(TransactionCase): self.assertEqual(len(move2.account_move_ids), 1) + self.assertAlmostEqual(self.product1.qty_available, 20.0) + self.assertAlmostEqual(self.product1.qty_at_date, 20.0) self.assertEqual(self.product1.stock_value, 220) # --------------------------------------------------------------------- @@ -2165,6 +2169,8 @@ class TestStockValuation(TransactionCase): self.assertEqual(len(move3.account_move_ids), 1) + self.assertAlmostEqual(self.product1.qty_available, 12.0) + self.assertAlmostEqual(self.product1.qty_at_date, 12.0) self.assertEqual(self.product1.stock_value, 140) # --------------------------------------------------------------------- @@ -2198,6 +2204,7 @@ class TestStockValuation(TransactionCase): # Ending # --------------------------------------------------------------------- self.assertEqual(self.product1.qty_available, 6) + self.assertAlmostEqual(self.product1.qty_at_date, 6.0) self.assertEqual(self.product1.stock_value, 72) self.assertEqual(sum(self._get_stock_input_move_lines().mapped('debit')), 0) self.assertEqual(sum(self._get_stock_input_move_lines().mapped('credit')), 220) @@ -2553,8 +2560,33 @@ class TestStockValuation(TransactionCase): move2.move_line_ids.qty_done = 1.0 move2._action_done() + self.assertAlmostEqual(self.product1.qty_available, 2.0) + self.assertAlmostEqual(self.product1.qty_at_date, 2.0) self.assertAlmostEqual(self.product1.standard_price, 7.5) + def test_average_perpetual_5(self): + ''' Set owner on incoming move => no valuation ''' + self.product1.product_tmpl_id.cost_method = 'average' + + move1 = self.env['stock.move'].create({ + 'name': 'Receive 1 unit at 10', + 'location_id': self.supplier_location.id, + 'location_dest_id': self.stock_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + 'price_unit': 10, + }) + move1._action_confirm() + move1._action_assign() + move1.move_line_ids.qty_done = 1.0 + move1.move_line_ids.owner_id = self.owner1.id + move1._action_done() + + self.assertAlmostEqual(self.product1.qty_available, 1.0) + self.assertAlmostEqual(self.product1.qty_at_date, 0.0) + self.assertAlmostEqual(self.product1.stock_value, 0.0) + def test_average_negative_1(self): """ Test edit in the past. Receive 10, send 20, edit the second move to only send 10. """ @@ -2837,6 +2869,77 @@ class TestStockValuation(TransactionCase): self.assertEqual(move7.value, 100.0) self.assertEqual(self.product1.standard_price, 10) + def test_average_manual_1(self): + ''' Set owner on incoming move => no valuation ''' + self.product1.product_tmpl_id.cost_method = 'average' + self.product1.product_tmpl_id.valuation = 'manual_periodic' + + move1 = self.env['stock.move'].create({ + 'name': 'Receive 1 unit at 10', + 'location_id': self.supplier_location.id, + 'location_dest_id': self.stock_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + 'price_unit': 10, + }) + move1._action_confirm() + move1._action_assign() + move1.move_line_ids.qty_done = 1.0 + move1.move_line_ids.owner_id = self.owner1.id + move1._action_done() + + self.assertAlmostEqual(self.product1.qty_available, 1.0) + self.assertAlmostEqual(self.product1.qty_at_date, 0.0) + self.assertAlmostEqual(self.product1.stock_value, 0.0) + + def test_standard_perpetual_1(self): + ''' Set owner on incoming move => no valuation ''' + self.product1.product_tmpl_id.cost_method = 'standard' + + move1 = self.env['stock.move'].create({ + 'name': 'Receive 1 unit at 10', + 'location_id': self.supplier_location.id, + 'location_dest_id': self.stock_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + 'price_unit': 10, + }) + move1._action_confirm() + move1._action_assign() + move1.move_line_ids.qty_done = 1.0 + move1.move_line_ids.owner_id = self.owner1.id + move1._action_done() + + self.assertAlmostEqual(self.product1.qty_available, 1.0) + self.assertAlmostEqual(self.product1.qty_at_date, 0.0) + self.assertAlmostEqual(self.product1.stock_value, 0.0) + + def test_standard_manual_1(self): + ''' Set owner on incoming move => no valuation ''' + self.product1.product_tmpl_id.cost_method = 'standard' + self.product1.product_tmpl_id.valuation = 'manual_periodic' + + move1 = self.env['stock.move'].create({ + 'name': 'Receive 1 unit at 10', + 'location_id': self.supplier_location.id, + 'location_dest_id': self.stock_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + 'price_unit': 10, + }) + move1._action_confirm() + move1._action_assign() + move1.move_line_ids.qty_done = 1.0 + move1.move_line_ids.owner_id = self.owner1.id + move1._action_done() + + self.assertAlmostEqual(self.product1.qty_available, 1.0) + self.assertAlmostEqual(self.product1.qty_at_date, 0.0) + self.assertAlmostEqual(self.product1.stock_value, 0.0) + def test_change_cost_method_1(self): """ Change the cost method from FIFO to AVCO. """ @@ -2889,6 +2992,8 @@ class TestStockValuation(TransactionCase): move3.move_line_ids.qty_done = 1.0 move3._action_done() + self.assertAlmostEqual(self.product1.qty_available, 19) + self.assertAlmostEqual(self.product1.qty_at_date, 19) self.assertEqual(self.product1.stock_value, 240) # --------------------------------------------------------------------- @@ -2957,6 +3062,8 @@ class TestStockValuation(TransactionCase): move3.move_line_ids.qty_done = 1.0 move3._action_done() + self.assertAlmostEqual(self.product1.qty_available, 19) + self.assertAlmostEqual(self.product1.qty_at_date, 19) self.assertEqual(self.product1.stock_value, 240) # --------------------------------------------------------------------- @@ -3030,6 +3137,8 @@ class TestStockValuation(TransactionCase): self.assertEqual(move1.value, 10) self.assertEqual(move1.remaining_value, 10) self.assertEqual(move1.remaining_qty, 1) + self.assertAlmostEqual(self.product1.qty_available, 0.0) + self.assertAlmostEqual(self.product1.qty_at_date, 2.0) self.assertEqual(self.product1.stock_value, 10) self.assertTrue(len(move1.account_move_ids), 1) @@ -3179,8 +3288,10 @@ class TestStockValuation(TransactionCase): move1.move_line_ids.qty_done = 10 move1._action_done() move1.date = date2 + move1.account_move_ids.write({'date': date2}) self.assertEqual(self.product1.qty_available, 10) + self.assertAlmostEqual(self.product1.qty_at_date, 10.0) self.assertEqual(self.product1.stock_value, 100) # receive 20 @@ -3197,8 +3308,10 @@ class TestStockValuation(TransactionCase): move2.move_line_ids.qty_done = 20 move2._action_done() move2.date = date3 + move2.account_move_ids.write({'date': date3}) self.assertEqual(self.product1.qty_available, 30) + self.assertAlmostEqual(self.product1.qty_at_date, 30.0) self.assertEqual(self.product1.stock_value, 300) # send 15 @@ -3215,8 +3328,10 @@ class TestStockValuation(TransactionCase): move3.move_line_ids.qty_done = 15 move3._action_done() move3.date = date4 + move3.account_move_ids.write({'date': date4}) self.assertEqual(self.product1.qty_available, 15) + self.assertAlmostEqual(self.product1.qty_at_date, 15.0) self.assertEqual(self.product1.stock_value, 150) # set the standard price to 5 @@ -3224,6 +3339,7 @@ class TestStockValuation(TransactionCase): self.env['product.price.history'].search([('product_id', '=', self.product1.id)], order='datetime desc, id DESC', limit=1).datetime = date5 self.assertEqual(self.product1.qty_available, 15) + self.assertAlmostEqual(self.product1.qty_at_date, 15.0) self.assertEqual(self.product1.stock_value, 75) # send 20 @@ -3240,8 +3356,10 @@ class TestStockValuation(TransactionCase): move4.move_line_ids.qty_done = 20 move4._action_done() move4.date = date6 + move4.account_move_ids.write({'date': date6}) self.assertEqual(self.product1.qty_available, -5) + self.assertAlmostEqual(self.product1.qty_at_date, -5.0) self.assertEqual(self.product1.stock_value, -25) # set the standard price to 7.5 @@ -3262,11 +3380,13 @@ class TestStockValuation(TransactionCase): move5.move_line_ids.qty_done = 100 move5._action_done() move5.date = date8 + move5.account_move_ids.write({'date': date8}) self.assertEqual(self.product1.qty_available, 95) + self.assertAlmostEqual(self.product1.qty_at_date, 95.0) self.assertEqual(self.product1.stock_value, 712.5) - # Quantity at date + # Quantity available at date self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_available, 0) self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_available, 10) self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_available, 30) @@ -3284,12 +3404,25 @@ class TestStockValuation(TransactionCase): self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -25) self.assertEqual(self.product1.with_context(to_date=Date.to_string(date8)).stock_value, 712.5) + # Quantity at date + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_at_date, 0.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 10.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_at_date, 30.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date4)).qty_at_date, 15.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date5)).qty_at_date, 15.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, -5.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date7)).qty_at_date, -5.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date8)).qty_at_date, 95.0) + # edit the done quantity of move1, decrease it self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_available, 10) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 10.0) move1.quantity_done = 5 + move1.account_move_ids.write({'date': date2}) # the quantity at date will reflect the change directly self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_available, 5) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 5.0) # as when we decrease a quantity on a recreipt, we consider it as a out move with the price # of today, the value will be decrease of 100 - (5*7.5) @@ -3301,12 +3434,17 @@ class TestStockValuation(TransactionCase): # edit move 4, send 15 instead of 20 # we now have +5 + 20 - 15 -20 = -10 * a standard price of 5 + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_available, -10.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, -10.0) self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -50) move4.quantity_done = 15 + move4.account_move_ids.write({'date': date6}) # -(20*5) + (5*7.5) self.assertEqual(move4.value, -62.5) # we now have +5 + 20 - 15 -15 = -5 * a standard price of 5 + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_available, -5.0) + self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, -5.0) self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -25) def test_at_date_fifo_1(self): @@ -3342,6 +3480,7 @@ class TestStockValuation(TransactionCase): move1.account_move_ids.write({'date': date1}) self.assertEqual(self.product1.qty_available, 10) + self.assertAlmostEqual(self.product1.qty_at_date, 10.0) self.assertEqual(self.product1.stock_value, 100) # receive 10@12 @@ -3362,6 +3501,7 @@ class TestStockValuation(TransactionCase): move2.account_move_ids.write({'date': date2}) self.assertEqual(self.product1.qty_available, 20) + self.assertAlmostEqual(self.product1.qty_at_date, 20) self.assertEqual(self.product1.stock_value, 220) # send 15 @@ -3381,6 +3521,7 @@ class TestStockValuation(TransactionCase): move3.account_move_ids.write({'date': date3}) self.assertEqual(self.product1.qty_available, 5) + self.assertAlmostEqual(self.product1.qty_at_date, 5.0) self.assertEqual(self.product1.stock_value, 60) # send 20 @@ -3400,6 +3541,7 @@ class TestStockValuation(TransactionCase): move4.account_move_ids.write({'date': date4}) self.assertEqual(self.product1.qty_available, -15) + self.assertAlmostEqual(self.product1.qty_at_date, -15.0) self.assertEqual(self.product1.stock_value, -180) # receive 100@15 @@ -3420,6 +3562,7 @@ class TestStockValuation(TransactionCase): move5.account_move_ids.write({'date': date5}) self.assertEqual(self.product1.qty_available, 85) + self.assertAlmostEqual(self.product1.qty_at_date, 85.0) self.assertEqual(self.product1.stock_value, 1320) # run the vacuum to compensate the negative stock move @@ -3427,6 +3570,7 @@ class TestStockValuation(TransactionCase): move4.account_move_ids[0].write({'date': date6}) self.assertEqual(self.product1.qty_available, 85) + self.assertAlmostEqual(self.product1.qty_at_date, 85.0) self.assertEqual(self.product1.stock_value, 1275) # Edit the quantity done of move1, increase it. @@ -3499,6 +3643,7 @@ class TestStockValuation(TransactionCase): move1.account_move_ids.write({'date': date1}) self.assertEqual(self.product1.qty_available, 10) + self.assertAlmostEqual(self.product1.qty_at_date, 10.0) self.assertEqual(self.product1.stock_value, 100) # receive 10@15 @@ -3519,6 +3664,7 @@ class TestStockValuation(TransactionCase): move2.account_move_ids.write({'date': date2}) self.assertEqual(self.product1.qty_available, 20) + self.assertAlmostEqual(self.product1.qty_at_date, 20.0) self.assertEqual(self.product1.stock_value, 250) # send 30 @@ -3538,6 +3684,7 @@ class TestStockValuation(TransactionCase): move3.account_move_ids.write({'date': date3}) self.assertEqual(self.product1.qty_available, -10) + self.assertAlmostEqual(self.product1.qty_at_date, -10.0) self.assertEqual(self.product1.stock_value, -150) # receive 10@20 @@ -3558,6 +3705,7 @@ class TestStockValuation(TransactionCase): move4.account_move_ids.write({'date': date4}) self.assertEqual(self.product1.qty_available, 0) + self.assertAlmostEqual(self.product1.qty_at_date, 0.0) self.assertEqual(self.product1.stock_value, 50) # receive 10@10 @@ -3578,6 +3726,7 @@ class TestStockValuation(TransactionCase): move5.account_move_ids.write({'date': date5}) self.assertEqual(self.product1.qty_available, 10) + self.assertAlmostEqual(self.product1.qty_at_date, 10.0) self.assertEqual(self.product1.stock_value, 150) # run the vacuum to compensate the negative stock move @@ -3585,6 +3734,7 @@ class TestStockValuation(TransactionCase): move3.account_move_ids[0].write({'date': date6}) self.assertEqual(self.product1.qty_available, 10) + self.assertAlmostEqual(self.product1.qty_at_date, 10.0) self.assertEqual(self.product1.stock_value, 100) # --------------------------------------------------------------------- diff --git a/addons/test_mail/tests/test_mail_activity.py b/addons/test_mail/tests/test_mail_activity.py index e8b9c980480..dcd3a4eeca7 100644 --- a/addons/test_mail/tests/test_mail_activity.py +++ b/addons/test_mail/tests/test_mail_activity.py @@ -1,17 +1,20 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from datetime import date +from datetime import date, datetime from dateutil.relativedelta import relativedelta from unittest.mock import patch from unittest.mock import DEFAULT -from odoo import exceptions +import pytz + +from odoo import exceptions, tests from odoo.addons.test_mail.tests.common import BaseFunctionalTest from odoo.addons.test_mail.models.test_mail_models import MailTestActivity from odoo.tools import mute_logger +@tests.tagged('mail_activity') class TestMailActivity(BaseFunctionalTest): @classmethod @@ -73,14 +76,18 @@ class TestMailActivity(BaseFunctionalTest): }) def test_activity_mixin(self): - today = date.today() + self.user_employee.tz = self.user_admin.tz with self.sudoAs('ernest'): self.assertEqual(self.test_record.env.user, self.user_employee) + now_utc = datetime.now(pytz.UTC) + now_user = now_utc.astimezone(pytz.timezone(self.env.user.tz or 'UTC')) + today_user = now_user.date() + # Test various scheduling of activities act1 = self.test_record.activity_schedule( 'test_mail.mail_act_test_todo', - today + relativedelta(days=1), + today_user + relativedelta(days=1), user_id=self.user_admin.id) self.assertEqual(act1.automated, True) @@ -91,13 +98,13 @@ class TestMailActivity(BaseFunctionalTest): act2 = self.test_record.activity_schedule( 'test_mail.mail_act_test_meeting', - today + relativedelta(days=-1)) + today_user + relativedelta(days=-1)) self.assertEqual(self.test_record.activity_state, 'overdue') self.assertEqual(self.test_record.activity_user_id, self.user_employee) act3 = self.test_record.activity_schedule( 'test_mail.mail_act_test_todo', - today + relativedelta(days=3), + today_user + relativedelta(days=3), user_id=self.user_employee.id) self.assertEqual(self.test_record.activity_state, 'overdue') self.assertEqual(self.test_record.activity_user_id, self.user_employee) @@ -116,7 +123,7 @@ class TestMailActivity(BaseFunctionalTest): self.assertEqual(self.test_record.activity_state, 'overdue') self.test_record.activity_reschedule( ['test_mail.mail_act_test_meeting', 'test_mail.mail_act_test_todo'], - date_deadline=today + relativedelta(days=3) + date_deadline=today_user + relativedelta(days=3) ) self.assertEqual(self.test_record.activity_state, 'planned') diff --git a/addons/web/static/lib/qweb/qweb-test-widgets.xml b/addons/web/static/lib/qweb/qweb-test-widgets.xml new file mode 100644 index 00000000000..503880bcc34 --- /dev/null +++ b/addons/web/static/lib/qweb/qweb-test-widgets.xml @@ -0,0 +1,26 @@ +<templates> + <t t-name="date-simple"><t t-esc='value' /></t> + <params id="date-simple">{"value": "1988-09-16"}</params> + <result id="date-simple">1988-09-16</result> + + <t t-name="datetime-simple"><t t-esc='value' /></t> + <params id="datetime-simple">{"value": "1988-09-16 14:00:00"}</params> + <result id="datetime-simple">1988-09-16 14:00:00</result> + + <t t-name="datetime-widget-datetime"><t t-esc='value' t-options="{'widget': 'datetime'}" /></t> + <params id="datetime-widget-datetime">{"value": "1988-09-16 14:00:00"}</params> + <result id="datetime-widget-datetime">09/16/1988 16:00:00</result> + + <t t-name="datetime-widget-date"><t t-esc='value' t-options="{'widget': 'date'}" /></t> + <params id="datetime-widget-date">{"value": "1988-09-16 14:00:00"}</params> + <result id="datetime-widget-date">09/16/1988</result> + + <t t-name="datetime-widget-date-tz2"><t t-esc='value' t-options="{'widget': 'date'}" /></t> + <params id="datetime-widget-date-tz2">{"value": "1988-09-16 01:00:00"}</params> + <result id="datetime-widget-date-tz2">09/16/1988</result> + + <t t-name="datetime-widget-date-tz"><t t-esc='value' t-options="{'widget': 'date'}" /></t> + <params id="datetime-widget-date-tz">{"value": "1988-09-16 23:00:00"}</params> + <result id="datetime-widget-date-tz">09/17/1988</result> + +</templates> diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 01b4cb4371d..43b4e349a66 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -498,7 +498,13 @@ var FieldDate = InputField.extend({ * @private */ _makeDatePicker: function () { - return new datepicker.DateWidget(this, {defaultDate: this.value}); + return new datepicker.DateWidget( + this, + _.defaults( + this.nodeOptions.datepicker || {}, + {defaultDate: this.value} + ) + ); }, /** diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Editor.js b/addons/web_editor/static/lib/summernote/src/js/module/Editor.js index b7ba30bfcd1..99947c346cf 100644 --- a/addons/web_editor/static/lib/summernote/src/js/module/Editor.js +++ b/addons/web_editor/static/lib/summernote/src/js/module/Editor.js @@ -628,9 +628,9 @@ define([ } var anchors = []; - // ODOO: adding this branch to modify existing anchor + // ODOO: adding this branch to modify existing anchor if it fully contains the range var ancestor_anchor = dom.ancestor(rng.sc, dom.isAnchor); - if(ancestor_anchor) { + if(ancestor_anchor && ancestor_anchor === dom.ancestor(rng.ec, dom.isAnchor)) { anchors.push($(ancestor_anchor).html(linkText).get(0)); } else if (isTextChanged) { // Create a new link when text changed. diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js index ce20c53ff49..14c0e26cab9 100644 --- a/addons/web_editor/static/src/js/editor/rte.summernote.js +++ b/addons/web_editor/static/src/js/editor/rte.summernote.js @@ -488,7 +488,7 @@ function prettify_html(html) { while (i--) space += ' '; return space; }, - reg = /^<\/?(a|span|font|strong|u|i|strong|b)(\s|>)/i, + reg = /^<\/?(a|span|font|u|em|i|strong|b)(\s|>)/i, inline_level = Infinity, tokens = _.compact(_.flatten(_.map(html.split(/</), function (value) { value = value.replace(/\s+/g, ' ').split(/>/); diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index ab8f166d221..d0df042fa82 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1406,6 +1406,8 @@ var LinkDialog = Dialog.extend({ if (dom.ancestor(nodes[i], dom.isImg)) { this.data.images.push(dom.ancestor(nodes[i], dom.isImg)); text += '[IMG]'; + } else if (!is_link && nodes[i].nodeType === 1) { + // just use text nodes from listBetween } else if (!is_link && i===0) { text += nodes[i].textContent.slice(so, Infinity); } else if (!is_link && i===nodes.length-1) { diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 0e5443ad79e..aa703071d9b 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -6,6 +6,7 @@ import traceback import os import unittest +import pytz import werkzeug import werkzeug.routing import werkzeug.utils @@ -74,6 +75,10 @@ class Http(models.AbstractModel): context = {} if not request.context.get('tz'): context['tz'] = request.session.get('geoip', {}).get('time_zone') + try: + pytz.timezone(context['tz'] or '') + except pytz.UnknownTimeZoneError: + context.pop('tz') request.website = request.env['website'].get_current_website() # can use `request.env` since auth methods are called context['website_id'] = request.website.id diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py index a06d9500233..bc1edfc5bb9 100644 --- a/addons/website_event/controllers/main.py +++ b/addons/website_event/controllers/main.py @@ -264,7 +264,7 @@ class WebsiteEventController(http.Controller): urls = event._get_event_resource_urls(Attendees.ids) return request.render("website_event.registration_complete", { - 'attendees': Attendees, + 'attendees': Attendees.sudo(), 'event': event, 'google_url': urls.get('google_url'), 'iCal_url': urls.get('iCal_url') diff --git a/addons/website_event/static/src/js/website_event.js b/addons/website_event/static/src/js/website_event.js index ebe38118559..53a304c707e 100644 --- a/addons/website_event/static/src/js/website_event.js +++ b/addons/website_event/static/src/js/website_event.js @@ -59,12 +59,16 @@ var EventRegistrationForm = Widget.extend({ $button.attr('disabled', true); return ajax.jsonRpc($form.attr('action'), 'call', post).then(function (modal) { var $modal = $(modal); + $modal.modal({backdrop: 'static', keyboard: false}); $modal.find('.modal-body > div').removeClass('container'); // retrocompatibility - REMOVE ME in master / saas-19 $modal.insertAfter($form).modal(); $modal.on('click', '.js_goto_event', function () { $modal.modal('hide'); $button.prop('disabled', false); }); + $modal.on('click', '.close', function () { + $button.prop('disabled', false); + }); }); } }, diff --git a/addons/website_event_track/views/event_track_templates.xml b/addons/website_event_track/views/event_track_templates.xml index 70ed14e680a..dbfceeb8ca6 100644 --- a/addons/website_event_track/views/event_track_templates.xml +++ b/addons/website_event_track/views/event_track_templates.xml @@ -50,8 +50,8 @@ <input type="text" class="form-control" placeholder="Filter Tracks..." id="event_track_search"/> </div> </section> - <t t-set="dayslist" t-value="days.keys()"/> - <t t-set="dayslist2" t-value="list(dayslist).sort()"/> <!-- display days in the right order --> + <t t-set="dayslist" t-value="list(days.keys())"/> + <t t-set="dayslist2" t-value="dayslist.sort()"/> <!-- display days in the right order --> <section class="container" t-foreach="dayslist" t-as="day"> <t t-set="locations" t-value="days[day]['locations']"/> <t t-set="dates" t-value="days[day]['dates']"/> diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index bb01a3fc3f9..7186e19f112 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -422,6 +422,11 @@ class Post(models.Model): raise KarmaError('User karma not sufficient to post an image or link.') return content + @api.constrains('parent_id') + def _check_parent_id(self): + if not self._check_recursion(): + raise ValidationError(_('You cannot create recursive forum posts.')) + @api.model def create(self, vals): if 'content' in vals and vals.get('forum_id'): diff --git a/addons/website_livechat/controllers/main.py b/addons/website_livechat/controllers/main.py index 753e4eef1ae..31af89c2e63 100644 --- a/addons/website_livechat/controllers/main.py +++ b/addons/website_livechat/controllers/main.py @@ -20,13 +20,17 @@ class WebsiteLivechat(http.Controller): @http.route('/livechat/channel/<model("im_livechat.channel"):channel>', type='http', auth='public', website=True) def channel_rating(self, channel, **kw): # get the last 100 ratings and the repartition per grade - ratings = request.env['rating.rating'].search([('res_model', '=', 'mail.channel'), ('res_id', 'in', channel.sudo().channel_ids.ids)], order='create_date desc', limit=100) - repartition = channel.sudo().channel_ids.rating_get_grades() + domain = [ + ('res_model', '=', 'mail.channel'), ('res_id', 'in', channel.sudo().channel_ids.ids), + ('consumed', '=', True), ('rating', '>=', 1), + ] + ratings = request.env['rating.rating'].search(domain, order='create_date desc', limit=100) + repartition = channel.sudo().channel_ids.rating_get_grades(domain=domain) # compute percentage percentage = dict.fromkeys(['great', 'okay', 'bad'], 0) for grade in repartition: - percentage[grade] = repartition[grade] * 100 / sum(repartition.values()) if sum(repartition.values()) else 0 + percentage[grade] = round(repartition[grade] * 100.0 / sum(repartition.values()), 1) if sum(repartition.values()) else 0 # the value dict to render the template values = { diff --git a/addons/website_mass_mailing/controllers/main.py b/addons/website_mass_mailing/controllers/main.py index d1aa21f5cd4..455ab1bdae0 100644 --- a/addons/website_mass_mailing/controllers/main.py +++ b/addons/website_mass_mailing/controllers/main.py @@ -11,7 +11,7 @@ class MassMailController(MassMailController): def mailing(self, mailing_id, email=None, res_id=None, **post): mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id) if mailing.exists(): - if mailing.mailing_model_name == 'mail.mass_mailing.contact': + if mailing.mailing_model_real == 'mail.mass_mailing.contact': contacts = request.env['mail.mass_mailing.contact'].sudo().search([('email', '=', email)]) return request.render('website_mass_mailing.page_unsubscribe', { 'contacts': contacts, diff --git a/addons/website_sale/views/sale_order_views.xml b/addons/website_sale/views/sale_order_views.xml index f9f409351f7..26275bd265b 100644 --- a/addons/website_sale/views/sale_order_views.xml +++ b/addons/website_sale/views/sale_order_views.xml @@ -117,7 +117,7 @@ <record id="action_view_unpaid_quotation_tree" model="ir.actions.act_window"> <field name="name">Unpaid Orders</field> <field name="res_model">sale.order</field> - <field name="view_mode">tree,kanban</field> + <field name="view_mode">tree,kanban,form</field> <field name="view_type">form</field> <field name="domain">[('state', '=', 'sent'), ('team_id.team_type', '=', 'website')]</field> <field name="context" eval="{'show_sale': True, 'create': False}"/> @@ -135,7 +135,7 @@ <field name="name">Abandoned Carts</field> <field name="res_model">sale.order</field> <field name="view_type">form</field> - <field name="view_mode">tree,kanban</field> + <field name="view_mode">tree,kanban,form</field> <field name="domain">[('state', '=', 'draft'), ('order_line', '!=', False), ('partner_id', '!=', context.get('public_partner_id')), ('team_id.team_type', '=', 'website'), ('date_order', '<=', (datetime.datetime.utcnow() - datetime.timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S'))]</field> <field name="context" eval="{'show_sale': True, 'create': False, 'public_partner_id': ref('base.public_partner')}"/> <field name="view_id" ref="sale.view_quotation_tree"/> diff --git a/addons/website_sale_stock/models/product_product.py b/addons/website_sale_stock/models/product_product.py index 829efd3f2dc..cfab171bb32 100644 --- a/addons/website_sale_stock/models/product_product.py +++ b/addons/website_sale_stock/models/product_product.py @@ -11,7 +11,7 @@ class ProductProduct(models.Model): cart_qty = fields.Integer(compute='_compute_cart_qty') def _compute_cart_qty(self): - website = getattr(request, 'website', None) + website = request and getattr(request, 'website', None) if not website: return cart = website.sale_get_order() diff --git a/addons/website_sale_wishlist/models/product_wishlist.py b/addons/website_sale_wishlist/models/product_wishlist.py index 6c73cf50608..381d5eceb45 100644 --- a/addons/website_sale_wishlist/models/product_wishlist.py +++ b/addons/website_sale_wishlist/models/product_wishlist.py @@ -36,14 +36,15 @@ class ProductWishlist(models.Model): @api.model def current(self): - """Get all wishlist items that belong to current user or session.""" - return self.search([ + """Get all wishlist items that belong to current user or session, + filter products that are unpublished.""" + return self.sudo().search([ "|", ("partner_id", "=", self.env.user.partner_id.id), "&", ("partner_id", "=", False), ("session", "=", self.env.user.current_session), - ]) + ]).filtered('product_id.product_tmpl_id.website_published') @api.model def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False, session=False): |
