[MERGE] forward port branch saas-17 up to a445548f07
@@ -1007,7 +1007,7 @@ class AccountInvoice(models.Model):
|
||||
return {
|
||||
'date_maturity': line.get('date_maturity', False),
|
||||
'partner_id': part,
|
||||
'name': line['name'][:64],
|
||||
'name': line['name'],
|
||||
'debit': line['price'] > 0 and line['price'],
|
||||
'credit': line['price'] < 0 and -line['price'],
|
||||
'account_id': line['account_id'],
|
||||
|
||||
@@ -1443,7 +1443,7 @@ class AccountPartialReconcile(models.Model):
|
||||
company_id = fields.Many2one('res.company', related='debit_move_id.company_id', store=True, string='Currency')
|
||||
full_reconcile_id = fields.Many2one('account.full.reconcile', string="Full Reconcile", copy=False)
|
||||
max_date = fields.Date(string='Max Date of Matched Lines', compute='_compute_max_date',
|
||||
readonly=True, copy=False, stored=True,
|
||||
readonly=True, copy=False, store=True,
|
||||
help='Technical field used to determine at which date this reconciliation needs to be shown on the aged receivable/payable reports.')
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -20,7 +20,6 @@ FormView.include({
|
||||
*/
|
||||
init: function (viewInfo) {
|
||||
this._super.apply(this, arguments);
|
||||
this.rendererParams.noContentHelp = this.controllerParams.noContentHelp;
|
||||
this.controllerParams.viewID = viewInfo.view_id;
|
||||
},
|
||||
});
|
||||
@@ -236,6 +235,7 @@ FormRenderer.include({
|
||||
var view = new View(viewInfo, {
|
||||
action: action,
|
||||
context: context,
|
||||
domain: params.domain,
|
||||
groupBy: context.group_by,
|
||||
modelName: action.res_model,
|
||||
hasSelectors: false,
|
||||
|
||||
@@ -99,7 +99,7 @@ QUnit.test('display the no content helper', function (assert) {
|
||||
});
|
||||
|
||||
QUnit.test('basic functionality, with one sub action', function (assert) {
|
||||
assert.expect(24);
|
||||
assert.expect(25);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
@@ -108,11 +108,11 @@ QUnit.test('basic functionality, with one sub action', function (assert) {
|
||||
arch: '<form string="My Dashboard">' +
|
||||
'<board style="2-1">' +
|
||||
'<column>' +
|
||||
'<action context="{}" view_mode="list" string="ABC" name="51" domain="[]"></action>' +
|
||||
'<action context="{}" view_mode="list" string="ABC" name="51" domain="[[\'foo\', \'!=\', \'False\']]"></action>' +
|
||||
'</column>' +
|
||||
'</board>' +
|
||||
'</form>',
|
||||
mockRPC: function (route) {
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/web/action/load') {
|
||||
assert.step('load action');
|
||||
return $.when({
|
||||
@@ -120,6 +120,9 @@ QUnit.test('basic functionality, with one sub action', function (assert) {
|
||||
views: [[4, 'list']],
|
||||
});
|
||||
}
|
||||
if (route === '/web/dataset/search_read') {
|
||||
assert.deepEqual(args.domain, [['foo', '!=', 'False']], "the domain should be passed");
|
||||
}
|
||||
if (route === '/board/static/src/img/layout_1-1-1.png') {
|
||||
return $.when();
|
||||
}
|
||||
|
||||
@@ -3593,8 +3593,7 @@ msgstr ""
|
||||
|
||||
#. module: crm
|
||||
#: model:ir.actions.act_window,help:crm.crm_opportunity_report_action_lead
|
||||
msgid "This report analyses the source of your leads\n"
|
||||
"(including those converted into opportunities)."
|
||||
msgid "This report analyses the source of your leads."
|
||||
msgstr ""
|
||||
|
||||
#. module: crm
|
||||
|
||||
@@ -77,8 +77,8 @@
|
||||
<field name="domain">[('probability', '=', 100)]</field>
|
||||
<field name="context">{'col_group_by': ['create_date:month'], 'group_by': ['country_id']}</field>
|
||||
</record>
|
||||
<record id="ir_filters_crm_opportunity_report_next_action" model="ir.filters">
|
||||
<field name="name">Expected Revenus by Team</field>
|
||||
<record id="filter_opportunity_expected_revenue" model="ir.filters">
|
||||
<field name="name">Expected Revenue by Team</field>
|
||||
<field name="model_id">crm.opportunity.report</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{'group_by': ['create_date:month', 'team_id'], 'measures': ['expected_revenue', '__count']}</field>
|
||||
@@ -191,13 +191,12 @@ with the the sales channel.</field>
|
||||
<field name="res_model">crm.opportunity.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="context">{'search_default_current': True, 'active_test': True}</field>
|
||||
<field name="context">{'search_default_current': True, 'active_test': True, 'search_default_lead': True}</field>
|
||||
<field name="view_ids"
|
||||
eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'graph', 'view_id': ref('crm_opportunity_report_view_graph_lead')}),
|
||||
(0, 0, {'view_mode': 'pivot', 'view_id': ref('crm_opportunity_report_view_pivot_lead')})]"/>
|
||||
<field name="help">This report analyses the source of your leads
|
||||
(including those converted into opportunities).</field>
|
||||
<field name="help">This report analyses the source of your leads.</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
|
||||
@@ -133,4 +133,39 @@ QUnit.test('dashboard: click on a button to execute an action', function (assert
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('dashboard should be displayed even if there is no content', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var dashboardData = this.dashboard_data;
|
||||
var kanban = createView({
|
||||
View: view_registry.get('sales_team_dashboard'),
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<kanban class="o_kanban_test">' +
|
||||
'<templates><t t-name="kanban-box">' +
|
||||
'<div><field name="foo"/></div>' +
|
||||
'</t></templates>' +
|
||||
'</kanban>',
|
||||
domain: [['id', '=', 239]], // no record will match this domain
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'retrieve_sales_dashboard') {
|
||||
return $.when(dashboardData);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
viewOptions: {
|
||||
action: {
|
||||
help: '<p>A help message</p>',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(kanban.$('div.o_sales_dashboard').length, 1,
|
||||
"should render the dashboard");
|
||||
assert.strictEqual(kanban.$('.oe_view_nocontent:contains(A help message)').length, 1,
|
||||
"should correctly render the nocontent helper");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ class DeliveryCarrier(models.Model):
|
||||
zip_to = fields.Char('Zip To')
|
||||
|
||||
margin = fields.Integer(help='This percentage will be added to the shipping price.')
|
||||
free_over = fields.Boolean('Free if order total is more than', help="If the order is more expensive than a certain amount, the customer can benefit from a free shipping", default=False, oldname='free_if_more_than')
|
||||
free_over = fields.Boolean('Free if order amount is above', help="If the order total amount (shipping excluded) is above or equal to this value, the customer benefits from a free shipping", default=False, oldname='free_if_more_than')
|
||||
amount = fields.Float(string='Amount', help="Amount of the order to benefit from a free shipping, expressed in the company currency")
|
||||
|
||||
_sql_constraints = [
|
||||
@@ -122,7 +122,7 @@ class DeliveryCarrier(models.Model):
|
||||
res['price'] = res['price'] * (1.0 + (float(self.margin) / 100.0))
|
||||
# free when order is large enough
|
||||
if res['success'] and self.free_over and order._compute_amount_total_without_delivery() >= self.amount:
|
||||
res['warning_message'] = _('Warning:\nTotal amount of this order is over %.2f, free shipping!\n(actual cost: %.2f)') % (self.amount, res['price'])
|
||||
res['warning_message'] = _('Info:\nTotal amount of this order is above %.2f, free shipping!\n(actual cost: %.2f)') % (self.amount, res['price'])
|
||||
res['price'] = 0.0
|
||||
return res
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class PriceRule(models.Model):
|
||||
elif rule.list_price and not rule.list_base_price:
|
||||
name = '%s %s times %s' % (name, rule.list_price, rule.variable_factor)
|
||||
else:
|
||||
name = '%s fixed price %s and %s times %s Extra' % (name, rule.list_base_price, rule.list_price, rule.variable_factor)
|
||||
name = '%s fixed price %s plus %s times %s' % (name, rule.list_base_price, rule.list_price, rule.variable_factor)
|
||||
rule.name = name
|
||||
|
||||
name = fields.Char(compute='_compute_name')
|
||||
@@ -50,7 +50,13 @@ class ProviderGrid(models.Model):
|
||||
'error_message': _('Error: no matching grid.'),
|
||||
'warning_message': False}
|
||||
|
||||
price_unit = self._get_price_available(order)
|
||||
try:
|
||||
price_unit = self._get_price_available(order)
|
||||
except UserError as e:
|
||||
return {'success': False,
|
||||
'price': 0.0,
|
||||
'error_message': e.name,
|
||||
'warning_message': False}
|
||||
if order.company_id.currency_id.id != order.pricelist_id.currency_id.id:
|
||||
price_unit = order.company_id.currency_id.with_context(date=order.date_order).compute(price_unit, order.pricelist_id.currency_id)
|
||||
|
||||
@@ -91,7 +97,7 @@ class ProviderGrid(models.Model):
|
||||
criteria_found = True
|
||||
break
|
||||
if not criteria_found:
|
||||
raise UserError(_("Selected product in the delivery method doesn't fulfill any of the delivery carrier(s) criteria."))
|
||||
raise UserError(_("No price rule matching this order; delivery cost cannot be computed."))
|
||||
|
||||
return price
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
<field name="operator"/>
|
||||
<field name="max_value"/>
|
||||
</div>
|
||||
<label for="list_base_price" string="Sale Price"/>
|
||||
<label for="list_base_price" string="Delivery Cost"/>
|
||||
<div>
|
||||
<field name="list_base_price" class="oe_inline"/>
|
||||
+
|
||||
@@ -193,7 +193,6 @@
|
||||
<tree string="Price Rules">
|
||||
<field name="sequence" widget="handle" />
|
||||
<field name="name"/>
|
||||
<field name="list_base_price" string="Sale Price"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * hr_timesheet_sheet
|
||||
#
|
||||
# Translators:
|
||||
# Xavier Belmere <Info@cartmeleon.com>, 2016
|
||||
# Martin Trigaux <mat@odoo.com>, 2016
|
||||
# Leo Schmitt <inactive+lschmitt@transifex.com>, 2016
|
||||
# Lucas Deliege <lud@odoo.com>, 2016
|
||||
# Clo <clo@odoo.com>, 2016
|
||||
# lucasdeliege <deliegelucas@gmail.com>, 2016
|
||||
# Miguel Vidali <mvidali129@gmail.com>, 2016
|
||||
# Olivier Lenoir <olivier.lenoir@free.fr>, 2016
|
||||
# Benedicte HANET <hanetb@gmail.com>, 2016
|
||||
# Maxime Chambreuil <mchambreuil@ursainfosystems.com>, 2016
|
||||
# Adriana Ierfino <adriana.ierfino@savoirfairelinux.com>, 2016
|
||||
# Xavier Symons <xsy@openerp.com>, 2016
|
||||
# Tony Galmiche <tony.galmiche@infosaone.com>, 2016
|
||||
# Nicolas JEUDY <nicolas@sudokeys.com>, 2016
|
||||
# Nancy Bolognesi <nb@microcom.ca>, 2016
|
||||
# Frédéric LIETART <stuff@tifred.fr>, 2016
|
||||
# Fabien Pinckaers <fp@openerp.com>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~14\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-02-15 09:49+0000\n"
|
||||
"PO-Revision-Date: 2017-02-15 09:49+0000\n"
|
||||
"Last-Translator: Fabien Pinckaers <fp@openerp.com>, 2016\n"
|
||||
"Language-Team: French (https://www.transifex.com/odoo/teams/41243/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid ""
|
||||
" * The 'Open' status is used when a user is encoding a new and unconfirmed timesheet. \n"
|
||||
"* The 'Waiting Approval' status is used to confirm the timesheet by user. \n"
|
||||
"* The 'Approved' status is used when the users timesheet is accepted by his/her senior."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:33
|
||||
#, python-format
|
||||
msgid "Add a Line"
|
||||
msgstr "Ajouter une ligne"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_account_analytic_line
|
||||
msgid "Analytic Line"
|
||||
msgstr "Ligne analytique"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_ids
|
||||
msgid "Analytic accounts"
|
||||
msgstr "Compte analytique"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Approve"
|
||||
msgstr "Approuver"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Approved"
|
||||
msgstr "Confirmé par responsable"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:137
|
||||
#, python-format
|
||||
msgid "Cannot approve a non-submitted timesheet."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:51
|
||||
#, python-format
|
||||
msgid "Click to add projects, contracts or analytic accounts."
|
||||
msgstr ""
|
||||
"Cliquez pour ajouter des projets, des contrats ou des comptes analytiques."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_res_company
|
||||
msgid "Companies"
|
||||
msgstr "Sociétés"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_company_id
|
||||
msgid "Company"
|
||||
msgstr "Société"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Confirmed Timesheets"
|
||||
msgstr "Feuilles de temps confirmées"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Créé par"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_date
|
||||
msgid "Created on"
|
||||
msgstr "Créé le"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_from
|
||||
msgid "Date From"
|
||||
msgstr "Date début"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_to
|
||||
msgid "Date To"
|
||||
msgstr "Date de fin"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_department_id
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Department"
|
||||
msgstr "Département"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Details"
|
||||
msgstr "Détails"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Afficher le nom"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_employee
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_employee_id
|
||||
msgid "Employee"
|
||||
msgstr "Employé"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Employee's Name"
|
||||
msgstr "Nom de l'employé"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Employees"
|
||||
msgstr "Employés"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Extra features"
|
||||
msgstr "Fonctionnalités supplémentaires"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Group By"
|
||||
msgstr "Regrouper par"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_department
|
||||
msgid "HR Department"
|
||||
msgstr "Département RH"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Hours"
|
||||
msgstr "Heures"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "In Draft"
|
||||
msgstr "A l'état \"Brouillon\""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:103
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:113
|
||||
#, python-format
|
||||
msgid ""
|
||||
"In order to create a timesheet for this employee, you must link him/her to a"
|
||||
" user."
|
||||
msgstr ""
|
||||
"Pour créer une feuille de temps pour cet employé, vous devez lier l'employé "
|
||||
"à un utilisateur."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account___last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Dernière Modification le"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Dernière mise à jour par"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Dernière mise à jour le"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Month"
|
||||
msgstr "Mois"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "More Info"
|
||||
msgstr "Plus d'infos"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form_my_current
|
||||
msgid "My Current Timesheet"
|
||||
msgstr "Ma Feuille de Présence actuelle"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "My Timesheets"
|
||||
msgstr "Mes feuilles de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "New"
|
||||
msgstr "Nouveau"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
msgid "New timesheet to approve."
|
||||
msgstr "Nouvelle feuille de temps à approuver."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_name
|
||||
msgid "Note"
|
||||
msgstr "Note"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:135
|
||||
#, python-format
|
||||
msgid "Only an HR Officer or Manager can approve timesheets."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:120
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only an HR Officer or Manager can refuse timesheets or reset them to draft."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "Open"
|
||||
msgstr "Ouvertes"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:26
|
||||
#, python-format
|
||||
msgid "Open Timesheet"
|
||||
msgstr "Ouvrir les feuilles de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Periodicity on which you validate your timesheets."
|
||||
msgstr "Période pour laquelle vous pouvez valider vos feuille de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_name
|
||||
msgid "Project / Analytic Account"
|
||||
msgstr "Projet /Compte analytique"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Refuse"
|
||||
msgstr "Refuser"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "Related user name for the resource to manage its access."
|
||||
msgstr "Utilisateur associé à la ressource pour gérer les droits d'accès."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_filter
|
||||
msgid "Search Account"
|
||||
msgstr "Recherche de comptes"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Search Timesheet"
|
||||
msgstr "Rechercher dans les feuilles de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Set to Draft"
|
||||
msgstr "Marquer comme brouillon"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id_computed
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_sheet_id
|
||||
msgid "Sheet"
|
||||
msgstr "Feuille"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid "Status"
|
||||
msgstr "Statut"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Submit to Manager"
|
||||
msgstr "Soumettre au responsable"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Summary"
|
||||
msgstr "Résumé"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet"
|
||||
msgstr "Feuille de présence"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Activities"
|
||||
msgstr "Activités de la feuille de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Period"
|
||||
msgstr "Période des feuilles de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.server,name:hr_timesheet_sheet.ir_actions_server_timesheet_sheet
|
||||
msgid "Timesheet Sheet: My Timesheet"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_module_project_timesheet_synchro
|
||||
msgid "Timesheet app for Chrome/Android/iOS"
|
||||
msgstr "Application Feuille de temps pour Chrome/Android/iOS"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Timesheet approved"
|
||||
msgstr "Feuille de temps approuvée"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
msgid "Timesheet by Accounts"
|
||||
msgstr "Feuille de temps par compte"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_timesheet_ids
|
||||
msgid "Timesheet lines"
|
||||
msgstr "Lignes des feuilles de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Timesheet range"
|
||||
msgstr "Plage de la feuille de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
msgid "Timesheet range *"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_department_timesheet_to_approve_count
|
||||
msgid "Timesheet to Approve"
|
||||
msgstr "Feuille de temps à approuver"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_employee_2_hr_timesheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_employee_timesheet_count
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_department_view_kanban
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_employee_extd_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_tree_simplified
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Timesheets"
|
||||
msgstr "Feuilles de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_approved
|
||||
msgid "Timesheets Approved"
|
||||
msgstr "Feuilles de temps approuvées"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet_account
|
||||
msgid "Timesheets by Period"
|
||||
msgstr "Feuilles de temps par période"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"Timesheets can also be invoiced to customers, depending on the\n"
|
||||
" configuration of each project's related contract."
|
||||
msgstr ""
|
||||
"Les feuilles de temps peuvent être facturées au client, en fonction\n"
|
||||
"de la configuration de chaque projet / contrat."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.hr_timesheet_action_from_department
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_confirmed
|
||||
msgid "Timesheets to Approve"
|
||||
msgstr "Feuilles de temps à approuver"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_hr_to_approve
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "To Approve"
|
||||
msgstr "À approuver"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:14
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:36
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
#, python-format
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_total
|
||||
msgid "Total Time"
|
||||
msgstr "Temps total"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unread Messages"
|
||||
msgstr "Messages non lus"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unvalidated Timesheets"
|
||||
msgstr "Feuilles de temps non validées"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "Waiting Approval"
|
||||
msgstr "En attente d'approbation"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Week"
|
||||
msgstr "Semaine"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:143
|
||||
#, python-format
|
||||
msgid "Week "
|
||||
msgstr "Semaine "
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:151
|
||||
#, python-format
|
||||
msgid "You cannot delete a timesheet which is already confirmed."
|
||||
msgstr ""
|
||||
"Vous ne pouvez pas supprimer des feuilles de temps qui sont déjà confirmées."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:97
|
||||
#, python-format
|
||||
msgid "You cannot duplicate a timesheet."
|
||||
msgstr "Vous ne pouvez pas dupliquer une feuille de temps."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/account_analytic_line.py:60
|
||||
#, python-format
|
||||
msgid "You cannot modify an entry in a confirmed timesheet."
|
||||
msgstr ""
|
||||
"Vous ne pouvez pas modifier une entrée dans une feuille de temps confirmée."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"You must record timesheets every day and confirm at the end\n"
|
||||
" of the week. Once the timesheet is confirmed, it should be\n"
|
||||
" validated by a manager."
|
||||
msgstr ""
|
||||
"Vous devez remplir votre feuille de temps chaque jour, et les confirmer\n"
|
||||
"à la fin de la semaine. Une fois la feuille confirmée, elle doit être\n"
|
||||
"validée par un gestionnaire."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:52
|
||||
#, python-format
|
||||
msgid "You will be able to register your working hours and activities."
|
||||
msgstr ""
|
||||
"Vous serez en mesure d'enregistrer vos heures de travail et vos activités."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_current_open
|
||||
msgid "hr.timesheet.current.open"
|
||||
msgstr "hr.timesheet.current.open"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "new timesheet."
|
||||
msgstr "nouvelle feuille de temps"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_project_config_settings
|
||||
msgid "project.config.settings"
|
||||
msgstr "project.config.settings"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "to"
|
||||
msgstr "au"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "waiting approval"
|
||||
msgstr "En attente d'approbation"
|
||||
@@ -1,542 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * hr_timesheet_sheet
|
||||
#
|
||||
# Translators:
|
||||
# Martin Trigaux <mat@odoo.com>, 2016
|
||||
# krnkris <krnkris@freemail.hu>, 2016
|
||||
# Oregional <geza.nagy@oregional.hu>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~14\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-02-15 09:49+0000\n"
|
||||
"PO-Revision-Date: 2017-02-15 09:49+0000\n"
|
||||
"Last-Translator: Oregional <geza.nagy@oregional.hu>, 2016\n"
|
||||
"Language-Team: Hungarian (https://www.transifex.com/odoo/teams/41243/hu/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: hu\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid ""
|
||||
" * The 'Open' status is used when a user is encoding a new and unconfirmed timesheet. \n"
|
||||
"* The 'Waiting Approval' status is used to confirm the timesheet by user. \n"
|
||||
"* The 'Approved' status is used when the users timesheet is accepted by his/her senior."
|
||||
msgstr ""
|
||||
" * A 'Nyitott' állapotot használja, ha a felhasználó olyan új időkimutatást táplál be ami még nincs visszaigazolva. \n"
|
||||
"* A 'Jóváhagyásra vár' állapotot használja, ha egy felhasználó visszaigazolta az időkimutatást. \n"
|
||||
"* Az 'Jóváhagyott' állapotot használja, ha a felhasználó időkimutatását elfogadta egy felettese."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:33
|
||||
#, python-format
|
||||
msgid "Add a Line"
|
||||
msgstr "Sor hozzáadása"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_account_analytic_line
|
||||
msgid "Analytic Line"
|
||||
msgstr "Analitikus/elemző tétel"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_ids
|
||||
msgid "Analytic accounts"
|
||||
msgstr "Gyűjtőkódok"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Approve"
|
||||
msgstr "Jóváhagyás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Approved"
|
||||
msgstr "Jóváhagyott"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:137
|
||||
#, python-format
|
||||
msgid "Cannot approve a non-submitted timesheet."
|
||||
msgstr "Nem lehet jóváhagyni egy beadatlan munkaidő-kiosztást."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:51
|
||||
#, python-format
|
||||
msgid "Click to add projects, contracts or analytic accounts."
|
||||
msgstr "Kattintson projekt, szerződések vagy elemző számlák hozzáadásához."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_res_company
|
||||
msgid "Companies"
|
||||
msgstr "Vállalatok"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_company_id
|
||||
msgid "Company"
|
||||
msgstr "Vállalat"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Confirmed Timesheets"
|
||||
msgstr "Jóváhagyott munkaidő kimutatások"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Készítette"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_date
|
||||
msgid "Created on"
|
||||
msgstr "Létrehozás dátuma"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_from
|
||||
msgid "Date From"
|
||||
msgstr "Dátumtól"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_to
|
||||
msgid "Date To"
|
||||
msgstr "Dátumig"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_department_id
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Department"
|
||||
msgstr "Osztály, részleg"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Details"
|
||||
msgstr "Részletek"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Megjelenített név"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_employee
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_employee_id
|
||||
msgid "Employee"
|
||||
msgstr "Alkalmazott"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Employee's Name"
|
||||
msgstr "Alkalmazott neve"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Employees"
|
||||
msgstr "Alkalmazottak"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Extra features"
|
||||
msgstr "Extra jellemzők"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Group By"
|
||||
msgstr "Csoportosítás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_department
|
||||
msgid "HR Department"
|
||||
msgstr "Emberi erőforrás Osztály, részleg"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Hours"
|
||||
msgstr "Órák"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_id
|
||||
msgid "ID"
|
||||
msgstr "Azonosító ID"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "In Draft"
|
||||
msgstr "Tervezet"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:103
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:113
|
||||
#, python-format
|
||||
msgid ""
|
||||
"In order to create a timesheet for this employee, you must link him/her to a"
|
||||
" user."
|
||||
msgstr ""
|
||||
"Ahhoz, hogy munkaidőbeosztás tudjon ennek a munkavállalónak készíteni, "
|
||||
"előszöz hozzá kell rendelnie egy felhasználóhoz."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account___last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Utoljára frissítve ekkor"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Utoljára frissítette"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Utoljára frissítve "
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Month"
|
||||
msgstr "Hónap"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "More Info"
|
||||
msgstr "További információ"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form_my_current
|
||||
msgid "My Current Timesheet"
|
||||
msgstr "Az én jelenlegi időkimutatásom"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "My Timesheets"
|
||||
msgstr "Munkaidő-kiosztásaim"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "New"
|
||||
msgstr "Új"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
msgid "New timesheet to approve."
|
||||
msgstr "Új, elfogadásra váró munkaidő-kiosztás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_name
|
||||
msgid "Note"
|
||||
msgstr "Jegyzet"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:135
|
||||
#, python-format
|
||||
msgid "Only an HR Officer or Manager can approve timesheets."
|
||||
msgstr ""
|
||||
"Csak egy HR emberi erőforrás hivatalnok vagy vezető tud jóváhagyni munkaidő-"
|
||||
"kiosztásokat."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:120
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only an HR Officer or Manager can refuse timesheets or reset them to draft."
|
||||
msgstr ""
|
||||
"Csak egy HR emberi erőforrás hivatalnok vagy vezető tud elutasítani vagy "
|
||||
"visszaállítani tervezetnek munkaidő-kiosztásokat."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "Open"
|
||||
msgstr "Megnyitás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:26
|
||||
#, python-format
|
||||
msgid "Open Timesheet"
|
||||
msgstr "Munkaidő-kiosztás megnyitása"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Periodicity on which you validate your timesheets."
|
||||
msgstr "Periódus, melyen az időkimutatásait érvényesíti."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_name
|
||||
msgid "Project / Analytic Account"
|
||||
msgstr "Projekt /Gyűjtőkód"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Refuse"
|
||||
msgstr "Elutasít"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "Related user name for the resource to manage its access."
|
||||
msgstr ""
|
||||
"Az erőforráshoz kapcsolódó felhasználó neve, aki kezeli a hozzáférést az "
|
||||
"erőforráshoz."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_filter
|
||||
msgid "Search Account"
|
||||
msgstr "Felhasználó keresés"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Search Timesheet"
|
||||
msgstr "Munkaidő-kiosztás keresése"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Set to Draft"
|
||||
msgstr "Beállítás tervezetnek"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id_computed
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_sheet_id
|
||||
msgid "Sheet"
|
||||
msgstr "Táblázat"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid "Status"
|
||||
msgstr "Állapot"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Submit to Manager"
|
||||
msgstr "Felső vezetőnek rendelkezésére"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Summary"
|
||||
msgstr "Összegzés"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet"
|
||||
msgstr "Munkaidő-kiosztás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Activities"
|
||||
msgstr "Időkimutatás tevékenység"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Period"
|
||||
msgstr "Időkimutatás időszak"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.server,name:hr_timesheet_sheet.ir_actions_server_timesheet_sheet
|
||||
msgid "Timesheet Sheet: My Timesheet"
|
||||
msgstr "Időkimutatás: Én időkimutatásaim"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_module_project_timesheet_synchro
|
||||
msgid "Timesheet app for Chrome/Android/iOS"
|
||||
msgstr "Időbeosztás alkalmazás Chrome/Android/iOS eszközökhöz"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Timesheet approved"
|
||||
msgstr "Elfogadott munkaidő-kiosztás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
msgid "Timesheet by Accounts"
|
||||
msgstr "Gyűjtőkódok szerinti munkaidő-kiosztás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_timesheet_ids
|
||||
msgid "Timesheet lines"
|
||||
msgstr "Munkaidő-kiosztás sorok"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Timesheet range"
|
||||
msgstr "Munkaidő-kiosztás időintervalluma"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
msgid "Timesheet range *"
|
||||
msgstr "Munkaidő-kiosztás időintervalluma *"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_department_timesheet_to_approve_count
|
||||
msgid "Timesheet to Approve"
|
||||
msgstr "Elfogadásra váró munkaidő-kiosztás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_employee_2_hr_timesheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_employee_timesheet_count
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_department_view_kanban
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_employee_extd_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_tree_simplified
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Timesheets"
|
||||
msgstr "Munkaidő-beosztás"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_approved
|
||||
msgid "Timesheets Approved"
|
||||
msgstr "Elfogadott munkaidő-kiosztások"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet_account
|
||||
msgid "Timesheets by Period"
|
||||
msgstr "Időszak szerinti munkaidő-kiosztások"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"Timesheets can also be invoiced to customers, depending on the\n"
|
||||
" configuration of each project's related contract."
|
||||
msgstr ""
|
||||
"Munkaidő kimutatást az ügyfeleknek is lehet számlázni, attól \n"
|
||||
" függően, hogyan lett minden egyes projekt beállítva a vonatkozó szerződéshez."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.hr_timesheet_action_from_department
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_confirmed
|
||||
msgid "Timesheets to Approve"
|
||||
msgstr "Elfogadásra váró munkaidő-kiosztások"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_hr_to_approve
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "To Approve"
|
||||
msgstr "Jóváhagyandó"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:14
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:36
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
#, python-format
|
||||
msgid "Total"
|
||||
msgstr "Összesen"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_total
|
||||
msgid "Total Time"
|
||||
msgstr "Összes idő"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unread Messages"
|
||||
msgstr "Olvasatlan üzenetek"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unvalidated Timesheets"
|
||||
msgstr "Nem jóváhagyott munkaidő-kiosztások"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "User"
|
||||
msgstr "Felhasználó"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "Waiting Approval"
|
||||
msgstr "Jóváhagyásra vár"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Week"
|
||||
msgstr "Hét"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:143
|
||||
#, python-format
|
||||
msgid "Week "
|
||||
msgstr "Hét "
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:151
|
||||
#, python-format
|
||||
msgid "You cannot delete a timesheet which is already confirmed."
|
||||
msgstr "Nem törölhet olyan időkimutatást, mely már visszaigazolt."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:97
|
||||
#, python-format
|
||||
msgid "You cannot duplicate a timesheet."
|
||||
msgstr "Nem többszörözhet egy időkimutatást."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/account_analytic_line.py:60
|
||||
#, python-format
|
||||
msgid "You cannot modify an entry in a confirmed timesheet."
|
||||
msgstr "Nem tud módosítani a visszaigazolt időkimutatáson."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"You must record timesheets every day and confirm at the end\n"
|
||||
" of the week. Once the timesheet is confirmed, it should be\n"
|
||||
" validated by a manager."
|
||||
msgstr ""
|
||||
"Rögzítenie kell a munkaóra kimutatásait minden nap és le kell jelentenie\n"
|
||||
" a hét végén. Ha a munkaidő-kiosztás lejelentett és elfogadott\n"
|
||||
" akkor a főnökének le kell igazolnia."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:52
|
||||
#, python-format
|
||||
msgid "You will be able to register your working hours and activities."
|
||||
msgstr ""
|
||||
"Lehetősége lesz a munka órái és a tevékenységei\n"
|
||||
" regisztrálására."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_current_open
|
||||
msgid "hr.timesheet.current.open"
|
||||
msgstr "hr.timesheet.current.open"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "new timesheet."
|
||||
msgstr "új munkaidő-kiosztás."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_project_config_settings
|
||||
msgid "project.config.settings"
|
||||
msgstr "project.config.settings"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "to"
|
||||
msgstr "ig"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "waiting approval"
|
||||
msgstr "Jóváhagyásra váró"
|
||||
@@ -1,540 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * hr_timesheet_sheet
|
||||
#
|
||||
# Translators:
|
||||
# Yenthe Van Ginneken <yenthespam@gmail.com>, 2016
|
||||
# Eric Geens <ericgeens@yahoo.com>, 2016
|
||||
# Martin Trigaux <mat@odoo.com>, 2016
|
||||
# Erwin van der Ploeg <erwin@odooexperts.nl>, 2016
|
||||
# Cas Vissers <casvissers@brahoo.nl>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~14\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-02-15 09:49+0000\n"
|
||||
"PO-Revision-Date: 2017-02-15 09:49+0000\n"
|
||||
"Last-Translator: Cas Vissers <casvissers@brahoo.nl>, 2017\n"
|
||||
"Language-Team: Dutch (https://www.transifex.com/odoo/teams/41243/nl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: nl\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid ""
|
||||
" * The 'Open' status is used when a user is encoding a new and unconfirmed timesheet. \n"
|
||||
"* The 'Waiting Approval' status is used to confirm the timesheet by user. \n"
|
||||
"* The 'Approved' status is used when the users timesheet is accepted by his/her senior."
|
||||
msgstr ""
|
||||
"* De 'Open' status wordt gebruikt als de gebruiker een nieuwe urenstaat invult.\n"
|
||||
"* De 'Wacht op goedkeuring' wordt gebruikt door de gebruiker om de urenstaat te bevestigen. \n"
|
||||
"* De 'Goedgekeurd' status wordt gebruikt als de urenstaat is goedgekeurd door zijn/haar manager."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:33
|
||||
#, python-format
|
||||
msgid "Add a Line"
|
||||
msgstr "Regel toevoegen"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_account_analytic_line
|
||||
msgid "Analytic Line"
|
||||
msgstr "Kostenplaatsregel"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_ids
|
||||
msgid "Analytic accounts"
|
||||
msgstr "Kostenplaatsen"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Approve"
|
||||
msgstr "Goedkeuren"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Approved"
|
||||
msgstr "Goedgekeurd"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:137
|
||||
#, python-format
|
||||
msgid "Cannot approve a non-submitted timesheet."
|
||||
msgstr "Een niet ingediende urenstaat kan niet goedgekeurd worden."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:51
|
||||
#, python-format
|
||||
msgid "Click to add projects, contracts or analytic accounts."
|
||||
msgstr "Klik om projecten, contracten of kostenplaatsen toe te voegen."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_res_company
|
||||
msgid "Companies"
|
||||
msgstr "Bedrijven"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_company_id
|
||||
msgid "Company"
|
||||
msgstr "Bedrijf"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Confirmed Timesheets"
|
||||
msgstr "Bevestigde urenstaten"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Aangemaakt door"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_date
|
||||
msgid "Created on"
|
||||
msgstr "Aangemaakt op"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_from
|
||||
msgid "Date From"
|
||||
msgstr "Datum vanaf"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_to
|
||||
msgid "Date To"
|
||||
msgstr "Datum tot"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_department_id
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Department"
|
||||
msgstr "Afdeling"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Details"
|
||||
msgstr "Details"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Schermnaam"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_employee
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_employee_id
|
||||
msgid "Employee"
|
||||
msgstr "Werknemer"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Employee's Name"
|
||||
msgstr "Naam werknemer"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Employees"
|
||||
msgstr "Werknemers"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Extra features"
|
||||
msgstr "Extra opties"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Group By"
|
||||
msgstr "Groepeer op"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_department
|
||||
msgid "HR Department"
|
||||
msgstr "HR afdeling"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Hours"
|
||||
msgstr "Uren"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "In Draft"
|
||||
msgstr "In concept"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:103
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:113
|
||||
#, python-format
|
||||
msgid ""
|
||||
"In order to create a timesheet for this employee, you must link him/her to a"
|
||||
" user."
|
||||
msgstr ""
|
||||
"Om een tijdschema voor deze medewerker aan te maken, moet u hem/haar eerst "
|
||||
"linken aan een gebruiker."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account___last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Laatst gewijzigd op"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Laatst bijgewerkt door"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Laatst bijgewerkt op"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Month"
|
||||
msgstr "Maand"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "More Info"
|
||||
msgstr "Meer info"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form_my_current
|
||||
msgid "My Current Timesheet"
|
||||
msgstr "Mijn huidige urenstaat"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "My Timesheets"
|
||||
msgstr "Mijn urenstaten"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "New"
|
||||
msgstr "Nieuw"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
msgid "New timesheet to approve."
|
||||
msgstr "Nieuwe urenstaat goed te keuren."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_name
|
||||
msgid "Note"
|
||||
msgstr "Opmerking"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:135
|
||||
#, python-format
|
||||
msgid "Only an HR Officer or Manager can approve timesheets."
|
||||
msgstr "Alleen een HR functionaris of manager kan urenstaten goedkeuren."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:120
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only an HR Officer or Manager can refuse timesheets or reset them to draft."
|
||||
msgstr ""
|
||||
"Alleen een HR functionaris of manager kan urenstaten weigeren of terugzetten"
|
||||
" naar concept."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "Open"
|
||||
msgstr "Open"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:26
|
||||
#, python-format
|
||||
msgid "Open Timesheet"
|
||||
msgstr "Open urenstaat"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Periodicity on which you validate your timesheets."
|
||||
msgstr "Frequentie waarmee u de urenstaten controleert."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_name
|
||||
msgid "Project / Analytic Account"
|
||||
msgstr "Project / Kostenplaats"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Refuse"
|
||||
msgstr "Weiger"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "Related user name for the resource to manage its access."
|
||||
msgstr ""
|
||||
"Gekoppelde gebruikersnaam voor de resource om zijn toegang te beheren."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_filter
|
||||
msgid "Search Account"
|
||||
msgstr "Rekening zoeken"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Search Timesheet"
|
||||
msgstr "Urenstaat zoeken"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Set to Draft"
|
||||
msgstr "Zet op concept"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id_computed
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_sheet_id
|
||||
msgid "Sheet"
|
||||
msgstr "Formulier"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid "Status"
|
||||
msgstr "Fase"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Submit to Manager"
|
||||
msgstr "Aanbieden aan manager"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Summary"
|
||||
msgstr "Samenvatting"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet"
|
||||
msgstr "Urenstaat"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Activities"
|
||||
msgstr "Urenstaat activiteiten"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Period"
|
||||
msgstr "Urenstaten periode"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.server,name:hr_timesheet_sheet.ir_actions_server_timesheet_sheet
|
||||
msgid "Timesheet Sheet: My Timesheet"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_module_project_timesheet_synchro
|
||||
msgid "Timesheet app for Chrome/Android/iOS"
|
||||
msgstr "Urenstaat app voor Chrome/android/iOS"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Timesheet approved"
|
||||
msgstr "Urenstaat goedgekeurd"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
msgid "Timesheet by Accounts"
|
||||
msgstr "Urenstaat per kostenplaats"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_timesheet_ids
|
||||
msgid "Timesheet lines"
|
||||
msgstr "Urenstaat regels"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Timesheet range"
|
||||
msgstr "Urenstaat bereik"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
msgid "Timesheet range *"
|
||||
msgstr "Urenstaat bereik *"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_department_timesheet_to_approve_count
|
||||
msgid "Timesheet to Approve"
|
||||
msgstr "Urenstaat goed te keuren"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_employee_2_hr_timesheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_employee_timesheet_count
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_department_view_kanban
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_employee_extd_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_tree_simplified
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Timesheets"
|
||||
msgstr "Urenstaten"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_approved
|
||||
msgid "Timesheets Approved"
|
||||
msgstr "Goedgekeurde urenstaten"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet_account
|
||||
msgid "Timesheets by Period"
|
||||
msgstr "Urenstaten per periode"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"Timesheets can also be invoiced to customers, depending on the\n"
|
||||
" configuration of each project's related contract."
|
||||
msgstr ""
|
||||
"Urenstaten kunnen ook aan klanten gefactureerd worden, afhankelijk van\n"
|
||||
"hoe het contract voor elk project opgemaakt werd."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.hr_timesheet_action_from_department
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_confirmed
|
||||
msgid "Timesheets to Approve"
|
||||
msgstr "Urenstaten goed te keuren"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_hr_to_approve
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "To Approve"
|
||||
msgstr "Goed te keuren"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:14
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:36
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
#, python-format
|
||||
msgid "Total"
|
||||
msgstr "Totaal"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_total
|
||||
msgid "Total Time"
|
||||
msgstr "Totale tijd"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unread Messages"
|
||||
msgstr "Ongelezen berichten"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unvalidated Timesheets"
|
||||
msgstr "Niet gevalideerde urenstaten"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "User"
|
||||
msgstr "Gebruiker"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "Waiting Approval"
|
||||
msgstr "Wachtend op goedkeuring"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Week"
|
||||
msgstr "Week"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:143
|
||||
#, python-format
|
||||
msgid "Week "
|
||||
msgstr "Week"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:151
|
||||
#, python-format
|
||||
msgid "You cannot delete a timesheet which is already confirmed."
|
||||
msgstr "U kunt geen urenstaat verwijderen welke al is bevestigd"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:97
|
||||
#, python-format
|
||||
msgid "You cannot duplicate a timesheet."
|
||||
msgstr "Het is niet mogelijk een urenstaat te dupliceren."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/account_analytic_line.py:60
|
||||
#, python-format
|
||||
msgid "You cannot modify an entry in a confirmed timesheet."
|
||||
msgstr "U kunt geen wijziging doen in een geaccordeerde tijdsregistratie."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"You must record timesheets every day and confirm at the end\n"
|
||||
" of the week. Once the timesheet is confirmed, it should be\n"
|
||||
" validated by a manager."
|
||||
msgstr ""
|
||||
"U moet uw urenstaat elke dag invullen en bevestigen op het einde\n"
|
||||
"van de week. Eenmaal de urenstaat bevestigd is, moet hij gevalideerd\n"
|
||||
"worden door een manager."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:52
|
||||
#, python-format
|
||||
msgid "You will be able to register your working hours and activities."
|
||||
msgstr ""
|
||||
"U heeft de mogelijkheid om uw werkuren en activiteiten te registreren."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_current_open
|
||||
msgid "hr.timesheet.current.open"
|
||||
msgstr "hr.timesheet.current.open"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "new timesheet."
|
||||
msgstr "nieuwe urenstaat."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_project_config_settings
|
||||
msgid "project.config.settings"
|
||||
msgstr "project.config.settings"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "to"
|
||||
msgstr "tot"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "waiting approval"
|
||||
msgstr "wachtend op goedkeuring"
|
||||
@@ -1,535 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * hr_timesheet_sheet
|
||||
#
|
||||
# Translators:
|
||||
# Mateus Lopes <mateus1@gmail.com>, 2016
|
||||
# grazziano <gra.negocia@gmail.com>, 2016
|
||||
# Rodrigo Macedo <rmsolucoeseminformatic4@gmail.com>, 2016
|
||||
# Martin Trigaux <mat@odoo.com>, 2016
|
||||
# Wagner Pereira <wagner@wagner.pereira.nom.br>, 2016
|
||||
# danimaribeiro <danimaribeiro@gmail.com>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~14\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-02-15 09:49+0000\n"
|
||||
"PO-Revision-Date: 2017-02-15 09:49+0000\n"
|
||||
"Last-Translator: danimaribeiro <danimaribeiro@gmail.com>, 2016\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"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: pt_BR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid ""
|
||||
" * The 'Open' status is used when a user is encoding a new and unconfirmed timesheet. \n"
|
||||
"* The 'Waiting Approval' status is used to confirm the timesheet by user. \n"
|
||||
"* The 'Approved' status is used when the users timesheet is accepted by his/her senior."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:33
|
||||
#, python-format
|
||||
msgid "Add a Line"
|
||||
msgstr "Adicionar uma linha"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_account_analytic_line
|
||||
msgid "Analytic Line"
|
||||
msgstr "Linha Analítica"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_ids
|
||||
msgid "Analytic accounts"
|
||||
msgstr "Contas analíticas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Approve"
|
||||
msgstr "Aprovar"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Approved"
|
||||
msgstr "Aprovada"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:137
|
||||
#, python-format
|
||||
msgid "Cannot approve a non-submitted timesheet."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:51
|
||||
#, python-format
|
||||
msgid "Click to add projects, contracts or analytic accounts."
|
||||
msgstr "Clique para adicionar projetos, contratos ou contas analíticas."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_res_company
|
||||
msgid "Companies"
|
||||
msgstr "Empresas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_company_id
|
||||
msgid "Company"
|
||||
msgstr "Empresa"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Confirmed Timesheets"
|
||||
msgstr "Folha de Horas Confirmadas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Criado por"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_date
|
||||
msgid "Created on"
|
||||
msgstr "Criado em"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_from
|
||||
msgid "Date From"
|
||||
msgstr "Data de"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_to
|
||||
msgid "Date To"
|
||||
msgstr "Dados do Pará"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_department_id
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Department"
|
||||
msgstr "Departamento"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Details"
|
||||
msgstr "Detalhes"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Nome para Exibição"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_employee
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_employee_id
|
||||
msgid "Employee"
|
||||
msgstr "Funcionário"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Employee's Name"
|
||||
msgstr "Nome do Funcionário"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Employees"
|
||||
msgstr "Funcionários"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Extra features"
|
||||
msgstr "Recursos Extras"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Group By"
|
||||
msgstr "Agrupar Por"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_department
|
||||
msgid "HR Department"
|
||||
msgstr "Departamento de RH"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Hours"
|
||||
msgstr "Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "In Draft"
|
||||
msgstr "Provisório"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:103
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:113
|
||||
#, python-format
|
||||
msgid ""
|
||||
"In order to create a timesheet for this employee, you must link him/her to a"
|
||||
" user."
|
||||
msgstr ""
|
||||
"Para criar uma planilha de horas para este funcionário, você precisa "
|
||||
"conectar ele(a) a um usuário."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account___last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Última Modificação em"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Última atualização por"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Última atualização em"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Month"
|
||||
msgstr "Mês"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "More Info"
|
||||
msgstr "Mais Informação"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form_my_current
|
||||
msgid "My Current Timesheet"
|
||||
msgstr "Minha Planilha de Horas Atual"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "My Timesheets"
|
||||
msgstr "Minhas Planilhas de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "New"
|
||||
msgstr "Novo"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
msgid "New timesheet to approve."
|
||||
msgstr "Novo quadro de horários para aprovar."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_name
|
||||
msgid "Note"
|
||||
msgstr "Nota"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:135
|
||||
#, python-format
|
||||
msgid "Only an HR Officer or Manager can approve timesheets."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:120
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only an HR Officer or Manager can refuse timesheets or reset them to draft."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "Open"
|
||||
msgstr "Aberto"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:26
|
||||
#, python-format
|
||||
msgid "Open Timesheet"
|
||||
msgstr "Abrir Folha de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Periodicity on which you validate your timesheets."
|
||||
msgstr "Periodicidade em que você valida suas Folhas de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_name
|
||||
msgid "Project / Analytic Account"
|
||||
msgstr "Projeto / Conta Analítica"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Refuse"
|
||||
msgstr "Recusar"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "Related user name for the resource to manage its access."
|
||||
msgstr "Usuário relacionado para o gerente controlar seus acessos"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_filter
|
||||
msgid "Search Account"
|
||||
msgstr "Buscar Conta"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Search Timesheet"
|
||||
msgstr "Procurar Folha de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Set to Draft"
|
||||
msgstr "Definir como Provisório"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id_computed
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_sheet_id
|
||||
msgid "Sheet"
|
||||
msgstr "Planilha"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid "Status"
|
||||
msgstr "Situação"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Submit to Manager"
|
||||
msgstr "Enviar ao Gerente"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Summary"
|
||||
msgstr "Resumo"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet"
|
||||
msgstr "Planilha de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Activities"
|
||||
msgstr "Atividades da Planilha de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Period"
|
||||
msgstr "Período da Planilha de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.server,name:hr_timesheet_sheet.ir_actions_server_timesheet_sheet
|
||||
msgid "Timesheet Sheet: My Timesheet"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_module_project_timesheet_synchro
|
||||
msgid "Timesheet app for Chrome/Android/iOS"
|
||||
msgstr "Aplicativo de Planilha de Horário para Chrome/Android/iOS"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Timesheet approved"
|
||||
msgstr "Quadro de horários aprovado"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
msgid "Timesheet by Accounts"
|
||||
msgstr "Apontamento de horas por Contas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_timesheet_ids
|
||||
msgid "Timesheet lines"
|
||||
msgstr "Linhas do apontamento de horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Timesheet range"
|
||||
msgstr "Faixa da Folha de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
msgid "Timesheet range *"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_department_timesheet_to_approve_count
|
||||
msgid "Timesheet to Approve"
|
||||
msgstr "Quadro de Horários para Aprovar"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_employee_2_hr_timesheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_employee_timesheet_count
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_department_view_kanban
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_employee_extd_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_tree_simplified
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Timesheets"
|
||||
msgstr "Planilhas de Horas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_approved
|
||||
msgid "Timesheets Approved"
|
||||
msgstr "Quadros de Horário Aprovados"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet_account
|
||||
msgid "Timesheets by Period"
|
||||
msgstr "Folha de Horas por Período"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"Timesheets can also be invoiced to customers, depending on the\n"
|
||||
" configuration of each project's related contract."
|
||||
msgstr ""
|
||||
"Quadros de horários também podem ser faturados aos clientes, dependendo da\n"
|
||||
"configuração do contrato relacionado a cada projeto."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.hr_timesheet_action_from_department
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_confirmed
|
||||
msgid "Timesheets to Approve"
|
||||
msgstr "Quadro de Horários para Aprovar"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_hr_to_approve
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "To Approve"
|
||||
msgstr "Para Aprovar"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:14
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:36
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
#, python-format
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_total
|
||||
msgid "Total Time"
|
||||
msgstr "Tempo Total"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unread Messages"
|
||||
msgstr "Mensagens não lidas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unvalidated Timesheets"
|
||||
msgstr "Planilhas de Horas Não Validadas"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "User"
|
||||
msgstr "Usuário"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "Waiting Approval"
|
||||
msgstr "Aguardando Aprovação"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Week"
|
||||
msgstr "Semana"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:143
|
||||
#, python-format
|
||||
msgid "Week "
|
||||
msgstr "Semana "
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:151
|
||||
#, python-format
|
||||
msgid "You cannot delete a timesheet which is already confirmed."
|
||||
msgstr "Você não pode excluir uma planilha de horas que já foi confirmada."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:97
|
||||
#, python-format
|
||||
msgid "You cannot duplicate a timesheet."
|
||||
msgstr "Você não pode duplicar uma planilha de horas."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/account_analytic_line.py:60
|
||||
#, python-format
|
||||
msgid "You cannot modify an entry in a confirmed timesheet."
|
||||
msgstr ""
|
||||
"você não pode modificar um lançamento em uma planilha de horas confirmada."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"You must record timesheets every day and confirm at the end\n"
|
||||
" of the week. Once the timesheet is confirmed, it should be\n"
|
||||
" validated by a manager."
|
||||
msgstr ""
|
||||
"Você deve gravar quadros de horários todos os dias e confirmar no final\n"
|
||||
"da semana. Uma vez que o quadro de horários é confirmado, deve ser\n"
|
||||
"validado por um gerente."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:52
|
||||
#, python-format
|
||||
msgid "You will be able to register your working hours and activities."
|
||||
msgstr "Você será capaz de registrar suas horas de trabalho e atividades."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_current_open
|
||||
msgid "hr.timesheet.current.open"
|
||||
msgstr "hr.timesheet.current.open"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "new timesheet."
|
||||
msgstr "Nova planilha de horas."
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_project_config_settings
|
||||
msgid "project.config.settings"
|
||||
msgstr "project.config.settings"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "to"
|
||||
msgstr "até"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "waiting approval"
|
||||
msgstr "aguardando aprovação"
|
||||
@@ -1,523 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * hr_timesheet_sheet
|
||||
#
|
||||
# Translators:
|
||||
# Jeffery CHEN <jeffery9@gmail.com>, 2016
|
||||
# Martin Trigaux <mat@odoo.com>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.saas~14\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-02-15 09:49+0000\n"
|
||||
"PO-Revision-Date: 2017-02-15 09:49+0000\n"
|
||||
"Last-Translator: Martin Trigaux <mat@odoo.com>, 2017\n"
|
||||
"Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: zh_CN\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid ""
|
||||
" * The 'Open' status is used when a user is encoding a new and unconfirmed timesheet. \n"
|
||||
"* The 'Waiting Approval' status is used to confirm the timesheet by user. \n"
|
||||
"* The 'Approved' status is used when the users timesheet is accepted by his/her senior."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:33
|
||||
#, python-format
|
||||
msgid "Add a Line"
|
||||
msgstr "添加明细"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_account_analytic_line
|
||||
msgid "Analytic Line"
|
||||
msgstr "分析行"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_ids
|
||||
msgid "Analytic accounts"
|
||||
msgstr "分析账户"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Approve"
|
||||
msgstr "批准"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Approved"
|
||||
msgstr "已批准"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:137
|
||||
#, python-format
|
||||
msgid "Cannot approve a non-submitted timesheet."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:51
|
||||
#, python-format
|
||||
msgid "Click to add projects, contracts or analytic accounts."
|
||||
msgstr "点击创建一个项目,合同或分析账户。"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_res_company
|
||||
msgid "Companies"
|
||||
msgstr "公司"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_company_id
|
||||
msgid "Company"
|
||||
msgstr "公司"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Confirmed Timesheets"
|
||||
msgstr "已确认的工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_uid
|
||||
msgid "Created by"
|
||||
msgstr "创建人"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_create_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_create_date
|
||||
msgid "Created on"
|
||||
msgstr "创建时间"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_from
|
||||
msgid "Date From"
|
||||
msgstr "起始日期"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_date_to
|
||||
msgid "Date To"
|
||||
msgstr "结束日期"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_department_id
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Department"
|
||||
msgstr "部门"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Details"
|
||||
msgstr "详细信息"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_display_name
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_display_name
|
||||
msgid "Display Name"
|
||||
msgstr "显示名称"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_employee
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_employee_id
|
||||
msgid "Employee"
|
||||
msgstr "员工"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Employee's Name"
|
||||
msgstr "员工姓名"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Employees"
|
||||
msgstr "员工"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Extra features"
|
||||
msgstr "额外功能"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Group By"
|
||||
msgstr "分组"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_department
|
||||
msgid "HR Department"
|
||||
msgstr "HR 部门"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Hours"
|
||||
msgstr "小时"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "In Draft"
|
||||
msgstr "在草稿"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:103
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:113
|
||||
#, python-format
|
||||
msgid ""
|
||||
"In order to create a timesheet for this employee, you must link him/her to a"
|
||||
" user."
|
||||
msgstr "要为员工创建工时单,您必须关联他/她到一个用户。"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet___last_update
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account___last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "最后修改日"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_uid
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "最后更新人"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_current_open_write_date
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "最后更新时间"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Month"
|
||||
msgstr "月"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "More Info"
|
||||
msgstr "更多信息"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form_my_current
|
||||
msgid "My Current Timesheet"
|
||||
msgstr "我当前的工时表"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "My Timesheets"
|
||||
msgstr "我的工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "New"
|
||||
msgstr "新的"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
msgid "New timesheet to approve."
|
||||
msgstr "待审核的新的工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_name
|
||||
msgid "Note"
|
||||
msgstr "备注"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:135
|
||||
#, python-format
|
||||
msgid "Only an HR Officer or Manager can approve timesheets."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:120
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Only an HR Officer or Manager can refuse timesheets or reset them to draft."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
msgid "Open"
|
||||
msgstr "打开"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:26
|
||||
#, python-format
|
||||
msgid "Open Timesheet"
|
||||
msgstr "开始的工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Periodicity on which you validate your timesheets."
|
||||
msgstr "提交工时单的周期"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_name
|
||||
msgid "Project / Analytic Account"
|
||||
msgstr "项目 / 分析账户"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Refuse"
|
||||
msgstr "拒绝"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,help:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "Related user name for the resource to manage its access."
|
||||
msgstr "用于管理资源访问权限的用户名"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_filter
|
||||
msgid "Search Account"
|
||||
msgstr "搜索账户"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Search Timesheet"
|
||||
msgstr "搜索工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Set to Draft"
|
||||
msgstr "设为草稿"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_account_analytic_line_sheet_id_computed
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_sheet_id
|
||||
msgid "Sheet"
|
||||
msgstr "表"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_state
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Submit to Manager"
|
||||
msgstr "提交给经理"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Summary"
|
||||
msgstr "摘要"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet"
|
||||
msgstr "工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Activities"
|
||||
msgstr "记工单活动"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "Timesheet Period"
|
||||
msgstr "工时单周期"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.server,name:hr_timesheet_sheet.ir_actions_server_timesheet_sheet
|
||||
msgid "Timesheet Sheet: My Timesheet"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_module_project_timesheet_synchro
|
||||
msgid "Timesheet app for Chrome/Android/iOS"
|
||||
msgstr "Chrome/Android/iOS的工时单应用程序"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_approved
|
||||
msgid "Timesheet approved"
|
||||
msgstr "已批准工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
msgid "Timesheet by Accounts"
|
||||
msgstr "计工单 按账户"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_timesheet_ids
|
||||
msgid "Timesheet lines"
|
||||
msgstr "工时单明细"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_res_company_timesheet_range
|
||||
msgid "Timesheet range"
|
||||
msgstr "工时单范围"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_project_config_settings_timesheet_range
|
||||
msgid "Timesheet range *"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_department_timesheet_to_approve_count
|
||||
msgid "Timesheet to Approve"
|
||||
msgstr "待确认工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_employee_2_hr_timesheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_employee_timesheet_count
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_department_view_kanban
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_employee_extd_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_tree_simplified
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_config_settings_form_inherit_hr_timesheet_sheet
|
||||
msgid "Timesheets"
|
||||
msgstr "工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_approved
|
||||
msgid "Timesheets Approved"
|
||||
msgstr "已批准工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_sheet_sheet_account
|
||||
msgid "Timesheets by Period"
|
||||
msgstr "工时单周期"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"Timesheets can also be invoiced to customers, depending on the\n"
|
||||
" configuration of each project's related contract."
|
||||
msgstr "工单也可以开票给客户,根据每个项目的合同配置"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.hr_timesheet_action_from_department
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_department_timesheet_confirmed
|
||||
msgid "Timesheets to Approve"
|
||||
msgstr "待批准的工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.menu,name:hr_timesheet_sheet.menu_hr_to_approve
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "To Approve"
|
||||
msgstr "待批准"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:14
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:36
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_form
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_account_tree
|
||||
#, python-format
|
||||
msgid "Total"
|
||||
msgstr "总计"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_account_total
|
||||
msgid "Total Time"
|
||||
msgstr "时间总计"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unread Messages"
|
||||
msgstr "未读消息"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.view_hr_timesheet_sheet_filter
|
||||
msgid "Unvalidated Timesheets"
|
||||
msgstr "不确认的时间表"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model.fields,field_description:hr_timesheet_sheet.field_hr_timesheet_sheet_sheet_user_id
|
||||
msgid "User"
|
||||
msgstr "用户"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:hr_timesheet_sheet.sheet,state:0
|
||||
#: model:mail.message.subtype,name:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "Waiting Approval"
|
||||
msgstr "等待审批"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: selection:res.company,timesheet_range:0
|
||||
msgid "Week"
|
||||
msgstr "周"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:143
|
||||
#, python-format
|
||||
msgid "Week "
|
||||
msgstr "周"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:151
|
||||
#, python-format
|
||||
msgid "You cannot delete a timesheet which is already confirmed."
|
||||
msgstr "您无法删除一张已确认的工时单。"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/hr_timesheet_sheet.py:97
|
||||
#, python-format
|
||||
msgid "You cannot duplicate a timesheet."
|
||||
msgstr "您无法复制工时单。"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/models/account_analytic_line.py:60
|
||||
#, python-format
|
||||
msgid "You cannot modify an entry in a confirmed timesheet."
|
||||
msgstr "你不能修改已确认的时间表分录"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_form
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid ""
|
||||
"You must record timesheets every day and confirm at the end\n"
|
||||
" of the week. Once the timesheet is confirmed, it should be\n"
|
||||
" validated by a manager."
|
||||
msgstr "必须每天输入工时单并在周末确认,一旦工时单被确认,然后应该被经理审批"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#. openerp-web
|
||||
#: code:addons/hr_timesheet_sheet/static/src/xml/timesheet.xml:52
|
||||
#, python-format
|
||||
msgid "You will be able to register your working hours and activities."
|
||||
msgstr "你将能登记工作时间和活动"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_hr_timesheet_current_open
|
||||
msgid "hr.timesheet.current.open"
|
||||
msgstr "hr.timesheet.current.open"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.actions.act_window,help:hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets
|
||||
msgid "new timesheet."
|
||||
msgstr "新建工时单"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.model,name:hr_timesheet_sheet.model_project_config_settings
|
||||
msgid "project.config.settings"
|
||||
msgstr "项目配置"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:ir.ui.view,arch_db:hr_timesheet_sheet.hr_timesheet_sheet_form
|
||||
msgid "to"
|
||||
msgstr "到"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:mail.message.subtype,description:hr_timesheet_sheet.mt_timesheet_confirmed
|
||||
msgid "waiting approval"
|
||||
msgstr "等待审批"
|
||||
@@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_analytic_line
|
||||
from . import hr_department
|
||||
from . import hr_employee
|
||||
from . import hr_timesheet_sheet
|
||||
from . import res_company
|
||||
@@ -1,207 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.translate import _
|
||||
from odoo.tools.sql import drop_view_if_exists
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class HrTimesheetSheet(models.Model):
|
||||
_name = "hr_timesheet_sheet.sheet"
|
||||
_inherit = ['mail.thread']
|
||||
_table = 'hr_timesheet_sheet_sheet'
|
||||
_order = "id desc"
|
||||
_description = "Timesheet"
|
||||
|
||||
def _default_date_from(self):
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
r = user.company_id and user.company_id.timesheet_range or 'month'
|
||||
if r == 'month':
|
||||
return time.strftime('%Y-%m-01')
|
||||
elif r == 'week':
|
||||
return (datetime.today() + relativedelta(weekday=0, days=-6)).strftime('%Y-%m-%d')
|
||||
elif r == 'year':
|
||||
return time.strftime('%Y-01-01')
|
||||
return fields.Date.context_today(self)
|
||||
|
||||
def _default_date_to(self):
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
r = user.company_id and user.company_id.timesheet_range or 'month'
|
||||
if r == 'month':
|
||||
return (datetime.today() + relativedelta(months=+1, day=1, days=-1)).strftime('%Y-%m-%d')
|
||||
elif r == 'week':
|
||||
return (datetime.today() + relativedelta(weekday=6)).strftime('%Y-%m-%d')
|
||||
elif r == 'year':
|
||||
return time.strftime('%Y-12-31')
|
||||
return fields.Date.context_today(self)
|
||||
|
||||
def _default_employee(self):
|
||||
emp_ids = self.env['hr.employee'].search([('user_id', '=', self.env.uid)])
|
||||
return emp_ids and emp_ids[0] or False
|
||||
|
||||
name = fields.Char(string="Note", states={'confirm': [('readonly', True)], 'done': [('readonly', True)]})
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee, required=True)
|
||||
user_id = fields.Many2one('res.users', related='employee_id.user_id', string='User', store=True, readonly=True)
|
||||
date_from = fields.Date(string='Date From', default=_default_date_from, required=True,
|
||||
index=True, readonly=True, states={'new': [('readonly', False)]})
|
||||
date_to = fields.Date(string='Date To', default=_default_date_to, required=True,
|
||||
index=True, readonly=True, states={'new': [('readonly', False)]})
|
||||
timesheet_ids = fields.One2many('account.analytic.line', 'sheet_id',
|
||||
string='Timesheet lines',
|
||||
readonly=True, states={
|
||||
'draft': [('readonly', False)],
|
||||
'new': [('readonly', False)]})
|
||||
# state is created in 'new', automatically goes to 'draft' when created. Then 'new' is never used again ...
|
||||
# (=> 'new' is completely useless)
|
||||
state = fields.Selection([
|
||||
('new', 'New'),
|
||||
('draft', 'Open'),
|
||||
('confirm', 'Waiting Approval'),
|
||||
('done', 'Approved')], default='new', track_visibility='onchange',
|
||||
string='Status', required=True, readonly=True, index=True,
|
||||
help=' * The \'Open\' status is used when a user is encoding a new and unconfirmed timesheet. '
|
||||
'\n* The \'Waiting Approval\' status is used to confirm the timesheet by user. '
|
||||
'\n* The \'Approved\' status is used when the users timesheet is accepted by his/her senior.')
|
||||
account_ids = fields.One2many('hr_timesheet_sheet.sheet.account', 'sheet_id', string='Analytic accounts', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Company')
|
||||
department_id = fields.Many2one('hr.department', string='Department',
|
||||
default=lambda self: self.env['res.company']._company_default_get())
|
||||
|
||||
@api.constrains('date_to', 'date_from', 'employee_id')
|
||||
def _check_sheet_date(self, forced_user_id=False):
|
||||
for sheet in self:
|
||||
new_user_id = forced_user_id or sheet.user_id and sheet.user_id.id
|
||||
if new_user_id:
|
||||
self.env.cr.execute('''
|
||||
SELECT id
|
||||
FROM hr_timesheet_sheet_sheet
|
||||
WHERE (date_from <= %s and %s <= date_to)
|
||||
AND user_id=%s
|
||||
AND id <> %s''',
|
||||
(sheet.date_to, sheet.date_from, new_user_id, sheet.id))
|
||||
if any(self.env.cr.fetchall()):
|
||||
raise ValidationError(_('You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.'))
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def onchange_employee_id(self):
|
||||
if self.employee_id:
|
||||
self.department_id = self.employee_id.department_id
|
||||
self.user_id = self.employee_id.user_id
|
||||
|
||||
def copy(self, *args, **argv):
|
||||
raise UserError(_('You cannot duplicate a timesheet.'))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if 'employee_id' in vals:
|
||||
if not self.env['hr.employee'].browse(vals['employee_id']).user_id:
|
||||
raise UserError(_('In order to create a timesheet for this employee, you must link him/her to a user.'))
|
||||
res = super(HrTimesheetSheet, self).create(vals)
|
||||
res.write({'state': 'draft'})
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if 'employee_id' in vals:
|
||||
new_user_id = self.env['hr.employee'].browse(vals['employee_id']).user_id.id
|
||||
if not new_user_id:
|
||||
raise UserError(_('In order to create a timesheet for this employee, you must link him/her to a user.'))
|
||||
self._check_sheet_date(forced_user_id=new_user_id)
|
||||
return super(HrTimesheetSheet, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
def action_timesheet_draft(self):
|
||||
if not self.env.user.has_group('hr_timesheet.group_hr_timesheet_user'):
|
||||
raise UserError(_('Only an HR Officer or Manager can refuse timesheets or reset them to draft.'))
|
||||
self.write({'state': 'draft'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def action_timesheet_confirm(self):
|
||||
for sheet in self:
|
||||
if sheet.employee_id and sheet.employee_id.parent_id and sheet.employee_id.parent_id.user_id:
|
||||
self.message_subscribe_users(user_ids=[sheet.employee_id.parent_id.user_id.id])
|
||||
self.write({'state': 'confirm'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def action_timesheet_done(self):
|
||||
if not self.env.user.has_group('hr_timesheet.group_hr_timesheet_user'):
|
||||
raise UserError(_('Only an HR Officer or Manager can approve timesheets.'))
|
||||
if self.filtered(lambda sheet: sheet.state != 'confirm'):
|
||||
raise UserError(_("Cannot approve a non-submitted timesheet."))
|
||||
self.write({'state': 'done'})
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
# week number according to ISO 8601 Calendar
|
||||
return [(r['id'], _('Week ') + str(datetime.strptime(r['date_from'], '%Y-%m-%d').isocalendar()[1]))
|
||||
for r in self.read(['date_from'], load='_classic_write')]
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
sheets = self.read(['state'])
|
||||
for sheet in sheets:
|
||||
if sheet['state'] in ('confirm', 'done'):
|
||||
raise UserError(_('You cannot delete a timesheet which is already confirmed.'))
|
||||
|
||||
analytic_timesheet_toremove = self.env['account.analytic.line']
|
||||
for sheet in self:
|
||||
analytic_timesheet_toremove += sheet.timesheet_ids.filtered(lambda t: not t.task_id)
|
||||
analytic_timesheet_toremove.unlink()
|
||||
|
||||
return super(HrTimesheetSheet, self).unlink()
|
||||
|
||||
# ------------------------------------------------
|
||||
# OpenChatter methods and notifications
|
||||
# ------------------------------------------------
|
||||
|
||||
@api.multi
|
||||
def _track_subtype(self, init_values):
|
||||
if self:
|
||||
record = self[0]
|
||||
if 'state' in init_values and record.state == 'confirm':
|
||||
return 'hr_timesheet_sheet.mt_timesheet_confirmed'
|
||||
elif 'state' in init_values and record.state == 'done':
|
||||
return 'hr_timesheet_sheet.mt_timesheet_approved'
|
||||
return super(HrTimesheetSheet, self)._track_subtype(init_values)
|
||||
|
||||
|
||||
class HrTimesheetSheetSheetAccount(models.Model):
|
||||
_name = "hr_timesheet_sheet.sheet.account"
|
||||
_description = "Timesheets by Period"
|
||||
_auto = False
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Many2one('account.analytic.account', string='Project / Analytic Account', readonly=True)
|
||||
sheet_id = fields.Many2one('hr_timesheet_sheet.sheet', string='Sheet', readonly=True)
|
||||
total = fields.Float('Total Time', digits=(16, 2), readonly=True)
|
||||
|
||||
# still seing _depends in BaseModel, ok to leave this as is?
|
||||
_depends = {
|
||||
'account.analytic.line': ['account_id', 'date', 'unit_amount', 'user_id'],
|
||||
'hr_timesheet_sheet.sheet': ['date_from', 'date_to', 'user_id'],
|
||||
}
|
||||
|
||||
@api.model_cr
|
||||
def init(self):
|
||||
drop_view_if_exists(self._cr, 'hr_timesheet_sheet_sheet_account')
|
||||
self._cr.execute("""create view hr_timesheet_sheet_sheet_account as (
|
||||
select
|
||||
min(l.id) as id,
|
||||
l.account_id as name,
|
||||
s.id as sheet_id,
|
||||
sum(l.unit_amount) as total
|
||||
from
|
||||
account_analytic_line l
|
||||
LEFT JOIN hr_timesheet_sheet_sheet s
|
||||
ON (s.date_to >= l.date
|
||||
AND s.date_from <= l.date
|
||||
AND s.user_id = l.user_id)
|
||||
group by l.account_id, s.id
|
||||
)""")
|
||||
@@ -1,364 +0,0 @@
|
||||
odoo.define('hr_timesheet_sheet.sheet', function (require) {
|
||||
"use strict";
|
||||
|
||||
return;
|
||||
|
||||
var concurrency = require('web.concurrency');
|
||||
var core = require('web.core');
|
||||
var form_common = require('web.view_dialogs');
|
||||
var field_utils = require('web.field_utils');
|
||||
var time = require('web.time');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidgetMixin, {
|
||||
events: {
|
||||
"click .oe_timesheet_weekly_account a": "go_to",
|
||||
},
|
||||
ignore_fields: function() {
|
||||
return ['line_id'];
|
||||
},
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.set({
|
||||
sheets: [],
|
||||
date_from: false,
|
||||
date_to: false,
|
||||
});
|
||||
|
||||
this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets);
|
||||
this.field_manager.on("field_changed:date_from", this, function() {
|
||||
this.set({"date_from": time.str_to_date(this.field_manager.get_field_value("date_from"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:date_to", this, function() {
|
||||
this.set({"date_to": time.str_to_date(this.field_manager.get_field_value("date_to"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:user_id", this, function() {
|
||||
this.set({"user_id": this.field_manager.get_field_value("user_id")});
|
||||
});
|
||||
this.on("change:sheets", this, this.update_sheets);
|
||||
this.res_o2m_drop = new concurrency.DropMisordered();
|
||||
this.render_drop = new concurrency.DropMisordered();
|
||||
this.description_line = _t("/");
|
||||
},
|
||||
go_to: function(event) {
|
||||
var id = JSON.parse($(event.target).data("id"));
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: "project.project",
|
||||
res_id: id,
|
||||
views: [[false, 'form']],
|
||||
});
|
||||
},
|
||||
query_sheets: function() {
|
||||
if (this.updating) {
|
||||
return;
|
||||
}
|
||||
this.querying = true;
|
||||
var commands = this.field_manager.get_field_value("timesheet_ids");
|
||||
var self = this;
|
||||
|
||||
this._rpc({
|
||||
model: this.view.model,
|
||||
method: 'resolve_2many_commands',
|
||||
args: ['timesheet_ids', commands, []],
|
||||
})
|
||||
.done(function(result) {
|
||||
self.set({sheets: result});
|
||||
self.querying = false;
|
||||
});
|
||||
},
|
||||
update_sheets: function() {
|
||||
if(this.querying) {
|
||||
return;
|
||||
}
|
||||
this.updating = true;
|
||||
|
||||
var commands = [form_common.commands.delete_all()];
|
||||
_.each(this.get("sheets"), function (_data) {
|
||||
var data = _.clone(_data);
|
||||
if(data.id) {
|
||||
commands.push(form_common.commands.link_to(data.id));
|
||||
commands.push(form_common.commands.update(data.id, data));
|
||||
} else {
|
||||
commands.push(form_common.commands.create(data));
|
||||
}
|
||||
});
|
||||
|
||||
var self = this;
|
||||
this.field_manager.set_values({'timesheet_ids': commands}).done(function() {
|
||||
self.updating = false;
|
||||
});
|
||||
},
|
||||
initialize_field: function() {
|
||||
form_common.ReinitializeWidgetMixin.initialize_field.call(this);
|
||||
this.on("change:sheets", this, this.initialize_content);
|
||||
this.on("change:date_to", this, this.initialize_content);
|
||||
this.on("change:date_from", this, this.initialize_content);
|
||||
this.on("change:user_id", this, this.initialize_content);
|
||||
},
|
||||
initialize_content: function() {
|
||||
if(this.setting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't render anything until we have date_to and date_from
|
||||
if (!this.get("date_to") || !this.get("date_from")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// it's important to use those vars to avoid race conditions
|
||||
var dates;
|
||||
var projects;
|
||||
var project_names;
|
||||
var default_get;
|
||||
var self = this;
|
||||
return this.render_drop.add(
|
||||
this._rpc({
|
||||
model: 'account.analytic.line',
|
||||
method: 'default_get',
|
||||
args: [['account_id','general_account_id','journal_id','date','name','user_id','product_id','product_uom_id','amount','unit_amount','project_id']],
|
||||
context: {'user_id': self.get('user_id')},
|
||||
})
|
||||
.then(function(result) {
|
||||
default_get = result;
|
||||
// calculating dates
|
||||
dates = [];
|
||||
var start = self.get("date_from");
|
||||
var end = self.get("date_to");
|
||||
while (start <= end) {
|
||||
dates.push(start);
|
||||
var m_start = moment(start).add(1, 'days');
|
||||
start = m_start.toDate();
|
||||
}
|
||||
// group by project
|
||||
projects = _.chain(self.get("sheets"))
|
||||
.map(_.clone)
|
||||
.each(function(el) {
|
||||
// much simpler to use only the id in all cases
|
||||
if (typeof(el.project_id) === "object") {
|
||||
el.project_id = el.project_id[0];
|
||||
}
|
||||
})
|
||||
.groupBy("project_id").value();
|
||||
|
||||
projects = _(projects).chain().map(function(lines, project_id) {
|
||||
var projects_defaults = _.extend({}, default_get, (projects[project_id] || {}).value || {});
|
||||
// group by days
|
||||
project_id = (project_id === "false")? false : Number(project_id);
|
||||
var index = _.groupBy(lines, "date");
|
||||
var days = _.map(dates, function(date) {
|
||||
var day = {day: date, lines: index[time.date_to_str(date)] || []};
|
||||
// add line where we will insert/remove hours
|
||||
var to_add = _.find(day.lines, function(line) { return line.name === self.description_line; });
|
||||
if (to_add) {
|
||||
day.lines = _.without(day.lines, to_add);
|
||||
day.lines.unshift(to_add);
|
||||
} else {
|
||||
day.lines.unshift(_.extend(_.clone(projects_defaults), {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: time.date_to_str(date),
|
||||
project_id: project_id,
|
||||
}));
|
||||
}
|
||||
return day;
|
||||
});
|
||||
return {project: project_id, days: days, projects_defaults: projects_defaults};
|
||||
}).value();
|
||||
|
||||
// we need the name_get of the projects
|
||||
this._rpc({
|
||||
model: 'project.project',
|
||||
method: 'name_get',
|
||||
args: [_.pluck(projects, 'project')],
|
||||
})
|
||||
.then(function (result) {
|
||||
project_names = {};
|
||||
_.each(result, function(el) {
|
||||
project_names[el[0]] = el[1];
|
||||
});
|
||||
projects = _.sortBy(projects, function(el) {
|
||||
return project_names[el.project];
|
||||
});
|
||||
});
|
||||
})).then(function () {
|
||||
// we put all the gathered data in self, then we render
|
||||
self.dates = dates;
|
||||
self.projects = projects;
|
||||
self.project_names = project_names;
|
||||
self.default_get = default_get;
|
||||
//real rendering
|
||||
self.display_data();
|
||||
});
|
||||
},
|
||||
destroy_content: function() {
|
||||
if (this.dfm) {
|
||||
this.dfm.destroy();
|
||||
this.dfm = undefined;
|
||||
}
|
||||
},
|
||||
is_valid_value:function(value){
|
||||
this.view.do_notify_change();
|
||||
var split_value = value.split(":");
|
||||
var valid_value = true;
|
||||
if (split_value.length > 2) {
|
||||
return false;
|
||||
}
|
||||
_.detect(split_value,function(num){
|
||||
if(isNaN(num)) {
|
||||
valid_value = false;
|
||||
}
|
||||
});
|
||||
return valid_value;
|
||||
},
|
||||
display_data: function() {
|
||||
var self = this;
|
||||
self.$el.html(QWeb.render("hr_timesheet_sheet.WeeklyTimesheet", {widget: self}));
|
||||
_.each(self.projects, function(project) {
|
||||
_.each(_.range(project.days.length), function(day_count) {
|
||||
if (!self.get('effective_readonly')) {
|
||||
self.get_box(project, day_count).val(self.sum_box(project, day_count, true)).change(function() {
|
||||
var num = $(this).val();
|
||||
if (self.is_valid_value(num) && num !== 0) {
|
||||
num = Number(self.parse_client(num));
|
||||
}
|
||||
if (isNaN(num)) {
|
||||
$(this).val(self.sum_box(project, day_count, true));
|
||||
} else {
|
||||
project.days[day_count].lines[0].unit_amount += num - self.sum_box(project, day_count);
|
||||
|
||||
if(!isNaN($(this).val())){
|
||||
$(this).val(self.sum_box(project, day_count, true));
|
||||
}
|
||||
|
||||
self.display_totals();
|
||||
self.sync();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.get_box(project, day_count).html(self.sum_box(project, day_count, true));
|
||||
}
|
||||
});
|
||||
});
|
||||
self.display_totals();
|
||||
if(!this.get('effective_readonly')) {
|
||||
this.init_add_project();
|
||||
}
|
||||
},
|
||||
init_add_project: function() {
|
||||
if (this.dfm) {
|
||||
this.dfm.destroy();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.$(".oe_timesheet_weekly_add_row").show();
|
||||
this.dfm = new form_common.DefaultFieldManager(this);
|
||||
this.dfm.extend_field_desc({
|
||||
project: {
|
||||
relation: "project.project",
|
||||
},
|
||||
});
|
||||
var FieldMany2One = core.form_widget_registry.get('many2one');
|
||||
this.project_m2o = new FieldMany2One(this.dfm, {
|
||||
attrs: {
|
||||
name: "project",
|
||||
type: "many2one",
|
||||
domain: [
|
||||
['id', 'not in', _.pluck(this.projects, "project")],
|
||||
],
|
||||
modifiers: '{"required": true}',
|
||||
},
|
||||
});
|
||||
this.project_m2o.prependTo(this.$(".o_add_timesheet_line > div")).then(function() {
|
||||
self.project_m2o.$el.addClass('oe_edit_only');
|
||||
});
|
||||
this.$(".oe_timesheet_button_add").click(function() {
|
||||
var id = self.project_m2o.get_value();
|
||||
if (id === false) {
|
||||
self.dfm.set({display_invalid_fields: true});
|
||||
return;
|
||||
}
|
||||
|
||||
var ops = self.generate_o2m_value();
|
||||
ops.push(_.extend({}, self.default_get, {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: time.date_to_str(self.dates[0]),
|
||||
project_id: id,
|
||||
}));
|
||||
|
||||
self.set({sheets: ops});
|
||||
self.destroy_content();
|
||||
});
|
||||
},
|
||||
get_box: function(project, day_count) {
|
||||
return this.$('[data-project="' + project.project + '"][data-day-count="' + day_count + '"]');
|
||||
},
|
||||
sum_box: function(project, day_count, show_value_in_hour) {
|
||||
var line_total = 0;
|
||||
_.each(project.days[day_count].lines, function(line) {
|
||||
line_total += line.unit_amount;
|
||||
});
|
||||
return (show_value_in_hour && line_total !== 0)?this.format_client(line_total):line_total;
|
||||
},
|
||||
display_totals: function() {
|
||||
var self = this;
|
||||
var day_tots = _.map(_.range(self.dates.length), function() { return 0; });
|
||||
var super_tot = 0;
|
||||
_.each(self.projects, function(project) {
|
||||
var acc_tot = 0;
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
var sum = self.sum_box(project, day_count);
|
||||
acc_tot += sum;
|
||||
day_tots[day_count] += sum;
|
||||
super_tot += sum;
|
||||
});
|
||||
self.$('[data-project-total="' + project.project + '"]').html(self.format_client(acc_tot));
|
||||
});
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
self.$('[data-day-total="' + day_count + '"]').html(self.format_client(day_tots[day_count]));
|
||||
});
|
||||
this.$('.oe_timesheet_weekly_supertotal').html(self.format_client(super_tot));
|
||||
},
|
||||
sync: function() {
|
||||
this.setting = true;
|
||||
this.set({sheets: this.generate_o2m_value()});
|
||||
this.setting = false;
|
||||
},
|
||||
//converts hour value to float
|
||||
parse_client: function(value) {
|
||||
return field_utils.parse.float_time(value);
|
||||
},
|
||||
//converts float value to hour
|
||||
format_client:function(value){
|
||||
return field_utils.format.float_time(value);
|
||||
},
|
||||
generate_o2m_value: function() {
|
||||
var ops = [];
|
||||
var ignored_fields = this.ignore_fields();
|
||||
_.each(this.projects, function(project) {
|
||||
_.each(project.days, function(day) {
|
||||
_.each(day.lines, function(line) {
|
||||
if (line.unit_amount !== 0) {
|
||||
var tmp = _.clone(line);
|
||||
_.each(line, function(v, k) {
|
||||
if (v instanceof Array) {
|
||||
tmp[k] = v[0];
|
||||
}
|
||||
});
|
||||
// we remove line_id as the reference to the _inherits field will no longer exists
|
||||
tmp = _.omit(tmp, ignored_fields);
|
||||
ops.push(tmp);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return ops;
|
||||
},
|
||||
});
|
||||
|
||||
core.form_custom_registry.add('weekly_timesheet', WeeklyTimesheet);
|
||||
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
|
||||
.o_web_client .oe_timesheet_weekly {
|
||||
overflow-x: auto;
|
||||
> table {
|
||||
width: 100%;
|
||||
|
||||
> tbody > tr {
|
||||
|
||||
> th {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
background-color: @odoo-brand-lightsecondary;
|
||||
}
|
||||
|
||||
> td {
|
||||
text-align: right;
|
||||
|
||||
&.oe_timesheet_weekly_account {
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
input.oe_timesheet_weekly_input {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.oe_timesheet_button_add {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.o_with_button + .oe_timesheet_button_add {
|
||||
margin-left: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.oe_timesheet_weekly_today {
|
||||
background-color: lighten(@odoo-brand-primary, 40%);
|
||||
}
|
||||
.oe_timesheet_total {
|
||||
font-weight: bold;
|
||||
background-color: @odoo-brand-lightsecondary;
|
||||
}
|
||||
.o_add_timesheet_line {
|
||||
width: 1%;
|
||||
background-color: @odoo-brand-lightsecondary;
|
||||
|
||||
> div {
|
||||
.o-flex-display();
|
||||
.o-align-items(center);
|
||||
|
||||
.o_field_many2one {
|
||||
min-width: 200px;
|
||||
margin: 0;
|
||||
}
|
||||
.o_external_button {
|
||||
background-color: @odoo-brand-lightsecondary;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.oe_timesheet_button_add {
|
||||
.o-flex(0, 0, auto);
|
||||
margin-left: 8px;
|
||||
}
|
||||
.o_with_button + .oe_timesheet_button_add {
|
||||
margin-left: 45px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.openerp .oe_timesheet_weekly {
|
||||
> table > tbody > tr {
|
||||
> th {
|
||||
color: #069;
|
||||
}
|
||||
.o_add_timesheet_line {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ class HrPayrollAdvice(models.Model):
|
||||
self.env['hr.payroll.advice.line'].create({
|
||||
'advice_id': advice.id,
|
||||
'name': slip.employee_id.bank_account_id.acc_number,
|
||||
'ifsc_code': slip.employee_id.bank_account_id.bank_bic or '',
|
||||
'employee_id': slip.employee_id.id,
|
||||
'bysal': payslip_line.total
|
||||
})
|
||||
@@ -147,6 +148,7 @@ class HrPayslipRun(models.Model):
|
||||
self.env['hr.payroll.advice.line'].create({
|
||||
'advice_id': advice.id,
|
||||
'name': slip.employee_id.bank_account_id.acc_number,
|
||||
'ifsc_code': slip.employee_id.bank_account_id.bank_bic or '',
|
||||
'employee_id': slip.employee_id.id,
|
||||
'bysal': payslip_line.total
|
||||
})
|
||||
|
||||
@@ -53,9 +53,9 @@ class MrpBom(models.Model):
|
||||
default='asap', required=True)
|
||||
picking_type_id = fields.Many2one(
|
||||
'stock.picking.type', 'Operation Type', domain=[('code', '=', 'mrp_operation')],
|
||||
help="When a procurement has a ‘produce’ route with a operation type set, it will try to create "
|
||||
help=u"When a procurement has a ‘produce’ route with a operation type set, it will try to create "
|
||||
"a Manufacturing Order for that product using a BoM of the same operation type. That allows "
|
||||
"to define procurement rules which trigger different manufacturing orders with different BoMs. ")
|
||||
"to define procurement rules which trigger different manufacturing orders with different BoMs.")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', 'Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get('mrp.bom'),
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
<page string="Miscellaneous">
|
||||
<group>
|
||||
<group groups="stock.group_stock_multi_locations">
|
||||
<field name="picking_type_id" attrs="{'readonly': [('has_moves', '=', True)]}"/>
|
||||
<field name="picking_type_id" domain="[('code', '=', 'mrp_operation')]" attrs="{'readonly': [('has_moves', '=', True)]}"/>
|
||||
<field name="location_src_id" domain="[('usage','=','internal')]" attrs="{'readonly': [('has_moves', '=', True)]}"/>
|
||||
<field name="location_dest_id" domain="[('usage','=','internal')]" attrs="{'readonly': [('has_moves', '=', True)]}"/>
|
||||
</group>
|
||||
|
||||
@@ -39,6 +39,25 @@ var FieldPad = AbstractField.extend({
|
||||
this.$(".oe_configured").addClass('hidden');
|
||||
return;
|
||||
}
|
||||
if (this.mode === 'edit' && _.str.startsWith(this.value, 'http')) {
|
||||
this.url = this.value;
|
||||
// please close your eyes and look elsewhere...
|
||||
// Since the pad value (the url) will not change during the edition
|
||||
// process, we have a problem: the description field will not be
|
||||
// properly updated. We need to explicitely write the value each
|
||||
// time someone edit the record in order to force the server to read
|
||||
// the updated value of the pad and put it in the description field.
|
||||
//
|
||||
// However, the basic model optimizes away the changes if they are
|
||||
// not really different from the current value. So, we need to
|
||||
// either add special configuration options to the basic model, or
|
||||
// to trick him into accepting the same value as being different...
|
||||
// Guess what we decided...
|
||||
var url = {};
|
||||
url.toJSON = _.constant(this.url);
|
||||
this._setValue(url);
|
||||
}
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
@@ -75,11 +94,11 @@ var FieldPad = AbstractField.extend({
|
||||
* @private
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
if (_.str.startsWith(this.value, 'http')) {
|
||||
if (this.url) {
|
||||
// here, we have a valid url, so we can simply display an iframe
|
||||
// with the correct src attribute
|
||||
var userName = encodeURIComponent(this.getSession().userName);
|
||||
var url = this.value + '?showChat=false&userName=' + userName;
|
||||
var url = this.url + '?showChat=false&userName=' + userName;
|
||||
var content = '<iframe width="100%" height="100%" frameborder="0" src="' + url + '"></iframe>';
|
||||
this.$('.oe_pad_content').html(content);
|
||||
} else if (this.value) {
|
||||
@@ -111,7 +130,8 @@ var FieldPad = AbstractField.extend({
|
||||
// We need to write the url of the pad to trigger
|
||||
// the write function which updates the actual value
|
||||
// of the field to the value of the pad content
|
||||
self._setValue(result.url);
|
||||
self.url = result.url;
|
||||
self._setValue(result.url, {doNotSetDirty: true});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -112,7 +112,7 @@ QUnit.module('pad widget', {
|
||||
});
|
||||
|
||||
QUnit.test('pad widget works, with existing data', function (assert) {
|
||||
assert.expect(2);
|
||||
assert.expect(3);
|
||||
|
||||
var contentDef = $.Deferred();
|
||||
|
||||
@@ -129,10 +129,17 @@ QUnit.module('pad widget', {
|
||||
'</form>',
|
||||
res_id: 2,
|
||||
mockRPC: function (route, args) {
|
||||
if (_.str.startsWith(route, 'http')) {
|
||||
return $.when(true);
|
||||
}
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (args.method === 'pad_get_content') {
|
||||
return contentDef.then(_.constant(result));
|
||||
}
|
||||
if (args.method === 'write') {
|
||||
assert.ok('description' in args.args[1],
|
||||
"should always send the description value");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
session: {
|
||||
@@ -144,8 +151,47 @@ QUnit.module('pad widget', {
|
||||
contentDef.resolve();
|
||||
assert.strictEqual(form.$('.oe_pad_content').text(), "we should rewrite this server in haskell",
|
||||
"should display proper value");
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
form.destroy();
|
||||
delete FieldPad.prototype.isPadConfigured;
|
||||
});
|
||||
|
||||
QUnit.test('pad widget is not considered dirty at creation', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'task',
|
||||
data: this.data,
|
||||
arch:'<form>' +
|
||||
'<sheet>' +
|
||||
'<group>' +
|
||||
'<field name="description" widget="pad"/>' +
|
||||
'</group>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
mockRPC: function (route, args) {
|
||||
if (!args.method) {
|
||||
return $.when(true);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
session: {
|
||||
userName: "batman",
|
||||
},
|
||||
});
|
||||
var def = form.canBeDiscarded();
|
||||
|
||||
assert.strictEqual($('.modal').length, 0,
|
||||
"should have no confirmation modal opened");
|
||||
|
||||
assert.strictEqual(def.state(), 'resolved',
|
||||
"can be discarded was succesfully resolved");
|
||||
form.destroy();
|
||||
delete FieldPad.prototype.isPadConfigured;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ class AuthorizeAPI():
|
||||
payment_profile = etree.SubElement(profile, "paymentProfiles")
|
||||
etree.SubElement(payment_profile, "customerType").text = 'business' if partner.is_company else 'individual'
|
||||
billTo = etree.SubElement(payment_profile, "billTo")
|
||||
etree.SubElement(billTo, "address").text = (partner.street + (partner.street2 if partner.street2 else '')) or None
|
||||
etree.SubElement(billTo, "address").text = (partner.street or '' + (partner.street2 if partner.street2 else '')) or None
|
||||
etree.SubElement(billTo, "city").text = partner.city
|
||||
etree.SubElement(billTo, "state").text = partner.state_id.name or None
|
||||
etree.SubElement(billTo, "zip").text = partner.zip
|
||||
|
||||
@@ -41,6 +41,7 @@ class OgoneController(http.Controller):
|
||||
token = acq.s2s_process(post)
|
||||
except Exception as e:
|
||||
# synthax error: 'CHECK ERROR: |Not a valid date\n\n50001111: None'
|
||||
token = False
|
||||
error = str(e).splitlines()[0].split('|')[-1] or ''
|
||||
|
||||
if token and post.get('verify_validity'):
|
||||
|
||||
@@ -30,5 +30,6 @@ class ResPartner(models.Model):
|
||||
if partner_id: # Modifying existing partner
|
||||
self.browse(partner_id).write(partner)
|
||||
else:
|
||||
partner['lang'] = self.env.user.lang
|
||||
partner_id = self.create(partner).id
|
||||
return partner_id
|
||||
|
||||
@@ -416,7 +416,7 @@ class PurchaseOrder(models.Model):
|
||||
moves = order.order_line._create_stock_moves(picking)
|
||||
moves = moves.filtered(lambda x: x.state not in ('done', 'cancel')).action_confirm()
|
||||
seq = 0
|
||||
for move in moves:
|
||||
for move in sorted(moves, key=lambda move: move.date_expected):
|
||||
seq += 5
|
||||
move.sequence = seq
|
||||
moves.action_assign()
|
||||
@@ -946,7 +946,7 @@ class ProcurementOrder(models.Model):
|
||||
if self.purchase_line_id:
|
||||
if not self.move_ids:
|
||||
return False
|
||||
return all(move.state == 'done' for move in self.move_ids)
|
||||
return all(move.state in ('done', 'cancel') for move in self.move_ids) and any(move.state == 'done' for move in self.move_ids)
|
||||
return super(ProcurementOrder, self)._check()
|
||||
|
||||
def _get_purchase_schedule_date(self):
|
||||
|
||||
@@ -571,7 +571,7 @@
|
||||
<group string="Warning when Purchasing this Product" groups="purchase.group_warning_purchase">
|
||||
<field name="purchase_line_warn" nolabel="1"/>
|
||||
<field name="purchase_line_warn_msg" colspan="3" nolabel="1"
|
||||
attrs="{'required':[('purchase_line_warn','!=','no-message')],'readonly':[('purchase_line_warn','=','no-message')]}"/>
|
||||
attrs="{'required':[('purchase_line_warn','!=','no-message')],'readonly':[('purchase_line_warn','=','no-message')], 'invisible':[('purchase_line_warn','=','no-message')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group name="description" position="inside">
|
||||
|
||||
@@ -157,5 +157,5 @@
|
||||
</record>
|
||||
|
||||
<menuitem action="action_report_intrastat_tree_all" id="menu_report_intrastat_all"
|
||||
parent="stock.menu_warehouse_report" sequence="6"/>
|
||||
parent="stock.menu_warehouse_report" sequence="120"/>
|
||||
</odoo>
|
||||
|
||||
@@ -640,7 +640,7 @@
|
||||
<group string="Warning when Selling this Product" groups="sale.group_warning_sale">
|
||||
<field name="sale_line_warn" nolabel="1"/>
|
||||
<field name="sale_line_warn_msg" colspan="3" nolabel="1"
|
||||
attrs="{'required':[('sale_line_warn','!=','no-message')],'readonly':[('sale_line_warn','=','no-message')]}"/>
|
||||
attrs="{'required':[('sale_line_warn','!=','no-message')],'readonly':[('sale_line_warn','=','no-message')], 'invisible':[('sale_line_warn','=','no-message')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
|
||||
@@ -195,7 +195,7 @@ class SaleOrderLine(models.Model):
|
||||
if move.location_dest_id.usage == "customer":
|
||||
if not move.origin_returned_move_id:
|
||||
qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom)
|
||||
elif move.location_dest_id.usage == "internal" and move.to_refund:
|
||||
elif move.location_dest_id.usage != "customer" and move.to_refund:
|
||||
qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom)
|
||||
return qty
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
'views/stock_location_views.xml',
|
||||
'views/procurement_views.xml',
|
||||
'views/stock_warehouse_views.xml',
|
||||
'views/stock_move_line_views.xml',
|
||||
'views/stock_move_views.xml',
|
||||
'views/product_views.xml',
|
||||
'views/stock_config_settings_views.xml',
|
||||
|
||||
@@ -521,7 +521,7 @@ class ProductTemplate(models.Model):
|
||||
def action_view_stock_moves(self):
|
||||
products = self.mapped('product_variant_ids')
|
||||
action = self.env.ref('stock.act_product_stock_move_open').read()[0]
|
||||
if self:
|
||||
if products:
|
||||
action['context'] = {'default_product_id': products.ids[0]}
|
||||
action['domain'] = [('product_id.product_tmpl_id', 'in', self.ids)]
|
||||
return action
|
||||
|
||||
@@ -34,6 +34,6 @@ class Company(models.Model):
|
||||
|
||||
# multi-company rules prevents creating warehouse and sub-locations
|
||||
self.env['stock.warehouse'].check_access_rights('create')
|
||||
self.env['stock.warehouse'].sudo().create({'name': company.name, 'code': company.name[:5], 'company_id': company.id})
|
||||
self.env['stock.warehouse'].sudo().create({'name': company.name, 'code': company.name[:5], 'company_id': company.id, 'partner_id': company.partner_id.id})
|
||||
company.create_transit_location()
|
||||
return company
|
||||
|
||||
@@ -298,8 +298,11 @@ class StockMove(models.Model):
|
||||
|
||||
@api.constrains('product_uom')
|
||||
def _check_uom(self):
|
||||
if any(move.product_id.uom_id.category_id.id != move.product_uom.category_id.id for move in self):
|
||||
raise UserError(_('You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.'))
|
||||
moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id.id != move.product_uom.category_id.id)
|
||||
if moves_error:
|
||||
user_warning = _('You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.')
|
||||
user_warning += '\n\nBlocking: %s' % ' ,'.join(moves_error.mapped('name'))
|
||||
raise UserError(user_warning)
|
||||
|
||||
@api.model_cr
|
||||
def init(self):
|
||||
@@ -645,7 +648,7 @@ class StockMove(models.Model):
|
||||
(move.picking_id.picking_type_id.use_existing_lots or move.picking_id.picking_type_id.use_create_lots) and \
|
||||
move.product_id.tracking != 'none' and \
|
||||
not (move_line and (move_line.product_id and move_line.pack_lot_ids)) or (move_line and not move_line.product_id):
|
||||
raise UserError(_('You need to provide a Lot/Serial Number for product %s') % move.product_id.name)
|
||||
raise UserError(_('You need to provide a Lot/Serial Number for product %s') % ("%s (%s)" % (move.product_id.name, move.picking_id.name)))
|
||||
|
||||
def _prepare_move_line_vals(self, quantity=None, reserved_quant=None):
|
||||
self.ensure_one()
|
||||
@@ -739,12 +742,18 @@ class StockMove(models.Model):
|
||||
else:
|
||||
# Check what our parents brought and what our siblings took in order to
|
||||
# determine what we can distribute.
|
||||
# `qty_done` is in `ml.product_uom_id` and, as we will later increase
|
||||
# the reserved quantity on the quants, convert it here in
|
||||
# `product_id.uom_id` (the UOM of the quants is the UOM of the product).
|
||||
move_lines_in = move.move_orig_ids.filtered(lambda m: m.state == 'done').mapped('move_line_ids')
|
||||
keys_in = ['location_dest_id', 'lot_id', 'result_package_id', 'owner_id']
|
||||
grouped_move_lines_in = {}
|
||||
for k, g in groupby(sorted(move_lines_in, key=itemgetter(*keys_in)), key=itemgetter(*keys_in)):
|
||||
grouped_move_lines_in[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('qty_done'))
|
||||
|
||||
# `qty_done` is in `ml.product_uom_id` and, as we will later increase the
|
||||
qty_done = 0
|
||||
for ml in g:
|
||||
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
grouped_move_lines_in[k] = qty_done
|
||||
move_lines_out_done = (move.move_orig_ids.mapped('move_dest_ids') - move)\
|
||||
.filtered(lambda m: m.state in ['done'])\
|
||||
.mapped('move_line_ids')
|
||||
@@ -754,7 +763,10 @@ class StockMove(models.Model):
|
||||
keys_out = ['location_id', 'lot_id', 'package_id', 'owner_id']
|
||||
grouped_move_lines_out = {}
|
||||
for k, g in groupby(sorted(move_lines_out_done, key=itemgetter(*keys_out)), key=itemgetter(*keys_out)):
|
||||
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('qty_done'))
|
||||
qty_done = 0
|
||||
for ml in g:
|
||||
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
|
||||
grouped_move_lines_out[k] = qty_done
|
||||
for k, g in groupby(sorted(move_lines_out_reserved, key=itemgetter(*keys_out)), key=itemgetter(*keys_out)):
|
||||
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty'))
|
||||
available_move_lines = {key: grouped_move_lines_in[key] - grouped_move_lines_out.get(key, 0) for key in grouped_move_lines_in.keys()}
|
||||
|
||||
@@ -98,8 +98,9 @@ class StockMoveLine(models.Model):
|
||||
if ml.state == 'done':
|
||||
Quant = self.env['stock.quant']
|
||||
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id,rounding_method='HALF-UP')
|
||||
in_date = None
|
||||
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
available_qty = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
available_qty, in_date = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
if available_qty < 0 and ml.lot_id:
|
||||
# see if we can compensate the negative quants with some untracked quants
|
||||
untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
@@ -108,7 +109,7 @@ class StockMoveLine(models.Model):
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id)
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date)
|
||||
next_moves = ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
|
||||
next_moves.do_unreserve()
|
||||
next_moves.action_assign()
|
||||
@@ -187,14 +188,16 @@ class StockMoveLine(models.Model):
|
||||
if updates or 'qty_done' in vals:
|
||||
for ml in self.filtered(lambda ml: ml.move_id.state == 'done'):
|
||||
# undo the original move line
|
||||
in_date = None
|
||||
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -ml.qty_done, lot_id=ml.lot_id,
|
||||
package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
in_date = Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -ml.qty_done, lot_id=ml.lot_id,
|
||||
package_id=ml.package_id, owner_id=ml.owner_id)[1]
|
||||
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_id, ml.qty_done, lot_id=ml.lot_id,
|
||||
package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
package_id=ml.package_id, owner_id=ml.owner_id, in_date=in_date)
|
||||
|
||||
# move what's been actually done
|
||||
in_date = None
|
||||
product_id = ml.product_id
|
||||
location_id = updates.get('location_id', ml.location_id)
|
||||
location_dest_id = updates.get('location_dest_id', ml.location_dest_id)
|
||||
@@ -206,7 +209,7 @@ class StockMoveLine(models.Model):
|
||||
quantity = ml.move_id.product_uom._compute_quantity(qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
|
||||
if location_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
ml._free_reservation(product_id, location_id, quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
|
||||
available_qty = Quant._update_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
|
||||
available_qty, in_date = Quant._update_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
|
||||
if available_qty < 0 and lot_id:
|
||||
# see if we can compensate the negative quants with some untracked quants
|
||||
untracked_qty = Quant._get_available_quantity(product_id, location_id, lot_id=False, package_id=package_id, owner_id=owner_id, strict=True)
|
||||
@@ -216,7 +219,7 @@ class StockMoveLine(models.Model):
|
||||
Quant._update_available_quantity(product_id, location_id, taken_from_untracked_qty, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
|
||||
ml._free_reservation(ml.product_id, location_id, untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
|
||||
if location_dest_id.should_impact_quants() and ml.product_id.type == 'product' and qty_done:
|
||||
Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id)
|
||||
Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id, in_date=in_date)
|
||||
# Unreserve and reserve following move in order to have the real reserved quantity on move_line.
|
||||
next_moves |= ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
|
||||
res = super(StockMoveLine, self).write(vals)
|
||||
@@ -304,8 +307,9 @@ class StockMoveLine(models.Model):
|
||||
|
||||
# move what's been actually done
|
||||
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
|
||||
in_date = None
|
||||
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
available_qty = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
available_qty, in_date = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
if available_qty < 0 and ml.lot_id:
|
||||
# see if we can compensate the negative quants with some untracked quants
|
||||
untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
@@ -314,7 +318,7 @@ class StockMoveLine(models.Model):
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id)
|
||||
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date)
|
||||
|
||||
def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None):
|
||||
""" When editing a done move line or validating one with some forced quantities, it is
|
||||
|
||||
@@ -57,6 +57,13 @@ class StockQuant(models.Model):
|
||||
if quant.quantity > 1 and quant.lot_id and quant.product_id.tracking == 'serial':
|
||||
raise ValidationError(_('A serial number should only be linked to a single product.'))
|
||||
|
||||
@api.multi
|
||||
@api.constrains('in_date', 'lot_id')
|
||||
def check_in_date(self):
|
||||
for quant in self:
|
||||
if quant.in_date and not quant.lot_id:
|
||||
raise ValidationError(_('An incoming date cannot be set to an untracked product.'))
|
||||
|
||||
@api.one
|
||||
def _compute_name(self):
|
||||
self.name = '%s: %s%s' % (self.lot_id.name or self.product_id.code or '', self.quantity, self.product_id.uom_id.name)
|
||||
@@ -126,14 +133,44 @@ class StockQuant(models.Model):
|
||||
return sum(quants.mapped('quantity')) - sum(quants.mapped('reserved_quantity'))
|
||||
|
||||
@api.model
|
||||
def _update_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None):
|
||||
def _update_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, in_date=None):
|
||||
""" Increase or decrease `reserved_quantity` of a set of quants for a given set of
|
||||
product_id/location_id/lot_id/package_id/owner_id.
|
||||
|
||||
:param product_id:
|
||||
:param location_id:
|
||||
:param quantity:
|
||||
:param lot_id:
|
||||
:param package_id:
|
||||
:param owner_id:
|
||||
:param datetime in_date: Should only be passed when calls to this method are done in
|
||||
order to move a quant. When creating a tracked quant, the
|
||||
current datetime will be used.
|
||||
:return: tuple (available_quantity, in_date as a datetime)
|
||||
"""
|
||||
self = self.sudo()
|
||||
quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
|
||||
|
||||
if lot_id:
|
||||
incoming_dates = quants.mapped('in_date') # `mapped` already filtered out falsy items
|
||||
incoming_dates = [fields.Datetime.from_string(incoming_date) for incoming_date in incoming_dates]
|
||||
if in_date:
|
||||
incoming_dates += [in_date]
|
||||
# If multiple incoming dates are available for a given lot_id/package_id/owner_id, we
|
||||
# consider only the oldest one as being relevant.
|
||||
if incoming_dates:
|
||||
in_date = fields.Datetime.to_string(min(incoming_dates))
|
||||
else:
|
||||
in_date = fields.Datetime.now()
|
||||
|
||||
for quant in quants:
|
||||
try:
|
||||
with self._cr.savepoint():
|
||||
self._cr.execute("SELECT 1 FROM stock_quant WHERE id = %s FOR UPDATE NOWAIT", [quant.id], log_exceptions=False)
|
||||
quant.quantity += quantity
|
||||
quant.write({
|
||||
'quantity': quant.quantity + quantity,
|
||||
'in_date': in_date,
|
||||
})
|
||||
# cleanup empty quants
|
||||
if quant.quantity == 0 and quant.reserved_quantity == 0:
|
||||
quant.unlink()
|
||||
@@ -151,8 +188,9 @@ class StockQuant(models.Model):
|
||||
'lot_id': lot_id and lot_id.id,
|
||||
'package_id': package_id and package_id.id,
|
||||
'owner_id': owner_id and owner_id.id,
|
||||
'in_date': in_date,
|
||||
})
|
||||
return self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=False)
|
||||
return self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=False), fields.Datetime.from_string(in_date)
|
||||
|
||||
@api.model
|
||||
def _update_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False):
|
||||
|
||||
@@ -114,7 +114,10 @@ class StockMove(TransactionCase):
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.supplier_location), 0.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location), 5.0)
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.supplier_location)), 0.0)
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.stock_location)), 1.0)
|
||||
quants = self.env['stock.quant']._gather(self.product3, self.stock_location)
|
||||
self.assertEqual(len(quants), 1.0)
|
||||
for quant in quants:
|
||||
self.assertNotEqual(quant.in_date, False)
|
||||
|
||||
def test_in_3(self):
|
||||
""" Receive 5 serial-tracked products from a supplier. The system should create 5 differents
|
||||
@@ -162,7 +165,10 @@ class StockMove(TransactionCase):
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 5.0)
|
||||
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.supplier_location)), 0.0)
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location)), 5.0)
|
||||
quants = self.env['stock.quant']._gather(self.product2, self.stock_location)
|
||||
self.assertEqual(len(quants), 5.0)
|
||||
for quant in quants:
|
||||
self.assertNotEqual(quant.in_date, False)
|
||||
|
||||
def test_out_1(self):
|
||||
""" Send products to a client. Check that a move line is created reserving products in
|
||||
@@ -2371,3 +2377,241 @@ class StockMove(TransactionCase):
|
||||
|
||||
self.assertEqual(move1.state, 'confirmed')
|
||||
self.assertEqual(len(move1.move_line_ids), 0)
|
||||
|
||||
def test_in_date_1(self):
|
||||
""" Check that moving a tracked quant keeps the incoming date.
|
||||
"""
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_in').id,
|
||||
})
|
||||
move1.action_confirm()
|
||||
move1.action_assign()
|
||||
move1.move_line_ids.lot_name = 'lot1'
|
||||
move1.move_line_ids.qty_done = 1
|
||||
move1.action_done()
|
||||
|
||||
quant = self.env['stock.quant']._gather(self.product3, self.stock_location)
|
||||
self.assertEqual(len(quant), 1.0)
|
||||
self.assertNotEqual(quant.in_date, False)
|
||||
|
||||
# Keep a reference to the initial incoming date in order to compare it later.
|
||||
initial_incoming_date = quant.in_date
|
||||
|
||||
move2 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.pack_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
move2.action_confirm()
|
||||
move2.action_assign()
|
||||
move2.move_line_ids.qty_done = 1
|
||||
move2.action_done()
|
||||
|
||||
quant = self.env['stock.quant']._gather(self.product3, self.pack_location)
|
||||
self.assertEqual(len(quant), 1.0)
|
||||
self.assertEqual(quant.in_date, initial_incoming_date)
|
||||
|
||||
def test_in_date_2(self):
|
||||
""" Check that editing a done move line for a tracked product and changing its lot
|
||||
correctly restores the original lot with its incoming date and remove the new lot
|
||||
with its incoming date.
|
||||
"""
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': self.product3.id,
|
||||
})
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': self.product3.id,
|
||||
})
|
||||
# receive lot1
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_in').id,
|
||||
})
|
||||
move1.action_confirm()
|
||||
move1.action_assign()
|
||||
move1.move_line_ids.lot_id = lot1
|
||||
move1.move_line_ids.qty_done = 1
|
||||
move1.action_done()
|
||||
|
||||
# receive lot2
|
||||
move2 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_in').id,
|
||||
})
|
||||
move2.action_confirm()
|
||||
move2.action_assign()
|
||||
move2.move_line_ids.lot_id = lot2
|
||||
move2.move_line_ids.qty_done = 1
|
||||
move2.action_done()
|
||||
|
||||
initial_in_date_lot2 = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
('lot_id', '=', lot2.id),
|
||||
]).in_date
|
||||
|
||||
# Edit lot1's incoming date.
|
||||
quant_lot1 = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
('lot_id', '=', lot1.id),
|
||||
])
|
||||
from datetime import datetime, timedelta
|
||||
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
|
||||
quant_lot1.in_date = initial_in_date_lot1
|
||||
|
||||
# Move one quant to pack location
|
||||
move3 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.pack_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
move3.action_confirm()
|
||||
move3.action_assign()
|
||||
move3.move_line_ids.qty_done = 1
|
||||
move3.action_done()
|
||||
quant_in_pack = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
('location_id', '=', self.pack_location.id),
|
||||
])
|
||||
# As lot1 has an older date and FIFO is set by default, it's the one that should be
|
||||
# in pack.
|
||||
self.assertEqual(len(quant_in_pack), 1)
|
||||
from odoo.fields import Datetime
|
||||
self.assertEqual(quant_in_pack.in_date, Datetime.to_string(initial_in_date_lot1))
|
||||
self.assertEqual(quant_in_pack.lot_id, lot1)
|
||||
|
||||
# Now, edit the move line and actually move the other lot
|
||||
move3.move_line_ids.lot_id = lot2
|
||||
|
||||
# Check that lot1 correctly is back to stock with its right in_date
|
||||
quant_lot1 = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
('lot_id', '=', lot1.id),
|
||||
])
|
||||
self.assertEqual(quant_lot1.location_id, self.stock_location)
|
||||
self.assertEqual(quant_lot1.in_date, Datetime.to_string(initial_in_date_lot1))
|
||||
|
||||
# Check that lo2 is in pack with is right in_date
|
||||
quant_lot2 = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
('lot_id', '=', lot2.id),
|
||||
])
|
||||
self.assertEqual(quant_lot2.location_id, self.pack_location)
|
||||
self.assertEqual(quant_lot2.in_date, initial_in_date_lot2)
|
||||
|
||||
def test_in_date_3(self):
|
||||
""" Check that, when creating a move line on a done stock move, the lot and its incoming
|
||||
date are correctly moved to the destination location.
|
||||
"""
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': self.product3.id,
|
||||
})
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': self.product3.id,
|
||||
})
|
||||
# receive lot1
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_in').id,
|
||||
})
|
||||
move1.action_confirm()
|
||||
move1.action_assign()
|
||||
move1.move_line_ids.lot_id = lot1
|
||||
move1.move_line_ids.qty_done = 1
|
||||
move1.action_done()
|
||||
|
||||
# receive lot2
|
||||
move2 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_in').id,
|
||||
})
|
||||
move2.action_confirm()
|
||||
move2.action_assign()
|
||||
move2.move_line_ids.lot_id = lot2
|
||||
move2.move_line_ids.qty_done = 1
|
||||
move2.action_done()
|
||||
|
||||
initial_in_date_lot2 = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
('lot_id', '=', lot2.id),
|
||||
]).in_date
|
||||
|
||||
# Edit lot1's incoming date.
|
||||
quant_lot1 = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
('lot_id', '=', lot1.id),
|
||||
])
|
||||
from datetime import datetime, timedelta
|
||||
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
|
||||
quant_lot1.in_date = initial_in_date_lot1
|
||||
|
||||
# Move one quant to pack location
|
||||
move3 = self.env['stock.move'].create({
|
||||
'name': 'test_in_date_1',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.pack_location.id,
|
||||
'product_id': self.product3.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
move3.action_confirm()
|
||||
move3.action_assign()
|
||||
move3.move_line_ids.qty_done = 1
|
||||
move3.action_done()
|
||||
|
||||
# Now, also move lot2
|
||||
self.env['stock.move.line'].create({
|
||||
'move_id': move3.id,
|
||||
'product_id': move3.product_id.id,
|
||||
'qty_done': 1,
|
||||
'product_uom_id': move3.product_uom.id,
|
||||
'location_id': move3.location_id.id,
|
||||
'location_dest_id': move3.location_dest_id.id,
|
||||
'lot_id': lot2.id,
|
||||
})
|
||||
|
||||
quants = self.env['stock.quant'].search([
|
||||
('product_id', '=', self.product3.id),
|
||||
])
|
||||
self.assertEqual(len(quants), 2)
|
||||
from odoo.fields import Datetime
|
||||
for quant in quants:
|
||||
if quant.lot_id == lot1:
|
||||
self.assertEqual(quant.in_date, Datetime.to_string(initial_in_date_lot1))
|
||||
elif quant.lot_id == lot2:
|
||||
self.assertEqual(quant.in_date, initial_in_date_lot2)
|
||||
|
||||
@@ -224,6 +224,61 @@ class TestPickShip(TestStockCommon):
|
||||
# Check if run action_assign does not crash
|
||||
picking_client.action_assign()
|
||||
|
||||
def test_chained_move_with_uom(self):
|
||||
""" Create pick ship with a different uom than the once used for quant.
|
||||
Check that reserved quantity and flow work correctly.
|
||||
"""
|
||||
picking_client = self.env['stock.picking'].create({
|
||||
'location_id': self.pack_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
'partner_id': self.partner_delta_id,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
})
|
||||
dest = self.MoveObj.create({
|
||||
'name': self.gB.name,
|
||||
'product_id': self.gB.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.uom_kg.id,
|
||||
'picking_id': picking_client.id,
|
||||
'location_id': self.pack_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
'state': 'waiting',
|
||||
})
|
||||
|
||||
picking_pick = self.env['stock.picking'].create({
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.pack_location,
|
||||
'partner_id': self.partner_delta_id,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
})
|
||||
|
||||
self.MoveObj.create({
|
||||
'name': self.gB.name,
|
||||
'product_id': self.gB.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.uom_kg.id,
|
||||
'picking_id': picking_pick.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.pack_location,
|
||||
'move_dest_ids': [(4, dest.id)],
|
||||
'state': 'confirmed',
|
||||
})
|
||||
location = self.env['stock.location'].browse(self.stock_location)
|
||||
pack_location = self.env['stock.location'].browse(self.pack_location)
|
||||
|
||||
# make some stock
|
||||
self.env['stock.quant']._update_available_quantity(self.gB, location, 10000.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, pack_location), 0.0)
|
||||
picking_pick.action_assign()
|
||||
picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0
|
||||
picking_pick.action_done()
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, location), 5000.0)
|
||||
self.assertEqual(self.env['stock.quant']._gather(self.gB, pack_location).quantity, 5000.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, pack_location), 0.0)
|
||||
self.assertEqual(picking_client.state, 'assigned')
|
||||
self.assertEqual(picking_client.move_lines.reserved_availability, 5.0)
|
||||
|
||||
|
||||
class TestSinglePicking(TestStockCommon):
|
||||
def test_backorder_1(self):
|
||||
|
||||
@@ -5,6 +5,8 @@ from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class StockQuant(TransactionCase):
|
||||
def setUp(self):
|
||||
@@ -588,3 +590,131 @@ class StockQuant(TransactionCase):
|
||||
quant.sudo(self.demo_user).write({'quantity': 2.0})
|
||||
with self.assertRaises(AccessError):
|
||||
quant.sudo(self.demo_user).unlink()
|
||||
|
||||
def test_in_date_1(self):
|
||||
""" Check that no incoming date is set when updating the quantity of an untracked quant.
|
||||
"""
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
product1 = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
})
|
||||
quantity, in_date = self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
|
||||
self.assertEqual(quantity, 1)
|
||||
self.assertEqual(in_date, None)
|
||||
|
||||
def test_in_date_2(self):
|
||||
""" Check that an incoming date is correctly set when updating the quantity of a tracked
|
||||
quant.
|
||||
"""
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
product1 = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': product1.id,
|
||||
})
|
||||
quantity, in_date = self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1)
|
||||
self.assertEqual(quantity, 1)
|
||||
self.assertNotEqual(in_date, None)
|
||||
|
||||
def test_in_date_3(self):
|
||||
""" Check that the FIFO strategies correctly applies when you have multiple lot received
|
||||
at different times for a tracked product.
|
||||
"""
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
product1 = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': product1.id,
|
||||
})
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': product1.id,
|
||||
})
|
||||
in_date_lot1 = datetime.now()
|
||||
in_date_lot2 = datetime.now() - timedelta(days=5)
|
||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date_lot1)
|
||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot2, in_date=in_date_lot2)
|
||||
|
||||
quants = self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1)
|
||||
|
||||
# Default removal strategy is FIFO, so lot2 should be received as it was received earlier.
|
||||
self.assertEqual(quants[0][0].lot_id.id, lot2.id)
|
||||
|
||||
def test_in_date_4(self):
|
||||
""" Check that the LIFO strategies correctly applies when you have multiple lot received
|
||||
at different times for a tracked product.
|
||||
"""
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
lifo_strategy = self.env['product.removal'].search([('method', '=', 'lifo')])
|
||||
stock_location.removal_strategy_id = lifo_strategy
|
||||
product1 = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': product1.id,
|
||||
})
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': product1.id,
|
||||
})
|
||||
in_date_lot1 = datetime.now()
|
||||
in_date_lot2 = datetime.now() - timedelta(days=5)
|
||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date_lot1)
|
||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot2, in_date=in_date_lot2)
|
||||
|
||||
quants = self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1)
|
||||
|
||||
# Removal strategy is LIFO, so lot1 should be received as it was received later.
|
||||
self.assertEqual(quants[0][0].lot_id.id, lot1.id)
|
||||
|
||||
def test_in_date_5(self):
|
||||
""" Receive the same lot at different times, once they're in the same location, the quants
|
||||
are merged and only the earliest incoming date is kept.
|
||||
"""
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
product1 = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
'tracking': 'lot',
|
||||
})
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': product1.id,
|
||||
})
|
||||
|
||||
in_date1 = datetime.now()
|
||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date1)
|
||||
|
||||
quant = self.env['stock.quant'].search([
|
||||
('product_id', '=', product1.id),
|
||||
('location_id', '=', stock_location.id),
|
||||
])
|
||||
self.assertEqual(len(quant), 1)
|
||||
self.assertEqual(quant.quantity, 1)
|
||||
self.assertEqual(quant.lot_id.id, lot1.id)
|
||||
from odoo.fields import Datetime
|
||||
self.assertEqual(quant.in_date, Datetime.to_string(in_date1))
|
||||
|
||||
in_date2 = datetime.now() - timedelta(days=5)
|
||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date2)
|
||||
|
||||
quant = self.env['stock.quant'].search([
|
||||
('product_id', '=', product1.id),
|
||||
('location_id', '=', stock_location.id),
|
||||
])
|
||||
self.assertEqual(len(quant), 1)
|
||||
self.assertEqual(quant.quantity, 2)
|
||||
self.assertEqual(quant.lot_id.id, lot1.id)
|
||||
self.assertEqual(quant.in_date, Datetime.to_string(in_date2))
|
||||
|
||||
@@ -96,5 +96,5 @@
|
||||
<menuitem action="action_procurement_compute" id="menu_procurement_compute" parent="menu_stock_sched" sequence="2" groups="base.group_no_one"/>
|
||||
<menuitem action="procurement.action_compute_schedulers" id="menu_stock_proc_schedulers" parent="menu_stock_sched" sequence="1" groups="stock.group_stock_manager"/>
|
||||
<menuitem id="menu_stock_procurement" name="Automatic Procurements" parent="stock.menu_stock_config_settings" sequence="5"/>
|
||||
<menuitem action="procurement.procurement_order_action_exceptions" id="menu_stock_procurement_action" parent="stock.menu_warehouse_report" sequence="150" groups="stock.group_stock_manager"/>
|
||||
<menuitem action="procurement.procurement_order_action_exceptions" id="menu_stock_procurement_action" parent="stock.menu_warehouse_report" sequence="130" groups="stock.group_stock_manager"/>
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_move_line_tree" model="ir.ui.view">
|
||||
<field name="name">stock.move.line.tree</field>
|
||||
<field name="model">stock.move.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-muted="state == 'cancel'" decoration-danger="(state not in ('cancel','done'))" string="Move Lines" create="0">
|
||||
<field name="picking_id" string="Reference"/>
|
||||
<field name="create_date" invisible="1" groups="base.group_no_one"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="product_uom_id" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_dest_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="date" groups="base.group_no_one"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_move_line_form" model="ir.ui.view">
|
||||
<field name="name">stock.move.line.form</field>
|
||||
<field name="model">stock.move.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Move Lines" create="0" edit="0">
|
||||
<header>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="picking_id"/>
|
||||
<field name="create_date" groups="base.group_no_one"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="product_uom_id" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="location_dest_id" groups="stock.group_stock_multi_locations"/>
|
||||
<field name="date" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="stock_move_line_action" model="ir.actions.act_window">
|
||||
<field name="name">Stock Move Lines</field>
|
||||
<field name="res_model">stock.move.line</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_move_line_tree"/>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="stock_move_line_action" id="stock_move_line_menu" parent="stock.menu_warehouse_report" sequence="150" groups="base.group_no_one"/>
|
||||
</odoo>
|
||||
@@ -226,7 +226,7 @@
|
||||
<field name="model">stock.move</field>
|
||||
<field eval="1" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Stock Moves">
|
||||
<form string="Stock Moves" create="0" edit="0">
|
||||
<header>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,assigned,done" />
|
||||
</header>
|
||||
@@ -515,6 +515,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="stock_move_action" id="stock_move_menu" parent="stock.menu_warehouse_report" sequence="130"/>
|
||||
<menuitem action="stock_move_action" id="stock_move_menu" parent="stock.menu_warehouse_report" sequence="140" groups="base.group_no_one"/>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
<tree decoration-danger="quantity_done > reserved_availability" decoration-muted="scrapped == True" string="Stock Moves" editable="bottom">
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="date_expected" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="state" invisible="1" readonly="0"/>
|
||||
<field name="picking_type_id" invisible="1"/>
|
||||
<field name="location_id" invisible="1"/>
|
||||
<field name="location_dest_id" invisible="1"/>
|
||||
@@ -294,7 +294,7 @@
|
||||
<field name="product_id" required="1" attrs="{'readonly': ['&', ('state', '!=', 'draft'), ('additional', '=', False)]}"/>
|
||||
<field name="is_initial_demand_editable" invisible="1"/>
|
||||
<field name="product_uom_qty" string="Initial Demand" attrs="{'readonly': [('is_initial_demand_editable', '=', False), ('state', '!=', 'draft')]}"/>
|
||||
<field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft')]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('additional', '=', False)]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="reserved_availability" string="Reserved"/>
|
||||
<field name="quantity_done" string="Done" attrs="{'readonly': [('show_details_visible', '=', True)], 'invisible': ['|', ('state', 'in', ['draft', 'confirmed', 'waiting']), ('show_operations', '=', True)]}"/>
|
||||
<button name="action_show_details" string="Register lots, packs, location" type="object" icon="fa-list" attrs="{'invisible': [('show_details_visible', '=', False)]}"/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="model">stock.quant</field>
|
||||
<field eval="10" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Inventory Valuation">
|
||||
<search string="Quants">
|
||||
<field name="product_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="package_id" groups="stock.group_tracking_lot"/>
|
||||
@@ -16,8 +16,8 @@
|
||||
<filter name='internal_loc' string="Internal Locations" domain="[('location_id.usage','=', 'internal')]"/>
|
||||
<filter name='transit_loc' string="Transit Locations" domain="[('location_id.usage' ,'=', 'transit')]"/>
|
||||
<separator/>
|
||||
<filter name="negative" string="Negative" domain="[('quantity', '<', 0.0)]"/>
|
||||
<filter name="positive" string="Positive" domain="[('quantity', '>', 0.0)]"/>
|
||||
<filter name="negative" string="Negative Stock" domain="[('quantity', '<', 0.0)]"/>
|
||||
<filter name="positive" string="Positive Stock" domain="[('quantity', '>', 0.0)]"/>
|
||||
</group>
|
||||
<group expand='0' string='Group by...'>
|
||||
<filter string='Location' name="locationgroup" domain="[]" context="{'group_by': 'location_id'}"/>
|
||||
@@ -87,11 +87,10 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="quantsact"> <!-- Menu + python -->
|
||||
<field name="name">Inventory Valuation</field>
|
||||
<field name="name">Inventory</field>
|
||||
<field name="res_model">stock.quant</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,pivot</field>
|
||||
<field name="context">{'search_default_internal_loc': 1, 'search_default_productgroup': 1}</field>
|
||||
<field name="help">This analysis gives you a fast overview on the current stock level of your products and their current inventory value.</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="product_template_open_quants"> <!-- Not used -->
|
||||
@@ -113,7 +112,7 @@
|
||||
<field name="res_model">stock.quant</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_quants" name="Inventory Valuation" parent="stock.menu_warehouse_report" sequence="120" action="quantsact"/>
|
||||
<menuitem id="menu_quants" name="Inventory" parent="stock.menu_warehouse_report" sequence="100" action="quantsact"/>
|
||||
|
||||
<record id="quant_package_search_view" model="ir.ui.view">
|
||||
<field name="name">stock.quant.package.search</field>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<group>
|
||||
<field name="product_return_moves" nolabel="1">
|
||||
<tree editable="top" create="0">
|
||||
<field name="product_id" options="{'no_create': True, 'no_open': True}"/>
|
||||
<field name="product_id" options="{'no_create': True, 'no_open': True}" force_save="1"/>
|
||||
<field name="quantity"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
@@ -27,5 +27,5 @@
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'default_compute_at_date': 0}</field>
|
||||
</record>
|
||||
<menuitem id="stock.menu_quants" name="Inventory Valuation" parent="stock.menu_warehouse_report" sequence="120" action="action_wizard_stock_valuation_history"/>
|
||||
<menuitem id="menu_valuation" name="Inventory Valuation" parent="stock.menu_warehouse_report" sequence="110" action="action_wizard_stock_valuation_history"/>
|
||||
</odoo>
|
||||
|
||||
@@ -1016,8 +1016,8 @@ class Binary(http.Controller):
|
||||
elif status != 200 and download:
|
||||
return request.not_found()
|
||||
|
||||
height = int(height)
|
||||
width = int(width)
|
||||
height = int(height or 0)
|
||||
width = int(width or 0)
|
||||
if content and (width or height):
|
||||
# resize maximum 500*500
|
||||
if width > 500:
|
||||
|
||||
@@ -65,12 +65,10 @@ var AbstractField = Widget.extend({
|
||||
* @constructor
|
||||
* @param {Widget} parent
|
||||
* @param {string} name The field name defined in the model
|
||||
* @param {Object} record A record object (result of the get method of
|
||||
* @param {Object} record A record object (result of the get method of
|
||||
* a basic model)
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.mode=readonly] should be 'readonly' or 'edit'
|
||||
* @param {string} [options.standalone=false] this flag indicates that the
|
||||
* field does not have a controller to delegate to
|
||||
*/
|
||||
init: function (parent, name, record, options) {
|
||||
this._super(parent);
|
||||
@@ -160,10 +158,6 @@ var AbstractField = Widget.extend({
|
||||
// calls to the format (resp. parse) function.
|
||||
this.formatOptions = {};
|
||||
this.parseOptions = {};
|
||||
|
||||
// standalone is a flag indicating that the field does not have a
|
||||
// controller to delegate actions to
|
||||
this.standalone = options.standalone;
|
||||
},
|
||||
/**
|
||||
* When a field widget is appended to the DOM, its start method is called,
|
||||
@@ -379,6 +373,11 @@ var AbstractField = Widget.extend({
|
||||
* @private
|
||||
* @param {any} value
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.doNotSetDirty=false] if true, the basic model
|
||||
* will not consider that this field is dirty, even though it was changed.
|
||||
* Please do not use this flag unless you really need it. Our only use
|
||||
* case is currently the pad widget, which does a _setValue in the
|
||||
* renderEdit method.
|
||||
* @param {boolean} [options.forceChange=false] if true, the change event will be
|
||||
* triggered even if the new value is the same as the old one
|
||||
* @returns {Deferred}
|
||||
@@ -407,6 +406,7 @@ var AbstractField = Widget.extend({
|
||||
dataPointID: this.dataPointID,
|
||||
changes: changes,
|
||||
viewType: this.viewType,
|
||||
doNotSetDirty: options && options.doNotSetDirty,
|
||||
onSuccess: def.resolve.bind(def),
|
||||
onFailure: def.reject.bind(def),
|
||||
});
|
||||
|
||||
@@ -151,6 +151,9 @@ var DebouncedField = AbstractField.extend({
|
||||
});
|
||||
|
||||
var InputField = DebouncedField.extend({
|
||||
custom_events: _.extend({}, DebouncedField.prototype.custom_events, {
|
||||
field_changed: '_onFieldChanged',
|
||||
}),
|
||||
events: _.extend({}, DebouncedField.prototype.events, {
|
||||
'input': '_onInput',
|
||||
'change': '_onChange',
|
||||
@@ -168,6 +171,12 @@ var InputField = DebouncedField.extend({
|
||||
if (this.mode === 'edit') {
|
||||
this.tagName = 'input';
|
||||
}
|
||||
// We need to know if the widget is dirty (i.e. if the user has changed
|
||||
// the value, and those changes haven't been acknowledged yet by the
|
||||
// environment), to prevent erasing that new value on a reset (e.g.
|
||||
// coming by an onchange on another field)
|
||||
this.isDirty = false;
|
||||
this.lastChangeEvent = undefined;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -183,14 +192,21 @@ var InputField = DebouncedField.extend({
|
||||
return this.$input || $();
|
||||
},
|
||||
/**
|
||||
* Do not re-render this field if it was the origin of the onchange call.
|
||||
* FIXME: make the onchange work on itself without disturbing the user typing
|
||||
* Re-renders the widget if it isn't dirty. The widget is dirty if the user
|
||||
* changed the value, and that change hasn't been acknowledged yet by the
|
||||
* environment. For example, another field with an onchange has been updated
|
||||
* and this field is updated before the onchange returns. Two '_setValue'
|
||||
* are done (this is sequential), the first one returns and this widget is
|
||||
* reset. However, it has pending changes, so we don't re-render.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
reset: function (record, event) {
|
||||
this._reset(record, event);
|
||||
if (event && event.target === this) {
|
||||
if (!event || event === this.lastChangeEvent) {
|
||||
this.isDirty = false;
|
||||
}
|
||||
if (this.isDirty || (event && event.target === this)) {
|
||||
return $.when();
|
||||
} else {
|
||||
return this._render();
|
||||
@@ -265,6 +281,16 @@ var InputField = DebouncedField.extend({
|
||||
_onChange: function () {
|
||||
this._doAction();
|
||||
},
|
||||
/**
|
||||
* Listens to events 'field_changed' to keep track of the last event that
|
||||
* has been trigerred. This allows to detect that all changes have been
|
||||
* acknowledged by the environment.
|
||||
*
|
||||
* @param {OdooEvent} event 'field_changed' event
|
||||
*/
|
||||
_onFieldChanged: function (event) {
|
||||
this.lastChangeEvent = event;
|
||||
},
|
||||
/**
|
||||
* Called when the user is typing text -> By default this only calls a
|
||||
* debounced method to notify the outside world of the changes.
|
||||
@@ -273,6 +299,7 @@ var InputField = DebouncedField.extend({
|
||||
* @private
|
||||
*/
|
||||
_onInput: function () {
|
||||
this.isDirty = true;
|
||||
this._doDebouncedAction();
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -223,25 +223,21 @@ var FieldMany2One = AbstractField.extend({
|
||||
_quickCreate: function (name) {
|
||||
var self = this;
|
||||
var slowCreate = this._searchCreatePopup.bind(this, "form", false, this._createContext(name));
|
||||
var action = function () {
|
||||
return self._rpc({
|
||||
model: self.field.relation,
|
||||
method: 'name_create',
|
||||
args: [name],
|
||||
context: self.record.getContext(self.recordParams),
|
||||
}).then(function (result) {
|
||||
if (self.mode === "edit") {
|
||||
self.reinitialize({id: result[0], display_name: result[1]});
|
||||
}
|
||||
}, slowCreate);
|
||||
};
|
||||
|
||||
if (this.nodeOptions.quick_create) {
|
||||
if (this.standalone) {
|
||||
action();
|
||||
} else {
|
||||
this.trigger_up('mutexify', {action: action});
|
||||
}
|
||||
this.trigger_up('mutexify', {
|
||||
action: function () {
|
||||
return self._rpc({
|
||||
model: self.field.relation,
|
||||
method: 'name_create',
|
||||
args: [name],
|
||||
context: self.record.getContext(self.recordParams),
|
||||
}).then(function (result) {
|
||||
if (self.mode === "edit") {
|
||||
self.reinitialize({id: result[0], display_name: result[1]});
|
||||
}
|
||||
}, slowCreate);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
slowCreate();
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ var RedirectWarningHandler = Dialog.extend(ExceptionHandler, {
|
||||
}
|
||||
});
|
||||
|
||||
core.crash_registry.add('openerp.exceptions.RedirectWarning', RedirectWarningHandler);
|
||||
core.crash_registry.add('odoo.exceptions.RedirectWarning', RedirectWarningHandler);
|
||||
|
||||
return CrashManager;
|
||||
});
|
||||
|
||||
@@ -32,7 +32,6 @@ var AbstractController = Widget.extend({
|
||||
* @param {string} params.modelName
|
||||
* @param {any} [params.handle] a handle that will be given to the model (some id)
|
||||
* @param {any} params.initialState the initialState
|
||||
* @param {string} [params.noContentHelp]
|
||||
*/
|
||||
init: function (parent, model, renderer, params) {
|
||||
this._super.apply(this, arguments);
|
||||
@@ -42,7 +41,6 @@ var AbstractController = Widget.extend({
|
||||
this.handle = params.handle;
|
||||
this.activeActions = params.activeActions;
|
||||
this.initialState = params.initialState;
|
||||
this.noContentHelp = params.noContentHelp;
|
||||
},
|
||||
/**
|
||||
* Simply renders and updates the url.
|
||||
@@ -223,31 +221,6 @@ var AbstractController = Widget.extend({
|
||||
_pushState: function (state) {
|
||||
this.trigger_up('push_state', state || {});
|
||||
},
|
||||
/**
|
||||
* Hide or show the nocontent helper. For this, it also remove the renderer
|
||||
* from the dom, and reattach it when necessary.
|
||||
*
|
||||
* This method is a helper for controllers that want to display a help
|
||||
* message when no content is available. It is suggested to override
|
||||
* _update to call this method.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} hasNoContent
|
||||
*/
|
||||
_toggleNoContentHelper: function (hasNoContent) {
|
||||
if (hasNoContent && this.noContentHelp) {
|
||||
this.renderer.$el.detach();
|
||||
var $msg = $('<div>')
|
||||
.addClass('oe_view_nocontent')
|
||||
.html(this.noContentHelp);
|
||||
this.$el.html($msg);
|
||||
} else {
|
||||
if (!document.contains(this.renderer.el)) {
|
||||
this.$('div.oe_view_nocontent').remove();
|
||||
this.$el.append(this.renderer.$el);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* This method is called after each update or when the start method is
|
||||
* completed.
|
||||
|
||||
@@ -15,11 +15,13 @@ return Widget.extend({
|
||||
* @param {Widget} parent
|
||||
* @param {any} state
|
||||
* @param {Object} params
|
||||
* @param {string} [params.noContentHelp]
|
||||
*/
|
||||
init: function (parent, state, params) {
|
||||
this._super(parent);
|
||||
this.state = state;
|
||||
this.arch = params.arch;
|
||||
this.noContentHelp = params.noContentHelp;
|
||||
},
|
||||
/**
|
||||
* The rendering can be asynchronous (but it is not encouraged). The start
|
||||
|
||||
@@ -78,6 +78,7 @@ var AbstractView = Class.extend({
|
||||
init: function (viewInfo, params) {
|
||||
this.rendererParams = {
|
||||
arch: viewInfo.arch,
|
||||
noContentHelp: params.action && params.action.help,
|
||||
};
|
||||
|
||||
this.controllerParams = {
|
||||
@@ -88,7 +89,6 @@ var AbstractView = Class.extend({
|
||||
delete: viewInfo.arch.attrs.delete ? JSON.parse(viewInfo.arch.attrs.delete) : true,
|
||||
duplicate: viewInfo.arch.attrs.duplicate ? JSON.parse(viewInfo.arch.attrs.duplicate) : true,
|
||||
},
|
||||
noContentHelp: params.action && params.action.help,
|
||||
};
|
||||
|
||||
this.loadParams = {
|
||||
|
||||
@@ -8,7 +8,6 @@ odoo.define('web.BasicController', function (require) {
|
||||
*/
|
||||
|
||||
var AbstractController = require('web.AbstractController');
|
||||
var concurrency = require('web.concurrency');
|
||||
var core = require('web.core');
|
||||
var Dialog = require('web.Dialog');
|
||||
var FieldManagerMixin = require('web.FieldManagerMixin');
|
||||
@@ -19,7 +18,6 @@ var _t = core._t;
|
||||
var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
custom_events: _.extend({}, AbstractController.prototype.custom_events, FieldManagerMixin.custom_events, {
|
||||
discard_changes: '_onDiscardChanges',
|
||||
mutexify: '_onMutexify',
|
||||
reload: '_onReload',
|
||||
sidebar_data_asked: '_onSidebarDataAsked',
|
||||
translate: '_onTranslate',
|
||||
@@ -39,7 +37,6 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
FieldManagerMixin.init.call(this, this.model);
|
||||
this.handle = params.initialState.id;
|
||||
this.mode = params.mode || 'readonly';
|
||||
this.mutex = new concurrency.Mutex();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -238,7 +235,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
var recordID = record.data.id;
|
||||
this.trigger_up('execute_action', {
|
||||
action_data: _.extend({}, attrs, {
|
||||
context: record.getContext({additionalContext: attrs.context}),
|
||||
context: record.getContext({additionalContext: attrs.context || {}}),
|
||||
}),
|
||||
model: record.model,
|
||||
res_ids: [recordID],
|
||||
@@ -361,17 +358,6 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
model: this.modelName,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Used by list and kanban views to determine whether or not to display
|
||||
* the no content helper (if there is no data in the state to display)
|
||||
*
|
||||
* @private
|
||||
* @param {Object} state
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_hasContent: function (state) {
|
||||
return state.count !== 0;
|
||||
},
|
||||
/**
|
||||
* Helper function to display a warning that some fields have an invalid
|
||||
* value. This is used when a save operation cannot be completed.
|
||||
@@ -537,15 +523,6 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
}
|
||||
FieldManagerMixin._onFieldChanged.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
* @param {function} ev.data.action the function to execute in the mutex
|
||||
*/
|
||||
_onMutexify: function (ev) {
|
||||
ev.stopPropagation(); // prevent other controllers from handling this request
|
||||
this.mutex.exec(ev.data.action);
|
||||
},
|
||||
/**
|
||||
* When a reload event triggers up, we need to reload the full view.
|
||||
* For example, after a form view dialog saved some data.
|
||||
|
||||
@@ -628,12 +628,12 @@ var BasicModel = AbstractModel.extend({
|
||||
*
|
||||
* @param {string} record_id
|
||||
* @param {Object} changes a map field => new value
|
||||
* @param {string} [viewType] current viewType. If not set, we will assume
|
||||
* main viewType from the record
|
||||
* @param {Object} [options] will be transferred to the applyChange method
|
||||
* @see _applyChange
|
||||
* @returns {string[]} list of changed fields
|
||||
*/
|
||||
notifyChanges: function (record_id, changes, viewType) {
|
||||
return this.mutex.exec(this._applyChange.bind(this, record_id, changes, viewType));
|
||||
notifyChanges: function (record_id, changes, options) {
|
||||
return this.mutex.exec(this._applyChange.bind(this, record_id, changes, options));
|
||||
},
|
||||
/**
|
||||
* Reload all data for a given resource
|
||||
@@ -956,23 +956,30 @@ var BasicModel = AbstractModel.extend({
|
||||
*
|
||||
* @param {string} recordID
|
||||
* @param {Object} changes
|
||||
* @param {string} [viewType] current viewType. If not set, we will assume
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.doNotSetDirty=false] if this flag is set to
|
||||
* true, then we will not tag the record as dirty. This should be avoided
|
||||
* for most situations.
|
||||
* @param {string} [options.viewType] current viewType. If not set, we will assume
|
||||
* main viewType from the record
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_applyChange: function (recordID, changes, viewType) {
|
||||
_applyChange: function (recordID, changes, options) {
|
||||
var self = this;
|
||||
var record = this.localData[recordID];
|
||||
var field;
|
||||
var defs = [];
|
||||
options = options || {};
|
||||
record._changes = record._changes || {};
|
||||
record._isDirty = true;
|
||||
if (!options.doNotSetDirty) {
|
||||
record._isDirty = true;
|
||||
}
|
||||
|
||||
// apply changes to local data
|
||||
for (var fieldName in changes) {
|
||||
field = record.fields[fieldName];
|
||||
if (field.type === 'one2many' || field.type === 'many2many') {
|
||||
defs.push(this._applyX2ManyChange(record, fieldName, changes[fieldName], viewType));
|
||||
defs.push(this._applyX2ManyChange(record, fieldName, changes[fieldName], options.viewType));
|
||||
} else if (field.type === 'many2one' || field.type === 'reference') {
|
||||
defs.push(this._applyX2OneChange(record, fieldName, changes[fieldName]));
|
||||
} else {
|
||||
@@ -993,7 +1000,7 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
var onchangeDef;
|
||||
if (onChangeFields.length) {
|
||||
onchangeDef = self._performOnChange(record, onChangeFields, viewType).then(function (result) {
|
||||
onchangeDef = self._performOnChange(record, onChangeFields, options.viewType).then(function (result) {
|
||||
delete record._warning;
|
||||
return _.keys(changes).concat(Object.keys(result && result.value || {}));
|
||||
});
|
||||
@@ -2196,7 +2203,7 @@ var BasicModel = AbstractModel.extend({
|
||||
// remove readonly fields from the list of changes
|
||||
if (!withReadonly && fieldName in changes || fieldName in commands) {
|
||||
var editionViewType = record._editionViewType[fieldName] || viewType;
|
||||
if (this._isFieldReadonly(record, fieldName, editionViewType)) {
|
||||
if (this._isFieldProtected(record, fieldName, editionViewType)) {
|
||||
delete changes[fieldName];
|
||||
continue;
|
||||
}
|
||||
@@ -2555,9 +2562,10 @@ var BasicModel = AbstractModel.extend({
|
||||
return context;
|
||||
},
|
||||
/**
|
||||
* Returns true if the field is readonly (checking first in the modifiers,
|
||||
* and if there is no readonly modifier, checking the readonly attribute of
|
||||
* the field).
|
||||
* Returns true if the field is protected against changes, looking for a
|
||||
* readonly modifier unless there is a force_save modifier (checking first
|
||||
* in the modifiers, and if there is no readonly modifier, checking the
|
||||
* readonly attribute of the field).
|
||||
*
|
||||
* @private
|
||||
* @param {Object} record an element from the localData
|
||||
@@ -2566,12 +2574,12 @@ var BasicModel = AbstractModel.extend({
|
||||
* main viewType from the record
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isFieldReadonly: function (record, fieldName, viewType) {
|
||||
_isFieldProtected: function (record, fieldName, viewType) {
|
||||
var fieldInfo = record.fieldsInfo[viewType || record.viewType][fieldName];
|
||||
if (fieldInfo) {
|
||||
var rawModifiers = JSON.parse(fieldInfo.modifiers || "{}");
|
||||
var modifiers = this._evalModifiers(record, rawModifiers);
|
||||
return modifiers.readonly;
|
||||
return modifiers.readonly && !fieldInfo.force_save;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -317,6 +317,16 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
_getModifiersData: function (node) {
|
||||
return _.findWhere(this.allModifiersData, {node: node});
|
||||
},
|
||||
/**
|
||||
* Used by list and kanban renderers to determine whether or not to display
|
||||
* the no content helper (if there is no data in the state to display)
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_hasContent: function () {
|
||||
return this.state.count !== 0;
|
||||
},
|
||||
/**
|
||||
* Registers or updates the modifiers data associated to the given node.
|
||||
* This method is quiet complex as it handles all the needs of the basic
|
||||
@@ -486,6 +496,20 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
|
||||
return widget;
|
||||
},
|
||||
/**
|
||||
* Renders the nocontent helper.
|
||||
*
|
||||
* This method is a helper for renderers that want to display a help
|
||||
* message when no content is available.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_renderNoContentHelper: function () {
|
||||
var $msg = $('<div>')
|
||||
.addClass('oe_view_nocontent')
|
||||
.html(this.noContentHelp);
|
||||
this.$el.html($msg);
|
||||
},
|
||||
/**
|
||||
* Actual rendering. Supposed to be overridden by concrete renderers.
|
||||
* The basic responsabilities of _renderView are:
|
||||
|
||||
@@ -302,44 +302,38 @@ return AbstractModel.extend({
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Converts this.data.filters into a domain
|
||||
*
|
||||
* @private
|
||||
* @returns {Array}
|
||||
*/
|
||||
_getFilterDomain: function () {
|
||||
var domain = [];
|
||||
_.each(this.data.filters, function (filter) {
|
||||
if (filter.all) return;
|
||||
var nb = {};
|
||||
var not = {};
|
||||
var contains = {};
|
||||
_.each(filter.filters, function (f) {
|
||||
if (!contains[filter.fieldName]) {
|
||||
contains[filter.fieldName] = [];
|
||||
not[filter.fieldName] = [];
|
||||
nb[filter.fieldName] = 0;
|
||||
}
|
||||
if (f.value === 'all' && f.active) {
|
||||
contains[filter.fieldName].all = true;
|
||||
} else {
|
||||
nb[filter.fieldName]++;
|
||||
}
|
||||
// List authorized values for every field
|
||||
// fields with an active 'all' filter are skipped
|
||||
var authorizedValues = {};
|
||||
|
||||
if (f.active && contains[filter.fieldName].indexOf(f.value) === -1) {
|
||||
contains[filter.fieldName].push(f.value);
|
||||
}
|
||||
if (!f.active && not[filter.fieldName].indexOf(f.value) === -1) {
|
||||
not[filter.fieldName].push(f.value);
|
||||
_.each(this.data.filters, function (filter) {
|
||||
// Skip 'all' filters because they do not affect the domain
|
||||
if (filter.all) return;
|
||||
|
||||
// Loop over subfilters to complete authorizedValues
|
||||
_.each(filter.filters, function (f) {
|
||||
if (!authorizedValues[filter.fieldName])
|
||||
authorizedValues[filter.fieldName] = [];
|
||||
|
||||
if (f.active) {
|
||||
authorizedValues[filter.fieldName].push(f.value);
|
||||
}
|
||||
});
|
||||
|
||||
for (var field in contains) {
|
||||
if (contains[field].all || contains[field].length === nb[field]) {
|
||||
continue;
|
||||
}
|
||||
if (contains[field].length) {
|
||||
domain.push([field, 'in', contains[field]]);
|
||||
} else if (not[field].length && not[field].length !== nb[field]) {
|
||||
domain.push([field, 'not in', not[field]]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Compute the domain
|
||||
var domain = [];
|
||||
for (var field in authorizedValues) {
|
||||
domain.push([field, 'in', authorizedValues[field]]);
|
||||
}
|
||||
|
||||
return domain;
|
||||
},
|
||||
/**
|
||||
@@ -393,19 +387,16 @@ return AbstractModel.extend({
|
||||
this.data.fc_options = this._getFullCalendarOptions();
|
||||
|
||||
var defs = _.map(this.data.filters, this._loadFilter.bind(this));
|
||||
|
||||
return $.when.apply($, defs).then(function () {
|
||||
var eventsDef = $.Deferred().resolve([]);
|
||||
var filterDomain = self._getFilterDomain();
|
||||
if (filterDomain.length || _.isEmpty(self.data.filters)) {
|
||||
eventsDef = self._rpc({
|
||||
return self._rpc({
|
||||
model: self.modelName,
|
||||
method: 'search_read',
|
||||
context: self.data.context,
|
||||
fields: self.fieldNames,
|
||||
domain: self.data.domain.concat(self._getRangeDomain()).concat(filterDomain)
|
||||
});
|
||||
}
|
||||
return eventsDef.then(function (events) {
|
||||
domain: self.data.domain.concat(self._getRangeDomain()).concat(self._getFilterDomain())
|
||||
})
|
||||
.then(function (events) {
|
||||
self._parseServerData(events);
|
||||
self.data.data = _.map(events, self._recordToCalendarEvent.bind(self));
|
||||
return $.when(
|
||||
|
||||
@@ -330,11 +330,12 @@ return AbstractRenderer.extend({
|
||||
}
|
||||
element.find('.fc-content .fc-time').text(display_hour);
|
||||
},
|
||||
height: 'auto',
|
||||
height: 'parent',
|
||||
unselectAuto: false,
|
||||
});
|
||||
|
||||
this.$calendar.fullCalendar(fc_options);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
},
|
||||
/**
|
||||
* Initialize the mini calendar in the sidebar
|
||||
|
||||
@@ -9,11 +9,13 @@ odoo.define('web.FieldManagerMixin', function (require) {
|
||||
*/
|
||||
|
||||
var BasicModel = require('web.BasicModel');
|
||||
var concurrency = require('web.concurrency');
|
||||
|
||||
var FieldManagerMixin = {
|
||||
custom_events: {
|
||||
field_changed: '_onFieldChanged',
|
||||
load: '_onLoad',
|
||||
mutexify: '_onMutexify',
|
||||
},
|
||||
/**
|
||||
* A FieldManagerMixin can be initialized with an instance of a basicModel.
|
||||
@@ -23,6 +25,7 @@ var FieldManagerMixin = {
|
||||
*/
|
||||
init: function (model) {
|
||||
this.model = model || new BasicModel(this);
|
||||
this.mutex = new concurrency.Mutex();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -43,7 +46,8 @@ var FieldManagerMixin = {
|
||||
*/
|
||||
_applyChanges: function (dataPointID, changes, event) {
|
||||
var self = this;
|
||||
return this.model.notifyChanges(dataPointID, changes, event.data.viewType)
|
||||
var options = _.pick(event.data, 'viewType', 'doNotSetDirty');
|
||||
return this.model.notifyChanges(dataPointID, changes, options)
|
||||
.then(function (result) {
|
||||
if (event.data.force_save) {
|
||||
return self.model.save(dataPointID).then(function () {
|
||||
@@ -129,6 +133,15 @@ var FieldManagerMixin = {
|
||||
data.on_success(self.model.get(db_id));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
* @param {function} ev.data.action the function to execute in the mutex
|
||||
*/
|
||||
_onMutexify: function (ev) {
|
||||
ev.stopPropagation(); // prevent other field managers from handling this request
|
||||
this.mutex.exec(ev.data.action);
|
||||
},
|
||||
};
|
||||
|
||||
return FieldManagerMixin;
|
||||
|
||||
@@ -115,18 +115,20 @@ var FormRenderer = BasicRenderer.extend({
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Disable stat buttons so that they can't be clicked anymore
|
||||
* Disable statusbar buttons and stat buttons so that they can't be clicked anymore
|
||||
*
|
||||
*/
|
||||
disableButtons: function () {
|
||||
this.$('.oe_button_box button').attr('disabled', true);
|
||||
this.$('.o_statusbar_buttons button, .oe_button_box button')
|
||||
.attr('disabled', true);
|
||||
},
|
||||
/**
|
||||
* Enable stat buttons so they can be clicked again
|
||||
* Enable statusbar buttons and stat buttons so they can be clicked again
|
||||
*
|
||||
*/
|
||||
enableButtons: function () {
|
||||
this.$('.oe_button_box button').removeAttr('disabled');
|
||||
this.$('.o_statusbar_buttons button, .oe_button_box button')
|
||||
.removeAttr('disabled');
|
||||
},
|
||||
/**
|
||||
* returns the active tab pages for each notebook
|
||||
|
||||
@@ -114,13 +114,13 @@ return AbstractModel.extend({
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_loadGraph: function () {
|
||||
var fields = _.map(this.chart.groupedBy, function (groupBy) {
|
||||
var groupedBy = this.chart.groupedBy.length ? this.chart.groupedBy : this.initialGroupBys;
|
||||
var fields = _.map(groupedBy, function (groupBy) {
|
||||
return groupBy.split(':')[0];
|
||||
});
|
||||
if (this.chart.measure !== '__count__') {
|
||||
fields = fields.concat(this.chart.measure);
|
||||
}
|
||||
var groupedBy = this.chart.groupedBy.length ? this.chart.groupedBy : this.initialGroupBys;
|
||||
return this._rpc({
|
||||
model: this.modelName,
|
||||
method: 'read_group',
|
||||
|
||||
@@ -80,19 +80,6 @@ var KanbanController = BasicController.extend({
|
||||
_confirmSave: function (id) {
|
||||
return this.renderer.updateRecord(this.model.get(id));
|
||||
},
|
||||
/**
|
||||
* The nocontent helper should be displayed in kanban:
|
||||
* - ungrouped: if there is no records
|
||||
* - grouped: if there is no groups and no column quick create
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_hasContent: function (state) {
|
||||
return this._super.apply(this, arguments) ||
|
||||
this.createColumnEnabled ||
|
||||
(state.groupedBy.length && state.data.length);
|
||||
},
|
||||
/**
|
||||
* The column quick create should be displayed in kanban iff grouped by an
|
||||
* m2o field and group_create action enabled.
|
||||
@@ -138,17 +125,6 @@ var KanbanController = BasicController.extend({
|
||||
.toggleClass('btn-default', createMuted);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} state
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_update: function (state) {
|
||||
var hasNoContent = !this._hasContent(state);
|
||||
this.$el.toggleClass('o_kanban_nocontent', hasNoContent);
|
||||
this._toggleNoContentHelper(hasNoContent);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
|
||||
@@ -100,6 +100,8 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
viewType: 'kanban',
|
||||
});
|
||||
this.columnOptions = _.extend({}, params.column_options, { qweb: this.qweb });
|
||||
|
||||
this._setState(state);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -167,11 +169,31 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
record.update(recordState);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
updateState: function (state) {
|
||||
this._setState(state);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The nocontent helper should be displayed in kanban:
|
||||
* - ungrouped: if there is no records
|
||||
* - grouped: if there is no groups and no column quick create
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_hasContent: function () {
|
||||
return this._super.apply(this, arguments) ||
|
||||
this.createColumnEnabled ||
|
||||
(this.state.groupedBy.length && this.state.data.length);
|
||||
},
|
||||
/**
|
||||
* Renders empty invisible divs in a document fragment.
|
||||
*
|
||||
@@ -193,28 +215,6 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
*/
|
||||
_renderGrouped: function (fragment) {
|
||||
var self = this;
|
||||
var groupByFieldAttrs = this.state.fields[this.state.groupedBy[0]];
|
||||
var groupByFieldInfo = this.state.fieldsInfo.kanban[this.state.groupedBy[0]];
|
||||
// Deactivate the drag'n'drop if the groupedBy field:
|
||||
// - is a date or datetime since we group by month or
|
||||
// - is readonly
|
||||
var draggable = true;
|
||||
if (groupByFieldAttrs) {
|
||||
if (groupByFieldAttrs.type === "date" || groupByFieldAttrs.type === "datetime") {
|
||||
draggable = false;
|
||||
} else if (groupByFieldAttrs.readonly !== undefined) {
|
||||
draggable = !(groupByFieldAttrs.readonly);
|
||||
}
|
||||
}
|
||||
var groupedByM2O = groupByFieldAttrs && (groupByFieldAttrs.type === 'many2one');
|
||||
var grouped_by_field = groupedByM2O && groupByFieldAttrs.relation;
|
||||
var groupByTooltip = groupByFieldInfo && groupByFieldInfo.options.group_by_tooltip;
|
||||
this.columnOptions = _.extend(this.columnOptions, {
|
||||
draggable: draggable,
|
||||
group_by_tooltip: groupByTooltip,
|
||||
grouped_by_m2o: groupedByM2O,
|
||||
relation: grouped_by_field,
|
||||
});
|
||||
|
||||
// Render columns
|
||||
_.each(this.state.data, function (group) {
|
||||
@@ -228,7 +228,7 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
}
|
||||
});
|
||||
|
||||
if (groupedByM2O) {
|
||||
if (this.groupedByM2O) {
|
||||
// Enable column sorting
|
||||
this.$el.sortable({
|
||||
axis: 'x',
|
||||
@@ -249,7 +249,7 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
});
|
||||
|
||||
// Enable column quickcreate
|
||||
if (this.columnOptions.group_creatable) {
|
||||
if (this.createColumnEnabled) {
|
||||
this.quickCreate = new ColumnQuickCreate(this);
|
||||
this.quickCreate.appendTo(fragment);
|
||||
}
|
||||
@@ -280,20 +280,62 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
_renderView: function () {
|
||||
var oldWidgets = this.widgets;
|
||||
this.widgets = [];
|
||||
var isGrouped = !!this.state.groupedBy.length;
|
||||
this.$el.toggleClass('o_kanban_grouped', isGrouped);
|
||||
this.$el.toggleClass('o_kanban_ungrouped', !isGrouped);
|
||||
this.$el.empty();
|
||||
|
||||
var fragment = document.createDocumentFragment();
|
||||
if (isGrouped) {
|
||||
this._renderGrouped(fragment);
|
||||
var displayNoContentHelper = !this._hasContent() && !!this.noContentHelp;
|
||||
this.$el.toggleClass('o_kanban_nocontent', displayNoContentHelper);
|
||||
if (displayNoContentHelper) {
|
||||
// display the no content helper if there is no data to display
|
||||
this._renderNoContentHelper();
|
||||
} else {
|
||||
this._renderUngrouped(fragment);
|
||||
var isGrouped = !!this.state.groupedBy.length;
|
||||
this.$el.toggleClass('o_kanban_grouped', isGrouped);
|
||||
this.$el.toggleClass('o_kanban_ungrouped', !isGrouped);
|
||||
var fragment = document.createDocumentFragment();
|
||||
// render the kanban view
|
||||
if (isGrouped) {
|
||||
this._renderGrouped(fragment);
|
||||
} else {
|
||||
this._renderUngrouped(fragment);
|
||||
}
|
||||
this.$el.append(fragment);
|
||||
}
|
||||
this.$el.append(fragment);
|
||||
|
||||
return this._super.apply(this, arguments).then(_.invoke.bind(_, oldWidgets, 'destroy'));
|
||||
},
|
||||
/**
|
||||
* Sets the current state and updates some internal attributes accordingly.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} state
|
||||
*/
|
||||
_setState: function (state) {
|
||||
this.state = state;
|
||||
|
||||
var groupByFieldAttrs = state.fields[state.groupedBy[0]];
|
||||
var groupByFieldInfo = state.fieldsInfo.kanban[state.groupedBy[0]];
|
||||
// Deactivate the drag'n'drop if the groupedBy field:
|
||||
// - is a date or datetime since we group by month or
|
||||
// - is readonly
|
||||
var draggable = true;
|
||||
if (groupByFieldAttrs) {
|
||||
if (groupByFieldAttrs.type === "date" || groupByFieldAttrs.type === "datetime") {
|
||||
draggable = false;
|
||||
} else if (groupByFieldAttrs.readonly !== undefined) {
|
||||
draggable = !(groupByFieldAttrs.readonly);
|
||||
}
|
||||
}
|
||||
this.groupedByM2O = groupByFieldAttrs && (groupByFieldAttrs.type === 'many2one');
|
||||
var grouped_by_field = this.groupedByM2O && groupByFieldAttrs.relation;
|
||||
var groupByTooltip = groupByFieldInfo && groupByFieldInfo.options.group_by_tooltip;
|
||||
this.columnOptions = _.extend(this.columnOptions, {
|
||||
draggable: draggable,
|
||||
group_by_tooltip: groupByTooltip,
|
||||
grouped_by_m2o: this.groupedByM2O,
|
||||
relation: grouped_by_field,
|
||||
});
|
||||
this.createColumnEnabled = this.groupedByM2O && this.columnOptions.group_creatable;
|
||||
},
|
||||
});
|
||||
|
||||
return KanbanRenderer;
|
||||
|
||||
@@ -188,7 +188,6 @@ var ListController = BasicController.extend({
|
||||
position: self.editable,
|
||||
});
|
||||
}).then(function (recordID) {
|
||||
self._toggleNoContentHelper(false);
|
||||
var state = self.model.get(self.handle);
|
||||
self.renderer.updateState(state, {});
|
||||
self.renderer.editRecord(recordID);
|
||||
@@ -283,12 +282,10 @@ var ListController = BasicController.extend({
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} state
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_update: function (state) {
|
||||
_update: function () {
|
||||
this.selectedRecords = [];
|
||||
this._toggleNoContentHelper(!this._hasContent(state));
|
||||
this._toggleSidebar();
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ odoo.define('web.EditableListRenderer', function (require) {
|
||||
*/
|
||||
var core = require('web.core');
|
||||
var ListRenderer = require('web.ListRenderer');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
@@ -153,12 +154,32 @@ ListRenderer.include({
|
||||
*/
|
||||
setRowMode: function (recordID, mode) {
|
||||
var self = this;
|
||||
var rowIndex = _.findIndex(this.state.data, {id: recordID});
|
||||
|
||||
// find the record and its row index (handles ungrouped and grouped cases
|
||||
// as even if the grouped list doesn't support edition, it may contain
|
||||
// a widget allowing the edition in readonly (e.g. priority), so it
|
||||
// should be able to update a record as well)
|
||||
var record;
|
||||
var rowIndex;
|
||||
if (this.state.groupedBy.length) {
|
||||
rowIndex = -1;
|
||||
var count = 0;
|
||||
utils.traverse_records(this.state, function (r) {
|
||||
if (r.id === recordID) {
|
||||
record = r;
|
||||
rowIndex = count;
|
||||
}
|
||||
count++;
|
||||
});
|
||||
} else {
|
||||
rowIndex = _.findIndex(this.state.data, {id: recordID});
|
||||
record = this.state.data[rowIndex];
|
||||
}
|
||||
|
||||
if (rowIndex < 0) {
|
||||
return $.when();
|
||||
}
|
||||
var editMode = (mode === 'edit');
|
||||
var record = this.state.data[rowIndex];
|
||||
|
||||
this.currentRow = editMode ? rowIndex : null;
|
||||
var $row = this.$('.o_data_row:nth(' + rowIndex + ')');
|
||||
|
||||
@@ -30,7 +30,6 @@ var FIELD_CLASSES = {
|
||||
};
|
||||
|
||||
var ListRenderer = BasicRenderer.extend({
|
||||
className: 'table-responsive',
|
||||
events: {
|
||||
'click tbody tr': '_onRowClicked',
|
||||
'click tbody .o_list_record_selector': '_onSelectRecord',
|
||||
@@ -577,12 +576,24 @@ var ListRenderer = BasicRenderer.extend({
|
||||
_renderView: function () {
|
||||
var self = this;
|
||||
|
||||
this.$el
|
||||
.removeClass('table-responsive')
|
||||
.empty();
|
||||
|
||||
// destroy the previously instantiated pagers, if any
|
||||
_.invoke(this.pagers, 'destroy');
|
||||
this.pagers = [];
|
||||
|
||||
// display the no content helper if there is no data to display
|
||||
if (!this._hasContent() && this.noContentHelp) {
|
||||
this._renderNoContentHelper();
|
||||
return this._super();
|
||||
}
|
||||
|
||||
var $table = $('<table>').addClass('o_list_view table table-condensed table-striped');
|
||||
this.$el.empty().append($table);
|
||||
this.$el
|
||||
.addClass('table-responsive')
|
||||
.append($table);
|
||||
var is_grouped = !!this.state.groupedBy.length;
|
||||
this._computeAggregates();
|
||||
$table.toggleClass('o_list_view_grouped', is_grouped);
|
||||
|
||||
@@ -170,21 +170,8 @@ var PivotController = AbstractController.extend({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_update: function (state) {
|
||||
_update: function () {
|
||||
this._updateButtons();
|
||||
var hasNoContent = !state.has_data || !state.measures.length;
|
||||
|
||||
if (hasNoContent) {
|
||||
if (!this.$('.oe_view_nocontent').length) {
|
||||
this.renderer.$el.detach();
|
||||
this.$el.append(QWeb.render('PivotView.nodata'));
|
||||
}
|
||||
} else {
|
||||
if (!document.contains(this.renderer.el)) {
|
||||
this.$('div.oe_view_nocontent').remove();
|
||||
this.$el.append(this.renderer.$el);
|
||||
}
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,11 @@ odoo.define('web.PivotRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractRenderer = require('web.AbstractRenderer');
|
||||
var core = require('web.core');
|
||||
var field_utils = require('web.field_utils');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
|
||||
var PivotRenderer = AbstractRenderer.extend({
|
||||
tagName: 'table',
|
||||
className: 'table-hover table-condensed table-bordered',
|
||||
@@ -15,15 +18,32 @@ var PivotRenderer = AbstractRenderer.extend({
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Used to determine whether or not to display the no content helper.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_hasContent: function () {
|
||||
return this.state.has_data && this.state.measures.length;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_render: function () {
|
||||
if (!this.state.has_data) {
|
||||
if (!this._hasContent()) {
|
||||
// display the nocontent helper
|
||||
this.replaceElement(QWeb.render('PivotView.nodata'));
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
|
||||
if (!this.$el.is('table')) {
|
||||
// coming from the no content helper, so the root element has to be
|
||||
// re-rendered before rendering and appending its content
|
||||
this.renderElement();
|
||||
}
|
||||
var $fragment = $(document.createDocumentFragment());
|
||||
var $table = $('<table>').appendTo($fragment);
|
||||
var $thead = $('<thead>').appendTo($table);
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
*/
|
||||
|
||||
// No content helper
|
||||
.o_view_manager_content > div {
|
||||
height: 100%;
|
||||
}
|
||||
.oe_view_nocontent {
|
||||
a {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -849,6 +849,122 @@ QUnit.module('basic_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('input field: change value before pending onchange returns', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
product_id: function () {},
|
||||
};
|
||||
|
||||
var def;
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="p">' +
|
||||
'<tree editable="bottom">' +
|
||||
'<field name="product_id"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (args.method === "onchange") {
|
||||
return $.when(def).then(function () {
|
||||
return result;
|
||||
});
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
});
|
||||
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
assert.strictEqual(form.$('input[name="foo"]').val(), 'My little Foo Value',
|
||||
'should contain the default value');
|
||||
|
||||
def = $.Deferred();
|
||||
form.$('.o_field_many2one input').click();
|
||||
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
|
||||
$dropdown.find('li:first()').click();
|
||||
|
||||
// set foo before onchange
|
||||
form.$('input[name="foo"]').val("tralala").trigger('input');
|
||||
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
|
||||
'input should contain tralala');
|
||||
|
||||
// complete the onchange
|
||||
def.resolve();
|
||||
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
|
||||
'input should contain the same value as before onchange');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('input field: change value before pending onchange renaming', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
product_id: function (obj) {
|
||||
obj.foo = 'on change value';
|
||||
},
|
||||
};
|
||||
|
||||
var def = $.Deferred();
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="product_id"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (args.method === "onchange") {
|
||||
return $.when(def).then(function () {
|
||||
return result;
|
||||
});
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('input[name="foo"]').val(), 'yop',
|
||||
'should contain the correct value');
|
||||
|
||||
form.$('.o_field_many2one input').click();
|
||||
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
|
||||
$dropdown.find('li:first()').click();
|
||||
|
||||
// set foo before onchange
|
||||
form.$('input[name="foo"]').val("tralala").trigger('input');
|
||||
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
|
||||
'input should contain tralala');
|
||||
|
||||
// complete the onchange
|
||||
def.resolve();
|
||||
assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala',
|
||||
'input should contain the same value as before onchange');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('UrlWidget');
|
||||
|
||||
QUnit.test('url widget in form view', function (assert) {
|
||||
|
||||
@@ -6,6 +6,7 @@ var concurrency = require('web.concurrency');
|
||||
var FormView = require('web.FormView');
|
||||
var ListView = require('web.ListView');
|
||||
var relationalFields = require('web.relational_fields');
|
||||
var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin');
|
||||
var testUtils = require('web.test_utils');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
@@ -565,7 +566,7 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('many2one field with option standalone', function (assert) {
|
||||
QUnit.test('standalone many2one field', function (assert) {
|
||||
assert.expect(3);
|
||||
var done = assert.async();
|
||||
|
||||
@@ -593,7 +594,14 @@ QUnit.module('relational_fields', {
|
||||
},
|
||||
}).then(function (recordID) {
|
||||
var record = model.get(recordID);
|
||||
var parent = new Widget();
|
||||
// create a new widget that uses the StandaloneFieldManagerMixin
|
||||
var StandaloneWidget = Widget.extend(StandaloneFieldManagerMixin, {
|
||||
init: function (parent) {
|
||||
this._super.apply(this, arguments);
|
||||
StandaloneFieldManagerMixin.init.call(this, parent);
|
||||
},
|
||||
});
|
||||
var parent = new StandaloneWidget(model);
|
||||
testUtils.addMockEnvironment(parent, {
|
||||
data: self.data,
|
||||
mockRPC: function (route, args) {
|
||||
@@ -607,7 +615,6 @@ QUnit.module('relational_fields', {
|
||||
record,
|
||||
{
|
||||
mode: 'edit',
|
||||
standalone: true,
|
||||
});
|
||||
|
||||
relField.appendTo(fixture);
|
||||
|
||||
@@ -1475,6 +1475,71 @@ QUnit.module('Views', {
|
||||
model.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('dont write on readonly fields unless save attribute is set', function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
this.params.fieldNames = ['foo', 'bar'];
|
||||
this.data.partner.onchanges.foo = function (obj) {
|
||||
obj.bar = obj.foo.length;
|
||||
};
|
||||
this.params.fieldsInfo = {
|
||||
default: {
|
||||
foo: {},
|
||||
bar: {
|
||||
modifiers:"{\"readonly\": true}",
|
||||
force_save: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var model = createModel({
|
||||
Model: BasicModel,
|
||||
data: this.data,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
assert.deepEqual(args.args[1], {bar: 14, foo: "verylongstring"},
|
||||
"should only save foo field");
|
||||
}
|
||||
if (args.method === 'create') {
|
||||
assert.deepEqual(args.args[0], {bar: 21, foo: "anotherverylongstring"},
|
||||
"should only save foo field");
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
model.load(this.params).then(function (resultID) {
|
||||
var record = model.get(resultID);
|
||||
assert.strictEqual(record.data.bar, 2,
|
||||
"should be initialized with correct value");
|
||||
|
||||
model.notifyChanges(resultID, {foo: "verylongstring"});
|
||||
|
||||
record = model.get(resultID);
|
||||
assert.strictEqual(record.data.bar, 14,
|
||||
"should be changed with correct value");
|
||||
|
||||
model.save(resultID);
|
||||
});
|
||||
|
||||
// start again, but with a new record
|
||||
delete this.params.res_id;
|
||||
model.load(this.params).then(function (resultID) {
|
||||
var record = model.get(resultID);
|
||||
assert.strictEqual(record.data.bar, 0,
|
||||
"should be initialized with correct value (0 as integer)");
|
||||
|
||||
model.notifyChanges(resultID, {foo: "anotherverylongstring"});
|
||||
|
||||
record = model.get(resultID);
|
||||
assert.strictEqual(record.data.bar, 21,
|
||||
"should be changed with correct value");
|
||||
|
||||
model.save(resultID);
|
||||
});
|
||||
model.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('default_get with one2many values', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ QUnit.module('Views', {
|
||||
fields: {
|
||||
id: {string: "ID", type: "integer"},
|
||||
user_id: {string: "user", type: "many2one", relation: 'user'},
|
||||
parnter_id: {string: "user", type: "many2one", relation: 'partner', related: 'user_id.parnter_id'},
|
||||
partner_id: {string: "user", type: "many2one", relation: 'partner', related: 'user_id.partner_id'},
|
||||
name: {string: "name", type: "char"},
|
||||
start_date: {string: "start date", type: "date"},
|
||||
stop_date: {string: "stop date", type: "date"},
|
||||
@@ -58,7 +58,7 @@ QUnit.module('Views', {
|
||||
fields: {
|
||||
id: {string: "ID", type: "integer"},
|
||||
display_name: {string: "Displayed name", type: "char"},
|
||||
parnter_id: {string: "partner", type: "many2one", relation: 'partner'},
|
||||
partner_id: {string: "partner", type: "many2one", relation: 'partner'},
|
||||
image: {string: "image", type: "integer"},
|
||||
},
|
||||
records: [
|
||||
@@ -158,7 +158,7 @@ QUnit.module('Views', {
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="parnter_id">'+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
@@ -194,7 +194,7 @@ QUnit.module('Views', {
|
||||
|
||||
var $typeFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(user))');
|
||||
assert.ok($typeFilter.length, "should display 'user' filter");
|
||||
assert.strictEqual($typeFilter.find('.o_calendar_filter_item').length, 1, "should display 1 filter items for 'user'");
|
||||
assert.strictEqual($typeFilter.find('.o_calendar_filter_item').length, 2, "should display 2 filter items for 'user'");
|
||||
|
||||
var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(attendees))');
|
||||
assert.ok($attendeesFilter.length, "should display 'attendees' filter");
|
||||
@@ -940,6 +940,100 @@ QUnit.module('Views', {
|
||||
assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body");
|
||||
});
|
||||
|
||||
QUnit.test('"all" filter', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar class="o_calendar_test" '+
|
||||
'event_open_popup="true" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 9,
|
||||
"should display 9 events on the week");
|
||||
|
||||
// Select the events only associated with partner 2
|
||||
calendar.$('.o_calendar_filter_item[data-id=2] input').click();
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 4,
|
||||
"should display 4 events on the week");
|
||||
|
||||
// Click on the 'all' filter to reload all events
|
||||
calendar.$('.o_calendar_filter_item[data-value=all] input').click();
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 9,
|
||||
"should display 9 events on the week");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('ensure calendar adapts to screen size', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
// We want to be able to restore changes on 'body'
|
||||
var bodyStyle = $('body').attr('style');
|
||||
|
||||
// Force the view height to be smaller than the natural calendar height
|
||||
// (FullCalendar requires that the height be modified on body not $target)
|
||||
var height = 500;
|
||||
$('body').height(height);
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch: '<calendar mode="week" date_start="start" />',
|
||||
});
|
||||
|
||||
var $scroller = calendar.$('.fc-scroller')
|
||||
assert.strictEqual($scroller.height() < height, true,
|
||||
"the scroller should be smaller than the view height");
|
||||
assert.strictEqual($scroller.prop('scrollHeight') > height, true,
|
||||
"the scroller should have a scrollbar");
|
||||
calendar.destroy();
|
||||
|
||||
$('body').removeAttr('style');
|
||||
if (bodyStyle) {
|
||||
$('body').attr('style', bodyStyle);
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('ensure events are still shown if filters give an empty domain', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch: '<calendar mode="week" date_start="start">' +
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>' +
|
||||
'</calendar>',
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 5,
|
||||
"should display 5 events");
|
||||
calendar.$('.o_calendar_filter_item[data-value=all] input[type=checkbox]').click();
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 5,
|
||||
"should display 5 events");
|
||||
calendar.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -3608,6 +3608,40 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('clicking on a stat button with no context', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<div class="oe_button_box" name="button_box">' +
|
||||
'<button class="oe_stat_button" type="action" name="1">' +
|
||||
'<field name="qux" widget="statinfo"/>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 2,
|
||||
viewOptions: {
|
||||
context: {some_context: true},
|
||||
},
|
||||
intercepts: {
|
||||
execute_action: function (e) {
|
||||
assert.deepEqual(e.data.action_data.context, {},
|
||||
"button context should have been evaluated and given to the action, without previous context");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
form.$('.oe_stat_button').click();
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('diplay a stat button outside a buttonbox', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
@@ -4946,8 +4980,8 @@ QUnit.module('Views', {
|
||||
_t.database.multi_lang = multi_lang;
|
||||
});
|
||||
|
||||
QUnit.test('buttons are disabled until action is resolved', function (assert) {
|
||||
assert.expect(3);
|
||||
QUnit.test('buttons are disabled until status bar action is resolved', function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
var def = $.Deferred();
|
||||
|
||||
@@ -4956,6 +4990,10 @@ QUnit.module('Views', {
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<header>' +
|
||||
'<button name="post" class="p" string="Confirm" type="object"/>' +
|
||||
'<button name="some_method" class="s" string="Do it" type="object"/>' +
|
||||
'</header>' +
|
||||
'<sheet>' +
|
||||
'<div name="button_box" class="oe_button_box">' +
|
||||
'<button class="oe_stat_button">' +
|
||||
@@ -4977,18 +5015,94 @@ QUnit.module('Views', {
|
||||
},
|
||||
});
|
||||
|
||||
assert.notOk(form.$('.oe_button_box button').attr('disabled'),
|
||||
assert.strictEqual(form.$buttons.find('button:not(:disabled)').length, 4,
|
||||
"control panel buttons should be enabled");
|
||||
assert.strictEqual(form.$('.o_form_statusbar button:not(:disabled)').length, 2,
|
||||
"status bar buttons should be enabled");
|
||||
assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1,
|
||||
"stat buttons should be enabled");
|
||||
|
||||
form.$('.o_form_statusbar button').click();
|
||||
|
||||
// The unresolved deferred lets us check the state of the buttons
|
||||
assert.strictEqual(form.$buttons.find('button:disabled').length, 4,
|
||||
"control panel buttons should be disabled");
|
||||
assert.strictEqual(form.$('.o_form_statusbar button:disabled').length, 2,
|
||||
"status bar buttons should be disabled");
|
||||
assert.strictEqual(form.$('.oe_button_box button:disabled').length, 1,
|
||||
"stat buttons should be disabled");
|
||||
|
||||
def.resolve();
|
||||
|
||||
assert.strictEqual(form.$buttons.find('button:not(:disabled)').length, 4,
|
||||
"control panel buttons should be enabled");
|
||||
assert.strictEqual(form.$('.o_form_statusbar button:not(:disabled)').length, 2,
|
||||
"status bar buttons should be enabled");
|
||||
assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1,
|
||||
"stat buttons should be enabled");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('buttons are disabled until button box action is resolved', function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
var def = $.Deferred();
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<header>' +
|
||||
'<button name="post" class="p" string="Confirm" type="object"/>' +
|
||||
'<button name="some_method" class="s" string="Do it" type="object"/>' +
|
||||
'</header>' +
|
||||
'<sheet>' +
|
||||
'<div name="button_box" class="oe_button_box">' +
|
||||
'<button class="oe_stat_button">' +
|
||||
'<field name="bar"/>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<group>' +
|
||||
'<field name="foo"/>' +
|
||||
'</group>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
intercepts: {
|
||||
execute_action: function (event) {
|
||||
return def.then(function() {
|
||||
event.data.on_success();
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$buttons.find('button:not(:disabled)').length, 4,
|
||||
"control panel buttons should be enabled");
|
||||
assert.strictEqual(form.$('.o_form_statusbar button:not(:disabled)').length, 2,
|
||||
"status bar buttons should be enabled");
|
||||
assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1,
|
||||
"stat buttons should be enabled");
|
||||
|
||||
form.$('.oe_button_box button').click();
|
||||
|
||||
// The unresolved deferred lets us check the state of the buttons
|
||||
assert.ok(form.$('.oe_button_box button').attr('disabled'),
|
||||
assert.strictEqual(form.$buttons.find('button:disabled').length, 4,
|
||||
"control panel buttons should be disabled");
|
||||
assert.strictEqual(form.$('.o_form_statusbar button:disabled').length, 2,
|
||||
"status bar buttons should be disabled");
|
||||
assert.strictEqual(form.$('.oe_button_box button:disabled').length, 1,
|
||||
"stat buttons should be disabled");
|
||||
|
||||
def.resolve();
|
||||
|
||||
assert.notOk(form.$('.oe_button_box button').attr('disabled'),
|
||||
assert.strictEqual(form.$buttons.find('button:not(:disabled)').length, 4,
|
||||
"control panel buttons should be enabled");
|
||||
assert.strictEqual(form.$('.o_form_statusbar button:not(:disabled)').length, 2,
|
||||
"status bar buttons should be enabled");
|
||||
assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1,
|
||||
"stat buttons should be enabled");
|
||||
|
||||
form.destroy();
|
||||
|
||||
@@ -372,6 +372,30 @@ QUnit.module('Views', {
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('reload graph with correct fields', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var graph = createView({
|
||||
View: GraphView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<graph>' +
|
||||
'<field name="product_id" type="row"/>' +
|
||||
'<field name="foo" type="measure"/>' +
|
||||
'</graph>',
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'read_group') {
|
||||
assert.deepEqual(args.kwargs.fields, ['product_id', 'foo'],
|
||||
"should read the correct fields");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
graph.reload({groupBy: []});
|
||||
|
||||
graph.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -965,7 +965,7 @@ QUnit.module('Views', {
|
||||
},
|
||||
});
|
||||
|
||||
assert.ok(kanban.$el.hasClass('o_kanban_nocontent'),
|
||||
assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_nocontent'),
|
||||
"$el should have correct no content class");
|
||||
|
||||
assert.strictEqual(kanban.$('.oe_view_nocontent').length, 1,
|
||||
|
||||
@@ -2483,6 +2483,34 @@ QUnit.module('Views', {
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('grouped list edition with toggle_button widget', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree><field name="bar" widget="toggle_button"/></tree>',
|
||||
groupBy: ['m2o'],
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
assert.deepEqual(args.args[1], {bar: false},
|
||||
"should write the correct value");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
list.$('.o_group_header:first').click(); // open the first group
|
||||
assert.strictEqual(list.$('.o_data_row:first .o_toggle_button_success').length, 1,
|
||||
"boolean value of the first record should be true");
|
||||
list.$('.o_data_row:first .o_icon_button').click(); // toggle the value
|
||||
assert.strictEqual(list.$('.o_data_row:first .text-muted:not(.o_toggle_button_success)').length, 1,
|
||||
"boolean button should have been updated");
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ var field_registry = require('web.field_registry');
|
||||
var utils = require('web.utils');
|
||||
|
||||
/**
|
||||
* Kanban widgets: GaugeWidget
|
||||
* Field widget: GaugeWidget
|
||||
* options
|
||||
* - max_value: maximum value of the gauge [default: 100]
|
||||
* - max_field: get the max_value from the field that must be present in the
|
||||
@@ -26,7 +26,15 @@ var utils = require('web.utils');
|
||||
var GaugeWidget = AbstractField.extend({
|
||||
className: "oe_gauge",
|
||||
|
||||
render: function() {
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
// parameters
|
||||
var max_value = this.nodeOptions.max_value || 100;
|
||||
if (this.nodeOptions.max_field) {
|
||||
@@ -124,4 +132,6 @@ var GaugeWidget = AbstractField.extend({
|
||||
|
||||
field_registry.add("gauge", GaugeWidget);
|
||||
|
||||
return GaugeWidget;
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
odoo.define('web_kanban_gauge.gauge_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var KanbanView = require('web.KanbanView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
QUnit.module('fields', {}, function () {
|
||||
|
||||
QUnit.module('basic_fields', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
int_field: {string: "int_field", type: "integer", sortable: true},
|
||||
},
|
||||
records: [
|
||||
{id: 1, int_field: 10},
|
||||
{id: 2, int_field: 4},
|
||||
]
|
||||
},
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
|
||||
QUnit.module('gauge widget');
|
||||
|
||||
QUnit.test('basic rendering', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var kanban = createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<kanban><templates><t t-name="kanban-box">' +
|
||||
'<div><field name="int_field" widget="gauge"/></div>' +
|
||||
'</t></templates></kanban>',
|
||||
});
|
||||
|
||||
assert.strictEqual(kanban.$('.o_kanban_record:first .oe_gauge svg').length, 1,
|
||||
"should render the gauge widget");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,4 +5,10 @@
|
||||
<script type="text/javascript" src="/web_kanban_gauge/static/src/js/kanban_gauge.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="qunit_suite" name="gauge_tests" inherit_id="web.qunit_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="text/javascript" src="/web_kanban_gauge/static/tests/gauge_tests.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -119,10 +119,13 @@ var RunningTourActionHelper = core.Class.extend({
|
||||
if (values.consume_event === "input") {
|
||||
values.$element.trigger("keydown").val(text).trigger("keyup").trigger("input");
|
||||
} else if (values.$element.is("select")) {
|
||||
values.$element.children("option")
|
||||
.prop("selected", false).removeProp("selected")
|
||||
.filter(function () { return $(this).val() === text; })
|
||||
.prop("selected", true);
|
||||
var $options = values.$element.children("option");
|
||||
$options.prop("selected", false).removeProp("selected");
|
||||
var $selectedOption = $options.filter(function () { return $(this).val() === text; });
|
||||
if ($selectedOption.length === 0) {
|
||||
$selectedOption = $options.filter(function () { return $(this).text() === text; });
|
||||
}
|
||||
$selectedOption.prop("selected", true);
|
||||
this._click(values);
|
||||
} else {
|
||||
values.$element.text(text);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from mock import patch
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
@@ -9,6 +10,24 @@ from odoo.addons.crm.tests.common import TestCrmCases
|
||||
|
||||
class TestPartnerAssign(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPartnerAssign, self).setUp()
|
||||
|
||||
def geo_find(addr):
|
||||
return {
|
||||
'Wavre, Belgium': (50.7158956, 4.6128075),
|
||||
'Cannon Hill Park, B46 3AG Birmingham, United Kingdom': (52.45216, -1.898578),
|
||||
}.get(addr)
|
||||
|
||||
patcher = patch('odoo.addons.base_geolocalize.models.res_partner.geo_find', wraps=geo_find)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
patcher = patch('odoo.addons.website_crm_partner_assign.models.crm_lead.geo_find',
|
||||
wraps=geo_find)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_partner_assign(self):
|
||||
""" Test the automatic assignation using geolocalisation """
|
||||
partner2 = self.env.ref('base.res_partner_2')
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<t t-foreach="invoices" t-as="i">
|
||||
<t t-set="report_url" t-value="'/my/invoices/%s' % i.id"/>
|
||||
<t t-set="report_url" t-value="'/my/invoices/pdf/%s' % i.id"/>
|
||||
<div>
|
||||
<a t-att-href="report_url"><span class="fa fa-download"/></a>
|
||||
<a t-att-href="report_url"><span t-esc="i.number"/></a>
|
||||
|
||||
@@ -47,7 +47,7 @@ class WebsiteSaleDelivery(WebsiteSale):
|
||||
if order.carrier_id and not order.delivery_rating_success:
|
||||
values['errors'].append(
|
||||
(_("Ouch, you cannot choose this carrier!"),
|
||||
_("%s does not ship to your address, please choose another one.") % order.carrier_id.name))
|
||||
_("%s does not ship to your address, please choose another one.\n(Error: %s)" % (order.carrier_id.name, order.delivery_message))))
|
||||
order._remove_delivery_line()
|
||||
|
||||
delivery_carriers = order._get_delivery_methods()
|
||||
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -28,66 +28,111 @@
|
||||
<record id="slide_0" model="slide.slide">
|
||||
<field name="name">Odoo MRP</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=QtIU0p8daAo</field>
|
||||
<field name="document_id">QtIU0p8daAo</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_1"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag2'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/QtIU0p8daAo.jpg"/>
|
||||
<field name="description">Discover how you can integrate MRP, PLM, Quality and Maintenance in a single application to maximize the efficiency of your manufacturing operations.
|
||||
|
||||
Read more about the features at: https://www.odoo.com/page/manufacturing-features
|
||||
|
||||
Discover Odoo MRP: https://www.odoo.com/page/manufacturing</field>
|
||||
</record>
|
||||
<record id="slide_1" model="slide.slide">
|
||||
<field name="name">Odoo CRM</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=fgdz8MH2YHY</field>
|
||||
<field name="document_id">fgdz8MH2YHY</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_1"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag1'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/fgdz8MH2YHY.jpg"/>
|
||||
<field name="description">Discover the CRM sales people love https://www.odoo.com/page/crm</field>
|
||||
</record>
|
||||
<record id="slide_2" model="slide.slide">
|
||||
<field name="name">Odoo All-in-One Software Demonstration</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=tUx39vAENy0</field>
|
||||
<field name="document_id">tUx39vAENy0</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_2"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag1')), (4, ref('tag2'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/tUx39vAENy0.jpg"/>
|
||||
<field name="description">Discover the all-in-one business management software solution that fits any business size and use case.
|
||||
|
||||
Learn more at https://odoo.com</field>
|
||||
</record>
|
||||
<record id="slide_3" model="slide.slide">
|
||||
<field name="name">Odoo Sign Demonstration</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=8DBabYQ1nUE</field>
|
||||
<field name="document_id">8DBabYQ1nUE</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_2"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag2'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/8DBabYQ1nUE.jpg"/>
|
||||
<field name="description">Stop doing manual printing and scanning. Move to electronic signature with Odoo Sign!
|
||||
|
||||
Discover more at https://www.odoo.com/page/sign</field>
|
||||
</record>
|
||||
<record id="slide_5" model="slide.slide">
|
||||
<field name="name">Awesome Timesheet by Odoo</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=v2O0YZU19ms</field>
|
||||
<field name="document_id">v2O0YZU19ms</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_1"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag2'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/v2O0YZU19ms.jpg"/>
|
||||
<field name="description">Download Awesome Timesheet by Odoo and discover how easy time tracking and employee management can be!
|
||||
|
||||
Chrome: http://bit.ly/2613LcY
|
||||
iOS: http://bit.ly/1ZUZsZD</field>
|
||||
</record>
|
||||
<record id="slide_6" model="slide.slide">
|
||||
<field name="name">Odoo Marketing</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=sDxfUTd-gs0</field>
|
||||
<field name="document_id">sDxfUTd-gs0</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_1"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag1')), (4, ref('tag2'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/sDxfUTd-gs0.jpg"/>
|
||||
<field name="description">Discover how easy marketing your business can be with Odoo</field>
|
||||
</record>
|
||||
<record id="slide_7" model="slide.slide">
|
||||
<field name="name">Odoo Inventory</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=Vp5xqQOWLzE</field>
|
||||
<field name="document_id">Vp5xqQOWLzE</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_1"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag1')), (4, ref('tag2'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/Vp5xqQOWLzE.jpg"/>
|
||||
<field name="description">Warehouse management software for the 21st century.
|
||||
|
||||
Discover Odoo Inventory: https://www.odoo.com/page/warehouse</field>
|
||||
</record>
|
||||
<record id="slide_8" model="slide.slide">
|
||||
<field name="name">Odoo Sales</field>
|
||||
<field name="url">https://www.youtube.com/watch?v=VMuCr5_arsY</field>
|
||||
<field name="document_id">VMuCr5_arsY</field>
|
||||
<field name="slide_type">video</field>
|
||||
<field name="channel_id" ref="channel_public"/>
|
||||
<field name="category_id" ref="category_1"/>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="tag_ids" eval="[(4, ref('tag1')), (4, ref('tag2'))]"/>
|
||||
<field name="image" type="base64" file="website_slides/data/demo-thumbnails/VMuCr5_arsY.jpg"/>
|
||||
<field name="description">Learn more at https://www.odoo.com/page/sales</field>
|
||||
</record>
|
||||
|
||||
<record id="slide_9" model="slide.slide">
|
||||
|
||||
@@ -366,7 +366,10 @@ class Slide(models.Model):
|
||||
if slide.id: # avoid to perform a slug on a not yet saved record in case of an onchange.
|
||||
# link_tracker is not in dependencies, so use it to shorten url only if installed.
|
||||
if self.env.registry.get('link.tracker'):
|
||||
url = self.env['link.tracker'].sudo().create({'url': '%s/slides/slide/%s' % (base_url, slug(slide))}).short_url
|
||||
url = self.env['link.tracker'].sudo().create({
|
||||
'url': '%s/slides/slide/%s' % (base_url, slug(slide)),
|
||||
'title': slide.name,
|
||||
}).short_url
|
||||
else:
|
||||
url = '%s/slides/slide/%s' % (base_url, slug(slide))
|
||||
slide.website_url = url
|
||||
@@ -379,7 +382,7 @@ class Slide(models.Model):
|
||||
values['image'] = values['datas']
|
||||
if values.get('website_published') and not values.get('date_published'):
|
||||
values['date_published'] = datetime.datetime.now()
|
||||
if values.get('url'):
|
||||
if values.get('url') and not values.get('document_id'):
|
||||
doc_data = self._parse_document_url(values['url']).get('values', dict())
|
||||
for key, value in pycompat.items(doc_data):
|
||||
values.setdefault(key, value)
|
||||
@@ -393,7 +396,7 @@ class Slide(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
if values.get('url'):
|
||||
if values.get('url') and values['url'] != self.url:
|
||||
doc_data = self._parse_document_url(values['url']).get('values', dict())
|
||||
for key, value in pycompat.items(doc_data):
|
||||
values.setdefault(key, value)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Belgium, 19/07/2017
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Vincent Adriaensen vincentadriaensen@users.noreply.github.com https://github.com/vincentadriaensen
|
||||
@@ -556,6 +556,7 @@
|
||||
<rng:optional><rng:attribute name="default_get"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="required"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="readonly"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="force_save"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="view_mode"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="widget"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="context"/></rng:optional>
|
||||
|
||||