[MERGE] forward port branch saas-17 up to a445548f07

This commit is contained in:
Christophe Simonis
2017-08-02 12:31:05 +02:00
112 changed files with 2393 additions and 3682 deletions
+1 -1
View File
@@ -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'],
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+6 -3
View File
@@ -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();
}
+1 -2
View File
@@ -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();
});
});
+2 -2
View File
@@ -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
+9 -3
View File
@@ -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
+1 -2
View File
@@ -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>
-549
View File
@@ -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"
-542
View File
@@ -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ó"
-540
View File
@@ -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"
-535
View File
@@ -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"
-523
View File
@@ -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
})
+2 -2
View File
@@ -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'),
+1 -1
View File
@@ -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>
+23 -3
View File
@@ -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});
});
}
},
+47 -1
View File
@@ -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
+1
View File
@@ -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
+2 -2
View File
@@ -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):
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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
+1
View File
@@ -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',
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+18 -6
View File
@@ -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()}
+13 -9
View File
@@ -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
+41 -3
View File
@@ -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):
+246 -2
View File
@@ -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)
+55
View File
@@ -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):
+130
View File
@@ -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))
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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>
+2 -2
View File
@@ -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': ['&amp;', ('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)]}"/>
+5 -6
View File
@@ -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', '&lt;', 0.0)]"/>
<filter name="positive" string="Positive" domain="[('quantity', '&gt;', 0.0)]"/>
<filter name="negative" string="Negative Stock" domain="[('quantity', '&lt;', 0.0)]"/>
<filter name="positive" string="Positive Stock" domain="[('quantity', '&gt;', 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>
+2 -2
View File
@@ -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);
+3
View File
@@ -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();
});
});
});
+119 -5
View File
@@ -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()
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

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">
+6 -3
View File
@@ -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)
+11
View File
@@ -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
+1
View File
@@ -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>

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