[MERGE] forward port branch saas-11.2 up to b170a753e1

This commit is contained in:
Christophe Simonis
2018-06-15 11:30:27 +02:00
130 changed files with 1279 additions and 557 deletions
+4 -3
View File
@@ -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
+10 -3
View File
@@ -396,7 +396,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 = ''
@@ -1523,6 +1523,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'):
@@ -1541,8 +1548,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):
@@ -1591,8 +1600,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)
+1 -1
View File
@@ -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',
@@ -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">
+1 -1
View File
@@ -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">
@@ -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 {
+4 -1
View File
@@ -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
@@ -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')
+4 -1
View File
@@ -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"));
}
+2
View File
@@ -237,6 +237,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:
+1 -1
View File
@@ -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
+6 -1
View File
@@ -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'))
+2
View File
@@ -9,6 +9,8 @@ class ResPartner(models.Model):
event_count = fields.Integer("Events", 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)])
@@ -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">
+1
View File
@@ -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>
+1 -1
View File
@@ -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
+2
View File
@@ -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
+10 -1
View File
@@ -150,6 +150,15 @@
<filter name="alert_true" domain="['|',('contract_renewal_due_soon','=',True),('contract_renewal_overdue','=',True)]" string="Has Alert(s)"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<separator/>
<filter string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<group expand="1" string="Group By">
<filter name="groupby_status" context="{'group_by' : 'state_id'}" string="Status"/>
<filter name="groupby_model" context="{'group_by' : 'model_id'}" string="Model"/>
@@ -163,7 +172,7 @@
<field name="name">fleet.vehicle.kanban</field>
<field name="model">fleet.vehicle</field>
<field name="arch" type="xml">
<kanban>
<kanban default_group_by="state_id">
<field name="license_plate" />
<field name="model_id" />
<field name="driver_id" />
+1 -1
View File
@@ -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
+1 -1
View File
@@ -87,7 +87,7 @@ class HolidaysRequest(models.Model):
number_of_days = fields.Float('Number of Days', compute='_compute_number_of_days', store=True, track_visibility='onchange')
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,
+15 -14
View File
@@ -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
@@ -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>
+12 -11
View File
@@ -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"
+1 -1
View File
@@ -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
+1 -1
View File
@@ -130,4 +130,4 @@ msgstr "Hizmet Adı"
#. module: iap
#: model:ir.model,name:iap.model_iap_account
msgid "iap.account"
msgstr ""
msgstr "iap.account"
+5
View File
@@ -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
+2 -2
View File
@@ -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">
+4 -4
View File
@@ -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"
+1 -1
View File
@@ -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"
+39 -28
View File
@@ -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
@@ -1620,7 +1620,7 @@ var ChatManager = AbstractService.extend({
}
this._removeChannel(channel);
this.chatBus.trigger("unsubscribe_from_channel", data.id);
web_client.do_notify(_("Unsubscribed"), msg);
web_client.do_notify(_t("Unsubscribed"), msg);
}
} else if (data.type === 'toggle_star') {
this._manageToggleStarNotification(data);
+6 -2
View File
@@ -128,7 +128,7 @@ var MessagingMenu = Widget.extend({
if (channelID === 'channel_inbox') {
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,
@@ -136,7 +136,11 @@ var MessagingMenu = Widget.extend({
res_id: resID
});
} 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'));
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
@@ -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;
},
@@ -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,
),
)
+2
View File
@@ -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):
+1
View File
@@ -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,
+7
View File
@@ -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()
+4 -2
View File
@@ -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>
+11 -2
View File
@@ -59,6 +59,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 = {
@@ -83,7 +92,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 '',
@@ -92,7 +101,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)
+1 -1
View File
@@ -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'
+19 -63
View File
@@ -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 get_input_value = function(name) {
return provider_form.find('input[name="' + name + '"]').val();
}
var currency = $("input[name='currency']").val();
var currency_id = $("input[name='currency_id']").val();
var amount = parseFloat($("input[name='amount']").val() || '0.0');
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");
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 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,
});
+1 -1
View File
@@ -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"
+3
View File
@@ -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
+2 -2
View File
@@ -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',
+12
View File
@@ -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'
+2 -1
View File
@@ -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,
+1 -1
View File
@@ -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(),
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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"
+2
View File
@@ -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]
+2 -1
View File
@@ -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,6 +108,6 @@ 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])
return self.search(args, limit=limit).name_get()
return super(ProductAttributeLine, self).name_search(name=name, args=args, operator=operator, limit=limit)
+1 -1
View File
@@ -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
+28
View File
@@ -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")
+2
View File
@@ -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
+1 -1
View File
@@ -143,7 +143,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
+2
View File
@@ -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
+1 -1
View File
@@ -868,7 +868,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
+1 -1
View File
@@ -537,7 +537,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)
+1 -1
View File
@@ -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" /> -&gt; <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/>
+1 -1
View File
@@ -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"
+14 -3
View File
@@ -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,
@@ -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">
@@ -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>
+1 -1
View File
@@ -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
+11
View File
@@ -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'})
+1
View File
@@ -33,4 +33,5 @@ have real delivered quantities in sales orders.
'data/sale_service_demo.xml',
],
'auto_install': True,
'uninstall_hook': 'uninstall_hook',
}
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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)
+16 -10
View File
@@ -661,7 +661,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
@@ -877,11 +877,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.
@@ -997,7 +992,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:
@@ -1079,6 +1084,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
@@ -1119,9 +1127,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.
@@ -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"/>
@@ -116,11 +117,15 @@
</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&amp;value=%s&amp;width=%s&amp;height=%s&amp;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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN8', move.product_id.barcode, 600, 100)" style="width:100%;height:35px"/>
<img t-else="" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;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>
+68 -6
View File
@@ -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.
+34 -29
View File
@@ -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?"),
}
}
+2 -1
View File
@@ -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.
@@ -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.
"""
@@ -2843,6 +2875,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.
"""
@@ -2895,6 +2998,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)
# ---------------------------------------------------------------------
@@ -2963,6 +3068,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)
# ---------------------------------------------------------------------
@@ -3036,6 +3143,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)
@@ -3185,8 +3294,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
@@ -3203,8 +3314,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
@@ -3221,8 +3334,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
@@ -3230,6 +3345,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
@@ -3246,8 +3362,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
@@ -3268,11 +3386,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)
@@ -3290,12 +3410,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)
@@ -3307,12 +3440,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):
@@ -3348,6 +3486,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
@@ -3368,6 +3507,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
@@ -3387,6 +3527,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
@@ -3406,6 +3547,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
@@ -3426,6 +3568,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
@@ -3433,6 +3576,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.
@@ -3505,6 +3649,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
@@ -3525,6 +3670,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
@@ -3544,6 +3690,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
@@ -3564,6 +3711,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
@@ -3584,6 +3732,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
@@ -3591,6 +3740,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)
# ---------------------------------------------------------------------
@@ -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>
@@ -178,7 +178,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
core.bus.on('connection_restored', this, this.on_connection_restored);
// crash manager integration
session.on('error', crash_manager, crash_manager.rpc_error);
core.bus.on('rpc_error', crash_manager, crash_manager.rpc_error);
window.onerror = function (message, file, line, col, error) {
// Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror.
// The browser will just give you a 'Script error.' as message and nothing else for security issue.
+3 -3
View File
@@ -26,9 +26,9 @@ var Loading = Widget.extend({
this._super(parent);
this.count = 0;
this.blocked_ui = false;
session.on("request", this, this.request_call);
session.on("response", this, this.response_call);
session.on("response_failed", this, this.response_call);
core.bus.on('rpc_request', this, this.request_call);
core.bus.on("rpc_response", this, this.response_call);
core.bus.on("rpc_response_failed", this, this.response_call);
},
destroy: function() {
this.on_rpc_event(-this.count);
+47 -4
View File
@@ -5,7 +5,12 @@ var core = require('web.core');
var utils = require('web.utils');
var time = require('web.time');
function genericJsonRpc (fct_name, params, fct) {
function genericJsonRpc (fct_name, params, settings, fct) {
var shadow = settings.shadow || false;
delete settings.shadow;
if (! shadow)
core.bus.trigger('rpc_request');
var data = {
jsonrpc: "2.0",
method: fct_name,
@@ -30,11 +35,49 @@ function genericJsonRpc (fct_name, params, fct) {
});
// FIXME: jsonp?
result.abort = function () { if (xhr.abort) xhr.abort(); };
return result;
var p = result.then(function (result) {
if (!shadow) {
core.bus.trigger('rpc_response');
}
return result;
}, function (type, error, textStatus, errorThrown) {
if (type === "server") {
if (!shadow) {
core.bus.trigger('rpc_response');
}
if (error.code === 100) {
core.bus.trigger('invalidate_session');
}
return $.Deferred().reject(error, $.Event());
} else {
if (!shadow) {
core.bus.trigger('rpc_response_failed');
}
var nerror = {
code: -32098,
message: "XmlHttpRequestError " + errorThrown,
data: {
type: "xhr"+textStatus,
debug: error.responseText,
objects: [error, errorThrown]
},
};
return $.Deferred().reject(nerror, $.Event());
}
});
return p.fail(function () { // Allow deferred user to disable rpc_error call in fail
p.fail(function (error, event) {
if (!event.isDefaultPrevented()) {
core.bus.trigger('rpc_error', error, event);
}
});
});
}
function jsonRpc(url, fct_name, params, settings) {
return genericJsonRpc(fct_name, params, function(data) {
settings = settings || {};
return genericJsonRpc(fct_name, params, settings, function(data) {
return $.ajax(url, _.extend({}, settings, {
url: url,
dataType: 'json',
@@ -47,7 +90,7 @@ function jsonRpc(url, fct_name, params, settings) {
function jsonpRpc(url, fct_name, params, settings) {
settings = settings || {};
return genericJsonRpc(fct_name, params, function(data) {
return genericJsonRpc(fct_name, params, settings, function(data) {
var payload_str = JSON.stringify(data, time.date_to_utc);
var payload_url = $.param({r:payload_str});
var force2step = settings.force2step || false;
+13 -37
View File
@@ -49,6 +49,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
this.qweb_mutex = new concurrency.Mutex();
this.currencies = {};
this._groups_def = {};
core.bus.on('invalidate_session', this, this._onInvalidateSession);
},
setup: function (origin, options) {
// must be able to customize server
@@ -327,23 +328,17 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
rpc: function (url, params, options) {
var self = this;
options = _.clone(options || {});
var shadow = options.shadow || false;
options.headers = _.extend({}, options.headers);
if (odoo.debug) {
options.headers["X-Debug-Mode"] = $.deparam($.param.querystring()).debug;
}
delete options.shadow;
return self.check_session_id().then(function () {
// TODO: remove
if (! _.isString(url)) {
_.extend(options, url);
url = url.url;
}
// TODO correct handling of timeouts
if (! shadow)
self.trigger('request');
var fct;
if (self.origin_server) {
fct = ajax.jsonRpc;
@@ -362,37 +357,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
url = self.url(url, null);
options.session_id = self.session_id || '';
}
var p = fct(url, "call", params, options);
p = p.then(function (result) {
if (! shadow)
self.trigger('response');
return result;
}, function (type, error, textStatus, errorThrown) {
if (type === "server") {
if (! shadow)
self.trigger('response');
if (error.code === 100) {
self.uid = false;
}
return $.Deferred().reject(error, $.Event());
} else {
if (! shadow)
self.trigger('response_failed');
var nerror = {
code: -32098,
message: "XmlHttpRequestError " + errorThrown,
data: {type: "xhr"+textStatus, debug: error.responseText, objects: [error, errorThrown] }
};
return $.Deferred().reject(nerror, $.Event());
}
});
return p.fail(function () { // Allow deferred user to disable rpc_error call in fail
p.fail(function (error, event) {
if (!event.isDefaultPrevented()) {
self.trigger('error', error, event);
}
});
});
return fct(url, "call", params, options);
});
},
url: function (path, params) {
@@ -418,6 +383,17 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
getTZOffset: function (date) {
return -new Date(date).getTimezoneOffset();
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onInvalidateSession: function () {
this.uid = false;
},
});
return Session;
@@ -491,7 +491,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}
)
);
},
/**
@@ -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.
@@ -474,7 +474,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(/>/);
@@ -1398,6 +1398,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) {
+5
View File
@@ -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
+2 -2
View File
@@ -10,6 +10,6 @@ class IrModel(models.Model):
@api.multi
def unlink(self):
self.env.cr.execute(
"DELETE FROM ir_model_fields WHERE name='website_id'")
self.env.cr.execute("DELETE FROM ir_model_fields WHERE name='website_id'")
self.env.cr.execute("DELETE FROM res_config_settings WHERE website_id IS NOT NULL")
return super(IrModel, self).unlink()
+1 -1
View File
@@ -261,6 +261,6 @@ class WebsiteEventController(http.Controller):
Attendees._prepare_attendee_values(registration))
return request.render("website_event.registration_complete", {
'attendees': Attendees,
'attendees': Attendees.sudo(),
'event': event,
})
@@ -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);
});
});
}
},
@@ -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']"/>
+5
View File
@@ -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'):
+7 -3
View File
@@ -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 = {
@@ -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,
@@ -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()
@@ -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):
+1
View File
@@ -13,3 +13,4 @@ List of contributors:
Damián Soriano ds@adhoc.com.ar https://github.com/damiansoriano
Juan José Scarafía jjs@adhoc.com.ar https://github.com/jjscarafia
Nicolás Mac Rouillon nmr@adhoc.com.ar https://github.com/nicomacr
Katherine Zaoral kz@adhoc.com.ar https://github.com/zaoral
-1
View File
@@ -14,7 +14,6 @@ List of contributors:
Aarón Henríquez ahenriquez@eficent.com https://github.com/aheficent
Lois Rilo lois.rilo@eficent.com https://github.com/lreficent
Roser Garcia roser.garcia@eficent.com https://github.com/rgarnau
Miquel Raich miquel.raich@eficent.com https://github.com/mreficent
Jordi Ballester jordi.ballester@eficent.com https://github.com/jbeficent
Hector Villarreal hector.villarreal@eficent.com https://github.com/hveficent
+19
View File
@@ -0,0 +1,19 @@
Serbia, 2018-06-11
Modoolar agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Petar Najman petar.najman@modoolar.com https://github.com/pnajman-modoolar
List of contributors:
- Aleksandar Gajić aleksandar.gajic@modoolar.com https://github.com/agajic-modoolar
- Igor Jovanović igor.jovanovic@modoolar.com https://github.com/ijovanovic-modoolar
- Mladen Meseldžija mladen.meseldzija@modoolar.com https://github.com/mmeseldzija-modoolar
- Petar Najman petar.najman@modoolar.com https://github.com/pnajman-modoolar
- Slađan Kantar sladjan.kantar@modoolar.com https://github.com/skantar

Some files were not shown because too many files have changed in this diff Show More