summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-09-19 13:05:04 +0200
committerChristophe Simonis <chs@odoo.com>2017-09-19 13:05:04 +0200
commit08db385d02dd67f82937e4325179dda1a82519b7 (patch)
tree9d1be20b01650218fefa38ec3ecae9a708fa0cf3
parent071b46c22786d9b0b7d1ef46387907cbfeb8980b (diff)
parentd15a31ae6d944cf04f9da8752b069b221cac0d9a (diff)
[MERGE] forward port branch saas-18 up to d15a31ae6d9
-rw-r--r--addons/account/models/account_invoice.py15
-rw-r--r--addons/account/static/src/js/account_dashboard_setup_bar.js4
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js64
-rw-r--r--addons/account/views/account_report.xml20
-rw-r--r--addons/account/views/report_invoice.xml4
-rw-r--r--addons/barcodes/static/src/js/barcode_events.js7
-rw-r--r--addons/bus/static/src/js/bus.js13
-rw-r--r--addons/hr_attendance/static/src/js/greeting_message.js2
-rw-r--r--addons/hr_attendance/static/src/js/kiosk_confirm.js2
-rw-r--r--addons/hr_recruitment/models/calendar.py2
-rw-r--r--addons/l10n_in/__init__.py2
-rw-r--r--addons/l10n_in/__manifest__.py9
-rw-r--r--addons/l10n_in/data/account_data.xml25
-rw-r--r--addons/l10n_in/data/account_fiscal_position_data.xml140
-rw-r--r--addons/l10n_in/data/account_tax_template_data.xml1045
-rw-r--r--addons/l10n_in/data/l10n_in_chart_data.xml94
-rw-r--r--addons/l10n_in/data/res_country_state_data.xml113
-rw-r--r--addons/l10n_in/models/__init__.py7
-rw-r--r--addons/l10n_in/models/account_invoice.py43
-rw-r--r--addons/l10n_in/models/chart_template.py23
-rw-r--r--addons/l10n_in/models/product_template.py10
-rw-r--r--addons/l10n_in/models/res_country_state.py10
-rw-r--r--addons/l10n_in/views/product_template_view.xml15
-rw-r--r--addons/l10n_in/views/report_invoice.xml130
-rw-r--r--addons/l10n_in/views/res_company_view.xml16
-rw-r--r--addons/l10n_in/views/res_country_state_view.xml15
-rw-r--r--addons/l10n_in/views/res_partner_views.xml14
-rw-r--r--addons/l10n_in_purchase/__init__.py2
-rw-r--r--addons/l10n_in_purchase/__manifest__.py19
-rw-r--r--addons/l10n_in_purchase/views/report_purchase_order.xml17
-rw-r--r--addons/l10n_in_sale/__init__.py2
-rw-r--r--addons/l10n_in_sale/__manifest__.py20
-rw-r--r--addons/l10n_in_sale/views/report_invoice_view.xml114
-rw-r--r--addons/l10n_in_sale/views/report_sale_order.xml10
-rw-r--r--addons/l10n_in_schedule6/__manifest__.py2
-rw-r--r--addons/l10n_in_schedule6/account_data.xml40
-rw-r--r--addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml108
-rw-r--r--addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml1207
-rw-r--r--addons/l10n_in_stock/__init__.py2
-rw-r--r--addons/l10n_in_stock/__manifest__.py19
-rw-r--r--addons/l10n_in_stock/views/report_stockpicking_operations.xml10
-rw-r--r--addons/mail/static/src/js/activity.js2
-rw-r--r--addons/mail/static/src/js/chatter_composer.js6
-rw-r--r--addons/mail/static/src/js/document_viewer.js2
-rw-r--r--addons/mail/static/src/js/many2many_tags_email.js1
-rw-r--r--addons/mail/static/src/js/thread.js10
-rw-r--r--addons/mail/static/src/less/thread.less4
-rw-r--r--addons/mail/static/src/xml/chat_window.xml4
-rw-r--r--addons/mail/static/src/xml/chatter.xml2
-rw-r--r--addons/mrp/models/stock_move.py4
-rw-r--r--addons/mrp_repair/report/mrp_repair_templates_repair_order.xml4
-rw-r--r--addons/payment_ogone/tests/test_ogone.py3
-rw-r--r--addons/payment_sips/models/payment.py21
-rw-r--r--addons/payment_sips/views/payment_views.xml5
-rw-r--r--addons/point_of_sale/static/src/js/models.js3
-rw-r--r--addons/point_of_sale/static/src/js/popups.js30
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml2
-rw-r--r--addons/portal/static/src/js/portal_chatter.js2
-rw-r--r--addons/pos_restaurant/static/src/xml/printbill.xml2
-rw-r--r--addons/purchase/models/purchase.py3
-rw-r--r--addons/purchase/report/purchase_order_templates.xml2
-rw-r--r--addons/purchase/report/purchase_quotation_templates.xml2
-rw-r--r--addons/purchase/tests/test_create_picking.py2
-rw-r--r--addons/rating/models/rating.py2
-rw-r--r--addons/rating/views/rating_view.xml2
-rw-r--r--addons/sale/models/sale.py3
-rw-r--r--addons/sale/report/invoice_report_templates.xml4
-rw-r--r--addons/sale/report/sale_report_templates.xml2
-rw-r--r--addons/sale_timesheet/static/src/js/timesheet_plan.js2
-rw-r--r--addons/stock/models/product.py1
-rw-r--r--addons/stock/models/stock_move.py117
-rw-r--r--addons/stock/models/stock_picking.py36
-rw-r--r--addons/stock/report/report_stockpicking_operations.xml2
-rw-r--r--addons/stock/tests/test_move2.py201
-rw-r--r--addons/stock/tests/test_stock_flow.py36
-rw-r--r--addons/stock/views/stock_move_line_views.xml2
-rw-r--r--addons/web/static/src/js/_deprecated/data.js1
-rw-r--r--addons/web/static/src/js/chrome/abstract_web_client.js11
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js14
-rw-r--r--addons/web/static/src/js/chrome/search_filters.js44
-rw-r--r--addons/web/static/src/js/chrome/search_view.js6
-rw-r--r--addons/web/static/src/js/chrome/view_manager.js7
-rw-r--r--addons/web/static/src/js/core/ajax.js2
-rw-r--r--addons/web/static/src/js/core/bus.js2
-rw-r--r--addons/web/static/src/js/core/class.js57
-rw-r--r--addons/web/static/src/js/core/concurrency.js15
-rw-r--r--addons/web/static/src/js/core/dialog.js14
-rw-r--r--addons/web/static/src/js/core/dom.js8
-rw-r--r--addons/web/static/src/js/core/mixins.js64
-rw-r--r--addons/web/static/src/js/core/pyeval.js10
-rw-r--r--addons/web/static/src/js/core/session.js4
-rw-r--r--addons/web/static/src/js/core/translation.js10
-rw-r--r--addons/web/static/src/js/core/utils.js12
-rw-r--r--addons/web/static/src/js/core/widget.js68
-rw-r--r--addons/web/static/src/js/fields/abstract_field.js4
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js21
-rw-r--r--addons/web/static/src/js/fields/field_utils.js8
-rw-r--r--addons/web/static/src/js/fields/special_fields.js8
-rw-r--r--addons/web/static/src/js/fields/upgrade_fields.js2
-rw-r--r--addons/web/static/src/js/services/config.js8
-rw-r--r--addons/web/static/src/js/services/core.js10
-rw-r--r--addons/web/static/src/js/services/crash_manager.js16
-rw-r--r--addons/web/static/src/js/services/data_manager.js8
-rw-r--r--addons/web/static/src/js/views/abstract_renderer.js3
-rw-r--r--addons/web/static/src/js/views/abstract_view.js2
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js4
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js27
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js1
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js2
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_record.js4
-rw-r--r--addons/web/static/src/js/views/standalone_field_manager_mixin.js12
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js8
-rw-r--r--addons/web/static/src/js/widgets/domain_selector.js12
-rw-r--r--addons/web/static/src/js/widgets/domain_selector_dialog.js3
-rw-r--r--addons/web/static/src/js/widgets/iframe_widget.js3
-rw-r--r--addons/web/static/src/less/form_view_extra.less12
-rw-r--r--addons/web/static/src/less/kanban_column_progressbar.less4
-rw-r--r--addons/web/static/src/less/kanban_view.less21
-rw-r--r--addons/web/static/src/less/search_view.less2
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js40
-rw-r--r--addons/web/static/tests/helpers/mock_server.js3
-rw-r--r--addons/web/static/tests/views/form_tests.js48
-rw-r--r--addons/web/views/report_templates.xml8
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.editor.js5
-rw-r--r--addons/web_editor/static/src/js/widgets/ace.js4
-rw-r--r--addons/web_editor/static/src/js/widgets/widgets.js6
-rw-r--r--addons/web_kanban_gauge/static/src/js/kanban_gauge.js13
-rw-r--r--addons/web_tour/static/src/js/tip.js5
-rw-r--r--addons/web_tour/static/src/js/tour_service.js10
-rw-r--r--addons/website/static/src/js/backend/dashboard.js3
-rw-r--r--addons/website_rating/static/src/js/website_mail.js17
-rw-r--r--addons/website_sale/static/src/js/website_sale_rating.js26
-rw-r--r--addons/website_theme_install/static/src/less/website_theme_install.less9
-rw-r--r--doc/_extensions/autojsdoc/README.rst87
-rw-r--r--doc/_extensions/autojsdoc/__init__.py1
-rw-r--r--doc/_extensions/autojsdoc/__main__.py154
-rw-r--r--doc/_extensions/autojsdoc/ext/__init__.py13
-rw-r--r--doc/_extensions/autojsdoc/ext/directives.py686
-rw-r--r--doc/_extensions/autojsdoc/ext/extractor.py87
-rw-r--r--doc/_extensions/autojsdoc/parser/__init__.py1
-rw-r--r--doc/_extensions/autojsdoc/parser/jsdoc.py324
-rw-r--r--doc/_extensions/autojsdoc/parser/parser.py552
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/README.rst6
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/support.py73
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_class.py227
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_crap.py75
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_module.py173
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_namespace.py177
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_params.py116
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_typespec.py81
-rw-r--r--doc/_extensions/autojsdoc/parser/types.py191
-rw-r--r--doc/_extensions/autojsdoc/parser/utils.py295
-rw-r--r--doc/_extensions/autojsdoc/parser/visitor.py126
-rw-r--r--doc/_extensions/conftest.py0
-rw-r--r--doc/_extensions/odoo_ext/static/style.css577
-rw-r--r--doc/_extensions/odoo_ext/static/style.less4
-rw-r--r--doc/_extensions/pyjsparser/LICENSE21
-rw-r--r--doc/_extensions/pyjsparser/README4
-rw-r--r--doc/_extensions/pyjsparser/__init__.py4
-rw-r--r--doc/_extensions/pyjsparser/parser.py2928
-rw-r--r--doc/_extensions/pyjsparser/pyjsparserdata.py303
-rw-r--r--doc/_extensions/pyjsparser/std_nodes.py470
-rw-r--r--doc/conf.py6
-rw-r--r--doc/reference/javascript.rst1401
-rw-r--r--doc/requirements.txt3
-rwxr-xr-xodoo-bin1
-rw-r--r--odoo/__init__.py6
-rw-r--r--odoo/addons/__init__.py5
-rw-r--r--odoo/addons/base/res/res_country.py1
-rw-r--r--odoo/addons/base/res/res_country_data.xml31
-rw-r--r--odoo/addons/base/res/res_country_view.xml1
-rw-r--r--odoo/api.py6
-rw-r--r--odoo/release.py2
-rw-r--r--setup/odoo1
174 files changed, 10290 insertions, 3623 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 1e76dbc4b63..f3af53af8fc 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -431,6 +431,18 @@ class AccountInvoice(models.Model):
sequence = journal_sequence._get_current_sequence()
sequence.number_next = int(result.group(2))
+ @api.multi
+ def _get_printed_report_name(self):
+ self.ensure_one()
+ return self.type == 'out_invoice' and self.state == 'draft' and _('Draft Invoice') or \
+ self.type == 'out_invoice' and self.state in ('open','paid') and _('Invoice - %s') % (self.number) or \
+ self.type == 'out_refund' and self.state == 'draft' and _('Credit Note') or \
+ self.type == 'out_refund' and _('Credit Note - %s') % (self.number) or \
+ self.type == 'in_invoice' and self.state == 'draft' and _('Vendor Bill') or \
+ self.type == 'in_invoice' and self.state in ('open','paid') and _('Vendor Bill - %s') % (self.number) or \
+ self.type == 'in_refund' and self.state == 'draft' and _('Vendor Credit Note') or \
+ self.type == 'in_refund' and _('Vendor Credit Note - %s') % (self.number)
+
@api.model
def create(self, vals):
onchanges = {
@@ -551,7 +563,8 @@ class AccountInvoice(models.Model):
default_template_id=template and template.id or False,
default_composition_mode='comment',
mark_invoice_as_sent=True,
- custom_layout="account.mail_template_data_notification_email_account_invoice"
+ custom_layout="account.mail_template_data_notification_email_account_invoice",
+ force_email=True
)
return {
'name': _('Compose Email'),
diff --git a/addons/account/static/src/js/account_dashboard_setup_bar.js b/addons/account/static/src/js/account_dashboard_setup_bar.js
index acbc188ccd3..1af9416008f 100644
--- a/addons/account/static/src/js/account_dashboard_setup_bar.js
+++ b/addons/account/static/src/js/account_dashboard_setup_bar.js
@@ -124,7 +124,7 @@ var AccountSetupBarModel = KanbanModel.extend({
/**
* @abstract
- * @returns {Deferred -> Object} resolves to the required dashboard data
+ * @returns {Deferred<Object>} resolves to the required dashboard data
*/
_fetchDashboardData: function () {
return $.when(this._rpc({
@@ -136,7 +136,7 @@ var AccountSetupBarModel = KanbanModel.extend({
/**
* @private
* @param {Deferred} super_def a deferred that resolves with a dataPoint id
- * @returns {Deferred -> string} resolves to the dataPoint id
+ * @returns {Deferred<string>} resolves to the dataPoint id
*/
_loadDashboard: function (super_def) {
var self = this;
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index b136af9a4ed..79e465f19c7 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -12,7 +12,7 @@ var _t = core._t;
* Model use to fetch, format and update 'account.bank.statement' and
* 'account.bank.statement.line' datas allowing reconciliation
*
- * The statement internal structure:
+ * The statement internal structure::
*
* {
* valuenow: integer
@@ -25,7 +25,7 @@ var _t = core._t;
* accounts: {id: code}
* }
*
- * The internal structure of each line is:
+ * The internal structure of each line is::
*
* {
* balance: {
@@ -114,7 +114,7 @@ var StatementModel = BasicModel.extend({
* account type
*
* @param {string} handle
- * @param {number} mv_lines id
+ * @param {number} mv_line_id
* @returns {Deferred}
*/
addProposition: function (handle, mv_line_id) {
@@ -167,13 +167,15 @@ var StatementModel = BasicModel.extend({
* change the mode line ('inactive', 'match', 'create'), and fetch the new
* matched lines or prepare to create a new line
*
- * 'match': display the matched lines, the user can select the lines to
- * apply there as proposition
- * 'create': display fields and quick create button to create a new
- * proposition for the reconciliation
+ * ``match``
+ * display the matched lines, the user can select the lines to apply
+ * there as proposition
+ * ``create``
+ * display fields and quick create button to create a new proposition
+ * for the reconciliation
*
* @param {string} handle
- * @param {string} mode
+ * @param {'inactive' | 'match' | 'create'} mode
* @returns {Deferred}
*/
changeMode: function (handle, mode) {
@@ -316,7 +318,7 @@ var StatementModel = BasicModel.extend({
/**
* get the line data for this handle
*
- * @param {OdooEvent} event
+ * @param {Object} handle
* @returns {Object}
*/
getLine: function (handle) {
@@ -324,6 +326,7 @@ var StatementModel = BasicModel.extend({
},
/**
* load data from
+ *
* - 'account.bank.statement' fetch the line id and bank_statement_id info
* - 'account.reconcile.model' fetch all reconcile model (for quick add)
* - 'account.account' fetch all account code
@@ -430,7 +433,7 @@ var StatementModel = BasicModel.extend({
* 'amount_type'
*
* @param {string} handle
- * @param {integer} reconcile model id
+ * @param {integer} reconcileModelId
* @returns {Deferred}
*/
quickCreateProposition: function (handle, reconcileModelId) {
@@ -460,7 +463,7 @@ var StatementModel = BasicModel.extend({
* Remove a proposition and switch to an active mode ('create' or 'match')
*
* @param {string} handle
- * @param {number} proposition id (move line id)
+ * @param {number} id (move line id)
* @returns {Deferred}
*/
removeProposition: function (handle, id) {
@@ -519,7 +522,7 @@ var StatementModel = BasicModel.extend({
* Then the total is recomputed to have 100%.
*
* @param {string} handle
- * @param {number[]} context.statement_ids
+ * @param {*} values
* @returns {Deferred}
*/
updateProposition: function (handle, values) {
@@ -636,7 +639,7 @@ var StatementModel = BasicModel.extend({
* stop the editable proposition line and remove it if it's invalid then
* compute the line
*
- * @see '_computeLine'
+ * See :func:`_computeLine`
*
* @private
* @param {string} handle
@@ -679,7 +682,7 @@ var StatementModel = BasicModel.extend({
* Check the taxes server side for each updated propositions with tax_id
*
* @private
- * @param {Object}
+ * @param {Object} line
* @returns {Deferred}
*/
_computeLine: function (line) {
@@ -771,7 +774,7 @@ var StatementModel = BasicModel.extend({
*
* @private
* @param {string} handle
- * @param {integer} reconcile model id
+ * @param {integer} reconcileModelId
*/
_computeReconcileModels: function (handle, reconcileModelId) {
var line = this.getLine(handle);
@@ -788,8 +791,7 @@ var StatementModel = BasicModel.extend({
* format a name_get into an object {id, display_name}, idempotent
*
* @private
- * @param {Object|Array} data or name_get
- * @param {Object|false} {id, display_name}
+ * @param {Object|Array} [value] data or name_get
*/
_formatNameGet: function (value) {
return value ? (value.id ? value : {'id': value[0], 'display_name': value[1]}) : false;
@@ -798,8 +800,8 @@ var StatementModel = BasicModel.extend({
* Format each propositions (amount, label, account_id)
*
* @private
- * @param {Object}
- * @param {Object[]}
+ * @param {Object} line
+ * @param {Object[]} props
*/
_formatLineProposition: function (line, props) {
var self = this;
@@ -818,7 +820,7 @@ var StatementModel = BasicModel.extend({
* @see '_computeLine'
*
* @private
- * @param {Object[]}
+ * @param {Object[]} lines
* @returns {Deferred}
*/
_formatLine: function (lines) {
@@ -845,7 +847,7 @@ var StatementModel = BasicModel.extend({
*
* @private
* @param {string} handle
- * @param {Object[]}
+ * @param {Object[]} mv_lines
* @returns {Deferred}
*/
_formatMoveLine: function (handle, mv_lines) {
@@ -869,8 +871,8 @@ var StatementModel = BasicModel.extend({
* base_amount with the decimal number from the currency
*
* @private
- * @param {Object}
- * @param {Object}
+ * @param {Object} line
+ * @param {Object} values
* @returns {Object}
*/
_formatQuickCreate: function (line, values) {
@@ -980,7 +982,7 @@ var StatementModel = BasicModel.extend({
*
* @private
* @param {object} line
- * @param {object} proposition
+ * @param {object} prop
* @returns {object}
*/
_formatToProcessReconciliation: function (line, prop) {
@@ -1073,7 +1075,7 @@ var ManualModel = StatementModel.extend({
return self._rpc({
model: 'account.move.line',
method: 'get_data_for_manual_reconciliation',
- args: ['account', model.account_ids || self.account_ids],
+ args: ['account', context.account_ids || self.account_ids],
context: context,
})
.then(function (result) {
@@ -1216,7 +1218,7 @@ var ManualModel = StatementModel.extend({
*
* @override
* @private
- * @param {Object}
+ * @param {Object} line
* @returns {Deferred}
*/
_computeLine: function (line) {
@@ -1237,8 +1239,8 @@ var ManualModel = StatementModel.extend({
* @see '_computeLine'
*
* @private
- * @param {string} 'customers', 'suppliers' or 'accounts'
- * @param {Object}
+ * @param {'customers' | 'suppliers' | 'accounts'} type
+ * @param {Object} data
* @returns {Deferred}
*/
_formatLine: function (type, data) {
@@ -1264,8 +1266,8 @@ var ManualModel = StatementModel.extend({
*
* @override
* @private
- * @param {Object}
- * @param {Object}
+ * @param {Object} line
+ * @param {Object} props
*/
_formatLineProposition: function (line, props) {
var self = this;
@@ -1330,4 +1332,4 @@ return {
StatementModel: StatementModel,
ManualModel: ManualModel,
};
-}); \ No newline at end of file
+});
diff --git a/addons/account/views/account_report.xml b/addons/account/views/account_report.xml
index c6f3ba536ff..6328d865c2c 100644
--- a/addons/account/views/account_report.xml
+++ b/addons/account/views/account_report.xml
@@ -10,15 +10,7 @@
name="account.report_invoice_with_payments"
file="account.report_invoice_with_payments"
attachment="(object.state in ('open','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')"
- print_report_name="(
- object.type == 'out_invoice' and object.state == 'draft' and 'Draft Invoice' or
- object.type == 'out_invoice' and object.state in ('open','paid') and 'Invoice - %s' % (object.number) or
- object.type == 'out_refund' and object.state == 'draft' and 'Credit Note' or
- object.type == 'out_refund' and 'Credit Note - %s' % (object.number) or
- object.type == 'in_invoice' and object.state == 'draft' and 'Vendor Bill' or
- object.type == 'in_invoice' and object.state in ('open','paid') and 'Vendor Bill - %s' % (object.number) or
- object.type == 'in_refund' and object.state == 'draft' and 'Vendor Credit Note' or
- object.type == 'in_refund' and 'Vendor Credit Note - %s' % (object.number))"
+ print_report_name="(object._get_printed_report_name())"
/>
<report
@@ -29,15 +21,7 @@
name="account.report_invoice"
file="account.report_invoice"
attachment="(object.state in ('open','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')"
- print_report_name="(
- object.type == 'out_invoice' and object.state == 'draft' and 'Draft Invoice' or
- object.type == 'out_invoice' and object.state in ('open','paid') and 'Invoice - %s' % (object.number) or
- object.type == 'out_refund' and object.state == 'draft' and 'Credit Note' or
- object.type == 'out_refund' and 'Credit Note - %s' % (object.number) or
- object.type == 'in_invoice' and object.state == 'draft' and 'Vendor Bill' or
- object.type == 'in_invoice' and object.state in ('open','paid') and 'Vendor Bill - %s' % (object.number) or
- object.type == 'in_refund' and object.state == 'draft' and 'Vendor Credit Note' or
- object.type == 'in_refund' and 'Vendor Credit Note - %s' % (object.number))"
+ print_report_name="(object._get_printed_report_name())"
/>
<report
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index bec3e095229..eec794ddd01 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -9,7 +9,7 @@
<div name="invoice_address" class="col-xs-5 col-xs-offset-7">
<address t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
- <div t-if="o.partner_id.vat" class="mt16">VAT: <span t-field="o.partner_id.vat"/></div>
+ <div t-if="o.partner_id.vat" class="mt16"><t t-esc="o.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="o.partner_id.vat"/></div>
</div>
</div>
@@ -53,7 +53,7 @@
<!-- Is there a discount on at least one line? -->
<t t-set="display_discount" t-value="any([l.discount for l in o.invoice_line_ids])"/>
- <table class="table table-condensed">
+ <table class="table table-condensed" name="invoice_line_table">
<thead>
<tr>
<th>Description</th>
diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js
index 0f13c59fe66..f26748ac7b3 100644
--- a/addons/barcodes/static/src/js/barcode_events.js
+++ b/addons/barcodes/static/src/js/barcode_events.js
@@ -182,9 +182,12 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
});
return {
- // Singleton that emits barcode_scanned events on core.bus
+ /** Singleton that emits barcode_scanned events on core.bus */
BarcodeEvents: new BarcodeEvents(),
- // List of barcode prefixes that are reserved for internal purposes
+ /**
+ * List of barcode prefixes that are reserved for internal purposes
+ * @type Array
+ */
ReservedBarcodePrefixes: ['O-CMD'],
};
diff --git a/addons/bus/static/src/js/bus.js b/addons/bus/static/src/js/bus.js
index a79c7500669..25a8b86903f 100644
--- a/addons/bus/static/src/js/bus.js
+++ b/addons/bus/static/src/js/bus.js
@@ -136,12 +136,13 @@ bus.Bus = Widget.extend({
* one in the list of open tabs. This one start polling for the other. When a notification is recieved from the poll, it
* is signaling through the localStorage too.
*
- * localStorage used keys are :
- * - bus.channels : shared public channel list to listen during the poll
- * - bus.options : shared options
- * - bus.notification : the received notifications from the last poll
- * - bus.tab_list : list of opened tab ids
- * - bus.tab_master : generated id of the master tab
+ * localStorage used keys are:
+ *
+ * - bus.channels : shared public channel list to listen during the poll
+ * - bus.options : shared options
+ * - bus.notification : the received notifications from the last poll
+ * - bus.tab_list : list of opened tab ids
+ * - bus.tab_master : generated id of the master tab
*/
var CrossTabBus = bus.Bus.extend({
init: function(){
diff --git a/addons/hr_attendance/static/src/js/greeting_message.js b/addons/hr_attendance/static/src/js/greeting_message.js
index 08c6879e077..ac6e3f86c73 100644
--- a/addons/hr_attendance/static/src/js/greeting_message.js
+++ b/addons/hr_attendance/static/src/js/greeting_message.js
@@ -23,7 +23,7 @@ var GreetingMessage = Widget.extend({
// to the (likely) appropriate menu, according to the user access rights
if(!action.attendance) {
this.activeBarcode = false;
- this.session.user_has_group('hr_attendance.group_hr_attendance_user').then(function(has_group) {
+ this.getSession().user_has_group('hr_attendance.group_hr_attendance_user').then(function(has_group) {
if(has_group) {
self.next_action = 'hr_attendance.hr_attendance_action_kiosk_mode';
} else {
diff --git a/addons/hr_attendance/static/src/js/kiosk_confirm.js b/addons/hr_attendance/static/src/js/kiosk_confirm.js
index b489102fe68..9ee035dc493 100644
--- a/addons/hr_attendance/static/src/js/kiosk_confirm.js
+++ b/addons/hr_attendance/static/src/js/kiosk_confirm.js
@@ -67,7 +67,7 @@ var KioskConfirm = Widget.extend({
start: function () {
var self = this;
- self.session.user_has_group('hr_attendance.group_hr_attendance_use_pin').then(function(has_group){
+ self.getSession().user_has_group('hr_attendance.group_hr_attendance_use_pin').then(function(has_group){
self.use_pin = has_group;
self.$el.html(QWeb.render("HrAttendanceKioskConfirm", {widget: self}));
self.start_clock();
diff --git a/addons/hr_recruitment/models/calendar.py b/addons/hr_recruitment/models/calendar.py
index 5da3ed28f51..976b5019b73 100644
--- a/addons/hr_recruitment/models/calendar.py
+++ b/addons/hr_recruitment/models/calendar.py
@@ -20,7 +20,7 @@ class CalendarEvent(models.Model):
# sync res_model / res_id to opportunity id (aka creating meeting from lead chatter)
if 'applicant_id' not in defaults and defaults.get('res_id') and (defaults.get('res_model') or defaults.get('res_model_id')):
- if (defaults.get('res_model') and defaults['res_model'] == 'hr.applicant') or self.env['ir.model'].sudo().browse(defaults['res_model_id']).model == 'hr.applicant':
+ if (defaults.get('res_model') and defaults['res_model'] == 'hr.applicant') or (defaults.get('res_model_id') and self.env['ir.model'].sudo().browse(defaults['res_model_id']).model == 'hr.applicant'):
defaults['applicant_id'] = defaults['res_id']
return defaults
diff --git a/addons/l10n_in/__init__.py b/addons/l10n_in/__init__.py
index 67dee8c60db..dc5e6b693d1 100644
--- a/addons/l10n_in/__init__.py
+++ b/addons/l10n_in/__init__.py
@@ -1,2 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/addons/l10n_in/__manifest__.py b/addons/l10n_in/__manifest__.py
index 476a3b5460d..2dd8452e84f 100644
--- a/addons/l10n_in/__manifest__.py
+++ b/addons/l10n_in/__manifest__.py
@@ -18,12 +18,19 @@ Sheet, now only Vertical format has been permitted Which is Supported By Odoo.
""",
'category': 'Localization',
'depends': [
- 'account',
+ 'account_tax_python',
],
'data': [
'data/account_data.xml',
'data/l10n_in_chart_data.xml',
'data/account_tax_template_data.xml',
+ 'data/account_fiscal_position_data.xml',
+ 'data/res_country_state_data.xml',
'data/account_chart_template_data.yml',
+ 'views/product_template_view.xml',
+ 'views/report_invoice.xml',
+ 'views/res_company_view.xml',
+ 'views/res_country_state_view.xml',
+ 'views/res_partner_views.xml',
],
}
diff --git a/addons/l10n_in/data/account_data.xml b/addons/l10n_in/data/account_data.xml
index cd46677af1b..724b3e3e876 100644
--- a/addons/l10n_in/data/account_data.xml
+++ b/addons/l10n_in/data/account_data.xml
@@ -3,26 +3,17 @@
<data noupdate="1">
<!-- Account Tax Group -->
- <record id="tax_group_1" model="account.tax.group">
- <field name="name">VAT 1%</field>
+ <record id="sgst_group" model="account.tax.group">
+ <field name="name">SGST</field>
</record>
- <record id="tax_group_25" model="account.tax.group">
- <field name="name">VAT 2.5%</field>
+ <record id="cgst_group" model="account.tax.group">
+ <field name="name">CGST</field>
</record>
- <record id="tax_group_4" model="account.tax.group">
- <field name="name">VAT 4%</field>
+ <record id="igst_group" model="account.tax.group">
+ <field name="name">IGST</field>
</record>
- <record id="tax_group_12" model="account.tax.group">
- <field name="name">VAT 12%</field>
- </record>
- <record id="tax_group_125" model="account.tax.group">
- <field name="name">VAT 12.5%</field>
- </record>
- <record id="tax_group_14" model="account.tax.group">
- <field name="name">VAT 14%</field>
- </record>
- <record id="tax_group_15" model="account.tax.group">
- <field name="name">VAT 15%</field>
+ <record id="cess_group" model="account.tax.group">
+ <field name="name">Cess</field>
</record>
</data>
diff --git a/addons/l10n_in/data/account_fiscal_position_data.xml b/addons/l10n_in/data/account_fiscal_position_data.xml
new file mode 100644
index 00000000000..d36e31fc1d0
--- /dev/null
+++ b/addons/l10n_in/data/account_fiscal_position_data.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <!-- Fiscal Position Templates -->
+ <record model="account.fiscal.position.template" id="fiscal_position_in_inter_state">
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="name">Inter State</field>
+ </record>
+
+ <record model="account.fiscal.position.template" id="fiscal_position_in_export">
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="name">Export</field>
+ </record>
+
+ <!-- Fiscal Position Tax Templates -->
+ <record id="account_fiscal_position_tax_in_sale_1_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_sale_1"/>
+ <field name="tax_dest_id" ref="igst_sale_1"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_2_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_sale_2"/>
+ <field name="tax_dest_id" ref="igst_sale_2"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_5_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_sale_5"/>
+ <field name="tax_dest_id" ref="igst_sale_5"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_12_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_sale_12"/>
+ <field name="tax_dest_id" ref="igst_sale_12"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_18_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_sale_18"/>
+ <field name="tax_dest_id" ref="igst_sale_18"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_28_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_sale_28"/>
+ <field name="tax_dest_id" ref="igst_sale_28"/>
+ </record>
+
+ <record id="account_fiscal_position_tax_in_purchase_1_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_purchase_1"/>
+ <field name="tax_dest_id" ref="igst_purchase_1"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_2_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_purchase_2"/>
+ <field name="tax_dest_id" ref="igst_purchase_2"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_5_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_purchase_5"/>
+ <field name="tax_dest_id" ref="igst_purchase_5"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_12_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_purchase_12"/>
+ <field name="tax_dest_id" ref="igst_purchase_12"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_18_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_purchase_18"/>
+ <field name="tax_dest_id" ref="igst_purchase_18"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_28_inter" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_inter_state"/>
+ <field name="tax_src_id" ref="sgst_purchase_28"/>
+ <field name="tax_dest_id" ref="igst_purchase_28"/>
+ </record>
+
+ <record id="account_fiscal_position_tax_in_sale_1_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_sale_1"/>
+ <field name="tax_dest_id" ref="igst_sale_1"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_2_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_sale_2"/>
+ <field name="tax_dest_id" ref="igst_sale_2"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_5_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_sale_5"/>
+ <field name="tax_dest_id" ref="igst_sale_5"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_12_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_sale_12"/>
+ <field name="tax_dest_id" ref="igst_sale_12"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_18_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_sale_18"/>
+ <field name="tax_dest_id" ref="igst_sale_18"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_sale_28_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_sale_28"/>
+ <field name="tax_dest_id" ref="igst_sale_28"/>
+ </record>
+
+ <record id="account_fiscal_position_tax_in_purchase_1_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_purchase_1"/>
+ <field name="tax_dest_id" ref="igst_purchase_1"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_2_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_purchase_2"/>
+ <field name="tax_dest_id" ref="igst_purchase_2"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_5_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_purchase_5"/>
+ <field name="tax_dest_id" ref="igst_purchase_5"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_12_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_purchase_12"/>
+ <field name="tax_dest_id" ref="igst_purchase_12"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_18_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_purchase_18"/>
+ <field name="tax_dest_id" ref="igst_purchase_18"/>
+ </record>
+ <record id="account_fiscal_position_tax_in_purchase_28_exp" model="account.fiscal.position.tax.template">
+ <field name="position_id" ref="fiscal_position_in_export"/>
+ <field name="tax_src_id" ref="sgst_purchase_28"/>
+ <field name="tax_dest_id" ref="igst_purchase_28"/>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_in/data/account_tax_template_data.xml b/addons/l10n_in/data/account_tax_template_data.xml
index e564bba381f..0261b910602 100644
--- a/addons/l10n_in/data/account_tax_template_data.xml
+++ b/addons/l10n_in/data/account_tax_template_data.xml
@@ -1,659 +1,718 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <!-- Account Tags -->
- <record id="st_tag_in_01" model="account.account.tag">
- <field name="name">Tax to Pay</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_02" model="account.account.tag">
- <field name="name">Excise Duty</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_03" model="account.account.tag">
- <field name="name">Excise Duty 12.5%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_04" model="account.account.tag">
- <field name="name">Excise Duty Collected on Sales</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_05" model="account.account.tag">
- <field name="name">Excise Duty Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_06" model="account.account.tag">
- <field name="name">Sales Tax</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_07" model="account.account.tag">
- <field name="name">Sales Tax 4%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_08" model="account.account.tag">
- <field name="name">Sales Tax 4% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_09" model="account.account.tag">
- <field name="name">Sales Tax 4% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_10" model="account.account.tag">
- <field name="name">Sales Tax 12%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_11" model="account.account.tag">
- <field name="name">Sales Tax 12% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_12" model="account.account.tag">
- <field name="name">Sales Tax 12% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_13" model="account.account.tag">
- <field name="name">Sales Tax 15%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_14" model="account.account.tag">
- <field name="name">Sales Tax 15% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
- <record id="st_tag_in_15" model="account.account.tag">
- <field name="name">Sales Tax 15% Paid on Purchase</field>
+ <record id="sgst_tag_tax" model="account.account.tag">
+ <field name="name">SGST</field>
<field name="applicability">taxes</field>
</record>
-
- <record id="st_tag_in_16" model="account.account.tag">
- <field name="name">Service Tax</field>
+ <record id="cgst_tag_tax" model="account.account.tag">
+ <field name="name">CGST</field>
<field name="applicability">taxes</field>
</record>
-
- <record id="st_tag_in_17" model="account.account.tag">
- <field name="name">Service Tax 14%</field>
+ <record id="igst_tag_tax" model="account.account.tag">
+ <field name="name">IGST</field>
<field name="applicability">taxes</field>
</record>
-
- <record id="st_tag_in_18" model="account.account.tag">
- <field name="name">Service Tax Collected on Sales of Services</field>
+ <record id="cess_tag_tax" model="account.account.tag">
+ <field name="name">CESS</field>
<field name="applicability">taxes</field>
</record>
- <record id="st_tag_in_19" model="account.account.tag">
- <field name="name">Swachh Bharat Cess 0.5% Service Tax Collected on Sales of Services</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- GST TAXES-->
- <record id="st_tag_in_20" model="account.account.tag">
- <field name="name">Krishi Kalayan Cess 0.5% Service Tax Collected on Sales of Services</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- CESS Tax -->
- <record id="st_tag_in_21" model="account.account.tag">
- <field name="name">Service Tax Paid on Purchase of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_22" model="account.account.tag">
- <field name="name">Swachh Bharat Cess 0.5% Service Tax Paid on Purchase of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_23" model="account.account.tag">
- <field name="name">Krishi Kalayan Cess 0.5% Service Tax Paid on Purchase of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_24" model="account.account.tag">
- <field name="name">VAT</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_25" model="account.account.tag">
- <field name="name">VAT 4%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_26" model="account.account.tag">
- <field name="name">VAT 4% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_27" model="account.account.tag">
- <field name="name">VAT 4% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_28" model="account.account.tag">
- <field name="name">VAT 12.5%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_29" model="account.account.tag">
- <field name="name">VAT 12.5% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_30" model="account.account.tag">
- <field name="name">VAT 12.5% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_31" model="account.account.tag">
- <field name="name">Additional VAT 1%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_32" model="account.account.tag">
- <field name="name">Additional VAT 1% Collected on Sale</field>
- <field name="applicability">taxes</field>
+ <record id="cess_sale_5" model="account.tax.template">
+ <field name="name">CESS Sale 5%</field>
+ <field name="description">CESS 5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">5</field>
+ <field name="tax_group_id" ref="cess_group"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cess_tag_tax'),])]"/>
</record>
- <record id="st_tag_in_33" model="account.account.tag">
- <field name="name">Additional VAT 1% Paid on Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="cess_sale_1591" model="account.tax.template">
+ <field name="name">CESS Sale 1591 Per Thousand</field>
+ <field name="description">1591 PER THOUSAND</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">fixed</field>
+ <field name="amount">1.591</field>
+ <field name="tax_group_id" ref="cess_group"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cess_tag_tax'),])]"/>
</record>
- <record id="st_tag_in_34" model="account.account.tag">
- <field name="name">Additional VAT 2.5%</field>
- <field name="applicability">taxes</field>
+ <record id="cess_5_plus_1591_sale" model="account.tax.template">
+ <field name="name">CESS 5% + RS.1591/THOUSAND </field>
+ <field name="description">CESS 5% + RS.1591/THOUSAND</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('cess_sale_5'),ref('cess_sale_1591'),])]"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cess_tag_tax'),])]"/>
</record>
- <record id="st_tag_in_35" model="account.account.tag">
- <field name="name">Additional VAT 2.5% Collected on Sale</field>
- <field name="applicability">taxes</field>
+ <record id="cess_21_4170_higer_sale" model="account.tax.template">
+ <field name="name">CESS 21% or RS.4170/THOUSAND </field>
+ <field name="description">CESS 21% or RS.4170/THOUSAND</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">code</field>
+ <field name="amount">0</field>
+ <field name="python_compute">result=base_amount * 0.21
+tax=quantity * 4.17
+if tax > result:result=tax</field>
+ <field name="tax_group_id" ref="cess_group"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cess_tag_tax'),])]"/>
</record>
- <record id="st_tag_in_36" model="account.account.tag">
- <field name="name">Additional VAT 2.5% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- IGST -->
- <record id="st_tag_in_37" model="account.account.tag">
- <field name="name">Tax Bases</field>
- <field name="applicability">taxes</field>
+ <record id="igst_sale_1" model="account.tax.template">
+ <field name="name">IGST 1%</field>
+ <field name="description">IGST 1%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="account_id" ref="p11234"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="st_tag_in_38" model="account.account.tag">
- <field name="name">Base Excise Duty</field>
- <field name="applicability">taxes</field>
+ <record id="igst_sale_2" model="account.tax.template">
+ <field name="name">IGST 2%</field>
+ <field name="description">IGST 2%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2</field>
+ <field name="account_id" ref="p11234"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="st_tag_in_39" model="account.account.tag">
- <field name="name">Base Excise Duty 12.5%</field>
- <field name="applicability">taxes</field>
+ <record id="igst_sale_28" model="account.tax.template">
+ <field name="name">IGST 28%</field>
+ <field name="description">IGST 28%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">28</field>
+ <field name="account_id" ref="p11234"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="st_tag_in_40" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="igst_sale_18" model="account.tax.template">
+ <field name="name">IGST 18%</field>
+ <field name="description">IGST 18%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">18</field>
+ <field name="account_id" ref="p11234"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="st_tag_in_41" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="igst_sale_12" model="account.tax.template">
+ <field name="name">IGST 12%</field>
+ <field name="description">IGST 12%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">12</field>
+ <field name="account_id" ref="p11234"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="st_tag_in_42" model="account.account.tag">
- <field name="name">Base Sales Tax</field>
- <field name="applicability">taxes</field>
+ <record id="igst_sale_5" model="account.tax.template">
+ <field name="name">IGST 5%</field>
+ <field name="description">IGST 5%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">5</field>
+ <field name="account_id" ref="p11234"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="st_tag_in_43" model="account.account.tag">
- <field name="name">Base Sales Tax 4%</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- SGST & CGST Sales Group Tax -->
- <record id="st_tag_in_44" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_0_5" model="account.tax.template">
+ <field name="name">SGST Sale 0.5%</field>
+ <field name="description">SGST 0.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0.5</field>
+ <field name="account_id" ref="p11232"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="st_tag_in_45" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="cgst_sale_0_5" model="account.tax.template">
+ <field name="name">CGST Sale 0.5%</field>
+ <field name="description">CGST 0.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0.5</field>
+ <field name="account_id" ref="p11233"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="st_tag_in_46" model="account.account.tag">
- <field name="name">Base Sales Tax 12%</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_1" model="account.tax.template">
+ <field name="name">GST 1%</field>
+ <field name="description">GST 1%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_0_5'), ref('cgst_sale_0_5'),])]"/>
</record>
- <record id="st_tag_in_47" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_1_2" model="account.tax.template">
+ <field name="name">SGST Sale 1%</field>
+ <field name="description">SGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="account_id" ref="p11232"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="st_tag_in_48" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="cgst_sale_1_2" model="account.tax.template">
+ <field name="name">CGST Sale 1%</field>
+ <field name="description">CGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="account_id" ref="p11233"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="st_tag_in_49" model="account.account.tag">
- <field name="name">Base Sales Tax 15%</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_2" model="account.tax.template">
+ <field name="name">GST 2%</field>
+ <field name="description">GST 2%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_1_2'), ref('cgst_sale_1_2'),])]"/>
</record>
- <record id="st_tag_in_50" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_14" model="account.tax.template">
+ <field name="name">SGST Sale 14%</field>
+ <field name="description">SGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="account_id" ref="p11232"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="st_tag_in_51" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="cgst_sale_14" model="account.tax.template">
+ <field name="name">CGST Sale 14%</field>
+ <field name="description">CGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="account_id" ref="p11233"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="st_tag_in_52" model="account.account.tag">
- <field name="name">Base Service Tax</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_28" model="account.tax.template">
+ <field name="name">GST 28%</field>
+ <field name="description">GST 28%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_14'),ref('cgst_sale_14'),])]"/>
</record>
- <record id="st_tag_in_53" model="account.account.tag">
- <field name="name">Base Service Tax 15%</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_9" model="account.tax.template">
+ <field name="name">SGST Sale 9%</field>
+ <field name="description">SGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="account_id" ref="p11232"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="st_tag_in_54" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="cgst_sale_9" model="account.tax.template">
+ <field name="name">CGST Sale 9%</field>
+ <field name="description">CGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="account_id" ref="p11233"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="st_tag_in_55" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_18" model="account.tax.template">
+ <field name="name">GST 18%</field>
+ <field name="description">GST 18%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_9'),ref('cgst_sale_9'),])]"/>
</record>
- <record id="st_tag_in_56" model="account.account.tag">
- <field name="name">Base VAT</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_6" model="account.tax.template">
+ <field name="name">SGST Sale 6%</field>
+ <field name="description">SGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="account_id" ref="p11232"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="st_tag_in_57" model="account.account.tag">
- <field name="name">Base VAT 4%</field>
- <field name="applicability">taxes</field>
+ <record id="cgst_sale_6" model="account.tax.template">
+ <field name="name">CGST Sale 6%</field>
+ <field name="description">CGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="account_id" ref="p11233"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="st_tag_in_58" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_12" model="account.tax.template">
+ <field name="name">GST 12%</field>
+ <field name="description">GST 12%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_6'),ref('cgst_sale_6'),])]"/>
</record>
- <record id="st_tag_in_59" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_2_5" model="account.tax.template">
+ <field name="name">SGST Sale 2.5%</field>
+ <field name="description">SGST 2.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="p11232"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="st_tag_in_60" model="account.account.tag">
- <field name="name">Base VAT 12.5%</field>
- <field name="applicability">taxes</field>
+ <record id="cgst_sale_2_5" model="account.tax.template">
+ <field name="name">CGST Sale 2.5%</field>
+ <field name="description">CGST 2.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="p11233"/>
+ <field name="refund_account_id" ref="p20011"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="st_tag_in_61" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="sgst_sale_5" model="account.tax.template">
+ <field name="name">GST 5%</field>
+ <field name="description">GST 5%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_sale_2_5'), ref('cgst_sale_2_5'),])]"/>
</record>
- <record id="st_tag_in_62" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- Purchase Taxes-->
- <record id="st_tag_in_63" model="account.account.tag">
- <field name="name">Base Additional VAT 1%</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- CESS Taxes -->
- <record id="st_tag_in_64" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="cess_purchase_5" model="account.tax.template">
+ <field name="name">CESS Purchase 5%</field>
+ <field name="description">CESS 5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">5</field>
+ <field name="tax_group_id" ref="cess_group"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cess_tag_tax'),])]"/>
</record>
- <record id="st_tag_in_65" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="cess_purchase_1591" model="account.tax.template">
+ <field name="name">CESS Purchase 1591 Per Thousand</field>
+ <field name="description">1591 PER THOUSAND</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">fixed</field>
+ <field name="amount">1.591</field>
+ <field name="tax_group_id" ref="cess_group"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cess_tag_tax'),])]"/>
</record>
- <record id="st_tag_in_66" model="account.account.tag">
- <field name="name">Base Additional VAT 2.5%</field>
- <field name="applicability">taxes</field>
+ <record id="cess_5_plus_1591_purchase" model="account.tax.template">
+ <field name="name">CESS 5% + RS.1591/THOUSAND </field>
+ <field name="description">CESS 5% + RS.1591/THOUSAND</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('cess_purchase_5'),ref('cess_purchase_1591'),])]"/>
</record>
- <record id="st_tag_in_67" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="cess_21_4170_higer_purchase" model="account.tax.template">
+ <field name="name">CESS 21% or RS.4170/THOUSAND </field>
+ <field name="description">CESS 21% or RS.4170/THOUSAND</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount_type">code</field>
+ <field name="amount">0</field>
+ <field name="python_compute">result=base_amount * 0.21
+tax=quantity * 4.17
+if tax > result:result=tax</field>
+ <field name="tax_group_id" ref="cess_group"/>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cess_tag_tax'),])]"/>
</record>
- <record id="st_tag_in_68" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
- </record>
- <!-- Account Tax Templates -->
+ <!-- IGST -->
- <record id="input_tax_15" model="account.tax.template">
- <field name="name">(standard)Input Sales Tax @ 15%</field>
- <field name="description">(standard)Input Sales Tax @ 15%</field>
- <field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
- <field name="amount">15</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p11235"/>
- <field name="refund_account_id" ref="p20011"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_14'), ref('st_tag_in_50')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
- </record>
- <record id="input_tax_12" model="account.tax.template">
- <field name="name">(standard)Input Sales Tax @ 12%</field>
- <field name="description">(standard)Input Sales Tax @ 12%</field>
- <field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
- <field name="amount">12</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p11235"/>
- <field name="refund_account_id" ref="p20011"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_11'), ref('st_tag_in_47')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
- </record>
- <record id="input_tax_4" model="account.tax.template">
- <field name="name">(standard)Input Sales Tax @ 4%</field>
- <field name="description">(standard)Input Sales Tax @ 4%</field>
- <field name="type_tax_use">sale</field>
+ <record id="igst_purchase_1" model="account.tax.template">
+ <field name="name">IGST 1%</field>
+ <field name="description">IGST 1%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p11235"/>
- <field name="refund_account_id" ref="p20011"/>
+ <field name="amount">1</field>
+ <field name="account_id" ref="p10053"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_08'), ref('st_tag_in_44')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="add_input_vat_1" model="account.tax.template">
- <field name="name">(standard)Additional Input VAT @ 1%</field>
- <field name="description">(standard)Additional Input VAT @ 1%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="igst_purchase_2" model="account.tax.template">
+ <field name="name">IGST 2%</field>
+ <field name="description">IGST 2%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">1</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="p11232"/>
- <field name="refund_account_id" ref="p20011"/>
+ <field name="amount">2</field>
+ <field name="account_id" ref="p10053"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_64'), ref('st_tag_in_32')])]"/>
- <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="input_vat_12" model="account.tax.template">
- <field name="name">(standard)Input VAT @ 12.5%</field>
- <field name="description">(standard)Input VAT @ 12.5%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="igst_purchase_28" model="account.tax.template">
+ <field name="name">IGST 28%</field>
+ <field name="description">IGST 28%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">12.5</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p11232"/>
- <field name="refund_account_id" ref="p20011"/>
+ <field name="amount">28</field>
+ <field name="account_id" ref="p10053"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_61'), ref('st_tag_in_29')])]"/>
- <field name="tax_group_id" ref="tax_group_125"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="add_input_vat_2" model="account.tax.template">
- <field name="name">(standard)Additional Input VAT @ 2.5%</field>
- <field name="description">(standard)Additional Input VAT @ 2.5%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="igst_purchase_18" model="account.tax.template">
+ <field name="name">IGST 18%</field>
+ <field name="description">IGST 18%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">2.5</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="p11232"/>
- <field name="refund_account_id" ref="p20011"/>
+ <field name="amount">18</field>
+ <field name="account_id" ref="p10053"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_67'), ref('st_tag_in_35')])]"/>
- <field name="tax_group_id" ref="tax_group_25"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="sale_Excise_Duty_12_5" model="account.tax.template">
- <field name="name">(standard)Input Excise Duty @ 12.5%</field>
- <field name="description">(standard)Input Excise Duty @ 12.5%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="igst_purchase_12" model="account.tax.template">
+ <field name="name">IGST 12%</field>
+ <field name="description">IGST 12%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">12.5</field>
- <field name="sequence">1</field>
- <field name="account_id" ref="p112331"/>
- <field name="refund_account_id" ref="p20011"/>
+ <field name="amount">12</field>
+ <field name="account_id" ref="p10053"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_04'), ref('st_tag_in_40')])]"/>
- <field name="tax_group_id" ref="tax_group_125"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="sale_service_tax14" model="account.tax.template">
- <field name="name">(standard)Input Service Tax 14%</field>
- <field name="description">(standard)Input Service Tax 14%</field>
- <field name="type_tax_use">none</field>
+
+ <record id="igst_purchase_5" model="account.tax.template">
+ <field name="name">IGST 5%</field>
+ <field name="description">IGST 5%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">14</field>
- <field name="sequence">13</field>
- <field name="account_id" ref="p112341"/>
- <field name="refund_account_id" ref="p20011"/>
+ <field name="amount">5</field>
+ <field name="account_id" ref="p10053"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_18')])]"/>
- <field name="tax_group_id" ref="tax_group_14"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="igst_group"/>
</record>
- <record id="sale_sb_cess_service_tax_0_5" model="account.tax.template">
- <field name="name">(standard)Swachh Bharat Cess @ 0.5% On Input Service</field>
- <field name="description">(standard)Swacha Bharat Cess @ 0.5%</field>
+
+ <!-- SGST & CGST -->
+
+ <record id="sgst_purchase_0_5" model="account.tax.template">
+ <field name="name">SGST Purchase 0.5%</field>
+ <field name="description">SGST 0.5%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
<field name="amount">0.5</field>
- <field name="sequence">14</field>
- <field name="account_id" ref="p112342"/>
+ <field name="account_id" ref="p10051"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_19')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="sale_kk_cess_service_tax_0_5" model="account.tax.template">
- <field name="name">(standard)Krishi Kalyan Cess @ 0.5% On Input Service</field>
- <field name="description">(standard)Input Higher Education Cess @ 0.5% On Service Tax</field>
+
+ <record id="cgst_purchase_0_5" model="account.tax.template">
+ <field name="name">CGST Purchase 0.5%</field>
+ <field name="description">CGST 0.5%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
<field name="amount">0.5</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="p112343"/>
+ <field name="account_id" ref="p10052"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_20')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="sale_service_tax_15" model="account.tax.template">
- <field name="name">(standard)Input Service Tax @ 15%</field>
- <field name="description">(standard)Input Service Tax @ 15%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="sgst_purchase_1" model="account.tax.template">
+ <field name="name">GST 1%</field>
+ <field name="description">GST 1%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">group</field>
- <field name="amount">14</field>
- <field name="include_base_amount">True</field>
- <field name="sequence">8</field>
- <field name="account_id" ref="p112341"/>
- <field name="refund_account_id" ref="p20011"/>
- <field name="children_tax_ids" eval="[(6, 0, [ref('sale_service_tax14'),ref('sale_sb_cess_service_tax_0_5'),ref('sale_kk_cess_service_tax_0_5')])]"/>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_54')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_0_5'),ref('cgst_purchase_0_5'),])]"/>
</record>
- <record id="input_vat_4" model="account.tax.template">
- <field name="name">(standard)Input VAT @ 4%</field>
- <field name="description">(standard)Input VAT @ 4%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="sgst_purchase_1_2" model="account.tax.template">
+ <field name="name">SGST Purchase 1%</field>
+ <field name="description">SGST 1%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p11232"/>
- <field name="refund_account_id" ref="p20011"/>
+ <field name="amount">1</field>
+ <field name="account_id" ref="p10051"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_26'), ref('st_tag_in_58')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <!--output purchase tax-->
- <record id="output_tax_15" model="account.tax.template">
- <field name="name">(standard)Output Purchase Tax @ 15%</field>
- <field name="description">(standard)Output Purchase Tax @ 15%</field>
- <field name="type_tax_use">purchase</field>
+ <record id="cgst_purchase_1_2" model="account.tax.template">
+ <field name="name">CGST Purchase 1%</field>
+ <field name="description">CGST 1%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">15</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p10050"/>
+ <field name="amount">1</field>
+ <field name="account_id" ref="p10052"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_51'), ref('st_tag_in_15')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="output_tax_12" model="account.tax.template">
- <field name="name">(standard)Output Purchase Tax @ 12%</field>
- <field name="description">(standard)Output Purchase Tax @ 12%</field>
+
+ <record id="sgst_purchase_2" model="account.tax.template">
+ <field name="name">GST 2%</field>
+ <field name="description">GST 2%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
- <field name="amount">12</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p10050"/>
- <field name="refund_account_id" ref="p2107"/>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_12'), ref('st_tag_in_48')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_1_2'),ref('cgst_purchase_1_2'),])]"/>
</record>
- <record id="output_tax_4" model="account.tax.template">
- <field name="name">(standard)Output Purchase Tax @ 4%</field>
- <field name="description">(standard)Output Purchase Tax @ 4%</field>
- <field name="type_tax_use">purchase</field>
+
+ <record id="sgst_purchase_14" model="account.tax.template">
+ <field name="name">SGST Purchase 14%</field>
+ <field name="description">SGST 14%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p10050"/>
+ <field name="amount">14</field>
+ <field name="account_id" ref="p10051"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_45'), ref('st_tag_in_09')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="add_output_vat_1" model="account.tax.template">
- <field name="name">(standard)Additional Output VAT @ 1%</field>
- <field name="description">(standard)Additional Output VAT @ 1%</field>
- <field name="type_tax_use">purchase</field>
+
+ <record id="cgst_purchase_14" model="account.tax.template">
+ <field name="name">CGST Purchase 14%</field>
+ <field name="description">CGST 14%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">1</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="p10051"/>
+ <field name="amount">14</field>
+ <field name="account_id" ref="p10052"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_65'), ref('st_tag_in_33')])]"/>
- <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="output_vat_12" model="account.tax.template">
- <field name="name">(standard)Output VAT @ 12.5%</field>
- <field name="description">(standard)Output VAT @ 12.5%</field>
+
+ <record id="sgst_purchase_28" model="account.tax.template">
+ <field name="name">GST 28%</field>
+ <field name="description">GST 28%</field>
<field name="type_tax_use">purchase</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_14'),ref('cgst_purchase_14'),])]"/>
+ </record>
+
+ <record id="sgst_purchase_9" model="account.tax.template">
+ <field name="name">SGST Purchase 9%</field>
+ <field name="description">SGST 9%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">12.5</field>
- <field name="sequence">10</field>
+ <field name="amount">9</field>
<field name="account_id" ref="p10051"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_30'), ref('st_tag_in_62')])]"/>
- <field name="tax_group_id" ref="tax_group_125"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="add_output_vat_2" model="account.tax.template">
- <field name="name">(standard)Additional Output VAT @ 2.5%</field>
- <field name="description">(standard)Additional Output VAT @ 2.5%</field>
- <field name="type_tax_use">purchase</field>
+
+ <record id="cgst_purchase_9" model="account.tax.template">
+ <field name="name">CGST Purchase 9%</field>
+ <field name="description">CGST 9%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">2.5</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="p10051"/>
+ <field name="amount">9</field>
+ <field name="account_id" ref="p10052"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_68'), ref('st_tag_in_36')])]"/>
- <field name="tax_group_id" ref="tax_group_25"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="purchase_Excise_Duty_12_5" model="account.tax.template">
- <field name="name">(standard)Output Excise Duty @ 12.5%</field>
- <field name="description">(standard)Output Excise Duty @ 12.5%</field>
+
+ <record id="sgst_purchase_18" model="account.tax.template">
+ <field name="name">GST 18%</field>
+ <field name="description">GST 18%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
- <field name="amount">12.5</field>
- <field name="sequence">1</field>
- <field name="account_id" ref="p100521"/>
- <field name="refund_account_id" ref="p2107"/>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_05'), ref('st_tag_in_41')])]"/>
- <field name="tax_group_id" ref="tax_group_125"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_9'),ref('cgst_purchase_9'),])]"/>
</record>
- <record id="purchase_service_tax14" model="account.tax.template">
- <field name="name">(standard)Output Serivce Tax 14%</field>
- <field name="description">(standard)Output Serivce Tax 14%</field>
+
+ <record id="sgst_purchase_6" model="account.tax.template">
+ <field name="name">SGST Purchase 6%</field>
+ <field name="description">SGST 6%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">14</field>
- <field name="sequence">9</field>
- <field name="account_id" ref="p100531"/>
+ <field name="amount">6</field>
+ <field name="account_id" ref="p10051"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_21')])]"/>
- <field name="tax_group_id" ref="tax_group_14"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="purchase_sb_cess_service_tax0_5" model="account.tax.template">
- <field name="name">(standard)Swachh Bharat Cess @ 0.5% On Output Service</field>
- <field name="description">(standard)Swachh Bharat Cess @ 0.5%</field>
+
+ <record id="cgst_purchase_6" model="account.tax.template">
+ <field name="name">CGST Purchase 6%</field>
+ <field name="description">CGST 6%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">0.5</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p100532"/>
+ <field name="amount">6</field>
+ <field name="account_id" ref="p10052"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_22')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="purchase_kk_cess_service_tax0_5" model="account.tax.template">
- <field name="name">(standard)Krishi Kalyan Cess @ 0.5% On Output Service</field>
- <field name="description">(standard)Krishi Kalyan Cess @ 0.5%</field>
+
+ <record id="sgst_purchase_12" model="account.tax.template">
+ <field name="name">GST 12%</field>
+ <field name="description">GST 12%</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_6'),ref('cgst_purchase_6'),])]"/>
+ </record>
+
+ <record id="sgst_purchase_2_5" model="account.tax.template">
+ <field name="name">SGST Purchase 2.5%</field>
+ <field name="description">SGST 2.5%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">0.5</field>
- <field name="sequence">11</field>
- <field name="account_id" ref="p100533"/>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="p10051"/>
+ <field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_23')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="sgst_group"/>
</record>
- <record id="purchase_service_tax_15" model="account.tax.template">
- <field name="name">(standard)Output Service Tax @ 15%</field>
- <field name="description">(standard)Output Service Tax @ 15%</field>
- <field name="type_tax_use">purchase</field>
- <field name="include_base_amount">True</field>
- <field name="amount_type">group</field>
- <field name="amount">14</field>
- <field name="sequence">8</field>
- <field name="account_id" ref="p100531"/>
+
+ <record id="cgst_purchase_2_5" model="account.tax.template">
+ <field name="name">CGST Purchase 2.5%</field>
+ <field name="description">CGST 2.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="p10052"/>
<field name="refund_account_id" ref="p2107"/>
- <field name="children_tax_ids" eval="[(6, 0, [ref('purchase_service_tax14'),ref('purchase_sb_cess_service_tax0_5'),ref('purchase_kk_cess_service_tax0_5')])]"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_55')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="cgst_group"/>
</record>
- <record id="output_vat_4" model="account.tax.template">
- <field name="name">(standard)Output VAT @ 4%</field>
- <field name="description">(standard)Output VAT @ 4%</field>
+
+ <record id="sgst_purchase_5" model="account.tax.template">
+ <field name="name">GST 5%</field>
+ <field name="description">GST 5%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="p10051"/>
- <field name="refund_account_id" ref="p2107"/>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_27'), ref('st_tag_in_59')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('sgst_purchase_2_5'),ref('cgst_purchase_2_5'),])]"/>
</record>
+
</odoo>
diff --git a/addons/l10n_in/data/l10n_in_chart_data.xml b/addons/l10n_in/data/l10n_in_chart_data.xml
index fefaf6bb309..bb6665970ac 100644
--- a/addons/l10n_in/data/l10n_in_chart_data.xml
+++ b/addons/l10n_in/data/l10n_in_chart_data.xml
@@ -20,6 +20,19 @@
<field name="chart_template_id" ref="indian_chart_template_standard"/>
</record>
+ <record id="sgst_tag_account" model="account.account.tag">
+ <field name="name">SGST</field>
+ <field name="applicability">accounts</field>
+ </record>
+ <record id="cgst_tag_account" model="account.account.tag">
+ <field name="name">CGST</field>
+ <field name="applicability">accounts</field>
+ </record>
+ <record id="igst_tag_account" model="account.account.tag">
+ <field name="name">IGST</field>
+ <field name="applicability">accounts</field>
+ </record>
+
<record model="account.account.template" id="p10031">
<field name="name">Inventories</field>
<field name="code">10031</field>
@@ -34,47 +47,31 @@
<field name="reconcile" eval="True"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
</record>
- <record model="account.account.template" id="p10050">
- <field name="name">Sales Tax Receivable</field>
- <field name="code">10050</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- </record>
<record model="account.account.template" id="p10051">
- <field name="name">VAT Receivable</field>
+ <field name="name">SGST Receivable</field>
<field name="code">10051</field>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_account'),])]"/>
</record>
- <record model="account.account.template" id="p100521">
- <field name="name">Excise Duty Receivable</field>
- <field name="code">100521</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- </record>
- <record model="account.account.template" id="p100531">
- <field name="name">Service Tax Receivable</field>
- <field name="code">100531</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- </record>
- <record model="account.account.template" id="p100532">
- <field name="name">Swachh Bharat Cess Receivable On Service</field>
- <field name="code">100532</field>
+
+ <record model="account.account.template" id="p10052">
+ <field name="name">CGST Receivable</field>
+ <field name="code">10052</field>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_account'),])]"/>
</record>
- <record model="account.account.template" id="p100533">
- <field name="name">Krishi Kalyan Cess Receivable On Service</field>
- <field name="code">100533</field>
+
+ <record model="account.account.template" id="p10053">
+ <field name="name">IGST Receivable</field>
+ <field name="code">10053</field>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_account'),])]"/>
</record>
<record model="account.account.template" id="p10054">
<field name="name">TDS Receivable</field>
@@ -207,46 +204,30 @@
<field name="chart_template_id" ref="indian_chart_template_standard"/>
</record>
<record model="account.account.template" id="p11232">
- <field name="name">VAT Payable</field>
+ <field name="name">SGST Payable</field>
<field name="code">11232</field>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('sgst_tag_account'),])]"/>
</record>
- <record model="account.account.template" id="p112331">
- <field name="name">Excise Duty Payable</field>
- <field name="code">112331</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- </record>
- <record model="account.account.template" id="p112341">
- <field name="name">Service Tax Payable</field>
- <field name="code">112341</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- </record>
- <record model="account.account.template" id="p112342">
- <field name="name">Swachh Bharat Cess Payable On Service</field>
- <field name="code">112342</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_standard"/>
- </record>
- <record model="account.account.template" id="p112343">
- <field name="name">Krishi Kalyan Cess Payable On Service</field>
- <field name="code">112343</field>
+
+ <record model="account.account.template" id="p11233">
+ <field name="name">CGST Payable</field>
+ <field name="code">11233</field>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('cgst_tag_account'),])]"/>
</record>
- <record model="account.account.template" id="p11235">
- <field name="name">Sales Tax Payable</field>
- <field name="code">11235</field>
+
+ <record model="account.account.template" id="p11234">
+ <field name="name">IGST Payable</field>
+ <field name="code">11234</field>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
+ <field name="tag_ids" eval="[(6,0,[ref('igst_tag_account'),])]"/>
</record>
<record model="account.account.template" id="p11241">
<field name="name">Wages Payable</field>
@@ -528,4 +509,5 @@
<field name="income_currency_exchange_account_id" ref="p2013"/>
<field name="expense_currency_exchange_account_id" ref="p2117"/>
</record>
+
</odoo>
diff --git a/addons/l10n_in/data/res_country_state_data.xml b/addons/l10n_in/data/res_country_state_data.xml
new file mode 100644
index 00000000000..c580d80eac8
--- /dev/null
+++ b/addons/l10n_in/data/res_country_state_data.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="base.state_in_an" model="res.country.state">
+ <field name="l10n_in_tin">35</field>
+ </record>
+ <record id="base.state_in_ap" model="res.country.state">
+ <field name="l10n_in_tin">28</field>
+ </record>
+ <record id="base.state_in_ar" model="res.country.state">
+ <field name="l10n_in_tin">12</field>
+ </record>
+ <record id="base.state_in_as" model="res.country.state">
+ <field name="l10n_in_tin">18</field>
+ </record>
+ <record id="base.state_in_br" model="res.country.state">
+ <field name="l10n_in_tin">10</field>
+ </record>
+ <record id="base.state_in_ch" model="res.country.state">
+ <field name="l10n_in_tin">04</field>
+ </record>
+ <record id="base.state_in_cg" model="res.country.state">
+ <field name="l10n_in_tin">22</field>
+ </record>
+ <record id="base.state_in_dn" model="res.country.state">
+ <field name="l10n_in_tin">26</field>
+ </record>
+ <record id="base.state_in_dd" model="res.country.state">
+ <field name="l10n_in_tin">25</field>
+ </record>
+ <record id="base.state_in_dl" model="res.country.state">
+ <field name="l10n_in_tin">07</field>
+ </record>
+ <record id="base.state_in_ga" model="res.country.state">
+ <field name="l10n_in_tin">30</field>
+ </record>
+ <record id="base.state_in_gj" model="res.country.state">
+ <field name="l10n_in_tin">24</field>
+ </record>
+ <record id="base.state_in_hr" model="res.country.state">
+ <field name="l10n_in_tin">06</field>
+ </record>
+ <record id="base.state_in_hp" model="res.country.state">
+ <field name="l10n_in_tin">02</field>
+ </record>
+ <record id="base.state_in_jk" model="res.country.state">
+ <field name="l10n_in_tin">01</field>
+ </record>
+ <record id="base.state_in_jh" model="res.country.state">
+ <field name="l10n_in_tin">20</field>
+ </record>
+ <record id="base.state_in_ka" model="res.country.state">
+ <field name="l10n_in_tin">29</field>
+ </record>
+ <record id="base.state_in_kl" model="res.country.state">
+ <field name="l10n_in_tin">32</field>
+ </record>
+ <record id="base.state_in_ld" model="res.country.state">
+ <field name="l10n_in_tin">31</field>
+ </record>
+ <record id="base.state_in_mp" model="res.country.state">
+ <field name="l10n_in_tin">23</field>
+ </record>
+ <record id="base.state_in_mh" model="res.country.state">
+ <field name="l10n_in_tin">27</field>
+ </record>
+ <record id="base.state_in_mn" model="res.country.state">
+ <field name="l10n_in_tin">14</field>
+ </record>
+ <record id="base.state_in_ml" model="res.country.state">
+ <field name="l10n_in_tin">17</field>
+ </record>
+ <record id="base.state_in_mz" model="res.country.state">
+ <field name="l10n_in_tin">15</field>
+ </record>
+ <record id="base.state_in_nl" model="res.country.state">
+ <field name="l10n_in_tin">13</field>
+ </record>
+ <record id="base.state_in_or" model="res.country.state">
+ <field name="l10n_in_tin">21</field>
+ </record>
+ <record id="base.state_in_py" model="res.country.state">
+ <field name="l10n_in_tin">34</field>
+ </record>
+ <record id="base.state_in_pb" model="res.country.state">
+ <field name="l10n_in_tin">03</field>
+ </record>
+ <record id="base.state_in_rj" model="res.country.state">
+ <field name="l10n_in_tin">08</field>
+ </record>
+ <record id="base.state_in_sk" model="res.country.state">
+ <field name="l10n_in_tin">11</field>
+ </record>
+ <record id="base.state_in_tn" model="res.country.state">
+ <field name="l10n_in_tin">33</field>
+ </record>
+ <record id="base.state_in_ts" model="res.country.state">
+ <field name="l10n_in_tin">36</field>
+ </record>
+ <record id="base.state_in_tr" model="res.country.state">
+ <field name="l10n_in_tin">16</field>
+ </record>
+ <record id="base.state_in_up" model="res.country.state">
+ <field name="l10n_in_tin">09</field>
+ </record>
+ <record id="base.state_in_uk" model="res.country.state">
+ <field name="l10n_in_tin">05</field>
+ </record>
+ <record id="base.state_in_wb" model="res.country.state">
+ <field name="l10n_in_tin">19</field>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_in/models/__init__.py b/addons/l10n_in/models/__init__.py
new file mode 100644
index 00000000000..a4c946eba25
--- /dev/null
+++ b/addons/l10n_in/models/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding:utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import account_invoice
+from . import chart_template
+from . import product_template
+from . import res_country_state
diff --git a/addons/l10n_in/models/account_invoice.py b/addons/l10n_in/models/account_invoice.py
new file mode 100644
index 00000000000..d6c55b95e9c
--- /dev/null
+++ b/addons/l10n_in/models/account_invoice.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+
+
+class AccountInvoice(models.Model):
+
+ _inherit = "account.invoice"
+
+ @api.depends('amount_total')
+ def _compute_amount_total_words(self):
+ for invoice in self:
+ invoice.amount_total_words = invoice.currency_id.amount_to_text(invoice.amount_total)
+
+ amount_total_words = fields.Char("Total (In Words)", compute="_compute_amount_total_words")
+
+ def _get_printed_report_name(self):
+ self.ensure_one()
+ if self.company_id.country_id.code != 'IN':
+ return super(AccountInvoice, self)._get_printed_report_name()
+ return self.type == 'out_invoice' and self.state == 'draft' and _('Draft %s') % (self.journal_id.name) or \
+ self.type == 'out_invoice' and self.state in ('open','paid') and '%s - %s' % (self.journal_id.name, self.number) or \
+ self.type == 'out_refund' and self.state == 'draft' and _('Credit Note') or \
+ self.type == 'out_refund' and _('Credit Note - %s') % (self.number) or \
+ self.type == 'in_invoice' and self.state == 'draft' and _('Vendor Bill') or \
+ self.type == 'in_invoice' and self.state in ('open','paid') and _('Vendor Bill - %s') % (self.number) or \
+ self.type == 'in_refund' and self.state == 'draft' and _('Vendor Credit Note') or \
+ self.type == 'in_refund' and _('Vendor Credit Note - %s') % (self.number)
+
+ @api.multi
+ def _invoice_line_tax_values(self):
+ self.ensure_one()
+ tax_datas = {}
+ TAX = self.env['account.tax']
+
+ for line in self.mapped('invoice_line_ids'):
+ price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
+ tax_lines = line.invoice_line_tax_ids.compute_all(price_unit, line.invoice_id.currency_id, line.quantity, line.product_id, line.invoice_id.partner_id)['taxes']
+ for tax_line in tax_lines:
+ tax_line['tag_ids'] = TAX.browse(tax_line['id']).tag_ids.ids
+ tax_datas[line.id] = tax_lines
+ return tax_datas
diff --git a/addons/l10n_in/models/chart_template.py b/addons/l10n_in/models/chart_template.py
new file mode 100644
index 00000000000..275087c7dbc
--- /dev/null
+++ b/addons/l10n_in/models/chart_template.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, _
+
+
+class AccountChartTemplate(models.Model):
+ _inherit = 'account.chart.template'
+
+
+ @api.multi
+ def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
+ res = super(AccountChartTemplate, self)._prepare_all_journals(
+ acc_template_ref, company, journals_dict=journals_dict)
+ if not self == self.env.ref('l10n_in.indian_chart_template_standard'):
+ return res
+ for journal in res:
+ if journal['code'] == 'INV':
+ journal['name'] = _('Tax Invoices')
+
+ res += [{'type': 'sale', 'name': _('Retail Invoices'), 'code': 'RETINV', 'company_id': company.id, 'show_on_dashboard': True,},
+ {'type': 'sale', 'name': _('Export Invoices'), 'code': 'EXPINV', 'company_id': company.id, 'show_on_dashboard': True,},]
+ return res
diff --git a/addons/l10n_in/models/product_template.py b/addons/l10n_in/models/product_template.py
new file mode 100644
index 00000000000..0a8b47196e9
--- /dev/null
+++ b/addons/l10n_in/models/product_template.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, fields
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ l10n_in_hsn_code = fields.Char(string="HSN/SAC Code", help="Harmonized System Nomenclature/Services Accounting Code")
diff --git a/addons/l10n_in/models/res_country_state.py b/addons/l10n_in/models/res_country_state.py
new file mode 100644
index 00000000000..6cb495a7365
--- /dev/null
+++ b/addons/l10n_in/models/res_country_state.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class CountryState(models.Model):
+ _inherit = 'res.country.state'
+
+ l10n_in_tin = fields.Char('TIN Number', size=2, help="TIN number-first two digits")
diff --git a/addons/l10n_in/views/product_template_view.xml b/addons/l10n_in/views/product_template_view.xml
new file mode 100644
index 00000000000..fdf0a1200a9
--- /dev/null
+++ b/addons/l10n_in/views/product_template_view.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record model="ir.ui.view" id="l10n_in.product_template_hs_code">
+ <field name="name">l10n_in.product.template.form.hs_code</field>
+ <field name="model">product.template</field>
+ <field name="inherit_id" ref="product.product_template_form_view"/>
+ <field name="arch" type="xml">
+ <field name="categ_id" position="after">
+ <field name="l10n_in_hsn_code"/>
+ </field>
+ </field>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_in/views/report_invoice.xml b/addons/l10n_in/views/report_invoice.xml
new file mode 100644
index 00000000000..f5cae06634c
--- /dev/null
+++ b/addons/l10n_in/views/report_invoice.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <template id="l10n_in_report_invoice_document_inherit" inherit_id="account.report_invoice_document">
+
+ <xpath expr="//table[@name='invoice_line_table']" position="before">
+ <t t-if="o.company_id.country_id.code == 'IN'">
+ <!-- Tag wise Tax Calculation -->
+ <t t-set="tag_cgst_id" t-value="env.ref('l10n_in.cgst_tag_tax').id"/>
+ <t t-set="tag_sgst_id" t-value="env.ref('l10n_in.sgst_tag_tax').id"/>
+ <t t-set="tag_igst_id" t-value="env.ref('l10n_in.igst_tag_tax').id"/>
+ <t t-set="tag_cess_id" t-value="env.ref('l10n_in.cess_tag_tax').id"/>
+ <t t-set="tax_map" t-value="o.tax_line_ids.mapped('tax_id.tag_ids.id')"/>
+ <t t-set="is_igst" t-value="tag_igst_id in tax_map"/>
+ <t t-set="is_cgst" t-value="tag_cgst_id in tax_map"/>
+ <t t-set="is_cess" t-value="tag_cess_id in tax_map"/>
+ <t t-set="tax_datas" t-value="o._invoice_line_tax_values()"/>
+ </t>
+ </xpath>
+ <xpath expr="//p[@t-if='o.comment']" position="before">
+ <t t-if="o.company_id.country_id.code == 'IN'">
+ <p id="total_in_words" class="mb16">
+ <strong>Total (In Words): </strong>
+ <span t-field="o.amount_total_words"/>
+ </p>
+ </t>
+ </xpath>
+ <xpath expr="//table[@name='invoice_line_table']" position="replace">
+ <table class="table table-condensed" name="invoice_line_table">
+ <thead>
+ <tr>
+ <th>Description</th>
+ <th class="hidden">Source Document</th>
+ <th class="text-right">Quantity</th>
+ <th class="text-right">Unit Price</th>
+ <th t-if="display_discount" class="text-right">Disc.(%)</th>
+ <t t-if="len(o.tax_line_ids) > 0">
+ <th class="text-right">Taxes</th>
+ </t>
+ <th class="text-right">Amount</th>
+ <t t-if="o.company_id.country_id.code == 'IN'">
+ <th class="text-right" t-if="is_cgst">CGST</th>
+ <th class="text-right" t-if="is_cgst">SGST</th>
+ <th class="text-right" t-if="is_igst">IGST</th>
+ <th class="text-right" t-if="is_cess">CESS</th>
+ </t>
+ </tr>
+ </thead>
+ <tbody class="invoice_tbody">
+ <tr t-foreach="o.invoice_line_ids" t-as="l">
+ <td><span t-field="l.name"/><t t-if="l.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="l.product_id.l10n_in_hsn_code"/></h6></t></td>
+ <td class="hidden"><span t-field="l.origin"/></td>
+ <td class="text-right">
+ <span t-field="l.quantity"/>
+ <span t-field="l.uom_id" groups="product.group_uom"/>
+ </td>
+ <td class="text-right">
+ <span t-field="l.price_unit"/>
+ </td>
+ <td t-if="display_discount" class="text-right">
+ <span t-field="l.discount"/>
+ </td>
+ <t t-if="len(o.tax_line_ids) > 0">
+ <td class="text-right">
+ <span t-esc="', '.join(map(lambda x: (x.description or x.name), l.invoice_line_tax_ids))"/>
+ </td>
+ </t>
+ <td class="text-right" id="subtotal">
+ <span t-field="l.price_subtotal"
+ t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
+ </td>
+ <t t-if="o.company_id.country_id.code == 'IN'">
+ <td class="text-right" t-if="is_cgst">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_cgst_id in tax_data['tag_ids']">
+ <t t-esc="tax_data['amount']"/>
+ </t>
+ </t>
+ </td>
+ <td class="text-right" t-if="is_cgst">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_sgst_id in tax_data['tag_ids']">
+ <t t-esc="tax_data['amount']"/>
+ </t>
+ </t>
+ </td>
+ <td class="text-right" t-if="is_igst">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_igst_id in tax_data['tag_ids']">
+ <t t-esc="tax_data['amount']"/>
+ </t>
+ </t>
+ </td>
+ <t t-set="total_cess" t-value="0.0"/>
+ <td class="text-right" t-if="is_cess">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_cess_id in tax_data['tag_ids']">
+ <t t-set="total_cess" t-value="total_cess + tax_data['amount']"/>
+ </t>
+ </t>
+ <t t-esc="total_cess"/>
+ </td>
+ </t>
+ </tr>
+ <tr t-foreach="range(max(5-len(o.invoice_line_ids),0))" t-as="l">
+ <td>&amp;nbsp;</td>
+ <td class="hidden"></td>
+ <td></td>
+ <td></td>
+ <td t-if="display_discount" groups="sale.group_discount_per_so_line"></td>
+ <td t-att-colspan="o.company_id.country_id.code == 'IN' and 5 or 1"></td>
+ </tr>
+ </tbody>
+ </table>
+ </xpath>
+
+ <xpath expr="//h2" position="replace">
+ <h2>
+ <span t-if="o.type == 'out_invoice' and (o.state == 'open' or o.state == 'paid')" t-field="o.journal_id.name"/>
+ <span t-if="o.type == 'out_invoice' and o.state == 'draft'">Draft <span t-field="o.journal_id.name"/></span>
+ <span t-if="o.type == 'out_invoice' and o.state == 'cancel'">Cancelled <span t-field="o.journal_id.name"/></span>
+ <span t-if="o.type == 'out_refund'">Credit Note</span>
+ <span t-if="o.type == 'in_refund'">Vendor Credit Note</span>
+ <span t-if="o.type == 'in_invoice'">Vendor Bill</span>
+ <span t-field="o.number"/>
+ </h2>
+ </xpath>
+ </template>
+
+</odoo>
diff --git a/addons/l10n_in/views/res_company_view.xml b/addons/l10n_in/views/res_company_view.xml
new file mode 100644
index 00000000000..0e3ba268898
--- /dev/null
+++ b/addons/l10n_in/views/res_company_view.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="l10n_in_view_company_form" model="ir.ui.view">
+ <field name="name">l10n.in.view.company.form</field>
+ <field name="model">res.company</field>
+ <field name="priority" eval="100"/>
+ <field name="inherit_id" ref="base.view_company_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='vat']" position="attributes">
+ <attribute name="string">GSTIN</attribute>
+ </xpath>
+ </field>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_in/views/res_country_state_view.xml b/addons/l10n_in/views/res_country_state_view.xml
new file mode 100644
index 00000000000..bdac8ff6241
--- /dev/null
+++ b/addons/l10n_in/views/res_country_state_view.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="l10n_in_view_country_state_form_inherit" model="ir.ui.view">
+ <field name="name">l10n.in.res.country.state.form.inhert</field>
+ <field name="model">res.country.state</field>
+ <field name="inherit_id" ref="base.view_country_state_form"/>
+ <field name="arch" type="xml">
+ <field name="code" position="after">
+ <field name="l10n_in_tin"/>
+ </field>
+ </field>
+ </record>
+
+</odoo>
diff --git a/addons/l10n_in/views/res_partner_views.xml b/addons/l10n_in/views/res_partner_views.xml
new file mode 100644
index 00000000000..de4c589d21c
--- /dev/null
+++ b/addons/l10n_in/views/res_partner_views.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="l10n_in_view_partner_form" model="ir.ui.view">
+ <field name="name">l10n.in.res.partner.vat.inherit</field>
+ <field name="model">res.partner</field>
+ <field name="priority" eval="100"/>
+ <field name="inherit_id" ref="base.view_partner_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='vat']" position="attributes">
+ <attribute name="string">GSTIN</attribute>
+ </xpath>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/l10n_in_purchase/__init__.py b/addons/l10n_in_purchase/__init__.py
new file mode 100644
index 00000000000..67dee8c60db
--- /dev/null
+++ b/addons/l10n_in_purchase/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
diff --git a/addons/l10n_in_purchase/__manifest__.py b/addons/l10n_in_purchase/__manifest__.py
new file mode 100644
index 00000000000..a37f00026d3
--- /dev/null
+++ b/addons/l10n_in_purchase/__manifest__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Indian - Purchase Report(GST)',
+ 'version': '1.0',
+ 'description': """GST Purchase Report""",
+ 'category': 'Localization',
+ 'depends': [
+ 'l10n_in',
+ 'purchase',
+ ],
+ 'data': [
+ 'views/report_purchase_order.xml',
+ ],
+ 'installable': True,
+ 'application': False,
+ 'auto_install': True,
+}
diff --git a/addons/l10n_in_purchase/views/report_purchase_order.xml b/addons/l10n_in_purchase/views/report_purchase_order.xml
new file mode 100644
index 00000000000..398e9fcf068
--- /dev/null
+++ b/addons/l10n_in_purchase/views/report_purchase_order.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <template id="gst_report_purchaseorder_document_inherit" inherit_id="purchase.report_purchaseorder_document">
+ <xpath expr="//tr[@t-foreach='o.order_line']/td[1]" position="replace">
+
+ <td><span t-field="line.name"/><t t-if="line.product_id.l10n_in_hsn_code and o.company_id.country_id.code == 'IN'"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="line.product_id.l10n_in_hsn_code"/></h6></t></td>
+ </xpath>
+ </template>
+
+ <template id="gst_report_purchasequotation_document_inherit" inherit_id="purchase.report_purchasequotation_document">
+ <xpath expr="//tr[@t-foreach='o.order_line']/td[1]" position="replace">
+ <td><span t-field="order_line.name"/><t t-if="order_line.product_id.l10n_in_hsn_code and o.company_id.country_id.code == 'IN'"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="order_line.product_id.l10n_in_hsn_code"/></h6></t></td>
+ </xpath>
+ </template>
+
+</odoo>
diff --git a/addons/l10n_in_sale/__init__.py b/addons/l10n_in_sale/__init__.py
new file mode 100644
index 00000000000..67dee8c60db
--- /dev/null
+++ b/addons/l10n_in_sale/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
diff --git a/addons/l10n_in_sale/__manifest__.py b/addons/l10n_in_sale/__manifest__.py
new file mode 100644
index 00000000000..6547ae0aa9d
--- /dev/null
+++ b/addons/l10n_in_sale/__manifest__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Indian - Sale Report(GST)',
+ 'version': '1.0',
+ 'description': """GST Sale Report""",
+ 'category': 'Localization',
+ 'depends': [
+ 'l10n_in',
+ 'sale',
+ ],
+ 'data': [
+ 'views/report_sale_order.xml',
+ 'views/report_invoice_view.xml',
+ ],
+ 'installable': True,
+ 'application': False,
+ 'auto_install': True,
+}
diff --git a/addons/l10n_in_sale/views/report_invoice_view.xml b/addons/l10n_in_sale/views/report_invoice_view.xml
new file mode 100644
index 00000000000..f3be1383da1
--- /dev/null
+++ b/addons/l10n_in_sale/views/report_invoice_view.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <template id="gst_report_invoice_layouted_inherit" inherit_id="sale.report_invoice_layouted">
+ <xpath expr="//t[@name='lines_layouted']" position="replace">
+ <t groups="sale.group_sale_layout" t-foreach="o.order_lines_layouted()" t-as="page" name="lines_layouted">
+ <table class="table table-condensed">
+ <thead>
+ <th>Description</th>
+ <th class="text-right">Quantity</th>
+ <th class="text-right">Unit Price</th>
+ <th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">Disc.(%)</th>
+ <th class="text-right" t-if="len(o.tax_line_ids) > 0">Tax</th>
+ <th class="text-right">Price</th>
+ <t t-if="o.company_id.country_id.code == 'IN'">
+ <th class="text-right" t-if="is_cgst">CGST</th>
+ <th class="text-right" t-if="is_cgst">SGST</th>
+ <th class="text-right" t-if="is_igst">IGST</th>
+ <th class="text-right" t-if="is_cess">CESS</th>
+ </t>
+ </thead>
+ <tbody class="invoice_tbody">
+ <t t-foreach="page" t-as="layout_category">
+
+ <t t-if="layout_category_size > 1 or page_size > 1" groups="sale.group_sale_layout">
+ <tr class="active">
+ <td colspan="7" style="font-weight: bold; border-bottom: 1px solid black;">&amp;bull;
+ <t t-esc="layout_category['name']"/>
+ </td>
+ </tr>
+ </t>
+
+ <!-- Lines associated -->
+ <t t-foreach="layout_category['lines']" t-as="l">
+ <tr>
+ <td><span t-field="l.name"/>
+ <t t-if="l.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="l.product_id.l10n_in_hsn_code"/></h6></t></td>
+ <td class="text-right">
+ <span t-field="l.quantity"/>
+ <span t-field="l.uom_id" groups="product.group_uom"/>
+ </td>
+ <td class="text-right">
+ <span t-field="l.price_unit"/>
+ </td>
+ <td t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
+ <span t-field="l.discount"/>
+ </td>
+ <td class="text-right">
+ <span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
+ </td>
+ <td class="text-right" groups="sale.group_show_price_subtotal">
+ <span t-field="l.price_subtotal"
+ t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
+ </td>
+ <td class="text-right" groups="sale.group_show_price_total">
+ <span t-field="l.price_total"
+ t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
+ </td>
+ <t t-if="o.company_id.country_id.code == 'IN'">
+ <td class="text-right" t-if="is_cgst">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_cgst_id in tax_data['tag_ids']">
+ <t t-esc="tax_data['amount']"/>
+ </t>
+ </t>
+ </td>
+ <td class="text-right" t-if="is_cgst">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_sgst_id in tax_data['tag_ids']">
+ <t t-esc="tax_data['amount']"/>
+ </t>
+ </t>
+ </td>
+ <td class="text-right" t-if="is_igst">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_igst_id in tax_data['tag_ids']">
+ <t t-esc="tax_data['amount']"/>
+ </t>
+ </t>
+ </td>
+ <t t-set="total_cess" t-value="0.0"/>
+ <td class="text-right" t-if="is_cess">
+ <t t-foreach="tax_datas[l.id]" t-as="tax_data">
+ <t t-if="tag_cess_id in tax_data['tag_ids']">
+ <t t-set="total_cess" t-value="total_cess + tax_data['amount']"/>
+ </t>
+ </t>
+ <t t-esc="total_cess"/>
+ </td>
+ </t>
+ </tr>
+ </t>
+
+ <t t-if="(layout_category_size > 1 or page_size > 1) and layout_category['subtotal']" groups="sale.group_sale_layout">
+ <tr class="text-right">
+ <td t-att-colspan="o.company_id.country_id.code == 'IN' and 9 or 6">
+ <strong>Subtotal: </strong>
+ <t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
+ <span t-esc="subtotal" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
+ </td>
+ </tr>
+ </t>
+ </t>
+ </tbody>
+ </table>
+
+ <t t-if="page_index &lt; page_size - 1" groups="sale.group_sale_layout">
+ <p style="page-break-before:always;"> </p>
+ </t>
+ </t>
+ </xpath>
+ </template>
+
+</odoo>
diff --git a/addons/l10n_in_sale/views/report_sale_order.xml b/addons/l10n_in_sale/views/report_sale_order.xml
new file mode 100644
index 00000000000..c9d241cb043
--- /dev/null
+++ b/addons/l10n_in_sale/views/report_sale_order.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <template id="gst_report_saleorder_document_inherit" inherit_id="sale.report_saleorder_document">
+ <xpath expr="//span[@t-field='l.name']" position="after">
+ <t t-if="l.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="l.product_id.l10n_in_hsn_code"/></h6></t>
+ </xpath>
+ </template>
+
+</odoo>
diff --git a/addons/l10n_in_schedule6/__manifest__.py b/addons/l10n_in_schedule6/__manifest__.py
index 90c92f0bdc5..ce04fdd376f 100644
--- a/addons/l10n_in_schedule6/__manifest__.py
+++ b/addons/l10n_in_schedule6/__manifest__.py
@@ -18,7 +18,7 @@ Sheet, now only Vertical format has been permitted Which is Supported By OpenERP
""",
'category': 'Localization',
'depends': [
- 'account',
+ 'account_tax_python',
],
'demo': [],
'data': [
diff --git a/addons/l10n_in_schedule6/account_data.xml b/addons/l10n_in_schedule6/account_data.xml
index f60c009f56d..1cadb77ec14 100644
--- a/addons/l10n_in_schedule6/account_data.xml
+++ b/addons/l10n_in_schedule6/account_data.xml
@@ -3,41 +3,17 @@
<data noupdate="1">
<!-- Account Tax Group -->
- <record id="tax_group_024" model="account.tax.group">
- <field name="name">VAT 0.24%</field>
+ <record id="schedule6_sgst_group" model="account.tax.group">
+ <field name="name">SGST</field>
</record>
- <record id="tax_group_01224" model="account.tax.group">
- <field name="name">VAT 0.1224%</field>
+ <record id="schedule6_cgst_group" model="account.tax.group">
+ <field name="name">CGST</field>
</record>
- <record id="tax_group_1" model="account.tax.group">
- <field name="name">VAT 1%</field>
+ <record id="schedule6_igst_group" model="account.tax.group">
+ <field name="name">IGST</field>
</record>
- <record id="tax_group_2" model="account.tax.group">
- <field name="name">VAT 2%</field>
- </record>
- <record id="tax_group_25" model="account.tax.group">
- <field name="name">VAT 2.5%</field>
- </record>
- <record id="tax_group_4" model="account.tax.group">
- <field name="name">VAT 4%</field>
- </record>
- <record id="tax_group_8" model="account.tax.group">
- <field name="name">VAT 8%</field>
- </record>
- <record id="tax_group_10" model="account.tax.group">
- <field name="name">VAT 10%</field>
- </record>
- <record id="tax_group_12" model="account.tax.group">
- <field name="name">VAT 12%</field>
- </record>
- <record id="tax_group_125" model="account.tax.group">
- <field name="name">VAT 12.5%</field>
- </record>
- <record id="tax_group_1236" model="account.tax.group">
- <field name="name">VAT 12.36%</field>
- </record>
- <record id="tax_group_15" model="account.tax.group">
- <field name="name">VAT 15%</field>
+ <record id="schedule6_cess_group" model="account.tax.group">
+ <field name="name">Cess</field>
</record>
</data>
diff --git a/addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml b/addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml
index 5552803bd7a..1b3ce822d00 100644
--- a/addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml
+++ b/addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
+<odoo>
<!-- India - Standard Accounting Chart -->
<record id="standard_transfer_account_id" model="account.account.template">
<field name="code">1206</field>
@@ -190,44 +189,23 @@
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
</record>
<!-- Other current liabilities -->
- <record model="account.account.template" id="c1142111">
- <field name="name">Excise Duty Payable</field>
- <field name="code">1142111</field>
+ <record model="account.account.template" id="c114210">
+ <field name="name">(schedule6)SGST Payable</field>
+ <field name="code">114210</field>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
</record>
- <record model="account.account.template" id="c1142112">
- <field name="name">Education Cess Payable On Excise Duty</field>
- <field name="code">1142112</field>
+ <record model="account.account.template" id="c114211">
+ <field name="name">(schedule6)CGST Payable</field>
+ <field name="code">114211</field>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
</record>
- <record model="account.account.template" id="c1142113">
- <field name="name">Higher Education Cess Payable On Excise Duty</field>
- <field name="code">1142113</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c1142121">
- <field name="name">Service Tax Payable</field>
- <field name="code">1142121</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c1142122">
- <field name="name">Education Cess Payable On Service Tax</field>
- <field name="code">1142122</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c1142123">
- <field name="name">Higher Education Cess Payable On Service Tax</field>
- <field name="code">1142123</field>
+ <record model="account.account.template" id="c114212">
+ <field name="name">(schedule6)IGST Payable</field>
+ <field name="code">114212</field>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
@@ -239,20 +217,6 @@
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
</record>
- <record model="account.account.template" id="c114214">
- <field name="name">Central Sales Tax Payable</field>
- <field name="code">114214</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c114215">
- <field name="name">VAT Payable</field>
- <field name="code">114215</field>
- <field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
<record model="account.account.template" id="c11422">
<field name="name">Out Standing Cheques for Clearance</field>
<field name="code">11422</field>
@@ -810,58 +774,23 @@
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
</record>
<!-- Other current assets -->
- <record model="account.account.template" id="c121610">
- <field name="name">Sales Tax Receivable</field>
- <field name="code">121610</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
<record model="account.account.template" id="c121611">
- <field name="name">VAT Receivable</field>
+ <field name="name">(schedule6)SGST Receivable</field>
<field name="code">121611</field>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
</record>
- <record model="account.account.template" id="c1216121">
- <field name="name">Excise Duty Receivable</field>
- <field name="code">1216121</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c1216122">
- <field name="name">Education Cess Receivable On Excise Duty</field>
- <field name="code">1216122</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c1216123">
- <field name="name">Higher Education Cess Receivable On Excise Duty</field>
- <field name="code">1216123</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c1216131">
- <field name="name">Service Tax Receivable</field>
- <field name="code">1216131</field>
- <field name="user_type_id" ref="account.data_account_type_current_assets"/>
- <field name="reconcile" eval="False"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- </record>
- <record model="account.account.template" id="c1216132">
- <field name="name">Education Cess Receivable On Service Tax</field>
- <field name="code">1216132</field>
+ <record model="account.account.template" id="c121612">
+ <field name="name">(schedule6)CGST Receivable</field>
+ <field name="code">121612</field>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
</record>
- <record model="account.account.template" id="c1216133">
- <field name="name">Higher Education Cess Receivable On Service Tax</field>
- <field name="code">1216133</field>
+ <record model="account.account.template" id="c121613">
+ <field name="name">(schedule6)IGST Receivable</field>
+ <field name="code">121613</field>
<field name="user_type_id" ref="account.data_account_type_current_assets"/>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
@@ -1429,5 +1358,4 @@
<field name="expense_currency_exchange_account_id" ref="c2170"/>
</record>
- </data>
-</openerp> \ No newline at end of file
+</odoo>
diff --git a/addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml b/addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml
index 3b6548acb39..d1400e7ac07 100644
--- a/addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml
+++ b/addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml
@@ -1,876 +1,673 @@
<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
+<odoo>
- <!-- Account Tags -->
- <record id="st_tag_in_01" model="account.account.tag">
- <field name="name">Tax to Pay</field>
+ <record id="schedule6_sgst_tag_tax" model="account.account.tag">
+ <field name="name">SGST</field>
<field name="applicability">taxes</field>
</record>
-
- <record id="st_tag_in_02" model="account.account.tag">
- <field name="name">C Forms</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_03" model="account.account.tag">
- <field name="name">C Forms Tax Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_04" model="account.account.tag">
- <field name="name">C Forms Tax Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_05" model="account.account.tag">
- <field name="name">Excise Duty</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_06" model="account.account.tag">
- <field name="name">Excise Duty 12.36%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_07" model="account.account.tag">
- <field name="name">Excise Duty Collected on Sales</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_08" model="account.account.tag">
- <field name="name">Education Cess 2% Excise Duty Collected on Sales</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_09" model="account.account.tag">
- <field name="name">Higher Education Cess 1% Excise Duty Collected on Sales</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_10" model="account.account.tag">
- <field name="name">Excise Duty Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_11" model="account.account.tag">
- <field name="name">Education Cess 2% Excise Duty Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_12" model="account.account.tag">
- <field name="name">Higher Education Cess 1% Excise Duty Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_13" model="account.account.tag">
- <field name="name">Sales Tax</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_14" model="account.account.tag">
- <field name="name">Sales Tax 4%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_15" model="account.account.tag">
- <field name="name">Sales Tax 4% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_16" model="account.account.tag">
- <field name="name">Sales Tax 4% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_17" model="account.account.tag">
- <field name="name">Sales Tax 12%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_18" model="account.account.tag">
- <field name="name">Sales Tax 12% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_19" model="account.account.tag">
- <field name="name">Sales Tax 12% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_20" model="account.account.tag">
- <field name="name">Sales Tax 15%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_21" model="account.account.tag">
- <field name="name">Sales Tax 15% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_22" model="account.account.tag">
- <field name="name">Sales Tax 15% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_23" model="account.account.tag">
- <field name="name">Service Tax</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_24" model="account.account.tag">
- <field name="name">Service Tax 12.36%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_25" model="account.account.tag">
- <field name="name">Service Tax Collected on Sales of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_26" model="account.account.tag">
- <field name="name">Education Cess 2% Service Tax Collected on Sales of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_27" model="account.account.tag">
- <field name="name">Higher Education Cess 1% Service Tax Collected on Sales of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_28" model="account.account.tag">
- <field name="name">Service Tax Paid on Purchase of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_29" model="account.account.tag">
- <field name="name">Education Cess 2% Service Tax Paid on Purchase of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_30" model="account.account.tag">
- <field name="name">Higher Education Cess 1% Service Tax Paid on Purchase of Services</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_31" model="account.account.tag">
- <field name="name">VAT</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_32" model="account.account.tag">
- <field name="name">VAT 4%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_33" model="account.account.tag">
- <field name="name">VAT 4% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_34" model="account.account.tag">
- <field name="name">VAT 4% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_35" model="account.account.tag">
- <field name="name">VAT 8%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_36" model="account.account.tag">
- <field name="name">VAT 8% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_37" model="account.account.tag">
- <field name="name">VAT 8% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_38" model="account.account.tag">
- <field name="name">VAT 10%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_39" model="account.account.tag">
- <field name="name">VAT 10% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_40" model="account.account.tag">
- <field name="name">VAT 10% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_41" model="account.account.tag">
- <field name="name">VAT 12.5%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_42" model="account.account.tag">
- <field name="name">VAT 12.5% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_43" model="account.account.tag">
- <field name="name">VAT 12.5% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_44" model="account.account.tag">
- <field name="name">Additional VAT 1%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_45" model="account.account.tag">
- <field name="name">Additional VAT 1% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_46" model="account.account.tag">
- <field name="name">Additional VAT 1% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_47" model="account.account.tag">
- <field name="name">Additional VAT 2.5%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_48" model="account.account.tag">
- <field name="name">Additional VAT 2.5% Collected on Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_49" model="account.account.tag">
- <field name="name">Additional VAT 2.5% Paid on Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_50" model="account.account.tag">
- <field name="name">Tax Bases</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_51" model="account.account.tag">
- <field name="name">Base C Forms</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_52" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_53" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_54" model="account.account.tag">
- <field name="name">Base Excise Duty</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_55" model="account.account.tag">
- <field name="name">Base Excise Duty 12.36%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_56" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_57" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_58" model="account.account.tag">
- <field name="name">Base Sales Tax</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_59" model="account.account.tag">
- <field name="name">Base Sales Tax 4%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_60" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_61" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_62" model="account.account.tag">
- <field name="name">Base Sales Tax 12%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_63" model="account.account.tag">
- <field name="name">Base of Sale</field>
+ <record id="schedule6_cgst_tag_tax" model="account.account.tag">
+ <field name="name">CGST</field>
<field name="applicability">taxes</field>
</record>
-
- <record id="st_tag_in_64" model="account.account.tag">
- <field name="name">Base of Purchase</field>
+ <record id="schedule6_igst_tag_tax" model="account.account.tag">
+ <field name="name">IGST</field>
<field name="applicability">taxes</field>
</record>
-
- <record id="st_tag_in_65" model="account.account.tag">
- <field name="name">Base Sales Tax 15%</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_66" model="account.account.tag">
- <field name="name">Base of Sale</field>
+ <record id="schedule6_cess_tag_tax" model="account.account.tag">
+ <field name="name">CESS</field>
<field name="applicability">taxes</field>
</record>
- <record id="st_tag_in_67" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
- </record>
-
- <record id="st_tag_in_68" model="account.account.tag">
- <field name="name">Base Service Tax</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- Account Tax Templates -->
- <record id="st_tag_in_69" model="account.account.tag">
- <field name="name">Base Service Tax 12.36%</field>
- <field name="applicability">taxes</field>
- </record>
+ <!-- Sale Taxes -->
- <record id="st_tag_in_70" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
- </record>
+ <record id="schedule6_sgst_sale_0_5" model="account.tax.template">
+ <field name="name">(schedule6)SGST Sale 0.5%</field>
+ <field name="description">SGST 0.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0.5</field>
+ <field name="account_id" ref="c114210"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
- <record id="st_tag_in_71" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
</record>
- <record id="st_tag_in_72" model="account.account.tag">
- <field name="name">Base VAT</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_cgst_sale_0_5" model="account.tax.template">
+ <field name="name">(schedule6)CGST Sale 0.5%</field>
+ <field name="description">CGST 0.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">0.5</field>
+ <field name="account_id" ref="c114211"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="st_tag_in_73" model="account.account.tag">
- <field name="name">Base VAT 4%</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_1" model="account.tax.template">
+ <field name="name">(schedule6)GST 1%</field>
+ <field name="description">GST 1%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_sale_0_5'), ref('schedule6_cgst_sale_0_5'),])]"/>
</record>
- <record id="st_tag_in_74" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_1_2" model="account.tax.template">
+ <field name="name">(schedule6)SGST Sale 1%</field>
+ <field name="description">SGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="account_id" ref="c114210"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="st_tag_in_75" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_cgst_sale_1_2" model="account.tax.template">
+ <field name="name">(schedule6)CGST Sale 1%</field>
+ <field name="description">CGST 1%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="account_id" ref="c114211"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="st_tag_in_76" model="account.account.tag">
- <field name="name">Base VAT 8%</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_2" model="account.tax.template">
+ <field name="name">(schedule6)GST 2%</field>
+ <field name="description">GST 2%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_sale_1_2'), ref('schedule6_cgst_sale_1_2'),])]"/>
</record>
- <record id="st_tag_in_77" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_2_5" model="account.tax.template">
+ <field name="name">(schedule6)SGST Sale 2.5%</field>
+ <field name="description">SGST 2.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="c114210"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="st_tag_in_78" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_cgst_sale_2_5" model="account.tax.template">
+ <field name="name">(schedule6)CGST Sale 2.5%</field>
+ <field name="description">CGST 2.5%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="c114211"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="st_tag_in_79" model="account.account.tag">
- <field name="name">Base VAT 10%</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_5" model="account.tax.template">
+ <field name="name">(schedule6)GST 5%</field>
+ <field name="description">GST 5%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_sale_2_5'), ref('schedule6_cgst_sale_2_5'),])]"/>
</record>
- <record id="st_tag_in_80" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_6" model="account.tax.template">
+ <field name="name">(schedule6)SGST Sale 6%</field>
+ <field name="description">SGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="account_id" ref="c114210"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="st_tag_in_81" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_cgst_sale_6" model="account.tax.template">
+ <field name="name">(schedule6)CGST Sale 6%</field>
+ <field name="description">CGST 6%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">6</field>
+ <field name="account_id" ref="c114211"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="st_tag_in_82" model="account.account.tag">
- <field name="name">Base VAT 12.5%</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_12" model="account.tax.template">
+ <field name="name">(schedule6)GST 12%</field>
+ <field name="description">GST 12%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_sale_6'),ref('schedule6_cgst_sale_6'),])]"/>
</record>
- <record id="st_tag_in_83" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_9" model="account.tax.template">
+ <field name="name">(schedule6)SGST Sale 9%</field>
+ <field name="description">SGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="account_id" ref="c114210"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="st_tag_in_84" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_cgst_sale_9" model="account.tax.template">
+ <field name="name">(schedule6)CGST Sale 9%</field>
+ <field name="description">CGST 9%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">9</field>
+ <field name="account_id" ref="c114211"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="st_tag_in_85" model="account.account.tag">
- <field name="name">Base Additional VAT 1%</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_18" model="account.tax.template">
+ <field name="name">(schedule6)GST 18%</field>
+ <field name="description">GST 18%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_sale_9'),ref('schedule6_cgst_sale_9'),])]"/>
</record>
- <record id="st_tag_in_86" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_14" model="account.tax.template">
+ <field name="name">(schedule6)SGST Sale 14%</field>
+ <field name="description">SGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="account_id" ref="c114210"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="st_tag_in_87" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_cgst_sale_14" model="account.tax.template">
+ <field name="name">(schedule6)CGST Sale 14%</field>
+ <field name="description">CGST 14%</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">14</field>
+ <field name="account_id" ref="c114211"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="st_tag_in_88" model="account.account.tag">
- <field name="name">Base Additional VAT 2.5%</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_sgst_sale_28" model="account.tax.template">
+ <field name="name">(schedule6)GST 28%</field>
+ <field name="description">GST 28%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_sale_14'),ref('schedule6_cgst_sale_14'),])]"/>
</record>
- <record id="st_tag_in_89" model="account.account.tag">
- <field name="name">Base of Sale</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_igst_sale_1" model="account.tax.template">
+ <field name="name">(schedule6)IGST 1%</field>
+ <field name="description">IGST 1%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">1</field>
+ <field name="account_id" ref="c114212"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="st_tag_in_90" model="account.account.tag">
- <field name="name">Base of Purchase</field>
- <field name="applicability">taxes</field>
+ <record id="schedule6_igst_sale_2" model="account.tax.template">
+ <field name="name">(schedule6)IGST 2%</field>
+ <field name="description">IGST 2%</field>
+ <field name="type_tax_use">sale</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">2</field>
+ <field name="account_id" ref="c114212"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <!-- Account Tax Templates -->
- <record id="input_tax_15" model="account.tax.template">
- <field name="name">(schedule6)Input Sales Tax @ 15%</field>
- <field name="description">(schedule6)Input Sales Tax @ 15%</field>
+ <record id="schedule6_igst_sale_5" model="account.tax.template">
+ <field name="name">(schedule6)IGST 5%</field>
+ <field name="description">IGST 5%</field>
<field name="type_tax_use">sale</field>
<field name="amount_type">percent</field>
- <field name="amount">15</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c114214"/>
+ <field name="amount">5</field>
+ <field name="account_id" ref="c114212"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_21'), ref('st_tag_in_66')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="input_tax_12" model="account.tax.template">
- <field name="name">(schedule6)Input Sales Tax @ 12%</field>
- <field name="description">(schedule6)Input Sales Tax @ 12%</field>
+
+ <record id="schedule6_igst_sale_12" model="account.tax.template">
+ <field name="name">(schedule6)IGST 12%</field>
+ <field name="description">IGST 12%</field>
<field name="type_tax_use">sale</field>
<field name="amount_type">percent</field>
<field name="amount">12</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c114214"/>
+ <field name="account_id" ref="c114212"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_18'), ref('st_tag_in_63')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="input_tax_4" model="account.tax.template">
- <field name="name">(schedule6)Input Sales Tax @ 4%</field>
- <field name="description">(schedule6)Input Sales Tax @ 4%</field>
+
+ <record id="schedule6_igst_sale_18" model="account.tax.template">
+ <field name="name">(schedule6)IGST 18%</field>
+ <field name="description">IGST 18%</field>
<field name="type_tax_use">sale</field>
<field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c114214"/>
+ <field name="amount">18</field>
+ <field name="account_id" ref="c114212"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_15'), ref('st_tag_in_60')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="add_input_vat_1" model="account.tax.template">
- <field name="name">(schedule6)Additional Input VAT @ 1%</field>
- <field name="description">(schedule6)Additional Input VAT @ 1%</field>
+
+ <record id="schedule6_igst_sale_28" model="account.tax.template">
+ <field name="name">(schedule6)IGST 28%</field>
+ <field name="description">IGST 28%</field>
<field name="type_tax_use">sale</field>
<field name="amount_type">percent</field>
- <field name="amount">1</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="c114215"/>
+ <field name="amount">28</field>
+ <field name="account_id" ref="c114212"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_86'), ref('st_tag_in_45')])]"/>
- <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="input_vat_12" model="account.tax.template">
- <field name="name">(schedule6)Input VAT @ 12.5%</field>
- <field name="description">(schedule6)Input VAT @ 12.5%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="schedule6_cess_sale_5" model="account.tax.template">
+ <field name="name">(schedule6)CESS Sale 5%</field>
+ <field name="description">CESS 5%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">12.5</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c114215"/>
+ <field name="amount">5</field>
+ <field name="tax_group_id" ref="schedule6_cess_group"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_83'), ref('st_tag_in_42')])]"/>
- <field name="tax_group_id" ref="tax_group_125"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cess_tag_tax'),])]"/>
</record>
- <record id="add_input_vat_2" model="account.tax.template">
- <field name="name">(schedule6)Additional Input VAT @ 2.5%</field>
- <field name="description">(schedule6)Additional Input VAT @ 2.5%</field>
- <field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
- <field name="amount">2.5</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="c114215"/>
+
+ <record id="schedule6_cess_sale_1591" model="account.tax.template">
+ <field name="name">(schedule6)CESS Sale 1591 Per Thousand</field>
+ <field name="description">1591 PER THOUSAND</field>
+ <field name="type_tax_use">none</field>
+ <field name="amount_type">fixed</field>
+ <field name="amount">1.591</field>
+ <field name="tax_group_id" ref="schedule6_cess_group"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_89'), ref('st_tag_in_48')])]"/>
- <field name="tax_group_id" ref="tax_group_25"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cess_tag_tax'),])]"/>
</record>
- <record id="sale_vat_8" model="account.tax.template">
- <field name="name">(schedule6)Input VAT @ 8%</field>
- <field name="description">(schedule6)Input VAT @ 8%</field>
+
+ <record id="schedule6_cess_5_plus_1591_sale" model="account.tax.template">
+ <field name="name">(schedule6)CESS 5% + RS.1591/THOUSAND </field>
+ <field name="description">CESS 5% + RS.1591/THOUSAND</field>
<field name="type_tax_use">sale</field>
- <field name="amount_type">percent</field>
- <field name="amount">8</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c114215"/>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_77'), ref('st_tag_in_36')])]"/>
- <field name="tax_group_id" ref="tax_group_8"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_cess_sale_5'),ref('schedule6_cess_sale_1591'),])]"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cess_tag_tax'),])]"/>
</record>
- <record id="sale_vat_10" model="account.tax.template">
- <field name="name">(schedule6)Input VAT @ 10%</field>
- <field name="description">(schedule6)Input VAT @ 10%</field>
+
+ <record id="schedule6_cess_21_4170_higer_sale" model="account.tax.template">
+ <field name="name">(schedule6)CESS 21% or RS.4170/THOUSAND </field>
+ <field name="description">CESS 21% or RS.4170/THOUSAND</field>
<field name="type_tax_use">sale</field>
+ <field name="amount_type">code</field>
+ <field name="amount">0</field>
+ <field name="python_compute">result=base_amount * 0.21
+tax=quantity * 4.17
+if tax > result:result=tax</field>
+ <field name="tax_group_id" ref="schedule6_cess_group"/>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cess_tag_tax'),])]"/>
+ </record>
+
+ <!-- Purchase Taxes -->
+
+ <record id="schedule6_sgst_purchase_0_5" model="account.tax.template">
+ <field name="name">(schedule6)SGST Purchase 0.5%</field>
+ <field name="description">SGST 0.5%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">10</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c114215"/>
+ <field name="amount">0.5</field>
+ <field name="account_id" ref="c121611"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_39'), ref('st_tag_in_80')])]"/>
- <field name="tax_group_id" ref="tax_group_10"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="sale_tax_12" model="account.tax.template">
- <field name="name">(schedule6)Input Tax 12%</field>
- <field name="description">(schedule6)Input Tax 12%</field>
+
+ <record id="schedule6_cgst_purchase_0_5" model="account.tax.template">
+ <field name="name">(schedule6)CGST Purchase 0.5%</field>
+ <field name="description">CGST 0.5%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">12</field>
- <field name="sequence">1</field>
- <field name="account_id" ref="c1142111"/>
+ <field name="amount">0.5</field>
+ <field name="account_id" ref="c121612"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_07')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="sale_edu_cess_Excise_Duty_2" model="account.tax.template">
- <field name="name">(schedule6)Input Education Cess @ 2% On Excise Duty</field>
- <field name="description">(schedule6)Input Education Cess @ 2% On Excise Duty</field>
+
+ <record id="schedule6_sgst_purchase_1" model="account.tax.template">
+ <field name="name">(schedule6)GST 1%</field>
+ <field name="description">GST 1%</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_purchase_0_5'),ref('schedule6_cgst_purchase_0_5'),])]"/>
+ </record>
+
+ <record id="schedule6_sgst_purchase_1_2" model="account.tax.template">
+ <field name="name">(schedule6)SGST Purchase 1%</field>
+ <field name="description">SGST 1%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">0.24</field>
- <field name="sequence">2</field>
- <field name="account_id" ref="c1142112"/>
+ <field name="amount">1</field>
+ <field name="account_id" ref="c121611"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_08')])]"/>
- <field name="tax_group_id" ref="tax_group_024"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="sale_hedu_cess_Excise_Duty_1" model="account.tax.template">
- <field name="name">(schedule6)Input Higher Education Cess @ 1% On Excise Duty</field>
- <field name="description">(schedule6)Input Higher Education Cess @ 1% On Excise Duty</field>
+
+ <record id="schedule6_cgst_purchase_1_2" model="account.tax.template">
+ <field name="name">(schedule6)CGST Purchase 1%</field>
+ <field name="description">CGST 1%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">0.1224</field>
- <field name="sequence">3</field>
- <field name="account_id" ref="c1142113"/>
+ <field name="amount">1</field>
+ <field name="account_id" ref="c121612"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_09')])]"/>
- <field name="tax_group_id" ref="tax_group_01224"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="sale_Excise_Duty_12" model="account.tax.template">
- <field name="name">(schedule6)Input Excise Duty @ 12.36%</field>
- <field name="description">(schedule6)Input Excise Duty @ 12.36%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="schedule6_sgst_purchase_2" model="account.tax.template">
+ <field name="name">(schedule6)GST 2%</field>
+ <field name="description">GST 2%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">group</field>
<field name="amount">0</field>
- <field name="sequence">1</field>
- <field name="account_id" ref="c1142111"/>
- <field name="children_tax_ids" eval="[(6, 0, [ref('sale_tax_12'),ref('sale_edu_cess_Excise_Duty_2'),ref('sale_hedu_cess_Excise_Duty_1')])]"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_56')])]"/>
- <field name="tax_group_id" ref="tax_group_1236"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_purchase_1_2'),ref('schedule6_cgst_purchase_1_2'),])]"/>
</record>
- <record id="sale_service_tax12" model="account.tax.template">
- <field name="name">(schedule6)Input Service Tax 12%</field>
- <field name="description">(schedule6)Input Service Tax 12%</field>
- <field name="type_tax_use">none</field>
- <field name="amount_type">percent</field>
- <field name="amount">12</field>
- <field name="sequence">13</field>
- <field name="account_id" ref="c1142121"/>
- <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_25')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
- </record>
- <record id="sale_edu_cess_service_tax_2" model="account.tax.template">
- <field name="name">(schedule6)Input Education Cess @ 2% On Service Tax</field>
- <field name="description">(schedule6)Input Education Cess @ 2% On Service Tax</field>
+
+ <record id="schedule6_sgst_purchase_2_5" model="account.tax.template">
+ <field name="name">(schedule6)SGST Purchase 2.5%</field>
+ <field name="description">SGST 2.5%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">0.24</field>
- <field name="sequence">14</field>
- <field name="account_id" ref="c1142122"/>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="c121611"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_26')])]"/>
- <field name="tax_group_id" ref="tax_group_024"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="sale_high_edu_service_tax_1" model="account.tax.template">
- <field name="name">(schedule6)Input Higher Education Cess @ 1% On Service Tax</field>
- <field name="description">(schedule6)Input Higher Education Cess @ 1% On Service Tax</field>
+
+ <record id="schedule6_cgst_purchase_2_5" model="account.tax.template">
+ <field name="name">(schedule6)CGST Purchase 2.5%</field>
+ <field name="description">CGST 2.5%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">0.1224</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="c1142123"/>
+ <field name="amount">2.5</field>
+ <field name="account_id" ref="c121612"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_27')])]"/>
- <field name="tax_group_id" ref="tax_group_01224"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="sale_service_tax_12" model="account.tax.template">
- <field name="name">(schedule6)Input Service Tax @ 12.36%</field>
- <field name="description">(schedule6)Input Service Tax @ 12.36%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="schedule6_sgst_purchase_5" model="account.tax.template">
+ <field name="name">(schedule6)GST 5%</field>
+ <field name="description">GST 5%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">group</field>
- <field name="amount">12</field>
- <field name="include_base_amount">True</field>
- <field name="sequence">8</field>
- <field name="account_id" ref="c1142121"/>
- <field name="children_tax_ids" eval="[(6, 0, [ref('sale_service_tax12'),ref('sale_edu_cess_service_tax_2'),ref('sale_high_edu_service_tax_1')])]"/>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_70')])]"/>
- <field name="tax_group_id" ref="tax_group_1236"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_purchase_2_5'),ref('schedule6_cgst_purchase_2_5'),])]"/>
</record>
- <record id="input_vat_4" model="account.tax.template">
- <field name="name">(schedule6)Input VAT @ 4%</field>
- <field name="description">(schedule6)Input VAT @ 4%</field>
- <field name="type_tax_use">sale</field>
+
+ <record id="schedule6_sgst_purchase_6" model="account.tax.template">
+ <field name="name">(schedule6)SGST Purchase 6%</field>
+ <field name="description">SGST 6%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c114215"/>
+ <field name="amount">6</field>
+ <field name="account_id" ref="c121611"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_33'), ref('st_tag_in_74')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <!--output purchase tax-->
- <record id="output_tax_15" model="account.tax.template">
- <field name="name">(schedule6)Output Purchase Tax @ 15%</field>
- <field name="description">(schedule6)Output Purchase Tax @ 15%</field>
- <field name="type_tax_use">purchase</field>
+ <record id="schedule6_cgst_purchase_6" model="account.tax.template">
+ <field name="name">(schedule6)CGST Purchase 6%</field>
+ <field name="description">CGST 6%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">15</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c121610"/>
+ <field name="amount">6</field>
+ <field name="account_id" ref="c121612"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_22'), ref('st_tag_in_67')])]"/>
- <field name="tax_group_id" ref="tax_group_15"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="output_tax_12" model="account.tax.template">
- <field name="name">(schedule6)Output Purchase Tax @ 12%</field>
- <field name="description">(schedule6)Output Purchase Tax @ 12%</field>
+
+ <record id="schedule6_sgst_purchase_12" model="account.tax.template">
+ <field name="name">(schedule6)GST 12%</field>
+ <field name="description">GST 12%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
- <field name="amount">12</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c121610"/>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_19'), ref('st_tag_in_64')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_purchase_6'),ref('schedule6_cgst_purchase_6'),])]"/>
</record>
- <record id="output_tax_4" model="account.tax.template">
- <field name="name">(schedule6)Output Purchase Tax @ 4%</field>
- <field name="description">(schedule6)Output Purchase Tax @ 4%</field>
- <field name="type_tax_use">purchase</field>
+
+ <record id="schedule6_sgst_purchase_9" model="account.tax.template">
+ <field name="name">(schedule6)SGST Purchase 9%</field>
+ <field name="description">SGST 9%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c121610"/>
+ <field name="amount">9</field>
+ <field name="account_id" ref="c121611"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_61'), ref('st_tag_in_16')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="add_output_vat_1" model="account.tax.template">
- <field name="name">(schedule6)Additional Output VAT @ 1%</field>
- <field name="description">(schedule6)Additional Output VAT @ 1%</field>
- <field name="type_tax_use">purchase</field>
+
+ <record id="schedule6_cgst_purchase_9" model="account.tax.template">
+ <field name="name">(schedule6)CGST Purchase 9%</field>
+ <field name="description">CGST 9%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">1</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="c121611"/>
+ <field name="amount">9</field>
+ <field name="account_id" ref="c121612"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_87'), ref('st_tag_in_46')])]"/>
- <field name="tax_group_id" ref="tax_group_1"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="output_vat_12" model="account.tax.template">
- <field name="name">(schedule6)Output VAT @ 12.5%</field>
- <field name="description">(schedule6)Output VAT @ 12.5%</field>
+
+ <record id="schedule6_sgst_purchase_18" model="account.tax.template">
+ <field name="name">(schedule6)GST 18%</field>
+ <field name="description">GST 18%</field>
<field name="type_tax_use">purchase</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="chart_template_id" ref="indian_chart_template_schedule6"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_purchase_9'),ref('schedule6_cgst_purchase_9'),])]"/>
+ </record>
+
+ <record id="schedule6_sgst_purchase_14" model="account.tax.template">
+ <field name="name">(schedule6)SGST Purchase 14%</field>
+ <field name="description">SGST 14%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">12.5</field>
- <field name="sequence">10</field>
+ <field name="amount">14</field>
<field name="account_id" ref="c121611"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_43'), ref('st_tag_in_84')])]"/>
- <field name="tax_group_id" ref="tax_group_125"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_sgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_sgst_group"/>
</record>
- <record id="add_output_vat_2" model="account.tax.template">
- <field name="name">(schedule6)Additional Output VAT @ 2.5%</field>
- <field name="description">(schedule6)Additional Output VAT @ 2.5%</field>
- <field name="type_tax_use">purchase</field>
+
+ <record id="schedule6_cgst_purchase_14" model="account.tax.template">
+ <field name="name">(schedule6)CGST Purchase 14%</field>
+ <field name="description">CGST 14%</field>
+ <field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">2.5</field>
- <field name="sequence">15</field>
- <field name="account_id" ref="c121611"/>
+ <field name="amount">14</field>
+ <field name="account_id" ref="c121612"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_90'), ref('st_tag_in_49')])]"/>
- <field name="tax_group_id" ref="tax_group_25"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cgst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_cgst_group"/>
</record>
- <record id="purchase_vat_8" model="account.tax.template">
- <field name="name">(schedule6)Output VAT @ 8%</field>
- <field name="description">(schedule6)Output VAT @ 8%</field>
+
+ <record id="schedule6_sgst_purchase_28" model="account.tax.template">
+ <field name="name">(schedule6)GST 28%</field>
+ <field name="description">GST 28%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
- <field name="amount">8</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c121611"/>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_78'), ref('st_tag_in_37')])]"/>
- <field name="tax_group_id" ref="tax_group_8"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_sgst_purchase_14'),ref('schedule6_cgst_purchase_14'),])]"/>
</record>
- <record id="purchase_vat_10" model="account.tax.template">
- <field name="name">(schedule6)Output VAT @ 10%</field>
- <field name="description">(schedule6)Output VAT @ 10%</field>
+
+ <record id="schedule6_igst_purchase_1" model="account.tax.template">
+ <field name="name">(schedule6)IGST 1%</field>
+ <field name="description">IGST 1%</field>
<field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">10</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c121611"/>
+ <field name="amount">1</field>
+ <field name="account_id" ref="c121613"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_81'), ref('st_tag_in_40')])]"/>
- <field name="tax_group_id" ref="tax_group_10"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="purchase_edu_cess_12" model="account.tax.template">
- <field name="name">(schedule6)Output Tax 12%</field>
- <field name="description">(schedule6)Output Tax 12%</field>
- <field name="type_tax_use">none</field>
+
+ <record id="schedule6_igst_purchase_2" model="account.tax.template">
+ <field name="name">(schedule6)IGST 2%</field>
+ <field name="description">IGST 2%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">12</field>
- <field name="sequence">2</field>
- <field name="account_id" ref="c1216121"/>
+ <field name="amount">2</field>
+ <field name="account_id" ref="c121613"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_10')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="purchase_edu_cess_Excise_Duty_2" model="account.tax.template">
- <field name="name">(schedule6)Output Education Cess @ 2% Excise Duty</field>
- <field name="description">(schedule6)Output Education Cess @ 2% Excise Duty</field>
- <field name="type_tax_use">none</field>
+
+ <record id="schedule6_igst_purchase_5" model="account.tax.template">
+ <field name="name">(schedule6)IGST 5%</field>
+ <field name="description">IGST 5%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">0.24</field>
- <field name="sequence">3</field>
- <field name="account_id" ref="c1216122"/>
+ <field name="amount">5</field>
+ <field name="account_id" ref="c121613"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_11')])]"/>
- <field name="tax_group_id" ref="tax_group_2"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="purchase_hedu_cess_Excise_Duty_1" model="account.tax.template">
- <field name="name">(schedule6)Output Higher Education Cess @ 1% Excise Duty</field>
- <field name="description">(schedule6)Output Higher Education Cess @ 1% Excise Duty</field>
- <field name="type_tax_use">none</field>
+
+ <record id="schedule6_igst_purchase_12" model="account.tax.template">
+ <field name="name">(schedule6)IGST 12%</field>
+ <field name="description">IGST 12%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">0.1224</field>
- <field name="sequence">4</field>
- <field name="account_id" ref="c1216123"/>
+ <field name="amount">12</field>
+ <field name="account_id" ref="c121613"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_12')])]"/>
- <field name="tax_group_id" ref="tax_group_01224"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="purchase_Excise_Duty_12" model="account.tax.template">
- <field name="name">(schedule6)Output Excise Duty @ 12.36%</field>
- <field name="description">(schedule6)Output Excise Duty @ 12.36%</field>
+
+ <record id="schedule6_igst_purchase_18" model="account.tax.template">
+ <field name="name">(schedule6)IGST 18%</field>
+ <field name="description">IGST 18%</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">group</field>
- <field name="amount">12</field>
- <field name="include_base_amount">True</field>
- <field name="sequence">1</field>
- <field name="account_id" ref="c1216121"/>
- <field name="children_tax_ids" eval="[(6, 0, [ref('purchase_edu_cess_12'),ref('purchase_edu_cess_Excise_Duty_2'),ref('purchase_hedu_cess_Excise_Duty_1')])]"/>
+ <field name="amount_type">percent</field>
+ <field name="amount">18</field>
+ <field name="account_id" ref="c121613"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_57')])]"/>
- <field name="tax_group_id" ref="tax_group_1236"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="purchase_edu_service_tax12" model="account.tax.template">
- <field name="name">(schedule6)Output Service Tax 12%</field>
- <field name="description">(schedule6)Output Service Tax 12%</field>
- <field name="type_tax_use">none</field>
+
+ <record id="schedule6_igst_purchase_28" model="account.tax.template">
+ <field name="name">(schedule6)IGST 28%</field>
+ <field name="description">IGST 28%</field>
+ <field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
- <field name="amount">12</field>
- <field name="sequence">9</field>
- <field name="account_id" ref="c1216131"/>
+ <field name="amount">28</field>
+ <field name="account_id" ref="c121613"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_28')])]"/>
- <field name="tax_group_id" ref="tax_group_12"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_igst_tag_tax'),])]"/>
+ <field name="tax_group_id" ref="schedule6_igst_group"/>
</record>
- <record id="purchase_edu_cess_service_tax_2" model="account.tax.template">
- <field name="name">(schedule6)Output Education Cess @ 2% On Service Tax</field>
- <field name="description">(schedule6)Output Education Cess @ 2% On Service Tax</field>
+
+ <record id="schedule6_cess_purchase_5" model="account.tax.template">
+ <field name="name">(schedule6)CESS Purchase 5%</field>
+ <field name="description">CESS 5%</field>
<field name="type_tax_use">none</field>
<field name="amount_type">percent</field>
- <field name="amount">0.24</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c1216132"/>
+ <field name="amount">5</field>
+ <field name="tax_group_id" ref="schedule6_cess_group"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_29')])]"/>
- <field name="tax_group_id" ref="tax_group_024"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cess_tag_tax'),])]"/>
</record>
- <record id="purchase_high_edu_service_tax_1" model="account.tax.template">
- <field name="name">(schedule6)Output Higher Education Cess @ 1% On Service Tax</field>
- <field name="description">(schedule6)Output Higher Education Cess @ 1% On Service Tax</field>
+
+ <record id="schedule6_cess_purchase_1591" model="account.tax.template">
+ <field name="name">(schedule6)CESS Purchase 1591 Per Thousand</field>
+ <field name="description">1591 PER THOUSAND</field>
<field name="type_tax_use">none</field>
- <field name="amount_type">percent</field>
- <field name="amount">0.1224</field>
- <field name="sequence">11</field>
- <field name="account_id" ref="c1216133"/>
+ <field name="amount_type">fixed</field>
+ <field name="amount">1.591</field>
+ <field name="tax_group_id" ref="schedule6_cess_group"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_30')])]"/>
- <field name="tax_group_id" ref="tax_group_01224"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cess_tag_tax'),])]"/>
</record>
- <record id="st_purchase_service_tax_12" model="account.tax.template">
- <field name="name">(schedule6)Output Service Tax @ 12.36%</field>
- <field name="description">(schedule6)Output Service Tax @ 12.36%</field>
+
+ <record id="schedule6_cess_5_plus_1591_purchase" model="account.tax.template">
+ <field name="name">(schedule6)CESS 5% + RS.1591/THOUSAND </field>
+ <field name="description">CESS 5% + RS.1591/THOUSAND</field>
<field name="type_tax_use">purchase</field>
- <field name="include_base_amount">True</field>
<field name="amount_type">group</field>
- <field name="amount">12</field>
- <field name="sequence">8</field>
- <field name="account_id" ref="c1216131"/>
- <field name="children_tax_ids" eval="[(6, 0, [ref('purchase_edu_service_tax12'),ref('purchase_edu_cess_service_tax_2'),ref('purchase_high_edu_service_tax_1')])]"/>
+ <field name="amount">0</field>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_71')])]"/>
- <field name="tax_group_id" ref="tax_group_1236"/>
+ <field name="children_tax_ids" eval="[(6, 0, [ref('schedule6_cess_purchase_5'),ref('schedule6_cess_purchase_1591'),])]"/>
</record>
- <record id="output_vat_4" model="account.tax.template">
- <field name="name">(schedule6)Output VAT @ 4%</field>
- <field name="description">(schedule6)Output VAT @ 4%</field>
+
+ <record id="schedule6_cess_21_4170_higer_purchase" model="account.tax.template">
+ <field name="name">(schedule6)CESS 21% or RS.4170/THOUSAND </field>
+ <field name="description">CESS 21% or RS.4170/THOUSAND</field>
<field name="type_tax_use">purchase</field>
- <field name="amount_type">percent</field>
- <field name="amount">4</field>
- <field name="sequence">10</field>
- <field name="account_id" ref="c121611"/>
+ <field name="amount_type">code</field>
+ <field name="amount">0</field>
+ <field name="python_compute">result=base_amount * 0.21
+tax=quantity * 4.17
+if tax > result:result=tax</field>
+ <field name="tax_group_id" ref="schedule6_cess_group"/>
<field name="chart_template_id" ref="indian_chart_template_schedule6"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_34'), ref('st_tag_in_75')])]"/>
- <field name="tax_group_id" ref="tax_group_4"/>
+ <field name="tag_ids" eval="[(6,0,[ref('schedule6_cess_tag_tax'),])]"/>
</record>
- </data>
-</openerp>
+</odoo>
diff --git a/addons/l10n_in_stock/__init__.py b/addons/l10n_in_stock/__init__.py
new file mode 100644
index 00000000000..67dee8c60db
--- /dev/null
+++ b/addons/l10n_in_stock/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
diff --git a/addons/l10n_in_stock/__manifest__.py b/addons/l10n_in_stock/__manifest__.py
new file mode 100644
index 00000000000..96761b0ee36
--- /dev/null
+++ b/addons/l10n_in_stock/__manifest__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Indian - Stock Report(GST)',
+ 'version': '1.0',
+ 'description': """GST Stock Report""",
+ 'category': 'Localization',
+ 'depends': [
+ 'l10n_in',
+ 'stock',
+ ],
+ 'data': [
+ 'views/report_stockpicking_operations.xml',
+ ],
+ 'installable': True,
+ 'application': False,
+ 'auto_install': True,
+}
diff --git a/addons/l10n_in_stock/views/report_stockpicking_operations.xml b/addons/l10n_in_stock/views/report_stockpicking_operations.xml
new file mode 100644
index 00000000000..334fc0a017e
--- /dev/null
+++ b/addons/l10n_in_stock/views/report_stockpicking_operations.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <template id="gst_report_picking_inherit" inherit_id="stock.report_picking">
+ <xpath expr="//span[@t-field='pack_operation.product_id.description_picking']" position="after">
+ <t t-if="pack_operation.product_id and pack_operation.product_id.l10n_in_hsn_code and o.company_id.country_id.code == 'IN'"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="pack_operation.product_id.l10n_in_hsn_code"/></h6></t>
+ </xpath>
+ </template>
+
+</odoo>
diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js
index 716f1ceed70..a2ae24dff91 100644
--- a/addons/mail/static/src/js/activity.js
+++ b/addons/mail/static/src/js/activity.js
@@ -13,7 +13,7 @@ var _t = core._t;
/**
* Set the 'label_delay' entry in activity data according to the deadline date
- * @param {Array} list of activity Object
+ * @param {Array} activities list of activity Object
* @return {Array} : list of modified activity Object
*/
var setDelayLabel = function(activities){
diff --git a/addons/mail/static/src/js/chatter_composer.js b/addons/mail/static/src/js/chatter_composer.js
index 77c8834252f..908dfd8b1c5 100644
--- a/addons/mail/static/src/js/chatter_composer.js
+++ b/addons/mail/static/src/js/chatter_composer.js
@@ -89,7 +89,7 @@ var ChatterComposer = composer.BasicComposer.extend({
/**
* Get the list of selected suggested partners
- * @returns Array() : list of 'recipient' selected partners (may not be created in db)
+ * @returns {Array} list of 'recipient' selected partners (may not be created in db)
**/
get_checked_suggested_partners: function () {
var self = this;
@@ -106,8 +106,8 @@ var ChatterComposer = composer.BasicComposer.extend({
/**
* Check the additional partners (not necessary registered partners), and open a popup form view
* for the ones who informations is missing.
- * @param Array : list of 'recipient' partners to complete informations or validate
- * @returns Deferred resolved with the list of checked suggested partners (real partner)
+ * @param {Array} checked_suggested_partners list of 'recipient' partners to complete informations or validate
+ * @returns {Deferred} resolved with the list of checked suggested partners (real partner)
**/
check_suggested_partners: function (checked_suggested_partners) {
var self = this;
diff --git a/addons/mail/static/src/js/document_viewer.js b/addons/mail/static/src/js/document_viewer.js
index b3ea0e18bfc..2fa34b613ba 100644
--- a/addons/mail/static/src/js/document_viewer.js
+++ b/addons/mail/static/src/js/document_viewer.js
@@ -34,7 +34,7 @@ var DocumentViewer = Widget.extend({
* Documents that are not of type image or video are filtered out.
*
* @override
- * @param {Array[Object]} attachments list of attachments
+ * @param {Array<Object>} attachments list of attachments
* @param {integer} activeAttachmentID
*/
init: function (parent, attachments, activeAttachmentID) {
diff --git a/addons/mail/static/src/js/many2many_tags_email.js b/addons/mail/static/src/js/many2many_tags_email.js
index c566d2a4706..96688a06dbc 100644
--- a/addons/mail/static/src/js/many2many_tags_email.js
+++ b/addons/mail/static/src/js/many2many_tags_email.js
@@ -80,6 +80,7 @@ var FieldMany2ManyTagsEmail = M2MTags.extend({
var pop = new form_common.FormViewDialog(self, {
res_model: self.field.relation,
res_id: resID,
+ context: self.record.context,
title: _t("Please complete partner's informations and Email"),
on_saved: function (record) {
if (record.data.email) {
diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js
index 80d4c4c8a4c..ec5ffdda1a9 100644
--- a/addons/mail/static/src/js/thread.js
+++ b/addons/mail/static/src/js/thread.js
@@ -130,11 +130,11 @@ var Thread = Widget.extend({
},
/**
- * Modifies $element to add the 'read more/read less' functionality
- * All element nodes with "data-o-mail-quote" attribute are concerned.
- * All text nodes after a ""#stopSpelling" element are concerned.
- * Those text nodes need to be wrapped in a span (toggle functionality).
- * All consecutive elements are joined in one 'read more/read less'.
+ * Modifies $element to add the 'read more/read less' functionality
+ * All element nodes with "data-o-mail-quote" attribute are concerned.
+ * All text nodes after a ``#stopSpelling`` element are concerned.
+ * Those text nodes need to be wrapped in a span (toggle functionality).
+ * All consecutive elements are joined in one 'read more/read less'.
*/
insert_read_more: function ($element) {
var self = this;
diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less
index 03bb984599a..f18643dde41 100644
--- a/addons/mail/static/src/less/thread.less
+++ b/addons/mail/static/src/less/thread.less
@@ -104,6 +104,10 @@
max-width: 100%;
word-wrap: break-word;
+ &.o_mail_note {
+ background-color: @odoo-color-silver-dark;
+ }
+
.o_mail_subject {
font-style: italic;
}
diff --git a/addons/mail/static/src/xml/chat_window.xml b/addons/mail/static/src/xml/chat_window.xml
index 525898dabd8..f21fe09991b 100644
--- a/addons/mail/static/src/xml/chat_window.xml
+++ b/addons/mail/static/src/xml/chat_window.xml
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="mail.ChatWindow">
- <div class="o_chat_window">
+ <div class="o_chat_window o_in_appswitcher">
<div class="o_chat_header">
<t t-call="mail.ChatWindowHeaderContent">
<t t-set="status" t-value="widget.status"/>
@@ -19,7 +19,7 @@
</t>
<t t-name="mail.ChatWindowsDropdown">
- <div t-attf-class="o_chat_window o_chat_window_dropdown dropup #{open ? 'open' : ''}">
+ <div t-attf-class="o_chat_window o_in_appswitcher o_chat_window_dropdown dropup #{open ? 'open' : ''}">
<span class="o_chat_window_dropdown_toggler dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-comments-o"/> <t t-esc="sessions.length"/>
<span class="o_total_unread_counter">
diff --git a/addons/mail/static/src/xml/chatter.xml b/addons/mail/static/src/xml/chatter.xml
index ef8bf100419..1d268471af2 100644
--- a/addons/mail/static/src/xml/chatter.xml
+++ b/addons/mail/static/src/xml/chatter.xml
@@ -53,7 +53,7 @@
<button t-if="log_note_btn" class="btn btn-sm btn-link o_chatter_button_log_note" title="Log a note. Followers will not be notified.">
Log note
</button>
- <button t-if="schedule_activity_btn" class="btn btn-sm btn-link o_chatter_button_schedule_activity" title="Lor or schedule an activity">
+ <button t-if="schedule_activity_btn" class="btn btn-sm btn-link o_chatter_button_schedule_activity" title="Log or schedule an activity">
<i class="fa fa-clock-o"/> Schedule activity
</button>
</t>
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 933307e5aed..2e4b03e0a10 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -124,12 +124,12 @@ class StockMove(models.Model):
}
return result
- def _action_confirm(self):
+ def _action_confirm(self, merge=True):
moves = self
for move in self.filtered(lambda m: m.production_id):
moves |= move.action_explode()
# we go further with the list of ids potentially changed by action_explode
- return super(StockMove, moves)._action_confirm()
+ return super(StockMove, moves)._action_confirm(merge=merge)
def action_explode(self):
""" Explodes pickings """
diff --git a/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml b/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml
index 54dca87b277..4c54eaf7d21 100644
--- a/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml
+++ b/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml
@@ -15,13 +15,13 @@
<strong t-if="o.address_id != o.partner_invoice_id">Invoice address: </strong>
<div t-field="o.partner_invoice_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
- <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+ <p t-if="o.partner_id.vat"><t t-esc="o.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="o.partner_id.vat"/></p>
</div>
<div t-if="o.address_id != o.partner_invoice_id">
<strong>Shipping address :</strong>
<div t-field="o.address_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
- <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+ <p t-if="o.partner_id.vat"><t t-esc="o.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
<div class="col-xs-5 col-xs-offset-1">
diff --git a/addons/payment_ogone/tests/test_ogone.py b/addons/payment_ogone/tests/test_ogone.py
index b27e7a3c4ce..76215ebf72e 100644
--- a/addons/payment_ogone/tests/test_ogone.py
+++ b/addons/payment_ogone/tests/test_ogone.py
@@ -175,7 +175,4 @@ class OgonePayment(PaymentAcquirerCommon):
'number': '4000000000000002',
'brand': 'VISA'})
- # check an alias is set, containing at least OPENERP
- self.assertIn('OPENERP', tx.partner_reference, 'ogone: wrong partner reference after creating an alias')
-
res = tx.ogone_s2s_execute({})
diff --git a/addons/payment_sips/models/payment.py b/addons/payment_sips/models/payment.py
index a6c9b390eae..f06b72461f9 100644
--- a/addons/payment_sips/models/payment.py
+++ b/addons/payment_sips/models/payment.py
@@ -40,16 +40,11 @@ class AcquirerSips(models.Model):
_inherit = 'payment.acquirer'
provider = fields.Selection(selection_add=[('sips', 'Sips')])
- sips_merchant_id = fields.Char('SIPS API User Password', required_if_provider='sips', groups='base.group_user')
- sips_secret = fields.Char('SIPS Secret', size=64, required_if_provider='sips', groups='base.group_user')
-
- def _get_sips_urls(self, environment):
- """ Worldline SIPS URLS """
- url = {
- 'prod': 'https://payment-webinit.sips-atos.com/paymentInit',
- 'test': 'https://payment-webinit.simu.sips-atos.com/paymentInit', }
-
- return {'sips_form_url': url.get(environment, url['test']), }
+ sips_merchant_id = fields.Char('Merchant ID', help="Used for production only", required_if_provider='sips', groups='base.group_user')
+ sips_secret = fields.Char('Secret Key', size=64, required_if_provider='sips', groups='base.group_user')
+ sips_test_url = fields.Char("Test's url", required_if_provider='sips', groups='base.group_no_one', default='https://payment-webinit.sips-atos.com/paymentInit')
+ sips_prod_url = fields.Char("Prod's url", required_if_provider='sips', groups='base.group_no_one', default='https://payment-webinit.simu.sips-atos.com/paymentInit')
+ sips_version = fields.Char("Interface Version", required_if_provider='sips', groups='base.group_no_one', default='HP_2.3')
def _sips_generate_shasign(self, values):
""" Generate the shasign for incoming or outgoing communications.
@@ -97,7 +92,7 @@ class AcquirerSips(models.Model):
u'transactionReference=%s|' % values['reference'] +
u'statementReference=%s|' % values['reference'] +
u'keyVersion=%s' % key_version,
- 'InterfaceVersion': 'HP_2.3',
+ 'InterfaceVersion': self.sips_version,
})
return_context = {}
@@ -113,7 +108,7 @@ class AcquirerSips(models.Model):
@api.multi
def sips_get_form_action_url(self):
self.ensure_one()
- return self._get_sips_urls(self.environment)['sips_form_url']
+ return self.environment == 'prod' and self.sips_prod_url or self.sips_test_url
class TxSips(models.Model):
@@ -172,8 +167,6 @@ class TxSips(models.Model):
# check what is bought
if float_compare(float(data.get('amount', '0.0')) / 100, self.amount, 2) != 0:
invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount))
- if self.partner_reference and data.get('customerId') != self.partner_reference:
- invalid_parameters.append(('customerId', data.get('customerId'), self.partner_reference))
return invalid_parameters
diff --git a/addons/payment_sips/views/payment_views.xml b/addons/payment_sips/views/payment_views.xml
index 4e5585af458..aa3b20bd92e 100644
--- a/addons/payment_sips/views/payment_views.xml
+++ b/addons/payment_sips/views/payment_views.xml
@@ -9,8 +9,11 @@
<field name="arch" type="xml">
<xpath expr='//group[@name="acquirer"]' position='after'>
<group attrs="{'invisible': [('provider', '!=', 'sips')]}">
- <field name="sips_secret"/>
<field name="sips_merchant_id"/>
+ <field name="sips_secret"/>
+ <field name="sips_test_url"/>
+ <field name="sips_prod_url"/>
+ <field name="sips_version"/>
</group>
</xpath>
</field>
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index a956ac62501..062825dc428 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -18,7 +18,6 @@ var _t = core._t;
var Mutex = concurrency.Mutex;
var round_di = utils.round_decimals;
var round_pr = utils.round_precision;
-var Backbone = window.Backbone;
var exports = {};
@@ -2208,6 +2207,8 @@ exports.Order = Backbone.Model.extend({
initialize_validation_date: function () {
this.validation_date = new Date();
+ this.formatted_validation_date = field_utils.format.datetime(
+ moment(this.validation_date), {}, {timezone: false});
},
set_tip: function(tip) {
diff --git a/addons/point_of_sale/static/src/js/popups.js b/addons/point_of_sale/static/src/js/popups.js
index 274c54c61a8..e27ffc75dd6 100644
--- a/addons/point_of_sale/static/src/js/popups.js
+++ b/addons/point_of_sale/static/src/js/popups.js
@@ -135,21 +135,23 @@ var ConfirmPopupWidget = PopupWidget.extend({
gui.define_popup({name:'confirm', widget: ConfirmPopupWidget});
/**
- * A popup that allows the user to select one item from a list.
+ * A popup that allows the user to select one item from a list.
*
- * show_popup('selection',{
- * title: "Popup Title",
- * list: [
- * { label: 'foobar', item: 45 },
- * { label: 'bar foo', item: 'stuff' },
- * ],
- * confirm: function(item) {
- * // get the item selected by the user.
- * },
- * cancel: function(){
- * // user chose nothing
- * }
- * });
+ * Example::
+ *
+ * show_popup('selection',{
+ * title: "Popup Title",
+ * list: [
+ * { label: 'foobar', item: 45 },
+ * { label: 'bar foo', item: 'stuff' },
+ * ],
+ * confirm: function(item) {
+ * // get the item selected by the user.
+ * },
+ * cancel: function(){
+ * // user chose nothing
+ * }
+ * });
*/
var SelectionPopupWidget = PopupWidget.extend({
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index abe9c5b6703..84648f58eff 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -1444,7 +1444,7 @@
<t t-name="PosTicket">
<div class="pos-sale-ticket">
- <div class="pos-center-align"><t t-esc="moment().format('L LT')"/> <t t-esc="order.name"/></div>
+ <div class="pos-center-align"><t t-esc="order.formatted_validation_date"/> <t t-esc="order.name"/></div>
<br />
<t t-esc="widget.pos.company.name"/><br />
<div class="receipt-phone">
diff --git a/addons/portal/static/src/js/portal_chatter.js b/addons/portal/static/src/js/portal_chatter.js
index 5aeab9bcf31..8e9868ad825 100644
--- a/addons/portal/static/src/js/portal_chatter.js
+++ b/addons/portal/static/src/js/portal_chatter.js
@@ -76,7 +76,7 @@ var PortalChatter = Widget.extend({
* Fetch the messages and the message count from the server for the
* current page and current domain.
*
- * @param {Array}
+ * @param {Array} domain
* @returns {Deferred}
*/
messageFetch: function(domain){
diff --git a/addons/pos_restaurant/static/src/xml/printbill.xml b/addons/pos_restaurant/static/src/xml/printbill.xml
index 79c82ea95b2..2771409e5e1 100644
--- a/addons/pos_restaurant/static/src/xml/printbill.xml
+++ b/addons/pos_restaurant/static/src/xml/printbill.xml
@@ -54,7 +54,7 @@
<div>Tel:<t t-esc='receipt.company.phone' /></div>
</t>
<t t-if='receipt.company.vat'>
- <div>VAT:<t t-esc='receipt.company.vat' /></div>
+ <div><t t-esc="receipt.company.country_id.vat_label or 'TIN'"/>:<t t-esc='receipt.company.vat' /></div>
</t>
<t t-if='receipt.company.email'>
<div><t t-esc='receipt.company.email' /></div>
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index d048cfefc02..422c0bd30f6 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -306,7 +306,8 @@ class PurchaseOrder(models.Model):
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
- 'custom_layout': "purchase.mail_template_data_notification_email_purchase_order"
+ 'custom_layout': "purchase.mail_template_data_notification_email_purchase_order",
+ 'force_email': True
})
return {
'name': _('Compose Email'),
diff --git a/addons/purchase/report/purchase_order_templates.xml b/addons/purchase/report/purchase_order_templates.xml
index 7ca0c433b33..c91660ba147 100644
--- a/addons/purchase/report/purchase_order_templates.xml
+++ b/addons/purchase/report/purchase_order_templates.xml
@@ -22,7 +22,7 @@
<div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
- <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+ <p t-if="o.partner_id.vat"><t t-esc="o.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
diff --git a/addons/purchase/report/purchase_quotation_templates.xml b/addons/purchase/report/purchase_quotation_templates.xml
index c14f3229b72..79a1d82ab75 100644
--- a/addons/purchase/report/purchase_quotation_templates.xml
+++ b/addons/purchase/report/purchase_quotation_templates.xml
@@ -22,7 +22,7 @@
<div class="col-xs-5 col-xs-offset-1">
<div t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
- <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+ <p t-if="o.partner_id.vat"><t t-esc="o.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="o.partner_id.vat"/></p>
</div>
</div>
diff --git a/addons/purchase/tests/test_create_picking.py b/addons/purchase/tests/test_create_picking.py
index ce66f74ea0c..c276c1f2264 100644
--- a/addons/purchase/tests/test_create_picking.py
+++ b/addons/purchase/tests/test_create_picking.py
@@ -49,7 +49,7 @@ class TestCreatePicking(common.TestProductCommon):
self.assertEqual(len(self.po.order_line.move_ids), 1, 'One move should be created')
# Change purchase order line product quantity
self.po.order_line.write({'product_qty': 7.0})
- self.assertEqual(len(self.po.order_line.move_ids), 2, 'Two move should be created')
+ self.assertEqual(len(self.po.order_line.move_ids), 1, 'The two moves should be merged in one')
# Validate first shipment
self.picking = self.po.picking_ids[0]
diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py
index 5825c284ca1..67360ffd3d3 100644
--- a/addons/rating/models/rating.py
+++ b/addons/rating/models/rating.py
@@ -69,7 +69,7 @@ class Rating(models.Model):
for rating in self:
try:
image_path = get_resource_path('rating', 'static/src/img', 'rating_%s.png' % (int(rating.rating),))
- rating.rating_image = base64.b64decode(open(image_path, 'rb').read())
+ rating.rating_image = base64.b64encode(open(image_path, 'rb').read())
except (IOError, OSError):
rating.rating_image = False
diff --git a/addons/rating/views/rating_view.xml b/addons/rating/views/rating_view.xml
index d2ae638e3b6..a3f18c899fd 100644
--- a/addons/rating/views/rating_view.xml
+++ b/addons/rating/views/rating_view.xml
@@ -26,7 +26,7 @@
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object" name="action_open_rated_object" icon="fa-file">
<div class="o_form_field o_stat_info">
- <span class="o_stat_text">Ressource</span>
+ <span class="o_stat_text">Resource</span>
</div>
</button>
</div>
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 9b354af7ab6..160264707a8 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -477,7 +477,8 @@ class SaleOrder(models.Model):
'default_composition_mode': 'comment',
'mark_so_as_sent': True,
'custom_layout': "sale.mail_template_data_notification_email_sale_order",
- 'proforma': self.env.context.get('proforma', False)
+ 'proforma': self.env.context.get('proforma', False),
+ 'force_email': True
}
return {
'type': 'ir.actions.act_window',
diff --git a/addons/sale/report/invoice_report_templates.xml b/addons/sale/report/invoice_report_templates.xml
index 865ec8a58d0..672fc817422 100644
--- a/addons/sale/report/invoice_report_templates.xml
+++ b/addons/sale/report/invoice_report_templates.xml
@@ -13,7 +13,7 @@
<attribute name="groups">!sale.group_sale_layout</attribute>
</xpath>
<xpath expr="//table" position="after">
- <t groups="sale.group_sale_layout" t-foreach="o.order_lines_layouted()" t-as="page">
+ <t groups="sale.group_sale_layout" t-foreach="o.order_lines_layouted()" t-as="page" name="lines_layouted">
<table class="table table-condensed">
<thead>
<tr>
@@ -100,7 +100,7 @@
<div class="col-xs-5 col-xs-offset-1" groups="sale.group_delivery_invoice_address">
<div t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'/>
- <div t-if="o.partner_id.vat" class="mt16">VAT: <span t-field="o.partner_id.vat"/></div>
+ <div t-if="o.partner_id.vat" class="mt16"><t t-esc="o.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="o.partner_id.vat"/></div>
</div>
</xpath>
</template>
diff --git a/addons/sale/report/sale_report_templates.xml b/addons/sale/report/sale_report_templates.xml
index c5a912625c8..1c8ef5e3497 100644
--- a/addons/sale/report/sale_report_templates.xml
+++ b/addons/sale/report/sale_report_templates.xml
@@ -24,7 +24,7 @@
<div class="col-xs-5 col-xs-offset-1">
<div t-field="doc.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
- <p t-if="doc.partner_id.vat">VAT: <span t-field="doc.partner_id.vat"/></p>
+ <p t-if="doc.partner_id.vat"><t t-esc="doc.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="doc.partner_id.vat"/></p>
</div>
</div>
diff --git a/addons/sale_timesheet/static/src/js/timesheet_plan.js b/addons/sale_timesheet/static/src/js/timesheet_plan.js
index e146fc4b365..2aa8dd7b631 100644
--- a/addons/sale_timesheet/static/src/js/timesheet_plan.js
+++ b/addons/sale_timesheet/static/src/js/timesheet_plan.js
@@ -101,7 +101,7 @@ var PlanAction = Widget.extend(ControlPanelMixin, {
//--------------------------------------------------------------------------
/**
* Refresh the DOM html
- * @param {string|html}
+ * @param {string|html} dom
* @private
*/
_refreshPlan: function(dom){
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 12c957855ad..577e5aefc7b 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -71,6 +71,7 @@ class Product(models.Model):
"Otherwise, this includes goods leaving any Stock "
"Location with 'internal' type.")
+ orderpoint_ids = fields.One2many('stock.warehouse.orderpoint', 'product_id', 'Minimum Stock Rules')
nbr_reordering_rules = fields.Integer('Reordering Rules', compute='_compute_nbr_reordering_rules')
reordering_min_qty = fields.Float(compute='_compute_nbr_reordering_rules')
reordering_max_qty = fields.Float(compute='_compute_nbr_reordering_rules')
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index a6b1af3664b..c85a47aeb57 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -462,6 +462,91 @@ class StockMove(models.Model):
if rules and (not move.origin_returned_move_id or move.origin_returned_move_id.location_dest_id.id != rules.location_dest_id.id):
rules._apply(move)
+ def _merge_moves_fields(self):
+ """ This method will return a dict of stock move’s values that represent the values of all moves in `self` merged. """
+ state = self._get_relevant_state_among_moves()
+ origin = '/'.join(set(self.filtered(lambda m: m.origin).mapped('origin')))
+ return {
+ 'product_uom_qty': sum(self.mapped('product_uom_qty')),
+ 'date': min(self.mapped('date')),
+ 'date_expected': min(self.mapped('date_expected')) if self.mapped('picking_id').move_type == 'direct' else max(self.mapped('date_expected')),
+ 'move_dest_ids': [(4, m.id) for m in self.mapped('move_dest_ids')],
+ 'move_orig_ids': [(4, m.id) for m in self.mapped('move_orig_ids')],
+ 'state': state,
+ 'origin': origin,
+ }
+
+ def _merge_moves(self):
+ """ This method will, for each move in `self`, go up in their linked picking and try to
+ find in their existing moves a candidate into which we can merge the move.
+ :return: Recordset of moves passed to this method. If some of the passed moves were merged
+ into another existing one, return this one and not the (now unlinked) original.
+ """
+ distinct_fields = ['price_unit', 'product_id', 'product_packaging',
+ 'product_uom', 'restrict_partner_id', 'scrapped', 'origin_returned_move_id']
+
+ def _keys_sorted(move):
+ return tuple([getattr(move, attr) for attr in distinct_fields])
+
+ # Move removed after merge
+ moves_to_unlink = self.env['stock.move']
+ moves_to_merge = []
+ for picking in self.mapped('picking_id'):
+ # First step find move to merge.
+ for k, g in groupby(sorted(picking.move_lines, key=_keys_sorted), key=itemgetter(*distinct_fields)):
+ moves = self.env['stock.move'].concat(*g).filtered(lambda m: m.state not in ('done', 'cancel', 'draft'))
+ # If we have multiple records we will merge then in a single one.
+ if len(moves) > 1:
+ moves_to_merge.append(moves)
+
+ # second step merge its move lines, initial demand, ...
+ for moves in moves_to_merge:
+ # link all move lines to record 0 (the one we will keep).
+ moves.mapped('move_line_ids').write({'move_id': moves[0].id})
+ # merge move data
+ moves[0].write(moves._merge_moves_fields())
+ # update merged moves dicts
+ moves_to_unlink |= moves[1:]
+
+ if moves_to_unlink:
+ # We are using propagate to False in order to not cancel destination moves merged in moves[0]
+ moves_to_unlink.write({'propagate': False})
+ moves_to_unlink._action_cancel()
+ moves_to_unlink.unlink()
+ return (self | self.env['stock.move'].concat(*moves_to_merge)) - moves_to_unlink
+
+ def _get_relevant_state_among_moves(self):
+ # We sort our moves by importance of state:
+ # ------------- 0
+ # | Assigned |
+ # -------------
+ # | Waiting |
+ # -------------
+ # | Partial |
+ # -------------
+ # | Confirm |
+ # ------------- len-1
+ sort_map = {
+ 'assigned': 4,
+ 'waiting': 3,
+ 'partially_available': 2,
+ 'confirmed': 1,
+ }
+ moves_todo = self\
+ .filtered(lambda move: move.state not in ['cancel', 'done'])\
+ .sorted(key=lambda move: sort_map.get(move.state, 0))
+ # The picking should be the same for all moves.
+ if moves_todo[0].picking_id.move_type == 'one':
+ if moves_todo[0].state in ('partially_available', 'confirmed'):
+ return 'confirmed'
+ else:
+ return moves_todo[0].state or 'draft'
+ elif moves_todo[0].state != 'assigned' and any(move.state in ['assigned', 'partially_available'] for move in moves_todo):
+ return 'partially_available'
+ else:
+ # take the less important state among all move_lines.
+ return moves_todo[-1].state or 'draft'
+
@api.onchange('product_id', 'product_qty')
def onchange_quantity(self):
if not self.product_id or self.product_qty < 0.0:
@@ -538,8 +623,11 @@ class StockMove(models.Model):
'location_dest_id': self.location_dest_id.id,
}
- def _action_confirm(self):
- """ Confirms stock move or put it in waiting if it's linked to another move. """
+ def _action_confirm(self, merge=True):
+ """ Confirms stock move or put it in waiting if it's linked to another move.
+ :param: merge: According to this boolean, a newly confirmed move will be merged
+ in another move of the same picking sharing its characteristics.
+ """
move_create_proc = self.env['stock.move']
move_to_confirm = self.env['stock.move']
move_waiting = self.env['stock.move']
@@ -574,6 +662,8 @@ class StockMove(models.Model):
for moves in to_assign.values():
moves._assign_picking()
self._push_apply()
+ if merge:
+ return self._merge_moves()
return self
def _prepare_procurement_values(self):
@@ -662,7 +752,7 @@ class StockMove(models.Model):
return taken_quantity
def _action_assign(self):
- """ Reserve stock moves by creating their stock move lines. A stock move is
+ """ Reserve stock moves by creating their stock move lines. A stock move is
considered reserved once the sum of `product_qty` for all its move lines is
equal to its `product_qty`. If it is less, the stock move is considered
partially available.
@@ -806,24 +896,6 @@ class StockMove(models.Model):
rounding_method ='UP')
extra_move_vals = self._prepare_extra_move_vals(extra_move_quantity)
extra_move = self.copy(default=extra_move_vals)._action_confirm()
-
- # link it to some move lines
- for move_line in self.move_line_ids.filtered(lambda ml: ml.qty_done):
- if float_compare(move_line.qty_done, extra_move_quantity, precision_rounding=rounding) <= 0:
- # move this move line to our extra move
- move_line.move_id = extra_move.id
- extra_move_quantity -= move_line.qty_done
- else:
- # split this move line and assign the new part to our extra move
- quantity_split = float_round(
- move_line.qty_done - extra_move_quantity,
- precision_rounding=self.product_uom.rounding,
- rounding_method='UP')
- move_line.qty_done = quantity_split
- move_line.copy(default={'move_id': extra_move.id, 'qty_done': extra_move_quantity, 'product_uom_qty': 0})
- extra_move_quantity -= extra_move_quantity
- if extra_move_quantity == 0.0:
- break
return extra_move
def _action_done(self):
@@ -937,7 +1009,8 @@ class StockMove(models.Model):
# new_move.write({'move_dest_id': new_move_prop})
# returning the first element of list returned by action_confirm is ok because we checked it wouldn't be exploded (and
# thus the result of action_confirm should always be a list of 1 element length)
- new_move._action_confirm()
+ # In this case we don't merge move since the new move with 0 quantity done will be used for the backorder.
+ new_move = new_move._action_confirm(merge=False)
# TDE FIXME: due to action confirm change
return new_move.id
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index afb80b4c44d..cd739b5bb42 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -305,40 +305,16 @@ class Picking(models.Model):
elif all(move.state in ['cancel', 'done'] for move in self.move_lines):
self.state = 'done'
else:
- # We sort our moves by importance of state:
- # ------------- 0
- # | Confirmed |
- # -------------
- # | Partial |
- # -------------
- # | Waiting |
- # -------------
- # | Assigned |
- # ------------- len - 1
- sort_map = {
- 'assigned': 4,
- 'waiting': 3,
- 'partially_available': 2,
- 'confirmed': 1,
- }
- moves_todo = self.move_lines\
- .filtered(lambda move: move.state not in ['cancel', 'done'])\
- .sorted(key=lambda move: sort_map.get(move.state, 0))
- if self.move_type == 'one':
- if moves_todo[0].state in ('partially_available', 'confirmed'):
- self.state = 'confirmed'
- else:
- self.state = moves_todo[0].state or 'draft'
- elif moves_todo[0].state != 'assigned' and any(x.state in ['assigned', 'partially_available'] for x in moves_todo):
- self.state = 'partially_available'
- else:
- # take the less important state among all move_lines.
- self.state = moves_todo[-1].state or 'draft'
+ self.state = self.move_lines._get_relevant_state_among_moves()
@api.one
@api.depends('move_lines.priority')
def _compute_priority(self):
- self.priority = self.mapped('move_lines') and max(self.mapped('move_lines').mapped('priority')) or '1'
+ if self.mapped('move_lines'):
+ priorities = [priority for priority in self.mapped('move_lines.priority') if priority] or ['1']
+ self.priority = max(priorities)
+ else:
+ self.priority = '1'
@api.one
def _set_priority(self):
diff --git a/addons/stock/report/report_stockpicking_operations.xml b/addons/stock/report/report_stockpicking_operations.xml
index 1169a9772d1..443e18c8a71 100644
--- a/addons/stock/report/report_stockpicking_operations.xml
+++ b/addons/stock/report/report_stockpicking_operations.xml
@@ -33,7 +33,7 @@
<div t-if="o.partner_id" name="partner_header">
<div t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["name", "phone"], "no_marker": True, "phone_icons": True}'/>
- <p t-if="o.sudo().partner_id.vat">VAT: <span t-field="o.sudo().partner_id.vat"/></p>
+ <p t-if="o.sudo().partner_id.vat"><t t-esc="o.company_id.country_id.vat_label or 'TIN'"/>: <span t-field="o.sudo().partner_id.vat"/></p>
</div>
</div>
</div>
diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py
index 62c76166c52..cea81e5a663 100644
--- a/addons/stock/tests/test_move2.py
+++ b/addons/stock/tests/test_move2.py
@@ -625,23 +625,13 @@ class TestSinglePicking(TestStockCommon):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location, allow_negative=True), -1.0)
- extra_move = delivery_order.move_lines - move1
- extra_move_line = extra_move.move_line_ids[0]
-
- # self.assertEqual(move1.product_qty, 1.0)
- # self.assertEqual(move1.quantity_done, 1.0)
+ self.assertEqual(move1.product_qty, 2.0)
+ self.assertEqual(move1.quantity_done, 2.0)
self.assertEqual(move1.reserved_availability, 0.0)
self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move
- self.assertEqual(move1.move_line_ids.qty_done, 1.0)
+ self.assertEqual(sum(move1.move_line_ids.mapped('qty_done')), 2.0)
self.assertEqual(move1.state, 'done')
- self.assertEqual(extra_move.product_qty, 1.0)
- self.assertEqual(extra_move.quantity_done, 1.0)
- self.assertEqual(extra_move.reserved_availability, 0.0)
- self.assertEqual(extra_move_line.product_qty, 0.0) # should not be able to reserve
- self.assertEqual(extra_move_line.qty_done, 1.0)
- self.assertEqual(extra_move.state, 'done')
-
def test_extra_move_2(self):
""" Check the good behavior of creating an extra move in a delivery order. This usecase
simulates the delivery of 3 item while the initial stock move had to move 1 and there's
@@ -681,23 +671,13 @@ class TestSinglePicking(TestStockCommon):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location, allow_negative=True), -2.0)
- extra_move = delivery_order.move_lines - move1
- extra_move_line = extra_move.move_line_ids[0]
-
- self.assertEqual(move1.product_qty, 1.0)
- self.assertEqual(move1.quantity_done, 1.0)
+ self.assertEqual(move1.product_qty, 3.0)
+ self.assertEqual(move1.quantity_done, 3.0)
self.assertEqual(move1.reserved_availability, 0.0)
self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move
- self.assertEqual(move1.move_line_ids.qty_done, 1.0)
+ self.assertEqual(sum(move1.move_line_ids.mapped('qty_done')), 3.0)
self.assertEqual(move1.state, 'done')
- self.assertEqual(extra_move.product_qty, 2.0)
- self.assertEqual(extra_move.quantity_done, 2.0)
- self.assertEqual(extra_move.reserved_availability, 0.0)
- self.assertEqual(extra_move_line.product_qty, 0.0) # should not be able to reserve
- self.assertEqual(extra_move_line.qty_done, 2.0)
- self.assertEqual(extra_move.state, 'done')
-
def test_extra_move_3(self):
""" Check the good behavior of creating an extra move in a receipt. This usecase simulates
the receipt of 2 item while the initial stock move had to move 1.
@@ -729,23 +709,13 @@ class TestSinglePicking(TestStockCommon):
receipt.with_context(debug=True).do_transfer()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 2.0)
- extra_move = receipt.move_lines - move1
- extra_move_line = extra_move.move_line_ids[0]
-
- self.assertEqual(move1.product_qty, 1.0)
- self.assertEqual(move1.quantity_done, 1.0)
+ self.assertEqual(move1.product_qty, 2.0)
+ self.assertEqual(move1.quantity_done, 2.0)
self.assertEqual(move1.reserved_availability, 0.0)
self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move
- self.assertEqual(move1.move_line_ids.qty_done, 1.0)
+ self.assertEqual(sum(move1.move_line_ids.mapped('qty_done')), 2.0)
self.assertEqual(move1.state, 'done')
- self.assertEqual(extra_move.product_qty, 1.0)
- self.assertEqual(extra_move.quantity_done, 1.0)
- self.assertEqual(extra_move.reserved_availability, 0.0)
- self.assertEqual(extra_move_line.product_qty, 0.0) # should not be able to reserve
- self.assertEqual(extra_move_line.qty_done, 1.0)
- self.assertEqual(extra_move.state, 'done')
-
def test_recheck_availability_1(self):
""" Check the good behavior of check availability. I create a DO for 2 unit with
only one in stock. After the first check availability, I should have 1 reserved
@@ -1208,6 +1178,159 @@ class TestSinglePicking(TestStockCommon):
move_line.lot_id = lot1
delivery_order.action_done()
+ def test_merge_moves_1(self):
+ receipt = self.env['stock.picking'].create({
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ 'partner_id': self.partner_delta_id,
+ 'picking_type_id': self.picking_type_in,
+ })
+ self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 3,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ })
+ self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 5,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ })
+ self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 1,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ })
+ self.MoveObj.create({
+ 'name': self.productB.name,
+ 'product_id': self.productB.id,
+ 'product_uom_qty': 5,
+ 'product_uom': self.productB.uom_id.id,
+ 'picking_id': receipt.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ })
+ receipt.action_confirm()
+ self.assertEqual(len(receipt.move_lines), 2, 'Moves were not merged')
+ self.assertEqual(receipt.move_lines.filtered(lambda m: m.product_id == self.productA).product_uom_qty, 9, 'Merged quantity is not correct')
+ self.assertEqual(receipt.move_lines.filtered(lambda m: m.product_id == self.productB).product_uom_qty, 5, 'Merge should not impact product B reserved quantity')
+
+ def test_merge_moves_2(self):
+ receipt = self.env['stock.picking'].create({
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ 'partner_id': self.partner_delta_id,
+ 'picking_type_id': self.picking_type_in,
+ })
+ self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 3,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ 'origin': 'MPS'
+ })
+ self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 5,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ 'origin': 'PO0001'
+ })
+ self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 3,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': self.stock_location,
+ 'origin': 'MPS'
+ })
+ receipt.action_confirm()
+ self.assertEqual(len(receipt.move_lines), 1, 'Moves were not merged')
+ self.assertEqual(receipt.move_lines.origin.count('MPS'), 1, 'Origin not merged together or duplicated')
+ self.assertEqual(receipt.move_lines.origin.count('PO0001'), 1, 'Origin not merged together or duplicated')
+
+ def test_merge_chained_moves(self):
+ """ Imagine multiple step delivery. Two different receipt picking for the same product should only generate
+ 1 picking from input to QC and another from QC to stock. The link at the end should follow this scheme.
+ Move receipt 1 \
+ Move Input-> QC - Move QC -> Stock
+ Move receipt 2 /
+ """
+ warehouse = self.env['stock.warehouse'].create({
+ 'name': 'TEST WAREHOUSE',
+ 'code': 'TEST1',
+ 'reception_steps': 'three_steps',
+ })
+ receipt1 = self.env['stock.picking'].create({
+ 'location_id': self.supplier_location,
+ 'location_dest_id': warehouse.wh_input_stock_loc_id.id,
+ 'partner_id': self.partner_delta_id,
+ 'picking_type_id': warehouse.in_type_id.id,
+ })
+ move_receipt_1 = self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 5,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt1.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': warehouse.wh_input_stock_loc_id.id,
+ })
+ receipt2 = self.env['stock.picking'].create({
+ 'location_id': self.supplier_location,
+ 'location_dest_id': warehouse.wh_input_stock_loc_id.id,
+ 'partner_id': self.partner_delta_id,
+ 'picking_type_id': warehouse.in_type_id.id,
+ })
+ move_receipt_2 = self.MoveObj.create({
+ 'name': self.productA.name,
+ 'product_id': self.productA.id,
+ 'product_uom_qty': 3,
+ 'product_uom': self.productA.uom_id.id,
+ 'picking_id': receipt2.id,
+ 'location_id': self.supplier_location,
+ 'location_dest_id': warehouse.wh_input_stock_loc_id.id,
+ })
+ receipt1.action_confirm()
+ receipt2.action_confirm()
+
+ # Check following move has been created and grouped in one picking.
+ self.assertTrue(move_receipt_1.move_dest_ids, 'No move created from push rules')
+ self.assertTrue(move_receipt_2.move_dest_ids, 'No move created from push rules')
+ self.assertEqual(move_receipt_1.move_dest_ids.picking_id, move_receipt_2.move_dest_ids.picking_id, 'Destination moves should be in the same picking')
+
+ # Check link for input move are correct.
+ input_move = move_receipt_2.move_dest_ids
+ self.assertEqual(len(input_move.move_dest_ids), 1)
+ self.assertEqual(set(input_move.move_orig_ids.ids), set((move_receipt_2 | move_receipt_1).ids),
+ 'Move from input to QC should be merged and have the two receipt moves as origin.')
+ self.assertEqual(move_receipt_1.move_dest_ids, input_move)
+ self.assertEqual(move_receipt_2.move_dest_ids, input_move)
+
+ # Check link for quality check move are also correct.
+ qc_move = input_move.move_dest_ids
+ self.assertEqual(len(qc_move), 1)
+ self.assertTrue(qc_move.move_orig_ids == input_move, 'Move between QC and stock should only have the input move as origin')
+
class TestStockUOM(TestStockCommon):
diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py
index 83e8391f954..adf4fedabf2 100644
--- a/addons/stock/tests/test_stock_flow.py
+++ b/addons/stock/tests/test_stock_flow.py
@@ -141,7 +141,7 @@ class TestStockFlow(TestStockCommon):
back_order_in = self.PickingObj.search([('backorder_id', '=', picking_in.id)])
self.assertEqual(len(back_order_in), 1, 'Back order should be created.')
# Check total move lines of back order.
- self.assertEqual(len(back_order_in.move_lines), 3, 'Wrong number of move lines.')
+ self.assertEqual(len(back_order_in.move_lines), 2, 'Wrong number of move lines.')
# Check back order should be created with 3 quantity of product C.
moves = self.MoveObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', back_order_in.id)])
product_c_qty = [move.product_uom_qty for move in moves]
@@ -320,13 +320,9 @@ class TestStockFlow(TestStockCommon):
# ----------------------------------------------------------------------
packD = self.StockPackObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', back_order_in.id)], order='product_qty')
- self.assertEqual(len(packD), 2, 'Wrong number of pack operation.')
+ self.assertEqual(len(packD), 1, 'Wrong number of pack operation.')
packD[0].write({
- 'qty_done': 3,
- 'lot_id': lot1_productD.id,
- })
- packD[1].write({
- 'qty_done': 5,
+ 'qty_done': 8,
'lot_id': lot1_productD.id,
})
packCs = self.StockPackObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', back_order_in.id)], limit=1)
@@ -376,7 +372,7 @@ class TestStockFlow(TestStockCommon):
# ----------------------------------------------------------------------
# Check total no of move lines.
- self.assertEqual(len(back_order_in.move_lines), 5, 'Wrong number of move lines')
+ self.assertEqual(len(back_order_in.move_lines), 4, 'Wrong number of move lines')
# Check incoming shipment state must be 'Done'.
self.assertEqual(back_order_in.state, 'done', 'Wrong state of picking.')
# Check incoming shipment move lines state must be 'Done'.
@@ -392,7 +388,7 @@ class TestStockFlow(TestStockCommon):
# Check product D done quantity must be 5.0 and 3.0
movesD = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', back_order_in.id)])
d_done_qty = [move.product_uom_qty for move in movesD]
- self.assertEqual(set(d_done_qty), set([3.0, 5.0]), 'Wrong quantity of moves product D.')
+ self.assertEqual(set(d_done_qty), set([8.0]), 'Wrong quantity of moves product D.')
# Check no back order is created.
self.assertFalse(self.PickingObj.search([('backorder_id', '=', back_order_in.id)]), "Should not create any back order.")
@@ -662,7 +658,7 @@ class TestStockFlow(TestStockCommon):
for move in picking_in_B.move_lines:
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
# Check total done move lines for incoming shipment.
- self.assertEqual(len(picking_in_B.move_lines), 6, 'Wrong number of move lines')
+ self.assertEqual(len(picking_in_B.move_lines), 5, 'Wrong number of move lines')
# Check product DozA done quantity.
moves_DozA = self.MoveObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', picking_in_B.id)], limit=1)
self.assertEqual(moves_DozA.product_uom_qty, 96, 'Wrong move quantity (%s found instead of 96)' % (moves_DozA.product_uom_qty))
@@ -681,7 +677,7 @@ class TestStockFlow(TestStockCommon):
self.assertEqual(moves_kgB.product_uom.id, self.uom_gm.id, 'Wrong uom in move for product kgB.')
# Check two moves created for product gB with quantity (0.525 kg and 0.3 g)
moves_gB_kg = self.MoveObj.search([('product_id', '=', self.gB.id), ('picking_id', '=', picking_in_B.id), ('product_uom', '=', self.uom_kg.id)], limit=1)
- self.assertEqual(moves_gB_kg.product_uom_qty, 0.525, 'Wrong move quantity (%s found instead of 0.525)' % (moves_gB_kg.product_uom_qty))
+ self.assertEqual(moves_gB_kg.product_uom_qty, 0.526, 'Wrong move quantity (%s found instead of 0.526)' % (moves_gB_kg.product_uom_qty))
self.assertEqual(moves_gB_kg.product_uom.id, self.uom_kg.id, 'Wrong uom in move for product gB.')
# TODO Test extra move once the uom is editable in the move_lines
@@ -1769,27 +1765,27 @@ class TestStockFlow(TestStockCommon):
'location_dest_id': self.customer_location,
'procure_method': 'make_to_order'})
move_with_ancestors = self.MoveObj.create({
- 'name': self.productA.name,
- 'product_id': self.productA.id,
+ 'name': self.productB.name,
+ 'product_id': self.productB.id,
'product_uom_qty': 2,
- 'product_uom': self.productA.uom_id.id,
+ 'product_uom': self.productB.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
self.MoveObj.create({
- 'name': self.productA.name,
- 'product_id': self.productA.id,
+ 'name': self.productB.name,
+ 'product_id': self.productB.id,
'product_uom_qty': 2,
- 'product_uom': self.productA.uom_id.id,
+ 'product_uom': self.productB.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
'move_dest_ids': [(4, move_with_ancestors.id, 0)]})
other_move = self.MoveObj.create({
- 'name': self.productA.name,
- 'product_id': self.productA.id,
+ 'name': self.productC.name,
+ 'product_id': self.productC.id,
'product_uom_qty': 2,
- 'product_uom': self.productA.uom_id.id,
+ 'product_uom': self.productC.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
diff --git a/addons/stock/views/stock_move_line_views.xml b/addons/stock/views/stock_move_line_views.xml
index f8793760889..43a983c8a88 100644
--- a/addons/stock/views/stock_move_line_views.xml
+++ b/addons/stock/views/stock_move_line_views.xml
@@ -61,7 +61,7 @@
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations"/>
<separator/>
- <filter string="To Do" name="todo" domain="[('state','not in ',['done', 'draft'])]"/>
+ <filter string="To Do" name="todo" domain="[('state','not in', ['done', 'draft'])]"/>
<filter string="Done" name="done" domain="[('state','=','done')]"/>
<separator/>
<filter string="Receptions" name="incoming" domain="[('picking_id.picking_type_id.code','=','incoming')]"/>
diff --git a/addons/web/static/src/js/_deprecated/data.js b/addons/web/static/src/js/_deprecated/data.js
index 14b3daeb2c6..be5496c55c7 100644
--- a/addons/web/static/src/js/_deprecated/data.js
+++ b/addons/web/static/src/js/_deprecated/data.js
@@ -733,6 +733,7 @@ var data = {
DataSet: DataSet,
DataSetStatic: DataSetStatic,
DataSetSearch: DataSetSearch,
+ /** @type String */
noDisplayContent: "<em class=\"text-warning\">" + _t("Unnamed") + "</em>",
};
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js
index 2a5bb5310ce..8492adc33f6 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -206,16 +206,17 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
// Window title handling
// --------------------------------------------------------------
/**
- Sets the first part of the title of the window, dedicated to the current action.
+ * Sets the first part of the title of the window, dedicated to the current action.
*/
set_title: function (title) {
this.set_title_part("action", title);
},
/**
- Sets an arbitrary part of the title of the window. Title parts are identified by strings. Each time
- a title part is changed, all parts are gathered, ordered by alphabetical order and displayed in the
- title of the window separated by '-'.
- */
+ * Sets an arbitrary part of the title of the window. Title parts are
+ * identified by strings. Each time a title part is changed, all parts
+ * are gathered, ordered by alphabetical order and displayed in the title
+ * of the window separated by ``-``.
+ */
set_title_part: function (part, title) {
var tmp = _.clone(this.get("title_part"));
tmp[part] = title;
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index 131a31ad776..e18a3d60734 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -67,7 +67,7 @@ var Action = core.Class.extend({
},
/**
* Stores the DOM fragment of the action
- * @param {jQuery} [fragment] the DOM fragment
+ * @param {jQuery} [$fragment] the DOM fragment
*/
set_fragment: function($fragment) {
this.$fragment = $fragment;
@@ -300,11 +300,11 @@ var ActionManager = Widget.extend({
/**
* Add a new action to the action manager
*
- * widget: typically, widgets added are openerp.web.ViewManager. The action manager
- * uses the stack of actions to handle the breadcrumbs.
- * action_descr: new action description
- * options.on_reverse_breadcrumb: will be called when breadcrumb is clicked on
- * options.clear_breadcrumbs: boolean, if true, action stack is destroyed
+ * @param {Widget} widget typically, widgets added are openerp.web.ViewManager. The action manager uses the stack of actions to handle the breadcrumbs.
+ * @param {Object} action_descr new action description
+ * @param {Object} options
+ * @param options.on_reverse_breadcrumb will be called when breadcrumb is clicked on
+ * @param options.clear_breadcrumbs: boolean, if true, action stack is destroyed
*/
push_action: function(widget, action_descr, options) {
var self = this;
@@ -613,7 +613,7 @@ var ActionManager = Widget.extend({
/**
* Execute an OpenERP action
*
- * @param {Number|String|String|Object} Can be either an action id, an action XML id, a client action tag or an action descriptor.
+ * @param {Number|String|String|Object} action Can be either an action id, an action XML id, a client action tag or an action descriptor.
* @param {Object} [options]
* @param {Boolean} [options.clear_breadcrumbs=false] Clear the breadcrumbs history list
* @param {Boolean} [options.replace_breadcrumb=false] Replace the current breadcrumb with the action
diff --git a/addons/web/static/src/js/chrome/search_filters.js b/addons/web/static/src/js/chrome/search_filters.js
index a0e3c1c077f..b9cf1b0fce9 100644
--- a/addons/web/static/src/js/chrome/search_filters.js
+++ b/addons/web/static/src/js/chrome/search_filters.js
@@ -96,7 +96,7 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext
},
});
-ExtendedSearchProposition.Field = Widget.extend({
+var Field = Widget.extend({
init: function (parent, field) {
this._super(parent);
this.field = field;
@@ -153,7 +153,7 @@ ExtendedSearchProposition.Field = Widget.extend({
}
});
-ExtendedSearchProposition.Char = ExtendedSearchProposition.Field.extend({
+var Char = Field.extend({
tagName: 'input',
className: 'o_input',
attributes: {
@@ -172,7 +172,7 @@ ExtendedSearchProposition.Char = ExtendedSearchProposition.Field.extend({
}
});
-ExtendedSearchProposition.DateTime = ExtendedSearchProposition.Field.extend({
+var DateTime = Field.extend({
tagName: 'span',
attributes: {
type: 'datetime'
@@ -241,7 +241,7 @@ ExtendedSearchProposition.DateTime = ExtendedSearchProposition.Field.extend({
},
});
-ExtendedSearchProposition.Date = ExtendedSearchProposition.DateTime.extend({
+var Date = DateTime.extend({
attributes: {
type: 'date'
},
@@ -250,7 +250,7 @@ ExtendedSearchProposition.Date = ExtendedSearchProposition.DateTime.extend({
},
});
-ExtendedSearchProposition.Integer = ExtendedSearchProposition.Field.extend({
+var Integer = Field.extend({
tagName: 'input',
className: 'o_input',
attributes: {
@@ -280,11 +280,11 @@ ExtendedSearchProposition.Integer = ExtendedSearchProposition.Field.extend({
}
});
-ExtendedSearchProposition.Id = ExtendedSearchProposition.Integer.extend({
+var Id = Integer.extend({
operators: [{value: "=", text: _lt("is")}]
});
-ExtendedSearchProposition.Float = ExtendedSearchProposition.Field.extend({
+var Float = Field.extend({
template: 'SearchView.extended_search.proposition.float',
operators: [
{value: "=", text: _lt("is equal to")},
@@ -313,7 +313,7 @@ ExtendedSearchProposition.Float = ExtendedSearchProposition.Field.extend({
}
});
-ExtendedSearchProposition.Selection = ExtendedSearchProposition.Field.extend({
+var Selection = Field.extend({
template: 'SearchView.extended_search.proposition.selection',
operators: [
{value: "=", text: _lt("is")},
@@ -331,7 +331,7 @@ ExtendedSearchProposition.Selection = ExtendedSearchProposition.Field.extend({
}
});
-ExtendedSearchProposition.Boolean = ExtendedSearchProposition.Field.extend({
+var Boolean = Field.extend({
tagName: 'span',
operators: [
{value: "=", text: _lt("is true")},
@@ -347,19 +347,19 @@ ExtendedSearchProposition.Boolean = ExtendedSearchProposition.Field.extend({
});
core.search_filters_registry
- .add('char', ExtendedSearchProposition.Char)
- .add('text', ExtendedSearchProposition.Char)
- .add('one2many', ExtendedSearchProposition.Char)
- .add('many2one', ExtendedSearchProposition.Char)
- .add('many2many', ExtendedSearchProposition.Char)
- .add('datetime', ExtendedSearchProposition.DateTime)
- .add('date', ExtendedSearchProposition.Date)
- .add('integer', ExtendedSearchProposition.Integer)
- .add('float', ExtendedSearchProposition.Float)
- .add('monetary', ExtendedSearchProposition.Float)
- .add('boolean', ExtendedSearchProposition.Boolean)
- .add('selection', ExtendedSearchProposition.Selection)
- .add('id', ExtendedSearchProposition.Id);
+ .add('char', Char)
+ .add('text', Char)
+ .add('one2many', Char)
+ .add('many2one', Char)
+ .add('many2many', Char)
+ .add('datetime', DateTime)
+ .add('date', Date)
+ .add('integer', Integer)
+ .add('float', Float)
+ .add('monetary', Float)
+ .add('boolean', Boolean)
+ .add('selection', Selection)
+ .add('id', Id);
return {
ExtendedSearchProposition: ExtendedSearchProposition
diff --git a/addons/web/static/src/js/chrome/search_view.js b/addons/web/static/src/js/chrome/search_view.js
index 630183d89ff..d8d02ac9a41 100644
--- a/addons/web/static/src/js/chrome/search_view.js
+++ b/addons/web/static/src/js/chrome/search_view.js
@@ -265,7 +265,7 @@ var SearchView = Widget.extend({
*
* @param parent
* @param dataset
- * @param fields_view
+ * @param fvg
* @param {Object} [options]
* @param {Boolean} [options.hidden=false] hide the search view
* @param {Boolean} [options.disable_custom_filters=false] do not load custom filters from ir.filters
@@ -499,8 +499,8 @@ var SearchView = Widget.extend({
.$el.focus();
},
/**
- * @param {openerp.web.search.SearchQuery | undefined} Undefined if event is change
- * @param {openerp.web.search.Facet}
+ * @param {openerp.web.search.SearchQuery | undefined} collection Undefined if event is change
+ * @param {openerp.web.search.Facet} model
* @param {Object} [options]
*/
renderFacets: function (collection, model, options) {
diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js
index 8f62ff101f7..c19fd8ab3e2 100644
--- a/addons/web/static/src/js/chrome/view_manager.js
+++ b/addons/web/static/src/js/chrome/view_manager.js
@@ -26,7 +26,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
search: function(event) {
var d = event.data;
_.extend(this.env, this._process_search_data(d.domains, d.contexts, d.groupbys));
- this.active_view.controller.reload(_.extend({}, this.env));
+ this.active_view.controller.reload(_.extend({offset: 0}, this.env));
},
add_filter: '_onAddFilter',
switch_view: function(event) {
@@ -232,8 +232,9 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
},
/**
* Returns the default view with the following fallbacks:
- * - use the default_view defined in the flags, if any
- * - use the first view in the view_order
+ *
+ * - use the default_view defined in the flags, if any
+ * - use the first view in the view_order
*
* @returns {Object} the default view
*/
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index 5bab96a0c1e..d69574d2e10 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -211,7 +211,7 @@ var loadJS = (function () {
* @param {HTMLFormElement} [options.form] the form to submit in order to fetch the file
* @param {Function} [options.success] callback in case of download success
* @param {Function} [options.error] callback in case of request error, provided with the error body
- * @param {Function} [options.complete] called after both ``success`` and ``error` callbacks have executed
+ * @param {Function} [options.complete] called after both ``success`` and ``error`` callbacks have executed
*/
function get_file(options) {
// need to detect when the file is done downloading (not used
diff --git a/addons/web/static/src/js/core/bus.js b/addons/web/static/src/js/core/bus.js
index bf33c1fe01e..1f715da4df1 100644
--- a/addons/web/static/src/js/core/bus.js
+++ b/addons/web/static/src/js/core/bus.js
@@ -6,6 +6,8 @@ var mixins = require('web.mixins');
/**
* Event Bus used to bind events scoped in the current instance
+ *
+ * @class Bus
*/
return Class.extend(mixins.EventDispatcherMixin, {
init: function() {
diff --git a/addons/web/static/src/js/core/class.js b/addons/web/static/src/js/core/class.js
index 8e53f0ba06d..4d1e6baaa8e 100644
--- a/addons/web/static/src/js/core/class.js
+++ b/addons/web/static/src/js/core/class.js
@@ -12,46 +12,47 @@ odoo.define('web.Class', function () {
* Defines The Class object. That object can be used to define and inherit classes using
* the extend() method.
*
- * Example:
+ * Example::
*
- * var Person = Class.extend({
- * init: function(isDancing){
- * this.dancing = isDancing;
- * },
- * dance: function(){
- * return this.dancing;
- * }
- * });
+ * var Person = Class.extend({
+ * init: function(isDancing){
+ * this.dancing = isDancing;
+ * },
+ * dance: function(){
+ * return this.dancing;
+ * }
+ * });
*
- * The init() method act as a constructor. This class can be instancied this way:
+ * The init() method act as a constructor. This class can be instanced this way::
*
- * var person = new Person(true);
- * person.dance();
+ * var person = new Person(true);
+ * person.dance();
*
- * The Person class can also be extended again:
+ * The Person class can also be extended again:
*
- * var Ninja = Person.extend({
- * init: function(){
- * this._super( false );
- * },
- * dance: function(){
- * // Call the inherited version of dance()
- * return this._super();
- * },
- * swingSword: function(){
- * return true;
- * }
- * });
+ * var Ninja = Person.extend({
+ * init: function(){
+ * this._super( false );
+ * },
+ * dance: function(){
+ * // Call the inherited version of dance()
+ * return this._super();
+ * },
+ * swingSword: function(){
+ * return true;
+ * }
+ * });
*
* When extending a class, each re-defined method can use this._super() to call the previous
* implementation of that method.
+ *
+ * @class Class
*/
+function OdooClass(){}
+
var initializing = false;
var fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/;
-// The web Class implementation (does nothing)
-function OdooClass(){};
-
/**
* Subclass an existing class
*
diff --git a/addons/web/static/src/js/core/concurrency.js b/addons/web/static/src/js/core/concurrency.js
index cab9df6529e..aeef29809d9 100644
--- a/addons/web/static/src/js/core/concurrency.js
+++ b/addons/web/static/src/js/core/concurrency.js
@@ -122,7 +122,7 @@ return {
* the last operation.
*
* For example, let us say that we have a _fetch method on a widget which
- * fetches data. We want to rerender the widget after. We could do this:
+ * fetches data. We want to rerender the widget after. We could do this::
*
* this._fetch().then(function (result) {
* self.state = result;
@@ -130,17 +130,18 @@ return {
* });
*
* Now, we have at least two problems:
+ *
* - if this code is called twice and the second _fetch completes before the
* first, the end state will be the result of the first _fetch, which is
* not what we expect
* - in any cases, the user interface will rerender twice, which is bad.
*
- * Now, if we have a DropPrevious:
+ * Now, if we have a DropPrevious::
*
* this.dropPrevious = new DropPrevious();
*
* Then we can wrap the _fetch in a DropPrevious and have the expected
- * result:
+ * result::
*
* this.dropPrevious
* .add(this._fetch())
@@ -153,7 +154,7 @@ return {
/**
* Registers a new deferred and rejects the previous one
*
- * @param {Deferred} the new deferred
+ * @param {Deferred} deferred the new deferred
* @returns {Promise}
*/
add: function (deferred) {
@@ -171,7 +172,7 @@ return {
*
* Imagine that we have a function to fetch some data _load(), which returns
* a deferred which resolves to something useful. Now, we have some code
- * looking like this:
+ * looking like this::
*
* return this._load().then(function (result) {
* this.state = result;
@@ -179,11 +180,11 @@ return {
*
* If this code is run twice, but the second execution ends before the
* first, then the final state will be the result of the first call to
- * _load. However, if we have a mutex:
+ * _load. However, if we have a mutex::
*
* this.mutex = new Mutex();
*
- * and if we wrap the calls to _load in a mutex:
+ * and if we wrap the calls to _load in a mutex::
*
* return this.mutex.exec(function() {
* return this._load().then(function (result) {
diff --git a/addons/web/static/src/js/core/dialog.js b/addons/web/static/src/js/core/dialog.js
index 88bfae2b545..8c75db392fb 100644
--- a/addons/web/static/src/js/core/dialog.js
+++ b/addons/web/static/src/js/core/dialog.js
@@ -9,17 +9,17 @@ var QWeb = core.qweb;
var _t = core._t;
/**
- A useful class to handle dialogs.
-
- Attributes:
- - $footer: A jQuery element targeting a dom part where buttons can be added. It always exists
- during the lifecycle of the dialog.
-*/
+ * A useful class to handle dialogs.
+ * Attributes:
+ *
+ * ``$footer``
+ * A jQuery element targeting a dom part where buttons can be added. It
+ * always exists during the lifecycle of the dialog.
+ **/
var Dialog = Widget.extend({
xmlDependencies: ['/web/static/src/xml/dialog.xml'],
/**
- * @constructor
* @param {Widget} parent
* @param {Object} [options]
* @param {string} [options.title=Odoo]
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js
index 7bae1bc29a5..6ca13f18e4e 100644
--- a/addons/web/static/src/js/core/dom.js
+++ b/addons/web/static/src/js/core/dom.js
@@ -115,10 +115,14 @@ return {
}
},
/**
- * jQuery find function behavior is:
+ * jQuery find function behavior is::
+ *
* $('A').find('A B') <=> $('A A B')
- * The searches behavior to find options' DOM needs to be
+ *
+ * The searches behavior to find options' DOM needs to be::
+ *
* $('A').find('A B') <=> $('A B')
+ *
* This is what this function does.
*
* @param {jQuery} $from - the jQuery element(s) from which to search
diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js
index cc99cc1aaa4..79de054bf10 100644
--- a/addons/web/static/src/js/core/mixins.js
+++ b/addons/web/static/src/js/core/mixins.js
@@ -10,6 +10,9 @@ var AbstractService = require('web.AbstractService');
* relationship. Each object can a have a parent and multiple children.
* When an object is destroyed, all its children are destroyed too releasing
* any resource they could have reserved before.
+ *
+ * @name ParentedMixin
+ * @mixin
*/
var ParentedMixin = {
__parentedMixin : true,
@@ -57,21 +60,21 @@ var ParentedMixin = {
return this.__parentedDestroyed;
},
/**
- Utility method to only execute asynchronous actions if the current
- object has not been destroyed.
-
- @param {$.Deferred} promise The promise representing the asynchronous
- action.
- @param {bool} [reject=false] If true, the returned promise will be
- rejected with no arguments if the current
- object is destroyed. If false, the
- returned promise will never be resolved
- or rejected.
- @returns {$.Deferred} A promise that will mirror the given promise if
- everything goes fine but will either be rejected
- with no arguments or never resolved if the
- current object is destroyed.
- */
+ * Utility method to only execute asynchronous actions if the current
+ * object has not been destroyed.
+ *
+ * @param {$.Deferred} promise The promise representing the asynchronous
+ * action.
+ * @param {bool} [reject=false] If true, the returned promise will be
+ * rejected with no arguments if the current
+ * object is destroyed. If false, the
+ * returned promise will never be resolved
+ * or rejected.
+ * @returns {$.Deferred} A promise that will mirror the given promise if
+ * everything goes fine but will either be rejected
+ * with no arguments or never resolved if the
+ * current object is destroyed.
+ */
alive: function (promise, reject) {
var self = this;
return $.Deferred(function (def) {
@@ -228,13 +231,16 @@ var Events = Class.extend({
});
/**
- Mixin containing an event system. Events are also registered by specifying the target object
- (the object which will receive the event when it is raised). Both the event-emitting object
- and the target object store or reference to each other. This is used to correctly remove all
- reference to the event handler when any of the object is destroyed (when the destroy() method
- from ParentedMixin is called). Removing those references is necessary to avoid memory leak
- and phantom events (events which are raised and sent to a previously destroyed object).
-*/
+ * Mixin containing an event system. Events are also registered by specifying the target object
+ * (the object which will receive the event when it is raised). Both the event-emitting object
+ * and the target object store or reference to each other. This is used to correctly remove all
+ * reference to the event handler when any of the object is destroyed (when the destroy() method
+ * from ParentedMixin is called). Removing those references is necessary to avoid memory leak
+ * and phantom events (events which are raised and sent to a previously destroyed object).
+ *
+ * @name EventDispatcherMixin
+ * @mixin
+ */
var EventDispatcherMixin = _.extend({}, ParentedMixin, {
__eventDispatcherMixin: true,
custom_events: {},
@@ -349,6 +355,10 @@ var EventDispatcherMixin = _.extend({}, ParentedMixin, {
}
});
+/**
+ * @name PropertiesMixin
+ * @mixin
+ */
var PropertiesMixin = _.extend({}, EventDispatcherMixin, {
init: function () {
EventDispatcherMixin.init.call(this);
@@ -432,6 +442,10 @@ odoo.define('web.ServicesMixin', function (require) {
var rpc = require('web.rpc');
+/**
+ * @mixin
+ * @name ServicesMixin
+ */
var ServicesMixin = {
call: function (service, method) {
var args = Array.prototype.slice.call(arguments, 2);
@@ -450,9 +464,9 @@ var ServicesMixin = {
* Builds and executes RPC query. Returns a deferred's promise resolved with
* the RPC result.
*
- * @param {string} arg1 either a route or a model
- * @param {string} method if a model is given, this argument is a method
- * @returns {Deferred's Promise}
+ * @param {string} params either a route or a model
+ * @param {string} options if a model is given, this argument is a method
+ * @returns {Promise}
*/
_rpc: function (params, options) {
var query = rpc.buildQuery(params);
diff --git a/addons/web/static/src/js/core/pyeval.js b/addons/web/static/src/js/core/pyeval.js
index 502c6a4ce90..ee4ac697fd9 100644
--- a/addons/web/static/src/js/core/pyeval.js
+++ b/addons/web/static/src/js/core/pyeval.js
@@ -530,11 +530,11 @@ datetime.date = py.type('date', null, {
}),
});
/**
- Returns the current local date, which means the date on the client (which can be different
- compared to the date of the server).
-
- @return {datetime.date}
-*/
+ * Returns the current local date, which means the date on the client (which can be different
+ * compared to the date of the server).
+ *
+ * @return {datetime.date}
+ */
function context_today() {
var d = new Date();
return py.PY_call(
diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js
index fc45e669407..0ecea92fb71 100644
--- a/addons/web/static/src/js/core/session.js
+++ b/addons/web/static/src/js/core/session.js
@@ -321,8 +321,6 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
* @param {String} url RPC endpoint
* @param {Object} params call parameters
* @param {Object} options additional options for rpc call
- * @param {Function} success_callback function to execute on RPC call success
- * @param {Function} error_callback function to execute on RPC call failure
* @returns {jQuery.Deferred} jquery-provided ajax deferred
*/
rpc: function (url, params, options) {
@@ -413,7 +411,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
* (host system settings) to UTC, for a given date. The offset is positive
* if the local timezone is behind UTC, and negative if it is ahead.
*
- * @param {string || moment} date a valid string date or moment instance
+ * @param {string | moment} date a valid string date or moment instance
* @returns {integer}
*/
getTZOffset: function (date) {
diff --git a/addons/web/static/src/js/core/translation.js b/addons/web/static/src/js/core/translation.js
index fae44774a07..76493fdb432 100644
--- a/addons/web/static/src/js/core/translation.js
+++ b/addons/web/static/src/js/core/translation.js
@@ -71,6 +71,16 @@ var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase#
}
});
+/**
+ * Eager translation function, performs translation immediately at call
+ * site. Beware using this outside of method bodies (before the
+ * translation database is loaded), you probably want :func:`_lt`
+ * instead.
+ *
+ * @function _t
+ * @param {String} source string to translate
+ * @returns {String} source translated into the current locale
+ */
var _t = new TranslationDataBase().build_translation_function();
/**
* Lazy translation function, only performs the translation when actually
diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js
index 585be4d37b8..9eefc12944c 100644
--- a/addons/web/static/src/js/core/utils.js
+++ b/addons/web/static/src/js/core/utils.js
@@ -107,7 +107,7 @@ var utils = {
/**
* Returns a human readable size
*
- * @param {Number} number of bytes
+ * @param {Number} size number of bytes
*/
human_size: function (size) {
var units = _t("Bytes,Kb,Mb,Gb,Tb,Pb,Eb,Zb,Yb").split(',');
@@ -271,8 +271,8 @@ var utils = {
/**
* performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision
* See the corresponding float_round() in server/tools/float_utils.py for more info
- * @param {Number} the value to be rounded
- * @param {Number} the number of decimals. eg: round_decimals(3.141592,2) -> 3.14
+ * @param {Number} value the value to be rounded
+ * @param {Number} decimals the number of decimals. eg: round_decimals(3.141592,2) -> 3.14
*/
round_decimals: function (value, decimals) {
return utils.round_precision(value, Math.pow(10,-decimals));
@@ -319,9 +319,9 @@ var utils = {
},
/**
* Create a cookie
- * @param {String} name : the name of the cookie
- * @param {String} value : the value stored in the cookie
- * @param {Integer} ttl : time to live of the cookie in millis. -1 to erase the cookie.
+ * @param {String} name the name of the cookie
+ * @param {String} value the value stored in the cookie
+ * @param {Integer} ttl time to live of the cookie in millis. -1 to erase the cookie.
*/
set_cookie: function (name, value, ttl) {
ttl = ttl || 24*60*60*365;
diff --git a/addons/web/static/src/js/core/widget.js b/addons/web/static/src/js/core/widget.js
index 8ef141a6020..c47deff7330 100644
--- a/addons/web/static/src/js/core/widget.js
+++ b/addons/web/static/src/js/core/widget.js
@@ -7,50 +7,51 @@ var mixins = require('web.mixins');
var ServicesMixin = require('web.ServicesMixin');
/**
- * Base class for all visual components. Provides a lot of functionalities helpful
+ * Base class for all visual components. Provides a lot of functions helpful
* for the management of a part of the DOM.
*
* Widget handles:
+ *
* - Rendering with QWeb.
- * - Life-cycle management and parenting (when a parent is destroyed, all its children are
- * destroyed too).
+ * - Life-cycle management and parenting (when a parent is destroyed, all its
+ * children are destroyed too).
* - Insertion in DOM.
*
- * Guide to create implementations of the Widget class:
- * ==============================================
+ * **Guide to create implementations of the Widget class**
*
- * Here is a sample child class:
+ * Here is a sample child class::
*
- * MyWidget = openerp.base.Widget.extend({
- * // the name of the QWeb template to use for rendering
- * template: "MyQWebTemplate",
+ * var MyWidget = openerp.base.Widget.extend({
+ * // the name of the QWeb template to use for rendering
+ * template: "MyQWebTemplate",
*
- * init: function(parent) {
- * this._super(parent);
- * // stuff that you want to init before the rendering
- * },
- * start: function() {
- * // stuff you want to make after the rendering, `this.$el` holds a correct value
- * this.$(".my_button").click(/* an example of event binding * /);
+ * init: function(parent) {
+ * this._super(parent);
+ * // stuff that you want to init before the rendering
+ * },
+ * start: function() {
+ * // stuff you want to make after the rendering, `this.$el` holds a correct value
+ * this.$(".my_button").click(/* an example of event binding * /);
*
- * // if you have some asynchronous operations, it's a good idea to return
- * // a promise in start()
- * var promise = this._rpc(...);
- * return promise;
- * }
- * });
+ * // if you have some asynchronous operations, it's a good idea to return
+ * // a promise in start()
+ * var promise = this._rpc(...);
+ * return promise;
+ * }
+ * });
*
- * Now this class can simply be used with the following syntax:
+ * Now this class can simply be used with the following syntax::
*
- * var my_widget = new MyWidget(this);
- * my_widget.appendTo($(".some-div"));
+ * var my_widget = new MyWidget(this);
+ * my_widget.appendTo($(".some-div"));
*
- * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
- * DOM inside the ".some-div" div and its events were binded.
+ * With these two lines, the MyWidget instance was initialized, rendered,
+ * inserted into the DOM inside the ``.some-div`` div and its events were
+ * bound.
*
- * And of course, when you don't need that widget anymore, just do:
+ * And of course, when you don't need that widget anymore, just do::
*
- * my_widget.destroy();
+ * my_widget.destroy();
*
* That will kill the widget in a clean way and erase its content from the dom.
*/
@@ -67,7 +68,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* The name of the QWeb template that will be used for rendering. Must be
* redefined in subclasses or the default render() method can not be used.
*
- * @type string
+ * @type {String}
*/
template: null,
/**
@@ -82,8 +83,6 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
/**
* Constructs the widget and sets its parent if a parent is given.
*
- * @constructs openerp.Widget
- *
* @param {openerp.Widget} parent Binds the current instance to the given Widget instance.
* When that widget is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
@@ -238,7 +237,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* (if any) by the new one in the DOM.
*
* @param {HTMLElement | jQuery} $el
- * @returns {*} this
+ * @returns {Widget} this
*/
replaceElement: function ($el) {
var $oldel = this.$el;
@@ -257,13 +256,14 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* Re-sets the widget's root element (el/$el/$el).
*
* Includes:
+ *
* * re-delegating events
* * re-binding sub-elements
* * if the widget already had a root element, replacing the pre-existing
* element in the DOM
*
* @param {HTMLElement | jQuery} element new root element for the widget
- * @return {*} this
+ * @return {Widget} this
*/
setElement: function (element) {
// NB: completely useless, as WidgetMixin#init creates a $el
diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js
index bdb745298b2..894ee57c813 100644
--- a/addons/web/static/src/js/fields/abstract_field.js
+++ b/addons/web/static/src/js/fields/abstract_field.js
@@ -59,6 +59,8 @@ var AbstractField = Widget.extend({
specialData: false,
/**
* to override to indicate which field types are supported by the widget
+ *
+ * @type Array<String>
*/
supportedFieldTypes: [],
@@ -196,7 +198,7 @@ var AbstractField = Widget.extend({
* will have the cursor at the very end.
*
* @param {Object} [options]
- * @param {boolean} [noselect=false] if false and the input
+ * @param {boolean} [options.noselect=false] if false and the input
* is of type text or textarea, the content will also be selected
* @param {Event} [options.event] the event which fired this activation
* @returns {boolean} true if the widget was activated, false if the
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index da7def82dd2..7e6a330120e 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -1741,10 +1741,9 @@ var FieldPercentPie = AbstractField.extend({
});
/**
- * FieldProgressBar
- * parameters
- * - title: title of the bar, displayed on top of the bar
- * options
+ * Node options:
+ *
+ * - title: title of the bar, displayed on top of the bar options
* - editable: boolean if value is editable
* - current_value: get the current_value from the field that must be present in the view
* - max_value: get the max_value from the field that must be present in the view
@@ -2099,8 +2098,9 @@ var FieldDomain = AbstractField.extend({
"click .o_field_domain_dialog_button": "_onDialogEditButtonClick",
}),
custom_events: _.extend({}, AbstractField.prototype.custom_events, {
- "domain_changed": "_onDomainSelectorValueChange",
- "domain_selected": "_onDomainSelectorDialogValueChange",
+ domain_changed: "_onDomainSelectorValueChange",
+ domain_selected: "_onDomainSelectorDialogValueChange",
+ open_record: "_onOpenRecord",
}),
/**
* @constructor
@@ -2281,6 +2281,15 @@ var FieldDomain = AbstractField.extend({
_onDomainSelectorDialogValueChange: function (e) {
this._setValue(Domain.prototype.arrayToString(e.data.domain));
},
+ /**
+ * Stops the propagation of the 'open_record' event, as we don't want the
+ * user to be able to open records from the list opened in a dialog.
+ *
+ * @param {OdooEvent} event
+ */
+ _onOpenRecord: function (event) {
+ event.stopPropagation();
+ },
});
/**
diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js
index cbb2a856337..369dda9f037 100644
--- a/addons/web/static/src/js/fields/field_utils.js
+++ b/addons/web/static/src/js/fields/field_utils.js
@@ -80,7 +80,7 @@ function formatChar(value, field, options) {
* Returns a string representing a date. If the value is false, then we return
* an empty string. Note that this is dependant on the localization settings
*
- * @param {Moment|false}
+ * @param {Moment|false} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
@@ -258,7 +258,7 @@ function formatX2Many(value) {
* @param {Object} [options]
* additional options to override the values in the python description of
* the field.
- * @param {Object} [options.currency] - the description of the currency to use
+ * @param {Object} [options.currency] the description of the currency to use
* @param {integer} [options.currency_id]
* the id of the 'res.currency' to use (ignored if options.currency)
* @param {string} [options.currency_field]
@@ -336,7 +336,7 @@ function formatSelection(value, field, options) {
* Create an Date object
* The method toJSON return the formated value to send value server side
*
- * @param {string}
+ * @param {string} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
@@ -375,7 +375,7 @@ function parseDate(value, field, options) {
* Create an Date object
* The method toJSON return the formated value to send value server side
*
- * @param {string}
+ * @param {string} value
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
diff --git a/addons/web/static/src/js/fields/special_fields.js b/addons/web/static/src/js/fields/special_fields.js
index 7a0bb9ed092..19db97fe985 100644
--- a/addons/web/static/src/js/fields/special_fields.js
+++ b/addons/web/static/src/js/fields/special_fields.js
@@ -10,10 +10,10 @@ var _t = core._t;
/**
- * This widget is intended to display a warning near a label of a 'timezone' field
- * indicating if the browser timezone is identical (or not) to the selected timezone.
- * This widget depends on a field given with the param 'tz_offset_field', which contains
- * the time difference between UTC time and local time, in minutes.
+ * This widget is intended to display a warning near a label of a 'timezone' field
+ * indicating if the browser timezone is identical (or not) to the selected timezone.
+ * This widget depends on a field given with the param 'tz_offset_field', which contains
+ * the time difference between UTC time and local time, in minutes.
*/
var FieldTimezoneMismatch = FieldSelection.extend({
/**
diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js
index 9d61036ab8b..119a64881e3 100644
--- a/addons/web/static/src/js/fields/upgrade_fields.js
+++ b/addons/web/static/src/js/fields/upgrade_fields.js
@@ -112,7 +112,7 @@ var AbstractFieldUpgrade = {
/**
* @private
- * @param {MouseEvent}
+ * @param {MouseEvent} event
*/
_onInputClicked: function (event) {
if ($(event.currentTarget).prop("checked")) {
diff --git a/addons/web/static/src/js/services/config.js b/addons/web/static/src/js/services/config.js
index 9b8d6c03e2f..f892801a593 100644
--- a/addons/web/static/src/js/services/config.js
+++ b/addons/web/static/src/js/services/config.js
@@ -16,23 +16,31 @@ var config = {
/**
* debug is a boolean flag. It is only considered true if the flag is set
* in the url
+ *
+ * @type Boolean
*/
debug: ($.deparam($.param.querystring()).debug !== undefined),
device: {
/**
* touch is a boolean, true if the device supports touch interaction
+ *
+ * @type Boolean
*/
touch: 'ontouchstart' in window || 'onmsgesturechange' in window,
/**
* size_class is an integer: 0, 1, 2 or 3, depending on the (current)
* size of the device. This is a dynamic property, updated whenever the
* browser is resized
+ *
+ * @type Number
*/
size_class: null,
/**
* A frequent use case is to have a different render in 'mobile' mode,
* meaning when the screen is small. This flag (boolean) is true when
* the size is not 3. It is also updated dynamically.
+ *
+ * @type Boolean
*/
isMobile: null,
/**
diff --git a/addons/web/static/src/js/services/core.js b/addons/web/static/src/js/services/core.js
index 0a52da1c521..86937c0e2c5 100644
--- a/addons/web/static/src/js/services/core.js
+++ b/addons/web/static/src/js/services/core.js
@@ -7,6 +7,11 @@ var QWeb = require('web.QWeb');
var Registry = require('web.Registry');
var translation = require('web.translation');
+/**
+ * Whether the client is currently in "debug" mode
+ *
+ * @type Boolean
+ */
var debug = $.deparam($.param.querystring()).debug !== undefined;
var bus = new Bus ();
@@ -43,8 +48,11 @@ return {
one2many_view_registry: new Registry(),
search_filters_registry: new Registry(),
search_widgets_registry: new Registry(),
-
+ /**
+ * @type {String}
+ */
csrf_token: odoo.csrf_token,
+ a_thing: 42,
};
});
diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js
index 533cac6f3ad..3ce280c3aa8 100644
--- a/addons/web/static/src/js/services/crash_manager.js
+++ b/addons/web/static/src/js/services/crash_manager.js
@@ -161,19 +161,19 @@ var CrashManager = core.Class.extend({
});
/**
- An interface to implement to handle exceptions. Register implementation in instance.web.crash_manager_registry.
+ * An interface to implement to handle exceptions. Register implementation in instance.web.crash_manager_registry.
*/
var ExceptionHandler = {
/**
- @param parent The parent.
- @param error The error object as returned by the JSON-RPC implementation.
- */
+ * @param parent The parent.
+ * @param error The error object as returned by the JSON-RPC implementation.
+ */
init: function(parent, error) {},
/**
- Called to inform to display the widget, if necessary. A typical way would be to implement
- this interface in a class extending instance.web.Dialog and simply display the dialog in this
- method.
- */
+ * Called to inform to display the widget, if necessary. A typical way would be to implement
+ * this interface in a class extending instance.web.Dialog and simply display the dialog in this
+ * method.
+ */
display: function() {},
};
diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js
index 4bf1748fbab..22c7e86ba7d 100644
--- a/addons/web/static/src/js/services/data_manager.js
+++ b/addons/web/static/src/js/services/data_manager.js
@@ -64,9 +64,11 @@ return core.Class.extend({
* Loads various information concerning views: fields_view for each view,
* the fields of the corresponding model, and optionally the filters.
*
- * @param {Object} [dataset] the dataset for which the views are loaded
- * @param {Array} [views_descr] array of [view_id, view_type]
- * @param {Object} [options] dictionnary of various options:
+ * @param {Object} params
+ * @param {String} params.model
+ * @param {Object} params.context
+ * @param {Array} params.views_descr array of [view_id, view_type]
+ * @param {Object} [options] dictionary of various options:
* - options.load_filters: whether or not to load the filters,
* - options.action_id: the action_id (required to load filters),
* - options.toolbar: whether or not a toolbar will be displayed,
diff --git a/addons/web/static/src/js/views/abstract_renderer.js b/addons/web/static/src/js/views/abstract_renderer.js
index d7eb5002f2d..935fcc6643e 100644
--- a/addons/web/static/src/js/views/abstract_renderer.js
+++ b/addons/web/static/src/js/views/abstract_renderer.js
@@ -9,6 +9,9 @@ odoo.define('web.AbstractRenderer', function (require) {
var Widget = require('web.Widget');
+/**
+ * @class AbstractRenderer
+ */
return Widget.extend({
/**
* @constructor
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index 556a9919336..594a4507963 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -228,7 +228,7 @@ var AbstractView = Class.extend({
attrs.limit = attrs.mode === "tree" ? 80 : 40;
- if (attrs.Widget.prototype.useSubview && !(attrs.invisible && JSON.parse(attrs.invisible)) && !attrs.views[attrs.mode]) {
+ if (attrs.Widget.prototype.useSubview && !attrs.__no_fetch && !attrs.views[attrs.mode]) {
var context = {};
var regex = /'([a-z]*_view_ref)' *: *'(.*?)'/g;
var matches;
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js
index 67de3ec8ad9..46359188577 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -114,7 +114,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
/**
* Method that will be overriden by the views with the ability to have selected ids
*
- * @returns []
+ * @returns {Array}
*/
getSelectedIds: function () {
return [];
@@ -517,7 +517,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* in readonly (e.g. Priority).
*
* @private
- * @param {OdooEvent}
+ * @param {OdooEvent} ev
*/
_onFieldChanged: function (ev) {
if (this.mode === 'readonly') {
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index bcccefbb984..5b6eb35b4ac 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -200,7 +200,7 @@ var BasicModel = AbstractModel.extend({
*
* @param {string} id local resource id of a record
* @param {string} viewType the current viewType
- * @returns {Deferred -> string} resolves to the id of the record
+ * @returns {Deferred<string>} resolves to the id of the record
*/
applyRawChanges: function (recordID, viewType) {
var record = this.localData[recordID];
@@ -277,7 +277,7 @@ var BasicModel = AbstractModel.extend({
* Duplicate a record (by calling the 'copy' route)
*
* @param {string} recordID id for a local resource
- * @returns {Deferred -> string} resolves to the id of duplicate record
+ * @returns {Deferred<string>} resolves to the id of duplicate record
*/
duplicateRecord: function (recordID) {
var self = this;
@@ -544,7 +544,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} params.fields contains the description of each field
* @param {string} [params.type] 'record' or 'list'
* @param {string} [params.recordID] an ID for an existing resource.
- * @returns {Deferred -> string} resolves to a local id, or handle
+ * @returns {Deferred<string>} resolves to a local id, or handle
*/
load: function (params) {
params.type = params.type || (params.res_id !== undefined ? 'record' : 'list');
@@ -694,7 +694,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {boolean} [options.keepChanges=false] if true, doesn't discard the
* changes on the record before reloading it
- * @returns {Deferred -> string} resolves to the id of the resource
+ * @returns {Deferred<string>} resolves to the id of the resource
*/
reload: function (id, options) {
options = options || {};
@@ -784,7 +784,7 @@ var BasicModel = AbstractModel.extend({
* Resequences records.
*
* @param {string} modelName the resIDs model
- * @param {Array[integer]} resIDs the new sequence of ids
+ * @param {Array<integer>} resIDs the new sequence of ids
* @param {string} parentID the localID of the parent
* @param {object} [options]
* @param {integer} [options.offset]
@@ -834,8 +834,7 @@ var BasicModel = AbstractModel.extend({
* - it needs to check all changes,
* - generate commands for x2many fields,
* - call the /create or /write method according to the record status
- * - After that, it has to reload all data, in case something changed,
- * server side.
+ * - After that, it has to reload all data, in case something changed, server side.
*
* @param {string} record_id local resource
* @param {Object} [options]
@@ -1006,7 +1005,7 @@ var BasicModel = AbstractModel.extend({
* data
*
* @param {string} groupId
- * @returns {Deferred -> string} resolves to the group id
+ * @returns {Deferred<string>} resolves to the group id
*/
toggleGroup: function (groupId) {
var self = this;
@@ -1048,7 +1047,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {string} [options.position=top] if the new record should be added
* on top or on bottom of the list
- * @returns {Deferred -> string} resolves to the new record id
+ * @returns {Deferred<string>} resolves to the new record id
*/
_addX2ManyDefaultRecord: function (list, options) {
var self = this;
@@ -1760,7 +1759,7 @@ var BasicModel = AbstractModel.extend({
* @param {string} [optinos.viewType] the type of view for which the record
* is fetched (usefull to load the adequate fields), by defaults, uses
* record.viewType
- * @returns {Deferred -> Object} resolves to the record or is rejected in
+ * @returns {Deferred<Object>} resolves to the record or is rejected in
* case no id given were valid ids
*/
_fetchRecord: function (record, options) {
@@ -2221,7 +2220,7 @@ var BasicModel = AbstractModel.extend({
* Fetch all data in a ungrouped list
*
* @param {Object} list a valid resource object
- * @returns {Deferred -> Object} resolves to the fecthed list
+ * @returns {Deferred<Object>} resolves to the fecthed list
*/
_fetchUngroupedList: function (list) {
var self = this;
@@ -3029,7 +3028,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} params.fields contains the description of each field
* @param {Object} params.context the context for the new record
* @param {string} params.viewType the key in fieldsInfo of the fields to load
- * @returns {Deferred -> string} resolves to the id for the created resource
+ * @returns {Deferred<string>} resolves to the id for the created resource
*/
_makeDefaultRecord: function (modelName, params) {
var self = this;
@@ -3318,7 +3317,7 @@ var BasicModel = AbstractModel.extend({
*
* @param {Object} record
* @param {Object} record
- * @returns {Deferred -> Object} resolves to the finished resource
+ * @returns {Deferred<Object>} resolves to the finished resource
*/
_postprocess: function (record, options) {
var self = this;
@@ -3455,7 +3454,7 @@ var BasicModel = AbstractModel.extend({
* instead of a /search_read.
*
* @param {Object} list a valid resource object
- * @returns {Deferred -> Object} resolves to the fetched list object
+ * @returns {Deferred<Object>} resolves to the fetched list object
*/
_readUngroupedList: function (list) {
var self = this;
diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js
index 0bd3c263666..e1a834b2c18 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -510,6 +510,7 @@ var FormController = BasicController.extend({
* @param {OdooEvent} event
*/
_onToggleColumnOrder: function (event) {
+ event.stopPropagation();
this.model.setSort(event.data.id, event.data.name);
var field = event.data.field;
var state = this.model.get(this.handle);
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index 8f1724c3e64..9c71c9eb2a3 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -79,7 +79,7 @@ var FormRenderer = BasicRenderer.extend({
* Show a warning message if the user modified a translated field. For each
* field, the notification provides a link to edit the field's translations.
*
- * @param {Object[]} alertFields: field list
+ * @param {Object[]} alertFields field list
*/
displayTranslationAlert: function (alertFields) {
this.$('.o_notification_box').remove();
diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js
index 52597e6ac71..495f98b28ee 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record.js
@@ -99,7 +99,7 @@ var KanbanRecord = Widget.extend({
* Generates the color classname from a given variable
*
* @private
- * @param {number || string} variable
+ * @param {number | string} variable
* @return {string} the classname
*/
_getColorClassname: function (variable) {
@@ -110,7 +110,7 @@ var KanbanRecord = Widget.extend({
* Computes a color id between 0 and 10 from a given value
*
* @private
- * @param {number || string} variable
+ * @param {number | string} variable
* @returns {integer} the color id
*/
_getColorID: function (variable) {
diff --git a/addons/web/static/src/js/views/standalone_field_manager_mixin.js b/addons/web/static/src/js/views/standalone_field_manager_mixin.js
index 2c01dc25622..da3cc19350d 100644
--- a/addons/web/static/src/js/views/standalone_field_manager_mixin.js
+++ b/addons/web/static/src/js/views/standalone_field_manager_mixin.js
@@ -1,13 +1,19 @@
odoo.define('web.StandaloneFieldManagerMixin', function (require) {
"use strict";
+
+var FieldManagerMixin = require('web.FieldManagerMixin');
+
/**
* The StandaloneFieldManagerMixin is a mixin, designed to be used by a widget
* that instanciates its own field widgets.
+ *
+ * @mixin
+ * @name StandaloneFieldManagerMixin
+ * @mixes FieldManagerMixin
+ * @property {Function} _confirmChange
+ * @property {Function} _registerWidget
*/
-
-var FieldManagerMixin = require('web.FieldManagerMixin');
-
var StandaloneFieldManagerMixin = _.extend({}, FieldManagerMixin, {
/**
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 655e94f9f43..26b73c27ac5 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -104,7 +104,7 @@ var DateWidget = Widget.extend({
}
},
/**
- * @param {Moment|false}
+ * @param {Moment|false} value
*/
setValue: function (value) {
this.set({'value': value});
@@ -121,7 +121,7 @@ var DateWidget = Widget.extend({
/**
* @private
- * @param {Moment}
+ * @param {Moment} v
* @returns {string}
*/
_formatClient: function (v) {
@@ -129,7 +129,7 @@ var DateWidget = Widget.extend({
},
/**
* @private
- * @param {string|false}
+ * @param {string|false} v
* @returns {Moment}
*/
_parseClient: function (v) {
@@ -137,7 +137,7 @@ var DateWidget = Widget.extend({
},
/**
* @private
- * @param {boolean}
+ * @param {boolean} readonly
*/
_setReadonly: function (readonly) {
this.readonly = readonly;
diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js
index 74572e81b39..8b273ed5aac 100644
--- a/addons/web/static/src/js/widgets/domain_selector.js
+++ b/addons/web/static/src/js/widgets/domain_selector.js
@@ -51,7 +51,6 @@ var DomainNode = Widget.extend({
"mouseleave button": "_onButtonLeft",
},
/**
- * @constructor
* A DomainNode needs a model and domain to work. It can also receive a set
* of options.
*
@@ -427,11 +426,12 @@ var DomainTree = DomainNode.extend({
* DomainTree specialization to use to have a fully working widget.
*
* Known limitations:
- * - Some operators like "child_of", "parent_of", "like", "not like",
- * "=like", "=ilike" will come only if you use them from demo data or
- * debug input.
- * - Some kind of domain can not be build right now
- * e.g ("country_id", "in", [1,2,3]) but you can insert from debug input.
+ *
+ * - Some operators like "child_of", "parent_of", "like", "not like",
+ * "=like", "=ilike" will come only if you use them from demo data or
+ * debug input.
+ * - Some kind of domain can not be build right now
+ * e.g ("country_id", "in", [1,2,3]) but you can insert from debug input.
*/
var DomainSelector = DomainTree.extend({
template: "DomainSelector",
diff --git a/addons/web/static/src/js/widgets/domain_selector_dialog.js b/addons/web/static/src/js/widgets/domain_selector_dialog.js
index c8f48d0ddc8..5f41973951d 100644
--- a/addons/web/static/src/js/widgets/domain_selector_dialog.js
+++ b/addons/web/static/src/js/widgets/domain_selector_dialog.js
@@ -7,6 +7,9 @@ var DomainSelector = require("web.DomainSelector");
var _t = core._t;
+/**
+ * @class DomainSelectorDialog
+ */
return Dialog.extend({
init: function (parent, model, domain, options) {
this.model = model;
diff --git a/addons/web/static/src/js/widgets/iframe_widget.js b/addons/web/static/src/js/widgets/iframe_widget.js
index cf5bc15c950..77d27fac5b4 100644
--- a/addons/web/static/src/js/widgets/iframe_widget.js
+++ b/addons/web/static/src/js/widgets/iframe_widget.js
@@ -6,7 +6,8 @@ var Widget = require('web.Widget');
/**
* Generic widget to create an iframe that listens for clicks
*
- * It should be extended by overwritting the methods:
+ * It should be extended by overwriting the methods::
+ *
* init: function(parent) {
* this._super(parent, <url_of_iframe>);
* },
diff --git a/addons/web/static/src/less/form_view_extra.less b/addons/web/static/src/less/form_view_extra.less
index 11dd9961bef..99903a30db0 100644
--- a/addons/web/static/src/less/form_view_extra.less
+++ b/addons/web/static/src/less/form_view_extra.less
@@ -97,6 +97,18 @@
vertical-align: middle;
}
}
+
+ &.o_full {
+ position: relative;
+ .o_button_more {
+ padding: 0 10px;
+ }
+ .o_dropdown_more {
+ .o-position-absolute(@top: 100%, @right: 0);
+ min-width: 0;
+ padding: 0px;
+ }
+ }
}
// Avatar
diff --git a/addons/web/static/src/less/kanban_column_progressbar.less b/addons/web/static/src/less/kanban_column_progressbar.less
index c1cf86adc81..47fe96a57f2 100644
--- a/addons/web/static/src/less/kanban_column_progressbar.less
+++ b/addons/web/static/src/less/kanban_column_progressbar.less
@@ -21,6 +21,10 @@
~ .oe_kanban_card_@{class} {
margin-top: -@o-kanban-inside-vgutter*0.5 - 1px;
}
+
+ ~ .o_kanban_load_more {
+ margin-top: -@o-kanban-inside-vgutter*0.5;
+ }
}
.o_kanban_load_more {
diff --git a/addons/web/static/src/less/kanban_view.less b/addons/web/static/src/less/kanban_view.less
index cedc5193e6d..ee510f75e29 100644
--- a/addons/web/static/src/less/kanban_view.less
+++ b/addons/web/static/src/less/kanban_view.less
@@ -446,20 +446,15 @@
margin-right: 0;
}
- &.o_kanban_small_column .o_kanban_group {
- &:not(.o_column_folded) {
- width: @o-kanban-small-record-width + 2*@o-kanban-group-padding;
- .o_kanban_record, .o_kanban_quick_create {
- width: @o-kanban-small-record-width;
- }
- }
+ &.o_kanban_small_column .o_kanban_group:not(.o_column_folded) {
+ width: @o-kanban-small-record-width + 2*@o-kanban-group-padding;
}
}
// Kanban Grouped Layout - Column default
.o_kanban_group {
.o-flex(0, 0, auto);
- padding: 0 @o-kanban-group-padding;
+ padding: 0 @o-kanban-group-padding @o-kanban-group-padding;
border: 1px solid @gray-lighter-dark;
background-color: @gray-lighter;
border-width: 0 1px;
@@ -500,11 +495,9 @@
}
.o_kanban_load_more {
+ padding: @o-kanban-record-margin 0;
+ box-shadow: inset 0 10px 13px -13px black;
text-align: center;
- a {
- color: white;
- font-weight: 300;
- }
}
&.ui-sortable-helper {
@@ -577,9 +570,11 @@
}
.o_kanban_quick_create {
- .o-position-absolute(-1px, 0, 0, (-@o-kanban-group-padding - 1px));
display: none;
width: @o-kanban-default-record-width;
+ height: 100%;
+ margin: 0 (-@o-kanban-group-padding/2);
+ border: none;
box-shadow: 0 0 20px -10px;
background-color: @gray-lighter;
diff --git a/addons/web/static/src/less/search_view.less b/addons/web/static/src/less/search_view.less
index a34905cb929..a9d6d28e9e4 100644
--- a/addons/web/static/src/less/search_view.less
+++ b/addons/web/static/src/less/search_view.less
@@ -185,6 +185,8 @@
// Favorites
.o_favorites_menu {
+ max-height: 75vh;
+ overflow-y: auto;
.o-selected-li;
.divider {display: none;} // hide the divider by default (no favorite)
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index 94fb0c4630c..9d0d0807199 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -3955,6 +3955,46 @@ QUnit.module('basic_fields', {
form.destroy();
});
+
+ QUnit.test('basic domain field: show the selection', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].foo = "[]";
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form>' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="foo" widget="domain" options="{\'model\': \'partner_type\'}"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ archs: {
+ 'partner_type,false,list': '<tree><field name="display_name"/></tree>',
+ 'partner_type,false,search': '<search><field name="name" string="Name"/></search>',
+ },
+ res_id: 1,
+ });
+
+ assert.equal(form.$(".o_domain_show_selection_button").text().trim().substr(0, 2), "2 ",
+ "selection should contain 2 records");
+
+ // open the selection
+ form.$(".o_domain_show_selection_button").click();
+ assert.strictEqual($('.modal .o_list_view .o_data_row').length, 2,
+ "should have open a list view with 2 records in a dialog");
+
+ // click on a record -> should not open the record
+ // we don't actually check that it doesn't open the record because even
+ // if it tries to, it will crash as we don't define an arch in this test
+ $('.modal .o_list_view .o_data_row:first .o_data_cell').click();
+
+ form.destroy();
+ });
});
});
});
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 5b559529371..330307195d0 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -947,8 +947,7 @@ var MockServer = Class.extend({
return $.when(this.data[args.model][args.method](args.args, args.kwargs));
}
- console.error("Unimplemented route", route, args);
- return $.when();
+ throw new Error("Unimplemented route: " + route);
},
/**
* helper function: traverse a tree and apply the function f to each of its
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index 4ac4213f378..716e581b44a 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -213,7 +213,7 @@ QUnit.module('Views', {
});
QUnit.test('invisible fields are properly hidden', function (assert) {
- assert.expect(3);
+ assert.expect(4);
var form = createView({
View: FormView,
@@ -226,6 +226,10 @@ QUnit.module('Views', {
'<field name="bar"/>' +
'</group>' +
'<field name="qux" invisible="1"/>' +
+ // x2many field without inline view: as it is always invisible, the view
+ // should not be fetched. we don't specify any view in this test, so if it
+ // ever tries to fetch it, it will crash, indicating that this is wrong.
+ '<field name="p" invisible="True"/>' +
'</sheet>' +
'</form>',
res_id: 1,
@@ -237,6 +241,8 @@ QUnit.module('Views', {
"should not contain span with field value");
assert.strictEqual(form.$('.o_field_widget.o_invisible_modifier:contains(0.4)').length, 1,
"field qux should be invisible");
+ assert.ok(form.$('.o_field_widget[name=p]').hasClass('o_invisible_modifier'),
+ "field p should be invisible");
form.destroy();
});
@@ -5845,6 +5851,46 @@ QUnit.module('Views', {
form.destroy();
});
+ QUnit.test('can toggle column in x2many in sub form view', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[2].p = [1,2];
+ this.data.partner.fields.foo.sortable = true;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="trululu"/>' +
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/get_formview_id') {
+ return $.when(false);
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ 'partner,false,form': '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ },
+ viewOptions: {mode: 'edit'},
+ });
+ form.$('.o_external_button').click();
+ assert.strictEqual($('.modal-body .o_form_view .o_list_view .o_data_cell').text(), "yopblip",
+ "table has some initial order");
+
+ $('.modal-body .o_form_view .o_list_view th').click();
+ assert.strictEqual($('.modal-body .o_form_view .o_list_view .o_data_cell').text(), "blipyop",
+ "table is now sorted");
+ form.destroy();
+ });
+
QUnit.test('rainbowman attributes correctly passed on button click', function (assert) {
assert.expect(1);
diff --git a/addons/web/views/report_templates.xml b/addons/web/views/report_templates.xml
index bb5a11bc684..8a2b3be34c2 100644
--- a/addons/web/views/report_templates.xml
+++ b/addons/web/views/report_templates.xml
@@ -157,7 +157,7 @@
<li t-if="company.phone"><i class="fa fa-phone"/> <span t-field="company.phone"/></li>
<li t-if="company.email"><i class="fa fa-at"/> <span t-field="company.email"/></li>
<li t-if="company.website"><i class="fa fa-globe"/> <span t-field="company.website"/></li>
- <li t-if="company.vat"><i class="fa fa-building-o"/> VAT: <span t-field="company.vat"/></li>
+ <li t-if="company.vat"><i class="fa fa-building-o"/><t t-esc="company.country_id.vat_label or 'TIN'"/>: <span t-field="company.vat"/></li>
</ul>
<div t-field="company.report_footer"/>
<div class="text-muted">
@@ -196,7 +196,7 @@
<li t-if="company.phone">Tel: <span t-field="company.phone"/></li>
<li t-if="company.email">Mail: <span t-field="company.email"/></li>
<li t-if="company.website">Web: <span t-field="company.website"/></li>
- <li t-if="company.vat">VAT: <span t-field="company.vat"/></li>
+ <li t-if="company.vat"><t t-esc="company.country_id.vat_label or 'TIN'"/>: <span t-field="company.vat"/></li>
</ul>
<div t-field="company.report_footer"/>
<div>
@@ -215,7 +215,7 @@
<div class="col-xs-5 col-xs-offset-1" name="company_address">
<ul class="list-unstyled">
<strong><li t-if="company.name"><span t-field="company.name"/></li></strong>
- <li t-if="company.vat">VAT: <span t-field="company.vat"/></li>
+ <li t-if="company.vat"><t t-esc="company.country_id.vat_label or 'TIN'"/>: <span t-field="company.vat"/></li>
<li t-if="company.phone">Tel: <span t-field="company.phone"/></li>
<li t-if="company.email"><span t-field="company.email"/></li>
<li t-if="company.website"><span t-field="company.website"/></li>
@@ -281,7 +281,7 @@
<li t-if="company.phone">Phone: <span t-field="company.phone"/></li>
<li t-if="company.email">Email: <span t-field="company.email"/></li>
<li t-if="company.website">Web: <span t-field="company.website"/></li>
- <li t-if="company.vat">VAT: <span t-field="company.vat"/></li>
+ <li t-if="company.vat"><t t-esc="company.country_id.vat_label or 'TIN'"/>: <span t-field="company.vat"/></li>
</ul>
<div name="financial_infos">
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index c0299fc2969..c08fd39f0db 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -33,7 +33,8 @@ var SnippetEditor = Widget.extend({
/**
* @constructor
* @param {Widget} parent
- * @param {DOMElement} target
+ * @param {Element} target
+ * @param templateOptions
*/
init: function (parent, target, templateOptions) {
this._super.apply(this, arguments);
@@ -640,7 +641,7 @@ var SnippetsMenu = Widget.extend({
* @private
* @param {jQuery} [$selectorSiblings]
* elements which must have siblings drop zones
- * @param {jQuery} [$selectorSiblings]
+ * @param {jQuery} [$selectorChildren]
* elements which must have child drop zones between each of existing
* child
*/
diff --git a/addons/web_editor/static/src/js/widgets/ace.js b/addons/web_editor/static/src/js/widgets/ace.js
index 3ba6d29b2b8..8053b7b12c8 100644
--- a/addons/web_editor/static/src/js/widgets/ace.js
+++ b/addons/web_editor/static/src/js/widgets/ace.js
@@ -790,7 +790,7 @@ var ViewEditor = Widget.extend({
* is toggled -> reloads the resources accordingly.
*
* @private
- * @param {Event}
+ * @param {Event} ev
*/
_onIncludeAllLessChange: function (ev) {
this.options.includeAllLess = $(ev.target).prop('checked');
@@ -801,7 +801,7 @@ var ViewEditor = Widget.extend({
* included is toggled -> reloads the resources accordingly.
*
* @private
- * @param {Event}
+ * @param {Event} ev
*/
_onIncludeBundlesChange: function (ev) {
this.options.includeBundles = $(ev.target).prop('checked');
diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js
index d25f1c631e2..1c2f542c6cd 100644
--- a/addons/web_editor/static/src/js/widgets/widgets.js
+++ b/addons/web_editor/static/src/js/widgets/widgets.js
@@ -391,11 +391,15 @@ var ImageDialog = Widget.extend({
},
});
-/* list of font icons to load by editor. The icons are displayed in the media editor and
+/**
+ * list of font icons to load by editor. The icons are displayed in the media editor and
* identified like font and image (can be colored, spinned, resized with fa classes).
* To add font, push a new object {base, parser}
+ *
* - base: class who appear on all fonts (eg: fa fa-refresh)
* - parser: regular expression used to select all font in css style sheets
+ *
+ * @type Array
*/
var fontIcons = [{'base': 'fa', 'parser': /(?=^|\s)(\.fa-[0-9a-z_-]+::?before)/i}];
diff --git a/addons/web_kanban_gauge/static/src/js/kanban_gauge.js b/addons/web_kanban_gauge/static/src/js/kanban_gauge.js
index b36ec8cb654..1b6362068ea 100644
--- a/addons/web_kanban_gauge/static/src/js/kanban_gauge.js
+++ b/addons/web_kanban_gauge/static/src/js/kanban_gauge.js
@@ -6,21 +6,20 @@ var field_registry = require('web.field_registry');
var utils = require('web.utils');
/**
- * 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
- * view; takes over max_value
+ * view; takes over max_value
* - gauge_value_field: if set, the value displayed below the gauge is taken
- from this field instead of the base field used for
- the gauge. This allows to display a number different
- from the gauge.
+ * from this field instead of the base field used for
+ * the gauge. This allows to display a number different
+ * from the gauge.
* - label: lable of the gauge, displayed below the gauge value
* - label_field: get the label from the field that must be present in the
- view; takes over label
+ * view; takes over label
* - title: title of the gauge, displayed on top of the gauge
* - style: custom style
- *
*/
var GaugeWidget = AbstractField.extend({
diff --git a/addons/web_tour/static/src/js/tip.js b/addons/web_tour/static/src/js/tip.js
index 48b1e67b21e..8fb5fe49ffd 100644
--- a/addons/web_tour/static/src/js/tip.js
+++ b/addons/web_tour/static/src/js/tip.js
@@ -11,7 +11,8 @@ var Tip = Widget.extend({
mouseleave: "_to_bubble_mode",
},
/**
- * @param {info} [Object] description of the tip, containing the following keys:
+ * @param {Widget} parent
+ * @param {Object} [info] description of the tip, containing the following keys:
* - content [String] the html content of the tip
* - event_handlers [Object] description of optional event handlers to bind to the tip:
* - event [String] the event name
@@ -40,7 +41,7 @@ var Tip = Widget.extend({
};
},
/**
- * @param {$anchor} [JQuery] the node on which the tip should be placed
+ * @param {jQuery} $anchor the node on which the tip should be placed
*/
attach_to: function ($anchor) {
this.$anchor = $anchor;
diff --git a/addons/web_tour/static/src/js/tour_service.js b/addons/web_tour/static/src/js/tour_service.js
index 23bafde1c4d..09b4e6896b9 100644
--- a/addons/web_tour/static/src/js/tour_service.js
+++ b/addons/web_tour/static/src/js/tour_service.js
@@ -23,6 +23,16 @@ var CallService = Class.extend(mixins.EventDispatcherMixin, mixins.ServiceProvid
},
});
+/**
+ * @namespace
+ * @property {Object} active_tooltips
+ * @property {Object} tours
+ * @property {Array} consumed_tours
+ * @property {String} running_tour
+ * @property {Number} running_step_delay
+ * @property {'community' | 'enterprise'} edition
+ * @property {Array} _log
+ */
return session.is_bound.then(function () {
var defs = [];
// Load the list of consumed tours and the tip template only if we are admin, in the frontend,
diff --git a/addons/website/static/src/js/backend/dashboard.js b/addons/website/static/src/js/backend/dashboard.js
index 966c37b49f9..c48da63e59d 100644
--- a/addons/website/static/src/js/backend/dashboard.js
+++ b/addons/website/static/src/js/backend/dashboard.js
@@ -61,6 +61,9 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
});
},
+ /**
+ * Fetches dashboard data
+ */
fetch_data: function() {
var self = this;
return this._rpc({
diff --git a/addons/website_rating/static/src/js/website_mail.js b/addons/website_rating/static/src/js/website_mail.js
index d672af6788d..4f22fe60eea 100644
--- a/addons/website_rating/static/src/js/website_mail.js
+++ b/addons/website_rating/static/src/js/website_mail.js
@@ -87,7 +87,7 @@ odoo.define('website_rating.thread', function(require) {
/**
* Update the messages format
*
- * @param {Array<Object>}
+ * @param {Array<Object>} messages
* @returns {Array}
*/
preprocessMessages: function(messages){
@@ -102,17 +102,20 @@ odoo.define('website_rating.thread', function(require) {
},
/**
* Round the given value with a precision of 0.5.
- * Example : 1.2 --> 1.0
- * 1.7 --> 1.5
- * 1.9 --> 2.0
- * @param Float
- * @returns Float
+ *
+ * Examples:
+ * - 1.2 --> 1.0
+ * - 1.7 --> 1.5
+ * - 1.9 --> 2.0
+ *
+ * @param {Number} value
+ * @returns Number
**/
round_to_half: function(value) {
var converted = parseFloat(value); // Make sure we have a number
var decimal = (converted - parseInt(converted, 10));
decimal = Math.round(decimal * 10);
- if(decimal == 5){
+ if(decimal === 5){
return (parseInt(converted, 10)+0.5);
}
if((decimal < 3) || (decimal > 7)){
diff --git a/addons/website_sale/static/src/js/website_sale_rating.js b/addons/website_sale/static/src/js/website_sale_rating.js
deleted file mode 100644
index 921645215bc..00000000000
--- a/addons/website_sale/static/src/js/website_sale_rating.js
+++ /dev/null
@@ -1,26 +0,0 @@
-odoo.define('website_sale.rating', function (require) {
- "use strict";
-
- var core = require('web.core');
- var ajax = require('web.ajax');
- var rating = require('rating.rating');
- var WebsiteMailThread = require('website_mail.thread');
-
- var qweb = core.qweb;
-
- ajax.loadXML('/rating/static/src/xml/rating_common.xml', qweb);
-
- WebsiteMailThread.include({
- prepend_message: function(message_data){
- var $elem = this._super.call(this, message_data);
- if(message_data['rating_default_value']){
- var rating_star = new rating.RatingStarWidget(this, {
- 'rating_default_value': message_data['rating_default_value'],
- 'rating_disabled': message_data['rating_disabled'],
- });
- rating_star.appendTo($elem.find('h5'));
- this.$('.stars i').mouseout();
- }
- },
- });
-});
diff --git a/addons/website_theme_install/static/src/less/website_theme_install.less b/addons/website_theme_install/static/src/less/website_theme_install.less
index f72d98eb4f0..775b5761dea 100644
--- a/addons/website_theme_install/static/src/less/website_theme_install.less
+++ b/addons/website_theme_install/static/src/less/website_theme_install.less
@@ -6,9 +6,11 @@
/// Un-grouped Layout (default)
&.o_kanban_ungrouped {
.o-justify-content(space-between);
+ margin: 0 0 0 (@o-kanban-record-margin - 2 * @grid-gutter-width);
- @media (min-width: @screen-md-min) {
- margin: 0 -@grid-gutter-width;
+ @media (max-width: @screen-xs-max) {
+ padding-left: @odoo-horizontal-padding - @o-kanban-record-margin;
+ padding-right: @odoo-horizontal-padding;
}
.o_kanban_record {
@@ -17,8 +19,7 @@
}
.o_kanban_record {
- margin-left: @grid-gutter-width;
- margin-right: @grid-gutter-width;
+ margin-left: @grid-gutter-width * 2;
padding: 0;
box-shadow: none;
background: none;
diff --git a/doc/_extensions/autojsdoc/README.rst b/doc/_extensions/autojsdoc/README.rst
new file mode 100644
index 00000000000..ef7681778a1
--- /dev/null
+++ b/doc/_extensions/autojsdoc/README.rst
@@ -0,0 +1,87 @@
+:orphan:
+
+======================================
+JSDoc parser/Sphinx extension for Odoo
+======================================
+
+Why?
+====
+
+Spent about a week trying to coerce "standard" javascript tools (jsdoc_ with
+the hope of using sphinx-js_ for integration or `documentation.js`_) and
+failed to ever get a sensible result: failed to get any result with the
+current state of the documentation, significant changes/additions/fixes to
+docstrings brought this up to "garbage output" level.
+
+Bug reports and mailing list posts didn't show any path to improvement on the
+ES5 codebase (if we ever go whole-hog on ES6 modules and classes things could
+be different, in fact most of JSDoc's current effort seem focused on
+ES6/ES2015 features) but both experience and looking at the mailing lists
+told me that spending more time would be wasted.
+
+Even more so as I was writing visitors/rewriters to generate documentation
+from our existing structure, which broadly speaking is relatively strict, and
+thus
+
+What?
+=====
+
+If it were possible to generate JSDoc annotations from our relatively
+well-defined code structures, it was obviously possible to extract documentary
+information directly from it, hence this Odoo-specific package/extension
+trying to do exactly that.
+
+This package should eventually provide:
+
+* a command-line interface which can be invoked via ``-m autojsdoc`` (assuming
+ your ``PYTHONPATH`` can find it) which should allow dumping the parsed AST
+ in a convenient-ish form, possibly doing searches through the AST, a
+ dependency graph extractor/analysis and a text dumper for the
+ documentation.
+
+* a sphinx extension (``autojsdoc.sphinx``) which can be used to integrate the
+ parsed JSDoc information into the Sphinx doc.
+
+How?
+====
+
+Sphinx-aside, the package relies on 3 libraries:
+
+* pyjsparser_, an Esprima-compliant ES5.1 parser (with bits of ES6 support),
+ sadly it does not support comments in its current form so I had to fork it.
+ Fed a javascript source file, pyjsparser_ simply generates a bunch of nested
+ dicts representing an Esprima ast, ast-types_ does a reasonably good job of
+ describing it once you understand that "bases" are basically just structural
+ mixins.
+
+ Because the original does not, this package provides a ``visitor`` module
+ for pyjsparser_ ASTs.
+
+* pyjsdoc_, a one-file "port" of jsdoc, can actually do much of the JS parsing
+ (using string munging) but its core semantics don't fit our needs so I'm
+ only using it to parse the actual JSDoc content, and the ``jsdoc`` module
+ contains some replacement classes, extensions & monkey patches for things
+ `pyjsdoc`_ itself does not support, at the time of this writing:
+
+ - a bug in FunctionDoc.return_val
+ - a type on FunctionDoc so it's compatible with ParamDoc
+ - a more reliable comments-parsing function
+ - a replacement ModuleDoc as the original does not materialise AMD modules
+ - a ClassDoc extension to support mixins
+ - two additional CommentDoc extensions for "namespaces" objects (bag of
+ attributes without any more information) and mixin objects
+
+* pytest_ to configure and run the test suite, which you can run by invoking
+ ``pytest doc/_extensions`` from the project top-level, the tests represent
+ both "happy path" things we want to parse and various code patterns which
+ tripped the happy path because e.g. they were matched and should not have,
+ they were not matched and should have, or they were more complex than the
+ happy path had expected
+
+.. _ast-types: _https://github.com/benjamn/ast-types/blob/master/def/core.js
+.. _documentation.js: http://documentation.js.org
+.. _jsdoc: http://usejsdoc.org
+.. _pyjsdoc: https://github.com/nostrademons/pyjsdoc
+.. _pyjsparser: https://github.com/PiotrDabkowski/pyjsparser
+.. _pytest: https://pytest.org/
+.. _sphinx-js: https://sphinx-js-howto.readthedocs.io
diff --git a/doc/_extensions/autojsdoc/__init__.py b/doc/_extensions/autojsdoc/__init__.py
new file mode 100644
index 00000000000..40a96afc6ff
--- /dev/null
+++ b/doc/_extensions/autojsdoc/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/doc/_extensions/autojsdoc/__main__.py b/doc/_extensions/autojsdoc/__main__.py
new file mode 100644
index 00000000000..20ab65c2c37
--- /dev/null
+++ b/doc/_extensions/autojsdoc/__main__.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+import cgitb
+import fnmatch
+import io
+import logging
+
+import click
+
+import pyjsdoc
+import pyjsparser
+import sys
+
+from .parser.parser import ModuleMatcher
+from .parser.visitor import Visitor, SKIP
+from . import jsdoc
+
+class Printer(Visitor):
+ def __init__(self, level=0):
+ super(Printer, self).__init__()
+ self.level = level
+
+ def _print(self, text):
+ print ' ' * self.level, text
+
+ def enter_generic(self, node):
+ self._print(node['type'])
+ self.level += 1
+
+ def exit_generic(self, node):
+ self.level -= 1
+
+ def enter_Identifier(self, node):
+ self._print(node['name'])
+ return SKIP
+
+ def enter_Literal(self, node):
+ self._print(node['value'])
+ return SKIP
+
+ def enter_BinaryExpression(self, node):
+ self._print(node['operator'])
+ self.level += 1
+
+def visit_files(files, visitor, ctx):
+ for name in files:
+ with io.open(name) as f:
+ ctx.logger.info("%s", name)
+ try:
+ yield visitor().visit(pyjsparser.parse(f.read()))
+ except Exception as e:
+ if ctx.logger.isEnabledFor(logging.DEBUG):
+ ctx.logger.exception("while visiting %s", name)
+ else:
+ ctx.logger.error("%s while visiting %s", e, name)
+
+# bunch of modules various bits depend on which are not statically defined
+# (or are outside the scope of the system)
+ABSTRACT_MODULES = [
+ jsdoc.ModuleDoc({
+ 'module': 'web.web_client',
+ 'dependency': {'web.AbstractWebClient'},
+ 'exports': jsdoc.NSDoc({
+ 'name': 'web_client',
+ 'doc': 'instance of AbstractWebClient',
+ }),
+ }),
+ jsdoc.ModuleDoc({
+ 'module': 'web.Tour',
+ 'dependency': {'web_tour.TourManager'},
+ 'exports': jsdoc.NSDoc({
+ 'name': 'Tour',
+ 'doc': 'maybe tourmanager instance?',
+ }),
+ }),
+ # OH FOR FUCK'S SAKE
+ jsdoc.ModuleDoc({
+ 'module': 'summernote/summernote',
+ 'exports': jsdoc.NSDoc({'doc': "totally real summernote"}),
+ })
+]
+
+@click.group(context_settings={'help_option_names': ['-h', '--help']})
+@click.option('-v', '--verbose', count=True)
+@click.option('-q', '--quiet', count=True)
+@click.pass_context
+def autojsdoc(ctx, verbose, quiet):
+ logging.basicConfig(
+ level=logging.INFO + (quiet - verbose) * 10,
+ format="[%(levelname)s %(created)f] %(message)s",
+ )
+ ctx.logger = logging.getLogger('autojsdoc')
+ ctx.visitor = None
+ ctx.files = []
+ ctx.kw = {}
+
+@autojsdoc.command()
+@click.argument('files', type=click.Path(exists=True), nargs=-1)
+@click.pass_context
+def ast(ctx, files):
+ """ Prints a structure tree of the provided files
+ """
+ if not files:
+ print(ctx.get_help())
+ visit_files(files, lambda: Printer(level=1), ctx.parent)
+
+@autojsdoc.command()
+@click.option('-m', '--module', multiple=True, help="Only shows dependencies matching any of the patterns")
+@click.argument('files', type=click.Path(exists=True), nargs=-1)
+@click.pass_context
+def dependencies(ctx, module, files):
+ """ Prints a dot file of all modules to stdout
+ """
+ if not files:
+ print(ctx.get_help())
+ byname = {
+ mod.name: mod.dependencies
+ for mod in ABSTRACT_MODULES
+ }
+ for modules in visit_files(files, ModuleMatcher, ctx.parent):
+ for mod in modules:
+ byname[mod.name] = mod.dependencies
+
+ print('digraph dependencies {')
+
+ todo = set()
+ # if module filters, roots are only matching modules
+ if module:
+ for f in module:
+ todo.update(fnmatch.filter(byname.keys(), f))
+
+ for m in todo:
+ # set a different box for selected roots
+ print(' "%s" [color=orangered]' % m)
+ else:
+ # otherwise check all modules
+ todo.update(byname)
+
+ done = set()
+ while todo:
+ node = todo.pop()
+ if node in done:
+ continue
+
+ done.add(node)
+ deps = byname[node]
+ todo.update(deps - done)
+ for dep in deps:
+ print(' "%s" -> "%s";' % (node, dep))
+ print('}')
+
+try:
+ autojsdoc.main(prog_name='autojsdoc')
+except Exception:
+ print(cgitb.text(sys.exc_info()))
diff --git a/doc/_extensions/autojsdoc/ext/__init__.py b/doc/_extensions/autojsdoc/ext/__init__.py
new file mode 100644
index 00000000000..e27a9e3d318
--- /dev/null
+++ b/doc/_extensions/autojsdoc/ext/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+from .directives import automodule_bound, autodirective_bound
+from .extractor import _get_roots
+
+
+def setup(app):
+ app.add_config_value('js_roots', _get_roots, 'env')
+ modules = {}
+ app.add_directive_to_domain('js', 'automodule', automodule_bound(app, modules)
+ )
+ autodirective = autodirective_bound(app, modules)
+ for n in ['autonamespace', 'automixin', 'autoclass', 'autofunction']:
+ app.add_directive_to_domain('js', n, autodirective)
diff --git a/doc/_extensions/autojsdoc/ext/directives.py b/doc/_extensions/autojsdoc/ext/directives.py
new file mode 100644
index 00000000000..98b3f9e8322
--- /dev/null
+++ b/doc/_extensions/autojsdoc/ext/directives.py
@@ -0,0 +1,686 @@
+# -*- coding: utf-8 -*-
+import abc
+import collections
+import contextlib
+import fnmatch
+import io
+import re
+
+from docutils import nodes
+from docutils.parsers.rst import Directive
+from docutils.statemachine import StringList
+from sphinx import addnodes
+from sphinx.ext.autodoc import members_set_option, bool_option, ALL
+
+from autojsdoc.ext.extractor import read_js
+from ..parser import jsdoc, types
+
+
+@contextlib.contextmanager
+def addto(parent, newnode):
+ assert isinstance(newnode, nodes.Node), \
+ "Expected newnode to be a Node, got %s" % (type(newnode))
+ yield newnode
+ parent.append(newnode)
+
+
+def documenter_for(directive, doc):
+ if isinstance(doc, jsdoc.FunctionDoc):
+ return FunctionDocumenter(directive, doc)
+ if isinstance(doc, jsdoc.ClassDoc):
+ return ClassDocumenter(directive, doc)
+ if isinstance(doc, jsdoc.MixinDoc):
+ return MixinDocumenter(directive, doc)
+ if isinstance(doc, (jsdoc.PropertyDoc, jsdoc.LiteralDoc)):
+ return PropertyDocumenter(directive, doc)
+ if isinstance(doc, jsdoc.InstanceDoc):
+ return InstanceDocumenter(directive, doc)
+ if isinstance(doc, jsdoc.Unknown):
+ return UnknownDocumenter(directive, doc)
+ if isinstance(doc, jsdoc.NSDoc):
+ return NSDocumenter(directive, doc)
+
+ raise TypeError("No documenter for %s" % type(doc))
+
+def to_list(doc, source=None):
+ return StringList([
+ line.rstrip('\n')
+ for line in io.StringIO(doc)
+ ], source=source)
+
+DIRECTIVE_OPTIONS = {
+ 'members': members_set_option,
+ 'undoc-members': bool_option,
+ 'private-members': bool_option,
+ 'undoc-matches': bool_option,
+}
+def automodule_bound(app, modules):
+ class AutoModuleDirective(Directive):
+ required_arguments = 1
+ has_content = True
+ option_spec = DIRECTIVE_OPTIONS
+
+ # self.state.nested_parse(string, offset, node) => parse context for sub-content (body which can contain RST data)
+ # => needed for doc (converted?) and for actual directive body
+ def run(self):
+ self.env = self.state.document.settings.env
+ modname = self.arguments[0].strip()
+
+ # TODO: cache/memoize modules & symbols?
+ if not modules:
+ read_js(app, modules)
+
+ mods = [
+ (name, mod)
+ for name, mod in modules.items()
+ if fnmatch.fnmatch(name, modname)
+ ]
+ ret = []
+ for name, mod in mods:
+ if mod.is_private:
+ continue
+ if name != modname and not (mod.doc or mod.exports):
+ # this module has no documentation, no exports and was
+ # not specifically requested through automodule -> skip
+ # unless requested
+ if not self.options.get('undoc-matches'):
+ continue
+ modsource = mod['sourcefile']
+ if modsource:
+ self.env.note_dependency(modsource)
+ # not sure what that's used for as normal xrefs are resolved using the id directly
+ target = nodes.target('', '', ids=['module-' + name], ismod=True)
+ self.state.document.note_explicit_target(target)
+
+ documenter = ModuleDocumenter(self, mod)
+
+ ret.append(target)
+ ret.extend(documenter.generate())
+ return ret
+
+ return AutoModuleDirective
+
+def autodirective_bound(app, modules):
+ documenters = {
+ 'js:autoclass': ClassDocumenter,
+ 'js:autonamespace': NSDocumenter,
+ 'js:autofunction': FunctionDocumenter,
+ 'js:automixin': MixinDocumenter,
+ }
+ class AutoDirective(Directive):
+ required_arguments = 1
+ has_content = True
+ option_spec = DIRECTIVE_OPTIONS
+
+ def run(self):
+ self.env = self.state.document.settings.env
+
+ # strip 'js:auto'
+ objname = self.arguments[0].strip()
+
+ path = self.env.temp_data.get('autojs:prefix', []) + [objname]
+ item = modules[path[0]]
+ # deref' namespaces until we reach the object we're looking for
+ for k in path[1:]:
+ item = item.get_property(k)
+
+ docclass = documenters[self.name]
+ return docclass(self, item).generate()
+
+ return AutoDirective
+
+class Documenter(object):
+ objtype = None
+ def __init__(self, directive, doc):
+ self.directive = directive
+ self.env = directive.env
+ self.item = doc
+
+ @property
+ def modname(self):
+ return self.env.temp_data.get('autojs:module', '')
+ @property
+ def classname(self):
+ return self.env.temp_data.get('autojs:class', '')
+ def generate(self, all_members=False):
+ """
+ :rtype: List[nodes.Node]
+ """
+ objname = self.item.name
+ prefixed = (self.item['sourcemodule'].name + '.' + objname) if self.item['sourcemodule'] else None
+ objtype = self.objtype
+ assert objtype, '%s has no objtype' % type(self)
+ root = addnodes.desc(domain='js', desctype=objtype, objtype=objtype)
+ with addto(root, addnodes.desc_signature(
+ module=self.modname or '',
+ fullname=objname,
+ )) as s:
+ s['class'] = self.classname
+
+ s['ids'] = []
+ if objname:
+ s['ids'].append(objname)
+ if prefixed:
+ s['ids'].append(prefixed)
+
+ if objtype:
+ s += addnodes.desc_annotation(
+ objtype, objtype,
+ nodes.Text(' '),
+ )
+
+ env = self.env
+ if objname:
+ env.domaindata['js']['objects'][objname] = (env.docname, objtype)
+ if prefixed:
+ env.domaindata['js']['objects'][prefixed] = (env.docname, objtype)
+
+ # TODO: linkcode_resolve
+ s += self.make_signature()
+ with addto(root, addnodes.desc_content()) as c:
+ # must be here otherwise nested_parse(self.content) will not have
+ # the prefix set
+ self.env.temp_data.setdefault('autojs:prefix', []).append(self.item.name)
+ c += self.make_content(all_members=all_members)
+ self.env.temp_data['autojs:prefix'].pop()
+ return [root]
+
+ def make_signature(self):
+ """
+ :rtype: List[nodes.Node]
+ """
+ return [addnodes.desc_name(self.item.name, self.item.name)]
+
+ @abc.abstractmethod
+ def make_content(self, all_members):
+ """
+ :rtype: List[nodes.Node]
+ """
+
+ def document_subtypes(self, subtypes):
+ docs = []
+ with with_mapping_value(self.directive.options, 'undoc-members', True):
+ for cls in subtypes:
+ docs += ClassDocumenter(self.directive, cls).generate(all_members=True)
+ return docs
+
+
+class NSDocumenter(Documenter):
+ objtype = 'namespace'
+ def make_content(self, all_members):
+ doc = self.item
+ ret = nodes.section()
+ if doc.doc:
+ self.directive.state.nested_parse(to_list(doc.doc), 0, ret)
+ ret += self.document_properties(all_members)
+ return ret.children
+
+ def should_document(self, member, name, all_members):
+ """
+ :type member: jsdoc.CommentDoc
+ :type name: str
+ :type all_members: bool
+ :rtype: bool
+ """
+ options = self.directive.options
+
+ members = options.get('members') or []
+ if not (all_members or members is ALL):
+ # if a member is requested by name, it's always documented
+ return name in members
+
+ # ctor params are merged into the class doc
+ if member.is_constructor:
+ return False
+
+ # only document "private" members if option is set
+ if self.is_private(member, name) and not options.get('private-members'):
+ return False
+
+ # TODO: what if member doesn't have a description but has non-desc tags set?
+ # TODO: add @public to force documenting symbol? => useful for implicit typedef
+ return bool(member.doc or options.get('undoc-members'))
+
+ def is_private(self, member, name):
+ return member.is_private
+
+ def document_properties(self, all_members):
+ ret = nodes.section()
+ # TODO: :member-order: [alphabetical | groupwise | bysource]
+ for (n, p) in self.item.properties:
+ if not self.should_document(p, n, all_members):
+ continue
+ # FIXME: maybe should use property name as name inside?
+ ret += documenter_for(self.directive, p).generate(all_members=True)
+ return ret.children
+
+_NONE = object()
+@contextlib.contextmanager
+def with_mapping_value(mapping, key, value, restore_to=_NONE):
+ """ Sets ``key`` to ``value`` for the duration of the context.
+
+ If ``restore_to`` is not provided, restores ``key``'s old value
+ afterwards, removes it entirely if there was no value for ``key`` in the
+ mapping.
+
+ .. warning:: for defaultdict & similar mappings, may restore the default
+ value (depends how the collections' .get behaves)
+ """
+ if restore_to is _NONE:
+ restore_to = mapping.get(key, _NONE)
+ mapping[key] = value
+ try:
+ yield
+ finally:
+ if restore_to is _NONE:
+ del mapping[key]
+ else:
+ mapping[key] = restore_to
+class ModuleDocumenter(NSDocumenter):
+ objtype = 'module'
+ def document_properties(self, all_members):
+ with with_mapping_value(self.env.temp_data, 'autojs:module', self.item.name, ''):
+ return super(ModuleDocumenter, self).document_properties(all_members)
+
+ def make_content(self, all_members):
+ doc = self.item
+ content = addnodes.desc_content()
+
+ if doc.exports or doc.dependencies:
+ with addto(content, nodes.field_list()) as fields:
+ if doc.exports:
+ with addto(fields, nodes.field()) as field:
+ field += nodes.field_name('Exports', 'Exports')
+ with addto(field, nodes.field_body()) as body:
+ ref = doc['exports'] # warning: not the same as doc.exports
+ label = ref or '<anonymous>'
+ link = addnodes.pending_xref(
+ ref, nodes.paragraph(ref, label),
+ refdomain='js',
+ reftype='any',
+ reftarget=ref,
+ )
+ link['js:module'] = doc.name
+ body += link
+
+ if doc.dependencies:
+ with addto(fields, nodes.field()) as field:
+ self.make_dependencies(field, doc)
+
+ self.directive.state.nested_parse(self.directive.content, 0, content)
+
+ if doc.doc:
+ # FIXME: source offset
+ self.directive.state.nested_parse(to_list(doc.doc, source=doc['sourcefile']), 0, content)
+
+ content += self.document_properties(all_members)
+
+ return content
+
+ def make_dependencies(self, field, doc):
+ field += nodes.field_name("Depends On", "Depends On")
+ with addto(field, nodes.field_body()) as body:
+ with addto(body, nodes.bullet_list()) as deps:
+ for dep in sorted(doc.dependencies):
+ ref = addnodes.pending_xref(
+ dep, nodes.paragraph(dep, dep),
+ refdomain='js',
+ reftype='module',
+ reftarget=dep,
+ )
+ deps += nodes.list_item(dep, ref)
+
+ def should_document(self, member, name, all_members):
+ # member can be Nothing?
+ if not member:
+ return False
+ modname = getattr(member['sourcemodule'], 'name', None)
+ doc = self.item
+
+ # always document exported symbol (regardless undoc, private, ...)
+ # otherwise things become... somewhat odd
+ if name == doc['exports']:
+ return True
+
+ # if doc['exports'] the module is exporting a "named" item which
+ # does not need to be documented twice, if not doc['exports'] it's
+ # exporting an anonymous item (e.g. object literal) which needs to
+ # be documented on its own
+ if name == '<exports>' and not doc['exports']:
+ return True
+
+ # TODO: :imported-members:
+ # FIXME: *directly* re-exported "foreign symbols"?
+ return (not modname or modname == doc.name) \
+ and super(ModuleDocumenter, self).should_document(member, name, all_members)
+
+
+class ClassDocumenter(NSDocumenter):
+ objtype = 'class'
+
+ def document_properties(self, all_members):
+ with with_mapping_value(self.env.temp_data, 'autojs:class', self.item.name, ''):
+ return super(ClassDocumenter, self).document_properties(all_members)
+
+ def make_signature(self):
+ sig = super(ClassDocumenter, self).make_signature()
+ sig.append(self.make_parameters())
+ return sig
+
+ def make_parameters(self):
+ params = addnodes.desc_parameterlist('', '')
+ ctor = self.item.constructor
+ if ctor:
+ params += make_desc_parameters(ctor.params)
+
+ return params
+
+ def make_content(self, all_members):
+ doc = self.item
+ ret = nodes.section()
+
+ ctor = self.item.constructor
+ params = subtypes = []
+ if ctor:
+ check_parameters(self, ctor)
+ params, subtypes = extract_subtypes(doc.name, ctor)
+
+ fields = nodes.field_list()
+ fields += self.make_super()
+ fields += self.make_mixins()
+ fields += self.make_params(params)
+ if fields.children:
+ ret += fields
+
+ if doc.doc:
+ self.directive.state.nested_parse(to_list(doc.doc), 0, ret)
+
+ ret += self.document_properties(all_members)
+
+ ret += self.document_subtypes(subtypes)
+
+ return ret.children
+
+ def is_private(self, member, name):
+ return name.startswith('_') or super(ClassDocumenter, self).is_private(member, name)
+
+ def make_super(self):
+ doc = self.item
+ if not doc.superclass:
+ return []
+
+ sup_link = addnodes.pending_xref(
+ doc.superclass.name, nodes.paragraph(doc.superclass.name, doc.superclass.name),
+ refdomain='js', reftype='class', reftarget=doc.superclass.name,
+ )
+ sup_link['js:module'] = doc.superclass['sourcemodule'].name
+ return nodes.field(
+ '',
+ nodes.field_name("Extends", "Extends"),
+ nodes.field_body(doc.superclass.name, sup_link),
+ )
+
+ def make_mixins(self):
+ doc = self.item
+ if not doc.mixins:
+ return []
+
+ ret = nodes.field('', nodes.field_name("Mixes", "Mixes"))
+ with addto(ret, nodes.field_body()) as body:
+ with addto(body, nodes.bullet_list()) as mixins:
+ for mixin in sorted(doc.mixins, key=lambda m: m.name):
+ mixin_link = addnodes.pending_xref(
+ mixin.name, nodes.paragraph(mixin.name, mixin.name),
+ refdomain='js', reftype='mixin', reftarget=mixin.name
+ )
+ mixin_link['js:module'] = mixin['sourcemodule'].name
+ mixins += nodes.list_item('', mixin_link)
+ return ret
+
+ def make_params(self, params):
+ if not params:
+ return []
+
+ ret = nodes.field('', nodes.field_name('Parameters', 'Parameters'))
+ with addto(ret, nodes.field_body()) as body,\
+ addto(body, nodes.bullet_list()) as holder:
+ holder += make_parameters(params, mod=self.modname)
+ return ret
+
+class InstanceDocumenter(Documenter):
+ objtype = 'object'
+ def make_signature(self):
+ cls = self.item.cls
+ ret = super(InstanceDocumenter, self).make_signature()
+ if cls:
+ super_ref = addnodes.pending_xref(
+ cls.name, nodes.Text(cls.name, cls.name),
+ refdomain='js', reftype='class', reftarget=cls.name
+ )
+ super_ref['js:module'] = cls['sourcemodule'].name
+ ret.append(addnodes.desc_annotation(' instance of ', ' instance of '))
+ ret.append(addnodes.desc_type(cls.name, '', super_ref))
+ if not ret:
+ return [addnodes.desc_name('???', '???')]
+ return ret
+
+ def make_content(self, all_members):
+ ret = nodes.section()
+ if self.item.doc:
+ self.directive.state.nested_parse(to_list(self.item.doc), 0, ret)
+ return ret.children
+ return ret.children
+
+class FunctionDocumenter(Documenter):
+ @property
+ def objtype(self):
+ return 'method' if self.classname else 'function'
+ def make_signature(self):
+ ret = super(FunctionDocumenter, self).make_signature()
+ with addto(ret, addnodes.desc_parameterlist()) as params:
+ params += make_desc_parameters(self.item.params)
+ retval = self.item.return_val
+ if retval.type or retval.doc:
+ ret.append(addnodes.desc_returns(retval.type or '*', retval.type or '*'))
+ return ret
+
+ def make_content(self, all_members):
+ ret = nodes.section()
+ doc = self.item
+ if doc.doc:
+ self.directive.state.nested_parse(to_list(doc.doc), 0, ret)
+
+ check_parameters(self, doc)
+
+ params, subtypes = extract_subtypes(self.item.name, self.item)
+ rdoc = doc.return_val.doc
+ rtype = doc.return_val.type
+ if params or rtype or rdoc:
+ with addto(ret, nodes.field_list()) as fields:
+ if params:
+ with addto(fields, nodes.field()) as field:
+ field += nodes.field_name('Parameters', 'Parameters')
+ with addto(field, nodes.field_body()) as body,\
+ addto(body, nodes.bullet_list()) as holder:
+ holder.extend(make_parameters(params, mod=doc['sourcemodule'].name))
+ if rdoc:
+ with addto(fields, nodes.field()) as field:
+ field += nodes.field_name("Returns", "Returns")
+ with addto(field, nodes.field_body()) as body,\
+ addto(body, nodes.paragraph()) as p:
+ p += nodes.inline(rdoc, rdoc)
+ if rtype:
+ with addto(fields, nodes.field()) as field:
+ field += nodes.field_name("Return Type", "Return Type")
+ with addto(field, nodes.field_body()) as body, \
+ addto(body, nodes.paragraph()) as p:
+ p += make_types(rtype, mod=doc['sourcemodule'].name)
+
+ ret += self.document_subtypes(subtypes)
+
+ return ret.children
+
+def pascal_case_ify(name):
+ """
+ Uppercase first letter of ``name``, or any letter following an ``_``. In
+ the latter case, also strips out the ``_``.
+
+ => key_for becomes KeyFor
+ => options becomes Options
+ """
+ return re.sub(r'(^|_)\w', lambda m: m.group(0)[-1].upper(), name)
+def extract_subtypes(parent_name, doc):
+ """ Extracts composite parameters (a.b) into sub-types for the parent
+ parameter, swaps the parent's type from whatever it is to the extracted
+ one, and returns the extracted type for inclusion into the parent.
+
+ :arg parent_name: name of the containing symbol (function, class), will
+ be used to compose subtype names
+ :type parent_name: str
+ :type doc: FunctionDoc
+ :rtype: (List[ParamDoc], List[ClassDoc])
+ """
+ # map of {param_name: [ParamDoc]} (from complete doc)
+ subparams = collections.defaultdict(list)
+ for p in map(jsdoc.ParamDoc, doc.get_as_list('param')):
+ pair = p.name.split('.', 1)
+ if len(pair) == 2:
+ k, p.name = pair # remove prefix from param name
+ subparams[k].append(p)
+
+ # keep original params order as that's the order of formal parameters in
+ # the function signature
+ params = collections.OrderedDict((p.name, p) for p in doc.params)
+ subtypes = []
+ # now we can use the subparams map to extract "compound" parameter types
+ # and swap the new type for the original param's type
+ for param_name, subs in subparams.items():
+ typename = '%s%s' % (
+ pascal_case_ify(parent_name),
+ pascal_case_ify(param_name),
+ )
+ param = params[param_name]
+ param.type = typename
+ subtypes.append(jsdoc.ClassDoc({
+ 'name': typename,
+ 'doc': param.doc,
+ '_members': [
+ (sub.name, jsdoc.PropertyDoc(dict(sub.to_dict(), sourcemodule=doc['sourcemodule'])))
+ for sub in subs
+ ],
+ 'sourcemodule': doc['sourcemodule'],
+ }))
+ return params.values(), subtypes
+
+def check_parameters(documenter, doc):
+ """
+ Check that all documented parameters match a formal parameter for the
+ function. Documented params which don't match the actual function may be
+ typos.
+ """
+ guessed = set(doc['guessed_params'] or [])
+ if not guessed:
+ return
+
+ documented = {
+ # param name can be of the form [foo.bar.baz=default]\ndescription
+ jsdoc.ParamDoc(text).name.split('.')[0]
+ for text in doc.get_as_list('param')
+ }
+ odd = documented - guessed
+ if not odd:
+ return
+
+ app = documenter.directive.env.app
+ app.warn("Found documented params %s not in formal parameter list "
+ "of function %s in module %s (%s)" % (
+ ', '.join(odd),
+ doc.name,
+ documenter.modname,
+ doc['sourcemodule']['sourcefile'],
+ ))
+
+def make_desc_parameters(params):
+ for p in params:
+ if '.' in p.name:
+ continue
+
+ node = addnodes.desc_parameter(p.name, p.name)
+ if p.optional:
+ node = addnodes.desc_optional('', '', node)
+ yield node
+
+def make_parameters(params, mod=None):
+ for param in params:
+ p = nodes.paragraph('', '', nodes.strong(param.name, param.name))
+ if param.default is not None:
+ p += nodes.Text('=', '=')
+ p += nodes.emphasis(param.default, param.default)
+ if param.type:
+ p += nodes.Text(' (')
+ p += make_types(param.type, mod=mod)
+ p += nodes.Text(')')
+ if param.doc:
+ p += [
+ nodes.Text(' -- '),
+ nodes.inline(param.doc, param.doc)
+ ]
+ yield p
+
+
+def _format_value(v):
+ if v == '|':
+ return nodes.emphasis(' or ', ' or ')
+ if v == ',':
+ return nodes.Text(', ', ', ')
+ return nodes.Text(v, v)
+def make_types(typespec, mod=None):
+ # TODO: in closure notation {type=} => optional, do we care?
+ def format_type(t):
+ ref = addnodes.pending_xref(
+ t, addnodes.literal_emphasis(t, t),
+ refdomain='js', reftype='class', reftarget=t,
+ )
+ if mod:
+ ref['js:module'] = mod
+ return ref
+
+ try:
+ return types.iterate(
+ types.parse(typespec),
+ format_type,
+ _format_value
+ )
+ except ValueError as e:
+ raise ValueError("%s in '%s'" % (e, typespec))
+
+
+class MixinDocumenter(NSDocumenter):
+ objtype = 'mixin'
+
+class PropertyDocumenter(Documenter):
+ objtype = 'attribute'
+ def make_signature(self):
+ ret = super(PropertyDocumenter, self).make_signature()
+ proptype = self.item.type
+ if proptype:
+ typeref = addnodes.pending_xref(
+ proptype, nodes.Text(proptype, proptype),
+ refdomain='js', reftype='class', reftarget=proptype
+ )
+ typeref['js:module'] = self.item['sourcemodule'].name
+ ret.append(nodes.Text(' '))
+ ret.append(typeref)
+ return ret
+
+ def make_content(self, all_members):
+ doc = self.item
+ ret = nodes.section()
+ if doc.doc:
+ self.directive.state.nested_parse(to_list(doc.doc), 0, ret)
+ return ret.children
+
+class UnknownDocumenter(Documenter):
+ objtype = 'unknown'
+ def make_content(self, all_members):
+ return []
diff --git a/doc/_extensions/autojsdoc/ext/extractor.py b/doc/_extensions/autojsdoc/ext/extractor.py
new file mode 100644
index 00000000000..36211cfd3b3
--- /dev/null
+++ b/doc/_extensions/autojsdoc/ext/extractor.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+import io
+import os
+
+import pyjsdoc
+import pyjsparser
+
+try:
+ from sphinx.util import status_iterator
+ def it(app): return status_iterator
+except ImportError:
+ # 1.2: Builder.status_iterator
+ # 1.3: Application.status_iterator (with alias on Builder)
+ # 1.6: sphinx.util.status_iterator (with *deprecated* aliases on Application and Builder)
+ # 1.7: removed Application and Builder aliases
+ # => if no sphinx.util.status_iterator, fallback onto the builder one for
+ # 1.2 compatibility, remove this entirely if we ever require 1.6+
+ status_iterator = None
+ def it(app): return app.builder.status_iterator
+
+from ..parser import jsdoc, parser
+
+
+def _get_roots(conf):
+ env_roots = os.environ.get('AUTOJSDOC_ROOTS_PATH')
+ if env_roots:
+ return env_roots.split(':')
+ return []
+
+def read_js(app, modules):
+ """
+ :type app: sphinx.application.Sphinx
+ :type modules: Dict[str, jsdoc.ModuleDoc]
+ """
+ roots = map(os.path.normpath, app.config.js_roots or [os.path.join(app.confdir, '..')])
+ files = [
+ os.path.join(r, f)
+ for root in roots
+ for r, _, fs in os.walk(root)
+ if 'static/src/js' in r
+ for f in fs
+ if f.endswith('.js')
+ ]
+
+ modules.update((mod.name, mod) for mod in ABSTRACT_MODULES)
+ for name in it(app)(files, "Parsing javascript files...", length=len(files)):
+ with io.open(name) as f:
+ ast = pyjsparser.parse(f.read())
+ modules.update(
+ (mod.name, mod)
+ for mod in parser.ModuleMatcher(name).visit(ast)
+ )
+ _resolve_references(modules)
+
+def _resolve_references(byname):
+ # must be done in topological order otherwise the dependent can't
+ # resolve non-trivial references to a dependency properly
+ for name in pyjsdoc.topological_sort(
+ *pyjsdoc.build_dependency_graph(
+ list(byname.keys()),
+ byname
+ )
+ ):
+ byname[name].post_process(byname)
+
+ABSTRACT_MODULES = [
+ jsdoc.ModuleDoc({
+ 'module': u'web.web_client',
+ 'dependency': {u'web.AbstractWebClient'},
+ 'exports': jsdoc.NSDoc({
+ 'name': u'web_client',
+ 'doc': u'instance of AbstractWebClient',
+ }),
+ }),
+ jsdoc.ModuleDoc({
+ 'module': u'web.Tour',
+ 'dependency': {u'web_tour.TourManager'},
+ 'exports': jsdoc.NSDoc({
+ 'name': u'Tour',
+ 'doc': u'maybe tourmanager instance?',
+ }),
+ }),
+ jsdoc.ModuleDoc({
+ 'module': u'summernote/summernote',
+ 'exports': jsdoc.NSDoc({'doc': u"totally real summernote"}),
+ })
+]
diff --git a/doc/_extensions/autojsdoc/parser/__init__.py b/doc/_extensions/autojsdoc/parser/__init__.py
new file mode 100644
index 00000000000..40a96afc6ff
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/doc/_extensions/autojsdoc/parser/jsdoc.py b/doc/_extensions/autojsdoc/parser/jsdoc.py
new file mode 100644
index 00000000000..00406df42c1
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/jsdoc.py
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+import re
+
+import collections
+
+import pyjsdoc
+
+def strip_stars(doc_comment):
+ """
+ Version of jsdoc.strip_stars which always removes 1 space after * if
+ one is available.
+ """
+ return re.sub('\n\s*?\*[\t ]?', '\n', doc_comment[3:-2]).strip()
+
+class ParamDoc(pyjsdoc.ParamDoc):
+ """
+ Replace ParamDoc because FunctionDoc doesn't properly handle optional
+ params or default values (TODO: or compounds) if guessed_params is used
+
+ => augment paramdoc with "required" and "default" items to clean up name
+ """
+ def __init__(self, text):
+ super(ParamDoc, self).__init__(text)
+ # param name and doc can be separated by - or :, strip it
+ self.doc = self.doc.strip().lstrip('-:').lstrip()
+ self.optional = False
+ self.default = None
+ # there may not be a space between the param name and the :, in which
+ # case the : gets attached to the name, strip *again*
+ # TODO: formal @param/@property parser to handle this crap properly once and for all
+ self.name = self.name.strip().rstrip(':')
+ if self.name.startswith('['):
+ self.name = self.name.strip('[]')
+ self.optional = True
+ if '=' in self.name:
+ self.name, self.default = self.name.rsplit('=', 1)
+ def to_dict(self):
+ d = super(ParamDoc, self).to_dict()
+ d['optional'] = self.optional
+ d['default'] = self.default
+ return d
+pyjsdoc.ParamDoc = ParamDoc
+
+class CommentDoc(pyjsdoc.CommentDoc):
+ namekey = object()
+ is_constructor = False
+
+ @property
+ def name(self):
+ return self[self.namekey] or self['name'] or self['guessed_name']
+ def set_name(self, name):
+ # not great...
+ if name != '<exports>':
+ self.parsed['guessed_name'] = name
+
+ @property
+ def is_private(self):
+ return 'private' in self.parsed
+
+ def to_dict(self):
+ d = super(CommentDoc, self).to_dict()
+ d['name'] = self.name
+ return d
+
+class PropertyDoc(CommentDoc):
+ @classmethod
+ def from_param(cls, s, sourcemodule=None):
+ parsed = ParamDoc(s).to_dict()
+ parsed['sourcemodule'] = sourcemodule
+ return cls(parsed)
+
+ @property
+ def type(self):
+ return self['type'].strip('{}')
+
+ def to_dict(self):
+ d = super(PropertyDoc, self).to_dict()
+ d['type'] = self.type
+ d['is_private'] = self.is_private
+ return d
+
+class InstanceDoc(CommentDoc):
+ @property
+ def cls(self):
+ return self['cls']
+
+ def to_dict(self):
+ return dict(super(InstanceDoc, self).to_dict(), cls=self.cls)
+
+class LiteralDoc(CommentDoc):
+ @property
+ def type(self):
+ if self['type']:
+ return self['type']
+ valtype = type(self['value'])
+ if valtype is bool:
+ return 'Boolean'
+ elif valtype is float:
+ return 'Number'
+ elif valtype is type(u''):
+ return 'String'
+ return ''
+
+ @property
+ def value(self):
+ return self['value']
+
+ def to_dict(self):
+ d = super(LiteralDoc, self).to_dict()
+ d['type'] = self.type
+ d['value'] = self.value
+ return d
+
+class FunctionDoc(CommentDoc):
+ type = 'Function'
+ namekey = 'function'
+
+ @property
+ def is_constructor(self):
+ return self.name == 'init'
+
+ @property
+ def params(self):
+ tag_texts = self.get_as_list('param')
+ if self.get('guessed_params') is None:
+ return [ParamDoc(text) for text in tag_texts]
+ else:
+ param_dict = {}
+ for text in tag_texts:
+ param = ParamDoc(text)
+ param_dict[param.name] = param
+ return [param_dict.get(name) or ParamDoc('{} ' + name)
+ for name in self.get('guessed_params')]
+ @property
+ def return_val(self):
+ ret = self.get('return') or self.get('returns')
+ type = self.get('type')
+ if '{' in ret and '}' in ret:
+ if not '} ' in ret:
+ # Ensure that name is empty
+ ret = re.sub(r'\}\s*', '} ', ret)
+ return ParamDoc(ret)
+ if ret and type:
+ return ParamDoc('{%s} %s' % (type, ret))
+ return ParamDoc(ret)
+
+ def to_dict(self):
+ d = super(FunctionDoc, self).to_dict()
+ d['name'] = self.name
+ d['params'] = [param.to_dict() for param in self.params]
+ d['return_val']= self.return_val.to_dict()
+ return d
+
+class NSDoc(CommentDoc):
+ namekey = 'namespace'
+ def __init__(self, parsed_comment):
+ super(NSDoc, self).__init__(parsed_comment)
+ self.members = collections.OrderedDict()
+ def add_member(self, name, member):
+ """
+ :type name: str
+ :type member: CommentDoc
+ """
+ member.set_name(name)
+ self.members[name] = member
+
+ @property
+ def properties(self):
+ if self.get('property'):
+ return [
+ (p.name, p)
+ for p in (
+ PropertyDoc.from_param(p, self['sourcemodule'])
+ for p in self.get_as_list('property')
+ )
+ ]
+ return self.members.items() or self['_members'] or []
+
+ def has_property(self, name):
+ return self.get_property(name) is not None
+
+ def get_property(self, name):
+ return next((p for n, p in self.properties if n == name), None)
+
+ def to_dict(self):
+ d = super(NSDoc, self).to_dict()
+ d['properties'] = [(n, p.to_dict()) for n, p in self.properties]
+ return d
+
+class MixinDoc(NSDoc):
+ namekey = 'mixin'
+
+class ModuleDoc(NSDoc):
+ namekey = 'module'
+ def __init__(self, parsed_comment):
+ super(ModuleDoc, self).__init__(parsed_comment)
+ #: callbacks to run with the modules mapping once every module is resolved
+ self._post_process = []
+
+ def post_process(self, modules):
+ for callback in self._post_process:
+ callback(modules)
+
+ @property
+ def module(self):
+ return self # lol
+
+ @property
+ def dependencies(self):
+ """
+ Returns the immediate dependencies of a module (only those explicitly
+ declared/used).
+ """
+ return self.get('dependency', None) or set()
+
+ @property
+ def exports(self):
+ """
+ Returns the actual item exported from the AMD module, can be a
+ namespace, a class, a function, an instance, ...
+ """
+ return self.get_property('<exports>')
+
+ def to_dict(self):
+ vars = super(ModuleDoc, self).to_dict()
+ vars['dependencies'] = self.dependencies
+ vars['exports'] = self.exports
+ return vars
+
+class ClassDoc(NSDoc):
+ namekey = 'class'
+ @property
+ def constructor(self):
+ return self.get_property('init')
+
+ @property
+ def superclass(self):
+ return self['extends'] or self['base']
+
+ def get_property(self, method_name):
+ if method_name == 'extend':
+ return FunctionDoc({
+ 'doc': 'Create subclass for %s' % self.name,
+ 'guessed_function': 'extend',
+ })
+ # FIXME: should ideally be a proxy namespace
+ if method_name == 'prototype':
+ return self
+ return super(ClassDoc, self).get_property(method_name)
+
+ @property
+ def mixins(self):
+ return self.get_as_list('mixes')
+
+ def to_dict(self):
+ d = super(ClassDoc, self).to_dict()
+ d['mixins'] = self.mixins
+ return d
+
+class UnknownNS(NSDoc):
+ def get_property(self, name):
+ return super(UnknownNS, self).get_property(name) or \
+ UnknownNS({'name': '{}.{}'.format(self.name, name)})
+
+class Unknown(CommentDoc):
+ @classmethod
+ def from_(cls, source):
+ def builder(parsed):
+ inst = cls(parsed)
+ inst.parsed['source'] = source
+ return inst
+ return builder
+
+ @property
+ def name(self):
+ return self['name'] + ' ' + self['source']
+
+ @property
+ def type(self):
+ return "Unknown"
+
+ def get_property(self, p):
+ return Unknown(dict(self.parsed, source=self.name, name=p + '<'))
+
+def parse_comments(comments, doctype=None):
+ # find last comment which starts with a *
+ docstring = next((
+ c['value']
+ for c in reversed(comments or [])
+ if c['value'].startswith(u'*')
+ ), None) or u""
+
+ # \n prefix necessary otherwise parse_comment fails to take first
+ # block comment parser strips delimiters, but strip_stars fails without
+ # them
+ extract = '\n' + strip_stars('/*' + docstring + '\n*/')
+ parsed = pyjsdoc.parse_comment(extract, u'')
+
+ if doctype == 'FunctionExpression':
+ doctype = FunctionDoc
+ elif doctype == 'ObjectExpression' or doctype is None:
+ doctype = guess
+
+ if doctype is guess:
+ return doctype(parsed)
+
+ # in case a specific doctype is given, allow overriding it anyway
+ return guess(parsed, default=doctype)
+
+def guess(parsed, default=NSDoc):
+ if 'class' in parsed:
+ return ClassDoc(parsed)
+ if 'function' in parsed:
+ return FunctionDoc(parsed)
+ if 'mixin' in parsed:
+ return MixinDoc(parsed)
+ if 'namespace' in parsed:
+ return NSDoc(parsed)
+ if 'module' in parsed:
+ return ModuleDoc(parsed)
+ if 'type' in parsed:
+ return PropertyDoc(parsed)
+
+ return default(parsed)
diff --git a/doc/_extensions/autojsdoc/parser/parser.py b/doc/_extensions/autojsdoc/parser/parser.py
new file mode 100644
index 00000000000..d91b9f22596
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/parser.py
@@ -0,0 +1,552 @@
+# -*- coding: utf-8 -*-
+import collections
+
+import pyjsdoc
+from attr import attrs, Factory, attr, astuple
+
+from . import jsdoc
+from . import utils
+from .visitor import Visitor, SKIP
+
+DECLARATOR_INIT_TO_REF = ('Literal', 'Identifier', 'MemberExpression')
+
+
+class ModuleMatcher(Visitor):
+ """Looks for structures of the form::
+
+ odoo.define($string, function ($name) {
+
+ These are *Odoo module definitions*, upon encountering one the
+ matcher:
+
+ * creates a module entry, optionally associated with the module comment
+ * spawns off a :class:`ModuleBodyMatcher` on the function's body with the
+ module name and the $name as "require" function
+ """
+ def __init__(self, filename):
+ super(ModuleMatcher, self).__init__()
+ self.filename = filename
+ self.result = []
+ def enter_Program(self, node):
+ pass # allows visiting toplevel
+ def enter_ExpressionStatement(self, node):
+ # we're interested in expression statements (toplevel call)
+ if utils.match(node, {'expression': {
+ 'callee': {
+ 'object': {'name': 'odoo'},
+ 'property': {'name': 'define'},
+ },
+ }}):
+ [module, func] = node['expression']['arguments']
+ mod = jsdoc.parse_comments(node.get('comments'), jsdoc.ModuleDoc)
+ # set module name
+ mod.set_name(module['value'])
+ mod.parsed['sourcefile'] = self.filename
+ self.result.append(mod)
+
+ # get name of require parameter
+ require = None # a module can have no dependencies
+ if func['params']:
+ require = func['params'][0]['name']
+ mod.parsed['dependency'], post = ModuleExtractor(mod, require).visit(func['body'])
+ mod._post_process.extend(post)
+ # don't recurse since we've fired off a sub-visitor for the
+ # bits we're interested in
+ return SKIP
+ def enter_generic(self, node):
+ # skip all other toplevel statements
+ return SKIP
+
+ref = collections.namedtuple('Ref', 'object property')
+def _name(r):
+ bits = []
+ while isinstance(r, ref):
+ bits.append(r.property)
+ r = r.object
+ return '.'.join(reversed(bits))
+def deref(item, prop=None):
+ assert isinstance(item, ref)
+
+ while isinstance(item, ref):
+ obj = item.object
+ if isinstance(obj, ref):
+ obj = deref(obj)
+
+ if isinstance(obj, (jsdoc.NSDoc, jsdoc.Unknown)):
+ item = obj.get_property(item.property)
+ elif isinstance(obj, dict):
+ item = obj[item.property]
+ elif isinstance(obj, jsdoc.PropertyDoc):
+ # f'n dynamic crap
+ item = jsdoc.Unknown(obj.to_dict()).get_property(item.property)
+ else:
+ raise ValueError("%r (%s) should be a dict or namespace" % (obj, type(obj)))
+ return item
+
+def m2r(me, scope):
+ # create a ref to the scope in case it's a hoisted function declaration,
+ # this may false-positive on other hoisting but w/e
+ if me['type'] == 'Literal':
+ return jsdoc.LiteralDoc({'value': me['value']})
+ if me['type'] == 'Identifier':
+ return ref(scope, me['name'])
+ if me['type'] != 'MemberExpression':
+ raise ValueError(me)
+ return ref(
+ m2r(me['object'], scope),
+ utils._value(me['property'], strict=True)
+ )
+
+@attrs(slots=True)
+class Declaration(object):
+ id = attr(default=None)
+ comments = attr(default=Factory(list))
+@attrs(slots=True)
+class ModuleContent(object):
+ dependencies = attr(default=Factory(set))
+ post = attr(default=Factory(list))
+ def __iter__(self):
+ yield self.dependencies
+ yield self.post
+
+@attrs # needs dict as it's replacing a ModuleProxy
+class Nothing(object):
+ name = attr()
+ def __bool__(self):
+ return False
+ __nonzero__ = __bool__
+
+class RefProxy(object):
+ def __init__(self, r):
+ self._ref = r
+ def become(self, modules):
+ s = self
+ other = deref(self._ref) or Nothing(_name(self._ref))
+ # ??? shouldn't all previous refs have been resolved?
+ if isinstance(other, (RefProxy, ModuleProxy)):
+ other = other.become(modules)
+ self.__class__ = other.__class__
+ self.__dict__ = other.__dict__
+ return self
+ def set_name(self, name):
+ pass # ???
+class ModuleProxy(object):
+ def __init__(self, name):
+ self._name = name
+
+ # replace the ModuleProxy by the module's exports
+ def become(self, modules):
+ s = self
+ m = modules[self._name].get_property('<exports>') or Nothing(self._name)
+ self.__class__ = m.__class__
+ self.__dict__ = m.__dict__
+ return self
+
+ def set_name(self, name):
+ pass # FIXME: ???
+
+jq = jsdoc.UnknownNS({
+ 'name': u'jQuery',
+ 'doc': u'<jQuery>',
+})
+window = jsdoc.UnknownNS({
+ 'doc': '<window>',
+ 'name': 'window',
+})
+BASE_SCOPE = {
+ '_': jsdoc.UnknownNS({'doc': u'<underscore.js>', 'name': u'_'}),
+ '$': jq, 'jQuery': jq,
+ 'nv': jsdoc.UnknownNS({
+ 'doc': u'<nvd3>',
+ 'name': u'nv',
+ }),
+ 'window': window,
+ 'document': window.get_property('document'),
+ 'Date': jsdoc.ClassDoc({
+ 'name': u'Date',
+ 'doc': u'',
+ }),
+ 'Backbone': jsdoc.UnknownNS({
+ '_members': [
+ ('Model', jsdoc.ClassDoc({
+ 'name': u'Model',
+ 'doc': u'',
+ })),
+ ('Collection', jsdoc.ClassDoc({
+ 'name': u'Collection',
+ 'doc': u'',
+ })),
+ ]
+ }),
+ 'odoo': jsdoc.UnknownNS({
+ 'name': u'odoo',
+ 'doc': u"Odoo",
+ '_members': [
+ ('name', jsdoc.PropertyDoc({'name': u'csrf_token', 'type': u'{String}'})),
+ ]
+ }),
+ 'undefined': jsdoc.LiteralDoc({'name': u'undefined', 'value': None})
+}
+
+class Scope(object):
+ """
+ Add hoc scope versioning/SSA such that rebinding a symbol in a module
+ scope does not screw everything up e.g. "Foo = Foo.extend({})" should not
+ have the final Foo extending itself...
+ """
+ def __init__(self, mapping):
+ self._namemap = {}
+ self._targets = []
+ for k, v in mapping.items():
+ self[k] = v
+
+ def __setitem__(self, k, v):
+ self._namemap[k] = len(self._targets)
+ self._targets.append(v)
+
+ def freeze(self):
+ return {
+ k: self._targets[v]
+ for k, v in self._namemap.items()
+ }
+
+class ModuleExtractor(Visitor):
+ def __init__(self, module, requirefunc):
+ super(ModuleExtractor, self).__init__()
+ self.module = module
+ self.requirefunc = requirefunc
+ self.result = ModuleContent()
+ self.scope = Scope(BASE_SCOPE)
+ self.declaration = None
+
+ def enter_BlockStatement(self, node):
+ Hoistifier(self).visit(node)
+ def exit_BlockStatement(self, node):
+ for k, v in self.scope._namemap.items():
+ if k not in BASE_SCOPE:
+ self.module.add_member(k, self.scope._targets[v])
+ for t in TypedefMatcher(self).visit(node):
+ self.module.add_member(t.name, t)
+
+ def enter_VariableDeclaration(self, node):
+ self.declaration = Declaration(comments=node.get('comments'))
+ def enter_VariableDeclarator(self, node):
+ # otherwise we've already hoisted the declaration so the variable
+ # already exist initialised to undefined, and ValueExtractor does not
+ # handle a None input node so it returns None which makes no sense
+ if node['init']:
+ self.declaration.id = node['id']['name']
+ self.scope[self.declaration.id] = ValueExtractor(
+ self, self.declaration
+ ).visit(node['init'] or [])
+ self.declaration.id = None
+ return SKIP
+ def exit_VariableDeclaration(self, node):
+ self.declaration = None
+
+ # as the name denotes, AssignmentExpression is an *expression*, which
+ # means at the module toplevel it is wrapped into an ExpressionStatement
+ # which is where the comments get attached
+ def enter_ExpressionStatement(self, node):
+ self.declaration = Declaration(comments=node.get('comments'))
+ def exit_ExpressionStatement(self, node):
+ self.declaration = None
+ def enter_AssignmentExpression(self, node):
+ target = node['left']
+ if target['type'] == 'Identifier':
+ self.declaration.id = target['name']
+ self.scope[self.declaration.id] = ValueExtractor(
+ self, self.declaration
+ ).visit(node['right'])
+ self.declaration.id = None
+ return SKIP
+
+ if target['type'] != 'MemberExpression':
+ raise ValueError("Unhandled assign to %s" % target['type'])
+
+ # only assign to straight a.b.c patterns (OK and trivial literals)
+ if target['computed'] and target['property']['type'] != 'Literal':
+ return SKIP
+
+ name = utils._value(target['property'], strict=True)
+ if isinstance(name, type(u'')) and name.endswith('.extend'):
+ return SKIP # ignore overwrite of .extend (WTF)
+ self.declaration.id = name
+ it = ValueExtractor(
+ self, self.declaration
+ ).visit(node['right'])
+ self.declaration.id = None
+ assert it, "assigned a non-value from %s to %s" % (node['right'], name)
+
+ @self.result.post.append
+ def _augment_module(modules):
+ try:
+ t = deref(m2r(target['object'], self.scope.freeze()))
+ except ValueError:
+ return # f'n extension of global libraries garbage
+ if not isinstance(t, jsdoc.NSDoc):
+ # function Foo(){}; Foo.prototype = bar
+ # fuck that yo
+ return
+ # TODO: note which module added this
+ m = it
+ if isinstance(m, jsdoc.LiteralDoc):
+ m = jsdoc.PropertyDoc(m.to_dict())
+ t.add_member(name, m)
+ return SKIP
+
+ def enter_FunctionDeclaration(self, node):
+ """ Already processed by hoistitifier
+ """
+ return SKIP
+
+ def enter_ReturnStatement(self, node):
+ self.declaration = Declaration(comments=node.get('comments'))
+ if node['argument']:
+ export = ValueExtractor(self, self.declaration).visit(node['argument'])
+ if isinstance(export, RefProxy):
+ self.module.parsed['exports'] = _name(export._ref)
+ self.scope['<exports>'] = export
+ self.declaration = None
+ return SKIP
+
+ def enter_CallExpression(self, node):
+ if utils.match(node, {
+ 'callee': {
+ 'type': 'MemberExpression',
+ 'object': lambda n: (
+ n['type'] in ('Identifier', 'MemberExpression')
+ # _.str.include
+ and not utils._name(n).startswith('_')
+ ),
+ 'property': {'name': 'include'},
+ }
+ }):
+ target = RefProxy(m2r(node['callee']['object'], self.scope.freeze()))
+ target_name = utils._name(node['callee']['object'])
+ items = ClassProcessor(self).visit(node['arguments'])
+ @self.result.post.append
+ def resolve_extension(modules):
+ t = target.become(modules)
+ if not isinstance(t, jsdoc.ClassDoc):
+ raise ValueError("include() subjects should be classes, %s is %s" % (target_name, type(t)))
+ # TODO: note which module added these
+ for it in items:
+ if isinstance(it, dict):
+ for n, member in it.items():
+ t.add_member(n, member)
+ else:
+ t.parsed.setdefault('mixes', []).append(it.become(modules))
+
+ return SKIP
+
+ def refify(self, node, also=None):
+ it = m2r(node, self.scope.freeze())
+ assert isinstance(it, ref), "Expected ref, got {}".format(it)
+ px = RefProxy(it)
+ @self.result.post.append
+ def resolve(modules):
+ p = px.become(modules)
+ if also: also(p)
+ return px
+
+class ValueExtractor(Visitor):
+ def __init__(self, parent, declaration=None):
+ super(ValueExtractor, self).__init__()
+ self.parent = parent
+ self.declaration = declaration or Declaration()
+
+ def enter_generic(self, node):
+ self.result = jsdoc.parse_comments(
+ self.declaration.comments,
+ jsdoc.Unknown.from_(node['type'])
+ )
+ self._update_result_meta()
+ return SKIP
+
+ def _update_result_meta(self, name=None):
+ self.result.parsed['sourcemodule'] = self.parent.module
+ n = name or self.declaration.id
+ if n:
+ self.result.set_name(n)
+
+ def enter_Literal(self, node):
+ self.result = jsdoc.parse_comments(
+ self.declaration.comments, jsdoc.LiteralDoc)
+ self._update_result_meta()
+ self.result.parsed['value'] = node['value']
+ return SKIP
+ def enter_Identifier(self, node):
+ self.result = self.parent.refify(node)
+ return SKIP
+
+ def enter_MemberExpression(self, node):
+ self.result = RefProxy(ref(
+ ValueExtractor(self.parent).visit(node['object']),
+ utils._value(node['property'], strict=True)
+ ))
+ self.parent.result.post.append(self.result.become)
+ return SKIP
+
+ def enter_FunctionExpression(self, node):
+ name, comments = astuple(self.declaration)
+ self.result = jsdoc.parse_comments(comments, jsdoc.FunctionDoc)
+ self.result.parsed['name'] = node['id'] and node['id']['name']
+ self._update_result_meta()
+ self.result.parsed['guessed_params'] = [p['name'] for p in node['params']]
+ return SKIP
+
+ def enter_NewExpression(self, node):
+ comments = self.declaration.comments if self.declaration else node.get('comments')
+ self.result = ns = jsdoc.parse_comments(comments, jsdoc.InstanceDoc)
+ self._update_result_meta()
+
+ def _update_contents(cls):
+ if not isinstance(cls, jsdoc.ClassDoc):
+ return
+ ns.parsed['cls'] = cls
+ self.parent.refify(node['callee'], also=_update_contents)
+ return SKIP
+
+ def enter_ObjectExpression(self, node):
+ self.result = obj = jsdoc.parse_comments(self.declaration.comments)
+ self._update_result_meta()
+ for n, p in MemberExtractor(parent=self.parent).visit(node['properties']).items():
+ obj.add_member(n, p)
+ return SKIP
+
+ def enter_CallExpression(self, node):
+ # require(a_module_name)
+ if utils.match(node, {'callee': {'type': 'Identifier', 'name': self.parent.requirefunc}}):
+ depname = node['arguments'][0]['value']
+ # TODO: clean this up
+ self.parent.result.dependencies.add(depname)
+ self.result = ModuleProxy(name=depname)
+ self.parent.result.post.append(self.result.become)
+
+ # Class.extend(..mixins, {})
+ elif utils.match(node, {
+ 'callee': {
+ 'type': 'MemberExpression',
+ 'object': lambda n: (
+ n['type'] in ('Identifier', 'MemberExpression')
+ # $.extend, $.fn.extend, _.extend
+ and not utils._name(n).startswith(('_', '$'))
+ ),
+ 'property': {'name': 'extend'},
+ },
+ }): # creates a new class, but may not actually return it
+ obj = node['callee']['object']
+ _, comments = astuple(self.declaration)
+ self.result = cls = jsdoc.parse_comments(comments, jsdoc.ClassDoc)
+ cls.parsed['extends'] = self.parent.refify(obj)
+ self._update_result_meta()
+ items = ClassProcessor(self.parent).visit(node['arguments'])
+
+ @self.parent.result.post.append
+ def add_to_class(modules):
+ for item in items:
+ if isinstance(item, dict):
+ # methods/attributes
+ for n, method in item.items():
+ cls.add_member(n, method)
+ else:
+ cls.parsed.setdefault('mixes', []).append(item)
+
+ # other function calls
+ else:
+ self.result = jsdoc.parse_comments(self.declaration.comments, jsdoc.guess)
+ self._update_result_meta()
+
+ return SKIP
+
+class ClassProcessor(Visitor):
+ def __init__(self, parent):
+ super(ClassProcessor, self).__init__()
+ self.result = []
+ self.parent = parent
+
+ def enter_generic(self, node):
+ self.result.append(self.parent.refify(node))
+ return SKIP
+ def enter_ObjectExpression(self, node):
+ self.result.append(MemberExtractor(parent=self.parent, for_class=True).visit(node['properties']))
+ return SKIP
+
+String = type(u'')
+class MemberExtractor(Visitor):
+ def __init__(self, parent, for_class=False):
+ super(MemberExtractor, self).__init__()
+ self.result = collections.OrderedDict()
+ self.parent = parent
+ self.for_class = for_class
+
+ def enter_Property(self, node):
+ name = utils._value(node['key'])
+ prop = ValueExtractor(
+ self.parent,
+ Declaration(id=name, comments=node.get('comments'))
+ ).visit(node['value'])
+
+ if isinstance(prop, jsdoc.LiteralDoc):
+ prop = jsdoc.PropertyDoc(prop.to_dict())
+
+ # ValueExtractor can return a Ref, maybe this should be sent as part
+ # of the decl/comments?
+ if name.startswith('_') and hasattr(prop, 'parsed'):
+ prop.parsed['private'] = True
+ self.result[name] = prop
+
+ return SKIP
+
+class Hoistifier(Visitor):
+ """
+ Processor for variable and function declarations properly hoisting them
+ to the "top" of a module such that they are available with the relevant
+ value afterwards.
+ """
+ def __init__(self, parent):
+ super(Hoistifier, self).__init__()
+ self.parent = parent
+
+ def enter_generic(self, node):
+ return SKIP
+
+ # nodes to straight recurse into, others are just skipped
+ enter_BlockStatement = enter_VariableDeclaration = lambda self, node: None
+
+ def enter_VariableDeclarator(self, node):
+ self.parent.scope[node['id']['name']] = BASE_SCOPE['undefined']
+
+ def enter_FunctionDeclaration(self, node):
+ funcname = node['id']['name']
+ self.parent.scope[funcname] = fn = jsdoc.parse_comments(
+ node.get('comments'),
+ jsdoc.FunctionDoc,
+ )
+ fn.parsed['sourcemodule'] = self.parent.module
+ fn.parsed['name'] = funcname
+ fn.parsed['guessed_params'] = [p['name'] for p in node['params']]
+ return SKIP
+
+class TypedefMatcher(Visitor):
+ def __init__(self, parent):
+ super(TypedefMatcher, self).__init__()
+ self.parent = parent
+ self.result = []
+
+ enter_BlockStatement = lambda self, node: None
+ def enter_generic(self, node):
+ # just traverse all top-level statements, check their comments, and
+ # bail
+ for comment in node.get('comments') or []:
+ if '@typedef' in comment['value']:
+ extract = '\n' + jsdoc.strip_stars('/*' + comment['value'] + '\n*/')
+ parsed = pyjsdoc.parse_comment(extract, u'')
+ p = jsdoc.ParamDoc(parsed['typedef'])
+ parsed['name'] = p.name
+ parsed['sourcemodule'] = self.parent.module
+ # TODO: add p.type as superclass somehow? Builtin types not in scope :(
+ self.result.append(jsdoc.ClassDoc(parsed))
+
+ return SKIP
diff --git a/doc/_extensions/autojsdoc/parser/tests/README.rst b/doc/_extensions/autojsdoc/parser/tests/README.rst
new file mode 100644
index 00000000000..dd862beae66
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/README.rst
@@ -0,0 +1,6 @@
+:orphan:
+
+These files should be run via pytest_, simply install pytest and from the top
+of the Odoo project run ``pytest doc/_extensions``.
+
+.. _pytest: https://pytest.org/
diff --git a/doc/_extensions/autojsdoc/parser/tests/support.py b/doc/_extensions/autojsdoc/parser/tests/support.py
new file mode 100644
index 00000000000..f533f98d90c
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/support.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+import operator
+
+import pyjsparser
+
+from autojsdoc.parser import jsdoc, parser
+
+params = operator.attrgetter('name', 'type', 'doc')
+
+
+def parse(s, source=None):
+ tree = pyjsparser.parse(s)
+ mods = parser.ModuleMatcher(source).visit(tree)
+ post(mods)
+ return mods
+
+def post(mods):
+ modules = dict(BASE_MODULES)
+ modules.update((m.name, m) for m in mods)
+
+ for mod in mods:
+ mod.post_process(modules)
+
+BASE_MODULES = {
+ 'other': jsdoc.ModuleDoc({
+ 'module': 'other',
+ '_members': [
+ ('<exports>', jsdoc.LiteralDoc({'name': 'value', 'value': "ok"})),
+ ],
+ }),
+ 'dep2': jsdoc.ModuleDoc({
+ 'module': 'dep2',
+ '_members': [
+ ('<exports>', jsdoc.LiteralDoc({'value': 42.})),
+ ],
+ }),
+ 'dep3': jsdoc.ModuleDoc({
+ 'module': 'dep3',
+ '_members': [
+ ('<exports>', jsdoc.LiteralDoc({'value': 56.})),
+ ],
+ }),
+ 'Class': jsdoc.ModuleDoc({
+ 'module': 'Class',
+ '_members': [
+ ('<exports>', jsdoc.ClassDoc({
+ 'name': 'Class',
+ 'doc': "Base Class"
+ })),
+ ],
+ }),
+ 'mixins': jsdoc.ModuleDoc({
+ 'module': 'mixins',
+ '_members': [
+ ('<exports>', jsdoc.NSDoc({
+ 'name': 'mixins',
+ '_members': [
+ ('Bob', jsdoc.ClassDoc({'class': "Bob"})),
+ ]
+ })),
+ ],
+ }),
+ 'Mixin': jsdoc.ModuleDoc({
+ 'module': 'Mixin',
+ '_members': [
+ ('<exports>', jsdoc.MixinDoc({
+ '_members': [
+ ('a', jsdoc.FunctionDoc({'function': 'a'})),
+ ]
+ })),
+ ],
+ })
+}
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_class.py b/doc/_extensions/autojsdoc/parser/tests/test_class.py
new file mode 100644
index 00000000000..403ae9b48b7
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_class.py
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+import pytest
+
+from autojsdoc.parser import jsdoc
+from support import params, parse
+
+def test_classvar():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ /**
+ * This is my class-kai
+ */
+ var A = Class.extend({});
+ return A;
+ });
+ """)
+ cls = mod.exports
+ assert type(cls) == jsdoc.ClassDoc
+
+ assert type(cls.superclass) == jsdoc.ClassDoc
+ assert cls.superclass.name == 'Class'
+
+ assert cls.name == 'A'
+ assert cls.constructor is None
+ assert cls.properties == []
+ assert cls['doc'] == 'This is my class-kai'
+
+def test_classret():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ /**
+ * This is my class-kai
+ */
+ return Class.extend({});
+ });
+ """)
+ cls = mod.exports
+ assert type(cls) == jsdoc.ClassDoc
+
+ assert cls.name == ''
+ assert cls.constructor is None
+ assert cls.properties == []
+ assert cls['doc'] == 'This is my class-kai'
+
+def test_methods():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ return Class.extend({
+ /**
+ * @param {Widget} parent
+ */
+ init: function (parent) {},
+ /**
+ * @returns {Widget}
+ */
+ itself: function () { return this; },
+ /**
+ * @param {MouseEvent} e
+ */
+ _onValidate: function (e) {},
+ });
+ });
+ """)
+ cls = mod.exports
+ assert len(cls.properties) == 3
+ assert cls.constructor
+ # assume methods are in source order
+ [_, init] = cls.properties[0]
+ assert init == cls.constructor
+ assert init.name == 'init'
+ assert not init.is_private
+ assert init.is_constructor
+ [param] = init.params
+ assert params(param) == ('parent', 'Widget', '')
+
+ [_, itself] = cls.properties[1]
+ assert itself.name == 'itself'
+ assert not itself.is_private
+ assert not itself.is_constructor
+ assert not itself.params
+ assert params(itself.return_val) == ('', 'Widget', '')
+
+ [_, _on] = cls.properties[2]
+ assert _on.name == '_onValidate'
+ assert _on.is_private
+ assert not _on.is_constructor
+ [param] = _on.params
+ assert params(param) == ('e', 'MouseEvent', '')
+
+def test_mixin_explicit():
+ [mod] = parse("""
+ odoo.define('a.A', function (require) {
+ var Class = require('Class');
+ var mixins = require('mixins');
+ /**
+ * This is my class-kai
+ * @mixes mixins.Bob
+ */
+ return Class.extend({});
+ });
+ """)
+ cls = mod.exports
+ # FIXME: ClassDoc may want to m2r(mixin, scope)?
+ assert cls.mixins == ['mixins.Bob']
+
+def test_mixin_implicit():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ var Mixin = require('Mixin');
+ /**
+ * This is my class-kai
+ */
+ return Class.extend(Mixin, { foo: function() {} });
+ });
+ """)
+ cls = mod.exports
+ [mixin] = cls.mixins
+ assert type(mixin) == jsdoc.MixinDoc
+ assert params(mixin.properties[0][1]) == ('a', 'Function', '')
+ assert params(mixin.get_property('a')) == ('a', 'Function', '')
+
+ assert params(cls.get_property('foo')) == ('foo', 'Function', '')
+
+def test_instanciation():
+ [A, a] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ /**
+ * @class A
+ */
+ return Class.extend({
+ foo: function () {}
+ });
+ });
+ odoo.define('a', function (r) {
+ var A = r('A');
+ var a = new A;
+ return a;
+ });
+ """)
+ assert type(a.exports) == jsdoc.InstanceDoc
+ assert a.exports.cls.name == A.exports.name
+
+def test_non_function_properties():
+ [A] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ return Class.extend({
+ template: 'thing',
+ a_prop: [1, 2, 3],
+ 'other': {a: 7}
+ });
+ });
+ """)
+ t = A.exports.get_property('template')
+ assert type(t) == jsdoc.PropertyDoc
+ assert params(t) == ('template', 'String', '')
+ assert not t.is_private
+
+def test_non_extend_classes():
+ [mod] = parse("""
+ odoo.define('A', function () {
+ /**
+ * @class
+ */
+ var Class = function () {}
+ return Class;
+ });
+ """)
+ assert type(mod.exports) == jsdoc.ClassDoc
+
+def test_extend():
+ [a, _] = parse("""
+ odoo.define('A', function (require) {
+ var Class = require('Class');
+ return Class.extend({});
+ });
+ odoo.define('B', function (require) {
+ var A = require('A');
+ A.include({
+ /** A property */
+ a: 3,
+ /** A method */
+ b: function () {}
+ });
+ });
+ """)
+ cls = a.exports
+ assert type(cls) == jsdoc.ClassDoc
+ a = cls.get_property('a')
+ assert type(a) == jsdoc.PropertyDoc
+ assert params(a) == ('a', 'Number', 'A property')
+ b = cls.get_property('b')
+ assert type(b) == jsdoc.FunctionDoc
+ assert params(b) == ('b', 'Function', 'A method')
+
+# TODO: also support virtual members?
+# TODO: computed properties?
+@pytest.mark.skip(reason="Need to implement member/var-parsing?")
+def test_members():
+ [mod] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ return Class.extend({
+ init: function () {
+ /**
+ * This is bob
+ * @var {Foo}
+ */
+ this.foo = 3;
+ this.bar = 42;
+ /**
+ * @member {Baz}
+ */
+ this.baz = null;
+ }
+ });
+ });
+ """)
+ cls = mod.exports
+ assert params(cls.members[0]) == ('foo', 'Foo', 'This is bob')
+ assert params(cls.members[1]) == ('bar', '', '')
+ assert params(cls.members[2]) == ('baz', 'Baz', '')
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_crap.py b/doc/_extensions/autojsdoc/parser/tests/test_crap.py
new file mode 100644
index 00000000000..70063e6dd47
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_crap.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+"""
+Test various crap patterns found in Odoo code to ensure they don't blow up
+the parser thingie
+"""
+from autojsdoc.parser import jsdoc
+from support import parse
+
+def test_export_external():
+ [mod] = parse("""
+ odoo.define('module', function () {
+ return $.Deferred().reject();
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.CommentDoc)
+ assert mod.exports.doc == ''
+
+def test_extend_jq():
+ parse("""
+ odoo.define('a', function (r) {
+ $.extend($.expr[':'], { a: function () {} });
+ $.fn.extend({ a: function () {} });
+ });
+ """)
+
+def test_extend_dynamic():
+ parse("""
+ odoo.define('a', function () {
+ foo.bar.baz[qux + '_external'] = function () {};
+ });
+ """)
+
+def test_extend_deep():
+ parse("""
+ odoo.define('a', function () {
+ var eventHandler = $.summernote.eventHandler;
+ var dom = $.summernote.core.dom;
+ dom.thing = function () {};
+
+ var fn_editor_currentstyle = eventHandler.modules.editor.currentStyle;
+ eventHandler.modules.editor.currentStyle = function () {}
+ });
+ """)
+
+def test_arbitrary():
+ parse("""
+ odoo.define('bob', function () {
+ var page = window.location.href.replace(/^.*\/\/[^\/]+/, '');
+ var mailWidgets = ['mail_followers', 'mail_thread', 'mail_activity', 'kanban_activity'];
+ var bob;
+ var fldj = foo.getTemplate().baz;
+ });
+ """)
+
+def test_prototype():
+ [A, B] = parse("""
+ odoo.define('mod1', function () {
+ var exports = {};
+ exports.Foo = Backbone.Model.extend({});
+ exports.Bar = Backbone.Model.extend({});
+ var BarCollection = Backbone.Collection.extend({
+ model: exports.Bar,
+ });
+ exports.Baz = Backbone.Model.extend({});
+ return exports;
+ });
+ odoo.define('mod2', function (require) {
+ var models = require('mod1');
+ var _super_orderline = models.Bar.prototype;
+ models.Foo = models.Bar.extend({});
+ var _super_order = models.Baz.prototype;
+ models.Bar = models.Baz.extend({});
+ });
+ """)
+
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_module.py b/doc/_extensions/autojsdoc/parser/tests/test_module.py
new file mode 100644
index 00000000000..2a63cc1754c
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_module.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+from autojsdoc.parser import jsdoc
+from support import params, parse, BASE_MODULES
+
+
+def test_single():
+ [mod] = parse("""
+ /**
+ * This is a super module!
+ */
+ odoo.define('supermodule', function (req) {
+ var other = req('other');
+ });
+ """)
+ assert mod.name == 'supermodule'
+ assert mod.dependencies == {'other'}
+ assert mod.exports is None
+ assert mod.doc == "This is a super module!"
+ m = mod.get_property('other')
+ assert m.name == 'value', "property is exported value not imported module"
+
+def test_multiple():
+ [mod1, mod2, mod3] = parse("""
+ odoo.define('module1', function (req) {
+ return 1;
+ });
+ odoo.define('module2', function (req) {
+ return req('dep2');
+ });
+ odoo.define('module3', function (req) {
+ var r = req('dep3');
+ return r;
+ });
+ """)
+ assert mod1.name == 'module1'
+ assert mod1.dependencies == set()
+ assert isinstance(mod1.exports, jsdoc.LiteralDoc)
+ assert mod1.exports.value == 1.0
+ assert mod1.exports['sourcemodule'] is mod1
+ assert mod1.doc == ""
+
+ assert mod2.name == 'module2'
+ assert mod2.dependencies == {'dep2'}
+ assert isinstance(mod2.exports, jsdoc.LiteralDoc)
+ assert mod2.exports.value == 42.0
+ assert mod2.doc == ""
+
+ assert mod3.name == 'module3'
+ assert mod3.dependencies == {'dep3'}
+ assert isinstance(mod3.exports, jsdoc.LiteralDoc)
+ assert mod3.exports.value == 56.0
+ assert mod3.doc == ''
+
+def test_func():
+ [mod] = parse("""
+ odoo.define('module', function (d) {
+ /**
+ * @param {Foo} bar this is a bar
+ * @param {Baz} qux this is a qux
+ */
+ return function (bar, qux) {
+ return 42;
+ }
+ });
+ """)
+ exports = mod.exports
+ assert type(exports) == jsdoc.FunctionDoc
+ assert exports['sourcemodule'] is mod
+
+ assert exports.name == ''
+ assert exports.is_constructor == False
+ assert exports.is_private == False
+
+ assert params(exports.params[0]) == ('bar', 'Foo', 'this is a bar')
+ assert params(exports.params[1]) == ('qux', 'Baz', 'this is a qux')
+ assert params(exports.return_val) == ('', '', '')
+
+def test_hoist():
+ [mod] = parse("""
+ odoo.define('module', function() {
+ return foo;
+ /**
+ * @param a_thing
+ */
+ function foo(a_thing) {
+ return 42;
+ }
+ });
+ """)
+ actual = mod.exports
+ assert type(actual) == jsdoc.FunctionDoc
+ [param] = actual.params
+ assert params(param) == ('a_thing', '', '')
+
+def test_export_instance():
+ [mod] = parse("""
+ odoo.define('module', function (require) {
+ var Class = require('Class');
+ /**
+ * Provides an instance of Class
+ */
+ return new Class();
+ });
+ """)
+ assert type(mod.exports) == jsdoc.InstanceDoc
+ assert mod.exports.doc == 'Provides an instance of Class'
+ assert mod.exports['sourcemodule'] is mod
+
+def test_bounce():
+ [m2, m1] = parse("""
+ odoo.define('m2', function (require) {
+ var Item = require('m1');
+ return {
+ Item: Item
+ };
+ });
+ odoo.define('m1', function (require) {
+ var Class = require('Class');
+ var Item = Class.extend({});
+ return Item;
+ });
+ """)
+ assert type(m2.exports) == jsdoc.NSDoc
+ it = m2.exports.get_property('Item')
+ assert type(it) == jsdoc.ClassDoc
+ assert it['sourcemodule'] is m1
+ assert sorted([n for n, _ in m1.properties]) == ['<exports>', 'Class', 'Item']
+
+def test_reassign():
+ [m] = parse("""
+ odoo.define('m', function (require) {
+ var Class = require('Class');
+ /** local class */
+ var Class = Class.extend({});
+ return Class
+ });
+ """)
+ assert m.exports.doc == 'local class'
+ # can't use equality or identity so use class comment...
+ assert m.exports.superclass.doc == 'Base Class'
+
+def test_attr():
+ [m1, m2] = parse("""
+ odoo.define('m1', function (require) {
+ var Class = require('Class');
+ var Item = Class.extend({});
+ return {Item: Item};
+ });
+ odoo.define('m2', function (require) {
+ var Item = require('m1').Item;
+ Item.include({});
+ return Item.extend({});
+ });
+ """)
+ assert type(m2.exports) == jsdoc.ClassDoc
+ # that these two are resolved separately may be an issue at one point (?)
+ assert m2.exports.superclass.to_dict() == m1.exports.get_property('Item').to_dict()
+
+def test_nothing_implicit():
+ [m] = parse("""
+ odoo.define('m', function () {
+ });
+ """)
+ assert m.exports is None
+
+def test_nothing_explicit():
+ [m] = parse("""
+ odoo.define('m', function () {
+ return;
+ });
+ """)
+ assert m.exports is None
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_namespace.py b/doc/_extensions/autojsdoc/parser/tests/test_namespace.py
new file mode 100644
index 00000000000..54cbcf8cbb4
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_namespace.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+from autojsdoc.parser import jsdoc
+from support import parse, params
+
+
+def test_empty():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ return {};
+ });
+ """)
+ assert type(mod.exports) == jsdoc.NSDoc
+ assert mod.exports.properties == []
+
+def test_inline():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ return {
+ /**
+ * a thing
+ * @type {Boolean}
+ */
+ a: true
+ }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.NSDoc)
+ [(n, p)] = mod.exports.properties
+ assert n == 'a'
+ assert params(p) == ('a', 'Boolean', 'a thing')
+
+def test_header():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ /**
+ * @property {Boolean} a a thing
+ */
+ return { a: true }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.NSDoc)
+ [(_, p)] = mod.exports.properties
+ assert params(p) == ('a', 'Boolean', 'a thing')
+
+def test_header_conflict():
+ """ should the header or the inline comment take precedence? """
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ /**
+ * @property {Boolean} a a thing
+ */
+ return {
+ /** @type {String} */
+ a: true
+ }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.NSDoc)
+ [(_, p)] = mod.exports.properties
+ assert params(p) == ('a', 'Boolean', 'a thing')
+
+def test_mixin():
+ [mod] = parse("""
+ odoo.define('a.mixin', function (r) {
+ /**
+ * @mixin
+ */
+ return {
+ /**
+ * @returns {Number} a number
+ */
+ do_thing: function other() { return 42; }
+ }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.MixinDoc)
+ [(n, p)] = mod.exports.properties
+ assert n == 'do_thing'
+ assert params(p) == ('other', 'Function', '')
+ assert params(p.return_val) == ('', 'Number', 'a number')
+
+def test_literal():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ /** whop whop */
+ return {
+ 'a': 1,
+ /** wheee */
+ 'b': 2,
+ };
+ });
+ """)
+ assert mod.exports.doc == 'whop whop'
+ [(_1, a), (_2, b)] = mod.exports.properties
+ assert params(a) == ('a', 'Number', '')
+ assert params(b) == ('b', 'Number', 'wheee')
+
+def test_fill_ns():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ var Class = r('Class');
+ var ns = {};
+ /** ok */
+ ns.a = 1;
+ /** @type {String} */
+ ns['b'] = 2;
+ /** Ike */
+ ns.c = Class.extend({});
+ ns.d = function () {}
+ return ns;
+ });
+ """)
+ ns = mod.exports
+ assert type(ns) == jsdoc.NSDoc
+ [(_a, a), (_b, b), (_c, c), (_d, d)] = ns.properties
+ assert params(a) == ('a', 'Number', 'ok')
+ assert params(b) == ('b', 'String', '')
+ assert type(c) == jsdoc.ClassDoc
+ assert type(d) == jsdoc.FunctionDoc
+
+def test_extend_other():
+ [o, b] = parse("""
+ odoo.define('a.ns', function () {
+ /** @name outer */
+ return {
+ /** @name inner */
+ a: {}
+ };
+ });
+ odoo.define('b', function (r) {
+ var o = r('a.ns');
+ var Class = r('Class');
+ /** Class 1 */
+ o.a.b = Class.extend({m_b: 1});
+ /** Class 2 */
+ o.a['c'] = Class.extend({m_c: 1});
+ });
+ """)
+ [(_, m)] = o.exports.properties
+ assert type(m) == jsdoc.NSDoc
+
+ b = m.get_property('b')
+ assert type(b) == jsdoc.ClassDoc
+ assert b.get_property('m_b')
+ assert b.doc == 'Class 1'
+
+ c = m.get_property('c')
+ assert type(c) == jsdoc.ClassDoc
+ assert c.get_property('m_c')
+ assert c.doc == 'Class 2'
+
+def test_ns_variables():
+ [mod] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ var Thing = Class.extend({});
+ return {
+ Thing: Thing
+ };
+ });
+ """)
+ p = mod.exports.get_property('Thing')
+ assert type(p) == jsdoc.ClassDoc
+
+def test_diff():
+ """ Have the NS key and the underlying object differ
+ """
+ [mod] = parse("""
+ odoo.define('mod', function (r) {
+ var Class = r('Class');
+ var Foo = Class.extend({});
+ return { Class: Foo };
+ });
+ """)
+ c = mod.exports.get_property('Class')
+ assert type(c) == jsdoc.ClassDoc
+ assert c.name == 'Foo'
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_params.py b/doc/_extensions/autojsdoc/parser/tests/test_params.py
new file mode 100644
index 00000000000..c51139b8cb3
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_params.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+from autojsdoc.parser.jsdoc import ParamDoc
+
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+
+def test_basic():
+ d = ParamDoc("Lorem ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'Lorem',
+ 'type': '',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("{Lorem} ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'ipsum',
+ 'type': 'Lorem',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'dolor sit amet, consectetur adipiscing elit.',
+ }
+
+def test_optional():
+ d = ParamDoc("[Lorem] ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'Lorem',
+ 'type': '',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("[Lorem=42] ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'Lorem',
+ 'type': '',
+ 'optional': True,
+ 'default': "42",
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("{Lorem} [ipsum] dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'ipsum',
+ 'type': 'Lorem',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("{Lorem} [ipsum=42] dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'ipsum',
+ 'type': 'Lorem',
+ 'optional': True,
+ 'default': '42',
+ 'doc': 'dolor sit amet, consectetur adipiscing elit.',
+ }
+
+def test_returns():
+ d = ParamDoc("{} ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': '',
+ 'type': '',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+ d = ParamDoc("{Lorem} ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': '',
+ 'type': 'Lorem',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+def test_odd():
+ d = ParamDoc("{jQuery} [$target] the node where content will be prepended").to_dict()
+ assert d == {
+ 'name': '$target',
+ 'type': 'jQuery',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'the node where content will be prepended',
+ }
+
+ d = ParamDoc("""{htmlString} [content] DOM element,
+ array of elements, HTML string or jQuery object to prepend to $target""").to_dict()
+ assert d == {
+ 'name': 'content',
+ 'type': 'htmlString',
+ 'optional': True,
+ 'default': None,
+ 'doc': "DOM element,\n array of elements, HTML string or jQuery object to prepend to $target",
+ }
+
+ d = ParamDoc("{Boolean} [options.in_DOM] true if $target is in the DOM").to_dict()
+ assert d == {
+ 'name': 'options.in_DOM',
+ 'type': 'Boolean',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'true if $target is in the DOM',
+ }
+
+ d = ParamDoc("Lorem\n ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d['doc'] == 'ipsum dolor sit amet, consectetur adipiscing elit.'
+
+ d = ParamDoc("Lorem - ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d['doc'] == 'ipsum dolor sit amet, consectetur adipiscing elit.'
+
+ d = ParamDoc("Lorem : ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d['doc'] == 'ipsum dolor sit amet, consectetur adipiscing elit.'
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_typespec.py b/doc/_extensions/autojsdoc/parser/tests/test_typespec.py
new file mode 100644
index 00000000000..d90fe7995dd
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_typespec.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import pytest
+
+from autojsdoc.parser import types
+
+def test_parser():
+ assert types.parse("MouseEvent|TouchEvent") == types.Alt([
+ types.Type('MouseEvent', []),
+ types.Type('TouchEvent', []),
+ ])
+
+ assert types.parse('Deferred<Object>') == types.Alt([
+ types.Type('Deferred', [
+ types.Alt([types.Type('Object', [])])
+ ])
+ ])
+
+ assert types.parse('Object|undefined') == types.Alt([
+ types.Type('Object', []),
+ types.Literal('undefined'),
+ ])
+ assert types.parse('any[]') == types.Alt([
+ types.Type('Array', [types.Type('any', [])])
+ ])
+ assert types.parse("'xml' | 'less'") == types.Alt([
+ types.Literal('xml'),
+ types.Literal('less'),
+ ])
+ assert types.parse('Foo<Bar | Bar[] | null>') == types.Alt([
+ types.Type('Foo', [types.Alt([
+ types.Type('Bar', []),
+ types.Type('Array', [types.Type('Bar', [])]),
+ types.Literal('null'),
+ ])])
+ ])
+
+def test_tokens():
+ toks = list(types.tokenize('A'))
+ assert toks == [(types.NAME, 'A')]
+
+ toks = list(types.tokenize('"foo" | "bar"'))
+ assert toks == [(types.LITERAL, 'foo'), (types.OP, '|'), (types.LITERAL, 'bar')]
+
+ toks = list(types.tokenize('1 or 2'))
+ assert toks == [(types.LITERAL, 1), (types.OP, '|'), (types.LITERAL, 2)]
+
+ toks = list(types.tokenize('a.b.c.d'))
+ assert toks == [
+ (types.NAME, 'a'), (types.OP, '.'),
+ (types.NAME, 'b'), (types.OP, '.'),
+ (types.NAME, 'c'), (types.OP, '.'),
+ (types.NAME, 'd'),
+ ]
+
+ toks = list(types.tokenize('Function<String, Object>'))
+ assert toks == [
+ (types.NAME, 'Function'),
+ (types.OP, '<'),
+ (types.NAME, 'String'),
+ (types.OP, ','),
+ (types.NAME, 'Object'),
+ (types.OP, '>')
+ ]
+
+def test_peekable():
+ p = types.Peekable(range(5))
+
+ assert p.peek() == 0
+ assert next(p) == 0, "consuming should yield previously peeked value"
+ next(p)
+ next(p)
+ assert next(p) == 3
+ assert p.peek() == 4
+ next(p)
+ assert p.peek(None) is None
+ with pytest.raises(StopIteration):
+ p.peek()
+ with pytest.raises(StopIteration):
+ next(p)
diff --git a/doc/_extensions/autojsdoc/parser/types.py b/doc/_extensions/autojsdoc/parser/types.py
new file mode 100644
index 00000000000..ecbccfe8651
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/types.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+import ast
+import collections
+import io
+import token
+from tokenize import generate_tokens
+
+LITERAL_NAME = ('null', 'undefined', 'true', 'false')
+
+__all__ = ['tokenize', 'parse', 'iterate', 'Alt', 'Type', 'Literal']
+
+class _Marker(object):
+ __slots__ = ['name']
+ def __init__(self, name): self.name = name
+ def __repr__(self): return '<%s>' % self.name
+
+OP = _Marker('OP')
+NAME = _Marker('NAME')
+LITERAL = _Marker('LITERAL')
+def tokenize(typespec):
+ typespec = typespec.replace('$', 'jQuery')
+ for toktype, string, _, _, _ in generate_tokens(io.StringIO(typespec).readline):
+ if toktype == token.NAME:
+ if string == 'or': # special case "A or B"
+ # TODO: deprecation warning
+ yield (OP, '|')
+ else:
+ yield (NAME, string)
+ elif toktype == token.OP:
+ if string in '|<>[].,':
+ yield (OP, string)
+ elif string == '*': # maybe?
+ yield (NAME, 'any')
+ elif string in '()':
+ # TODO: deprecation warning
+ # seems useless unless we add support for "..."
+ continue
+ else:
+ raise ValueError("Unknown typespec operator %r" % string)
+ elif toktype in (token.STRING, token.NUMBER): # enum-ish
+ yield (LITERAL, ast.literal_eval(string))
+ elif toktype == token.ENDMARKER:
+ return
+ else:
+ raise ValueError("Unknown typespec token %s" % token.tok_name[toktype])
+
+Alt = collections.namedtuple('Alt', ['params'])
+Literal = collections.namedtuple('Literal', ['value'])
+Type = collections.namedtuple('Type', ['name', 'params'])
+
+def iterate(t, fortype, forliteral):
+ if isinstance(t, Alt):
+ ps = Peekable(t.params)
+ for param in ps:
+ for it in iterate(param, fortype, forliteral):
+ yield it
+ if ps.peek(None):
+ yield forliteral('|')
+ elif isinstance(t, Type):
+ yield fortype(t.name)
+ if not t.params:
+ return
+ yield forliteral('<')
+ ps = Peekable(t.params)
+ for param in ps:
+ for it in iterate(param, fortype, forliteral):
+ yield it
+ if ps.peek(None):
+ yield forliteral(',')
+ yield forliteral('>')
+ elif isinstance(t, Literal):
+ yield forliteral(t.value)
+ else:
+ raise ValueError('Unknown item %s' % t)
+
+def parse(typespec):
+ tokens = Peekable(tokenize(typespec))
+ return parse_alt(tokens)
+
+# alt = param
+# | param '|' alt
+def parse_alt(tokens):
+ alt = Alt([])
+ while True:
+ alt.params.append(parse_param(tokens))
+ if not next_is(tokens, (OP, '|')):
+ break
+ return alt
+
+# param = 'null' | 'undefined' | 'true' | 'false'
+# | number
+# | string
+# | typename '[]'
+# X '[' alt ']'
+# | typename '<' params '>'
+# params = alt | alt ',' params
+def parse_param(tokens):
+ t, v = tokens.peek()
+ if t == LITERAL or (t == NAME and v in LITERAL_NAME):
+ next(tokens)
+ return Literal(str(v))
+
+ # # [typespec] # should this be Array<T> or an n-uple allowing multiple items?
+ # if tok == (OP, '['):
+ # next(tokens)
+ # t = Type('Array', [parse_alt(tokens)])
+ # rest = next(tokens)
+ # expect(rest, (OP, ']'))
+ # return t
+
+ t = Type(parse_typename(tokens), [])
+ # type[]
+ peek = tokens.peek(None)
+ if peek == (OP, '['):
+ next(tokens) # skip peeked
+ expect(next(tokens), (OP, ']'))
+ return Type('Array', [t])
+ # type<typespec, ...>
+ if peek == (OP, '<'):
+ next(tokens) # skip peeked
+ while True:
+ t.params.append(parse_alt(tokens))
+ n = next(tokens)
+ if n == (OP, ','):
+ continue
+ if n == (OP, '>'):
+ break
+ raise ValueError("Expected OP ',' or OP ',', got %s '%s'" % n)
+ return t
+
+# typename = name | name '.' typename
+def parse_typename(tokens):
+ typename = []
+ while True:
+ (t, n) = next(tokens)
+ if t != NAME:
+ raise ValueError("Expected a name token, got %s '%s'" % (t, n))
+ if n in LITERAL_NAME:
+ raise ValueError("Expected a type name, got literal %s" % n)
+ typename.append(n)
+ if not next_is(tokens, (OP, '.')):
+ break
+ return '.'.join(typename)
+
+def next_is(tokens, expected):
+ """
+ Consumes the next token if it's `expected` otherwise does not touch the
+ tokens.
+
+ Returns whether it consumed a token
+ """
+ if tokens.peek(None) == expected:
+ next(tokens)
+ return True
+ return False
+
+def expect(actual, expected):
+ """ Raises ValueError if `actual` and `expected` are different
+
+ :type actual: (object, str)
+ :type expected: (object, str)
+ """
+ if actual != expected:
+ raise ValueError("Expected %s '%s', got %s '%s'" % (expected + actual))
+
+
+NONE = object()
+class Peekable(object):
+ __slots__ = ['_head', '_it']
+ def __init__(self, iterable):
+ self._head = NONE
+ self._it = iter(iterable)
+
+ def __iter__(self):
+ return self
+ def __next__(self):
+ if self._head is not NONE:
+ r, self._head = self._head, NONE
+ return r
+ return next(self._it)
+ next = __next__
+
+ def peek(self, default=NONE):
+ if self._head is NONE:
+ try:
+ self._head = next(self._it)
+ except StopIteration:
+ if default is not NONE:
+ return default
+ raise
+ return self._head
diff --git a/doc/_extensions/autojsdoc/parser/utils.py b/doc/_extensions/autojsdoc/parser/utils.py
new file mode 100644
index 00000000000..b8b1a97e7aa
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/utils.py
@@ -0,0 +1,295 @@
+# -*- coding: utf-8 -*-
+def _name(node):
+ if node['type'] == 'Identifier':
+ return node['name']
+ if node['type'] == 'MemberExpression':
+ return "%s.%s" % (_name(node['object']), _name(node['property']))
+ raise ValueError("Unnamable node type %s" % node['type'])
+
+def _value(node, strict=False):
+ t = node['type']
+ if t == 'Identifier':
+ return node['name']
+ elif t == 'Literal':
+ return node['value']
+ msg = '<%s has no value>' % _identify(node, {})
+ if strict:
+ raise ValueError(msg)
+ return msg
+
+def _identify(valnode, ns):
+ if valnode is None:
+ return "None"
+ # already identified and re-set in the ns?
+ if isinstance(valnode, Printable):
+ return valnode
+
+ # check other non-empty returns
+ t = valnode['type']
+ if t == "Literal":
+ if valnode.get('regex'):
+ return valnode['regex']['pattern']
+ return valnode['value']
+ elif t == "Identifier":
+ n = valnode['name']
+ return ns.get(n, Global(n))
+ elif t == "NewExpression":
+ return Instance(_identify(valnode['callee'], ns))
+ elif t == "ObjectExpression":
+ return Namespace({
+ _value(prop['key']): _identify(prop['value'], ns)
+ for prop in valnode['properties']
+ })
+ elif t == "MemberExpression":
+ return Deref(
+ _identify(valnode['object'], ns),
+ _identify(valnode['property'], ns) if valnode['computed'] else valnode['property']['name'],
+ )
+ elif t == "CallExpression":
+ return Call(
+ _identify(valnode['callee'], ns),
+ [_identify(arg, ns) for arg in valnode['arguments']],
+ )
+ elif t == 'BinaryExpression':
+ return "%s %s %s" % (
+ _identify(valnode['left'], ns),
+ valnode['operator'],
+ _identify(valnode['right'], ns),
+ )
+ else:
+ return t
+
+class Counter(object):
+ __slots__ = ['_number']
+ def __init__(self):
+ self._number = 0
+
+ def __bool__(self):
+ return bool(self._number)
+ __nonzero__ = __bool__
+ def __int__(self):
+ return self._number
+
+ def get(self):
+ return self._number
+ def increment(self):
+ self._number += 1
+ return self._number
+ def decrement(self):
+ self._number -= 1
+ return self._number
+
+ def __enter__(self):
+ self._number += 1
+ return self._number
+ def __exit__(self, *args):
+ self._number -= 1
+
+class Printable(object):
+ __slots__ = []
+ depth = Counter()
+ def __repr__(self):
+ with self.depth as i:
+ if i > 2:
+ return "%s(...)" % type(self).__name__
+ return "%s(%s)" % (type(self).__name__, ', '.join(
+ "%s=%r" % (k, getattr(self, k))
+ for k in self.__slots__
+ if not k.startswith('_')
+ if getattr(self, k)
+ ))
+
+
+def resolve(obj, store):
+ if obj is None:
+ return None
+
+ if isinstance(obj, (unicode, bool, int, float)):
+ return obj
+
+ if isinstance(obj, ModuleProxy):
+ return resolve(obj.get(), store)
+
+ try:
+ if getattr(obj, 'resolved', False):
+ return obj
+ return obj.resolve(store)
+ except AttributeError:
+ raise TypeError("Unresolvable {!r}".format(obj))
+
+class Resolvable(Printable):
+ """
+ For types resolved in place
+ """
+ __slots__ = ['resolved']
+
+ def __init__(self):
+ super(Resolvable, self).__init__()
+ self.resolved = False
+ def resolve(self, store):
+ self.resolved = True
+ return self
+
+class Namespace(Resolvable):
+ __slots__ = ['attrs']
+ def __init__(self, attrs):
+ super(Namespace, self).__init__()
+ self.attrs = attrs
+ def resolve(self, store):
+ r = super(Namespace, self).resolve(store)
+ self.attrs = {
+ k: resolve(v, store)
+ for k, v in self.attrs.iteritems()
+ }
+ return r
+ def __getitem__(self, key):
+ assert isinstance(key, unicode), "%r is not a namespace key" % key
+ try:
+ return self.attrs[key]
+ except KeyError:
+ return Global(self)[key]
+
+class Module(Resolvable):
+ __slots__ = ('name', 'comments', 'exports', 'augments', 'dependencies', '_store')
+ def __init__(self, name, store, comments=()):
+ super(Module, self).__init__()
+ self.name = name
+ self._store = store
+ self.comments = tuple(comments)
+ self.exports = None
+ self.augments = []
+ self.dependencies = set()
+ store[name] = self
+ def add_dependency(self, depname):
+ dep = ModuleProxy(depname, self._store)
+ self.dependencies.add(dep)
+ return dep
+ def get(self):
+ return self
+ def resolve(self, store=None):
+ r = super(Module, self).resolve(store)
+ self.exports = resolve(self.exports, self._store)
+ self.augments = [resolve(a, self._store) for a in self.augments]
+ self.dependencies = {
+ resolve(d, self._store)
+ for d in self.dependencies
+ }
+ return r
+ def __getitem__(self, k):
+ try:
+ return self.exports[k]
+ except KeyError:
+ return Global(self)[k]
+
+class ModuleProxy(object):
+ def __init__(self, name, store):
+ self.name = name
+ self.store = store
+ def get(self):
+ # web.web_client is not an actual module
+ return self.store.get(self.name, '<unstored %s>' % self.name)
+ def __repr__(self):
+ return repr(self.get())
+ def __hash__(self):
+ return hash(self.name)
+ def __eq__(self, other):
+ return self.name == other.name
+
+class Class(Resolvable):
+ __slots__ = ['name', 'members', 'extends', 'mixins', 'comments']
+ def __init__(self, name, extends):
+ super(Class, self).__init__()
+ self.name = name
+ self.extends = extends
+ self.members = {}
+ self.mixins = []
+ self.comments = []
+
+ def resolve(self, store):
+ r = super(Class, self).resolve(store)
+ self.extends = resolve(self.extends, store)
+ self.mixins = [resolve(m, store) for m in self.mixins]
+ return r
+
+class Instance(Resolvable):
+ __slots__ = ['type']
+ def __init__(self, type):
+ super(Instance, self).__init__()
+ self.type = type
+
+ def resolve(self, store):
+ r = super(Instance, self).resolve(store)
+ self.type = resolve(self.type, store)
+ if isinstance(self.type, Module):
+ self.type = self.type.exports
+ return r
+
+ def __getitem__(self, key):
+ return Global(self)['key']
+
+class Function(Resolvable):
+ __slots__ = ['comments']
+ def __init__(self, comments):
+ super(Function, self).__init__()
+ self.comments = comments
+
+class Deref(Printable):
+ __slots__ = ['object', 'property']
+ def __init__(self, object, property):
+ self.object = object
+ self.property = property
+ def resolve(self, store):
+ return resolve(self.object, store)[self.property]
+
+class Call(Resolvable):
+ __slots__ = ['callable']
+ def __init__(self, callable, arguments):
+ super(Call, self).__init__()
+ self.callable = callable
+ def resolve(self, store):
+ r = super(Call, self).resolve(store)
+ self.callable = resolve(self.callable, store)
+ return r
+ def __getitem__(self, item):
+ return Global(self)[item]
+
+def get(node, it, *path):
+ if not path:
+ return node[it]
+ return get(node[it], *path)
+
+
+def match(node, pattern):
+ """Checks that ``pattern`` is a subset of ``node``.
+
+ If a sub-pattern is a callable it will be called with the current
+ sub-node and its result is the match's. Other sub-patterns are
+ checked by equality against the correspoding sub-node.
+
+ Can be used to quickly check the descendants of a node of suitable
+ type.
+
+ TODO: support checking list content? Would be convenient to
+ constraint e.g. CallExpression based on their parameters
+
+ """
+ if node is None and pattern is not None:
+ return
+ if callable(pattern):
+ return pattern(node)
+ if isinstance(node, list):
+ return all(len(node) > i and match(node[i], v) for i, v in pattern.items())
+ if isinstance(pattern, dict):
+ return all(match(node.get(k), v) for k, v in pattern.items())
+ return node == pattern
+
+
+class Global(object):
+ def __init__(self, name):
+ self.name = name
+ def __repr__(self):
+ return '<external %s>' % self.name
+ def __getitem__(self, name):
+ return Global('%s.%s' % (self.name, name))
+ def resolve(self, store):
+ return self
diff --git a/doc/_extensions/autojsdoc/parser/visitor.py b/doc/_extensions/autojsdoc/parser/visitor.py
new file mode 100644
index 00000000000..e5529d511dc
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/visitor.py
@@ -0,0 +1,126 @@
+from pyjsparser.pyjsparserdata import Syntax
+
+_binary = lambda n: [n['left'], n['right']]
+_function = lambda n: n['params'] + n['defaults'] + [n['body']]
+# yields children in visitation order
+_children = {
+ Syntax.ArrayExpression: lambda n: n['elements'],
+ Syntax.ArrayPattern: lambda n: n['elements'],
+ Syntax.ArrowFunctionExpression: _function,
+ Syntax.AssignmentExpression: _binary,
+ Syntax.AssignmentPattern: _binary,
+ Syntax.BinaryExpression: _binary,
+ Syntax.BlockStatement: lambda n: n['body'],
+ Syntax.BreakStatement: lambda n: [],
+ Syntax.CallExpression: lambda n: [n['callee']] + n['arguments'],
+ Syntax.CatchClause: lambda n: [n['param'], n['body']],
+ Syntax.ClassBody: lambda n: [n['body']],
+ Syntax.ClassDeclaration: lambda n: [n['superClass'], n['body']],
+ Syntax.ClassExpression: lambda n: [n['superClass'], n['body']],
+ Syntax.ConditionalExpression: lambda n: [n['test'], n['consequent'], n['alternate']],
+ Syntax.ContinueStatement: lambda n: [],
+ Syntax.DebuggerStatement: lambda n: [],
+ Syntax.DoWhileStatement: lambda n: [n['body'], n['test']],
+ Syntax.EmptyStatement: lambda n: [],
+ Syntax.ExportAllDeclaration: lambda n: [n['source']],
+ Syntax.ExportDefaultDeclaration: lambda n: [n['declaration']],
+ Syntax.ExportNamedDeclaration: lambda n: ([n['declaration']] if n['declaration'] else n['specifiers']) + [n['source']],
+ Syntax.ExportSpecifier: lambda n: [n['local'], n['exported']],
+ Syntax.ExpressionStatement: lambda n: [n['expression']],
+ Syntax.ForStatement: lambda n: [n['init'], n['test'], n['update'], n['body']],
+ Syntax.ForInStatement: lambda n: [n['left'], n['right'], n['body']],
+ Syntax.FunctionDeclaration: _function,
+ Syntax.FunctionExpression: _function,
+ Syntax.Identifier: lambda n: [],
+ Syntax.IfStatement: lambda n: [n['test'], n['consequent'], n['alternate']],
+ Syntax.ImportDeclaration: lambda n: n['specifiers'] + [n['source']],
+ Syntax.ImportDefaultSpecifier: lambda n: [n['local']],
+ Syntax.ImportNamespaceSpecifier: lambda n: [n['local']],
+ Syntax.ImportSpecifier: lambda n: [n['local'], n['imported']],
+ Syntax.LabeledStatement: lambda n: [n['body']],
+ Syntax.Literal: lambda n: [],
+ Syntax.LogicalExpression: _binary,
+ Syntax.MemberExpression: lambda n: [n['object'], n['property']],
+ #Syntax.MethodDefinition: lambda n: [],
+ Syntax.NewExpression: lambda n: [n['callee']] + n['arguments'],
+ Syntax.ObjectExpression: lambda n: n['properties'],
+ Syntax.ObjectPattern: lambda n: n['properties'],
+ Syntax.Program: lambda n: n['body'],
+ Syntax.Property: lambda n: [n['key'], n['value']],
+ Syntax.RestElement: lambda n: [n['argument']],
+ Syntax.ReturnStatement: lambda n: [n['argument']],
+ Syntax.SequenceExpression: lambda n: n['expressions'],
+ Syntax.SpreadElement: lambda n: [n['argument']],
+ Syntax.Super: lambda n: [],
+ Syntax.SwitchCase: lambda n: [n['test'], n['consequent']],
+ Syntax.SwitchStatement: lambda n: [n['discriminant']] + n['cases'],
+ Syntax.TaggedTemplateExpression: lambda n: [n['tag'], n['quasi']],
+ Syntax.TemplateElement: lambda n: [],
+ Syntax.TemplateLiteral: lambda n: n['quasis'] + n['expressions'],
+ Syntax.ThisExpression: lambda n: [],
+ Syntax.ThrowStatement: lambda n: [n['argument']],
+ Syntax.TryStatement: lambda n: [n['block'], n['handler'], n['finalizer']],
+ Syntax.UnaryExpression: lambda n: [n['argument']],
+ Syntax.UpdateExpression: lambda n: [n['argument']],
+ Syntax.VariableDeclaration: lambda n: n['declarations'],
+ Syntax.VariableDeclarator: lambda n: [n['id'], n['init']],
+ Syntax.WhileStatement: lambda n: [n['test'], n['body']],
+ Syntax.WithStatement: lambda n: [n['object'], n['body']],
+}
+
+SKIP = object()
+class Visitor(object):
+ """
+ Generic visitor for the pyjsparser AST.
+
+ Visitation is driven by the ``visit`` method, which iterates the tree in
+ depth-first pre-order.
+
+ For each node, calls ``enter_$NODETYPE``, visits the children then calls
+ ``exit_$NODETYPE``. If the enter or exit methods are not present on the
+ visitor, falls back on ``enter_generic`` and ``exit_generic``.
+
+ Any ``enter_`` method can return ``SKIP`` to suppress both the traversal
+ of the subtree *and* the call to the corresponding ``exit_`` method
+ (whether generic or specific).
+
+ For convenience, ``visit`` will return whatever is set as the visitor's
+ ``result`` attribute, ``None`` by default.
+
+ ``visit`` can be given multiple root nodes, and it can be called multiple
+ times. The ``result`` attribute is cleared at each call but not between
+ two roots of the same ``visit`` call.
+ """
+ def __init__(self):
+ super(Visitor, self).__init__()
+ self.result = None
+
+ def enter_generic(self, node): pass
+ def exit_generic(self, node): pass
+
+ def visit(self, nodes):
+ if isinstance(nodes, dict):
+ nodes = [nodes]
+ # if multiple nodes are passed in, we need to reverse the order in
+ # order to traverse front-to-back rather than the other way around
+ nodes = list(reversed(nodes))
+
+ while nodes:
+ node = nodes.pop()
+ # should probably filter None descendants in _children...
+ if node is None:
+ continue
+ node_type = node['type']
+ if node_type == '_exit':
+ node = node['node']
+ getattr(self, 'exit_' + node['type'], self.exit_generic)(node)
+ continue
+
+ if getattr(self, 'enter_' + node_type, self.enter_generic)(node) is SKIP:
+ continue
+
+ nodes.append({'type': '_exit', 'node': node})
+ nodes.extend(reversed(_children[node_type](node)))
+
+ return self.result
+
diff --git a/doc/_extensions/conftest.py b/doc/_extensions/conftest.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/doc/_extensions/conftest.py
diff --git a/doc/_extensions/odoo_ext/static/style.css b/doc/_extensions/odoo_ext/static/style.css
index d521ff30677..ead78f9101d 100644
--- a/doc/_extensions/odoo_ext/static/style.css
+++ b/doc/_extensions/odoo_ext/static/style.css
@@ -1076,7 +1076,7 @@ body {
font-size: 15px;
line-height: 1.42857143;
color: #666c75;
- background-color: #f9f9f9;
+ background-color: #F9F9F9;
}
input,
button,
@@ -1087,7 +1087,7 @@ textarea {
line-height: inherit;
}
a {
- color: #875a7b;
+ color: #875A7B;
text-decoration: none;
}
a:hover,
@@ -1122,8 +1122,8 @@ img {
.img-thumbnail {
padding: 4px;
line-height: 1.42857143;
- background-color: #f9f9f9;
- border: 1px solid #dddddd;
+ background-color: #F9F9F9;
+ border: 1px solid #ddd;
border-radius: 4px;
-webkit-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
@@ -1139,7 +1139,7 @@ hr {
margin-top: 21px;
margin-bottom: 21px;
border: 0;
- border-top: 1px solid #f2f2f2;
+ border-top: 1px solid #F2F2F2;
}
.sr-only {
position: absolute;
@@ -1402,7 +1402,7 @@ a.bg-danger:focus {
.page-header {
padding-bottom: 9.5px;
margin: 42px 0 21px;
- border-bottom: 1px solid #f2f2f2;
+ border-bottom: 1px solid #F2F2F2;
}
ul,
ol {
@@ -1470,7 +1470,7 @@ blockquote {
padding: 10.5px 21px;
margin: 0 0 21px;
font-size: 18.75px;
- border-left: 5px solid #f2f2f2;
+ border-left: 5px solid #F2F2F2;
}
blockquote p:last-child,
blockquote ul:last-child,
@@ -1494,7 +1494,7 @@ blockquote .small:before {
blockquote.pull-right {
padding-right: 15px;
padding-left: 0;
- border-right: 5px solid #f2f2f2;
+ border-right: 5px solid #F2F2F2;
border-left: 0;
text-align: right;
}
@@ -1535,8 +1535,8 @@ code {
kbd {
padding: 2px 4px;
font-size: 90%;
- color: #ffffff;
- background-color: #333333;
+ color: #fff;
+ background-color: #333;
border-radius: 3px;
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
}
@@ -1554,9 +1554,9 @@ pre {
line-height: 1.42857143;
word-break: break-all;
word-wrap: break-word;
- color: #6b6d70;
+ color: #6B6D70;
background-color: #f5f5f5;
- border: 1px solid #cccccc;
+ border: 1px solid #ccc;
border-radius: 4px;
}
pre code {
@@ -2264,11 +2264,11 @@ th {
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
- border-top: 1px solid #dddddd;
+ border-top: 1px solid #ddd;
}
.table > thead > tr > th {
vertical-align: bottom;
- border-bottom: 2px solid #dddddd;
+ border-bottom: 2px solid #ddd;
}
.table > caption + thead > tr:first-child > th,
.table > colgroup + thead > tr:first-child > th,
@@ -2279,10 +2279,10 @@ th {
border-top: 0;
}
.table > tbody + tbody {
- border-top: 2px solid #dddddd;
+ border-top: 2px solid #ddd;
}
.table .table {
- background-color: #f9f9f9;
+ background-color: #F9F9F9;
}
.table-condensed > thead > tr > th,
.table-condensed > tbody > tr > th,
@@ -2293,7 +2293,7 @@ th {
padding: 5px;
}
.table-bordered {
- border: 1px solid #dddddd;
+ border: 1px solid #ddd;
}
.table-bordered > thead > tr > th,
.table-bordered > tbody > tr > th,
@@ -2301,7 +2301,7 @@ th {
.table-bordered > thead > tr > td,
.table-bordered > tbody > tr > td,
.table-bordered > tfoot > tr > td {
- border: 1px solid #dddddd;
+ border: 1px solid #ddd;
}
.table-bordered > thead > tr > th,
.table-bordered > thead > tr > td {
@@ -2439,7 +2439,7 @@ table th[class*="col-"] {
margin-bottom: 15.75px;
overflow-y: hidden;
-ms-overflow-style: -ms-autohiding-scrollbar;
- border: 1px solid #dddddd;
+ border: 1px solid #ddd;
}
.table-responsive > .table {
margin-bottom: 0;
@@ -2491,7 +2491,7 @@ legend {
margin-bottom: 21px;
font-size: 22.5px;
line-height: inherit;
- color: #6b6d70;
+ color: #6B6D70;
border: 0;
border-bottom: 1px solid #e5e5e5;
}
@@ -2545,9 +2545,9 @@ output {
font-size: 15px;
line-height: 1.42857143;
color: #888888;
- background-color: #ffffff;
+ background-color: #fff;
background-image: none;
- border: 1px solid #cccccc;
+ border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
@@ -2562,14 +2562,14 @@ output {
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.form-control::-moz-placeholder {
- color: #999999;
+ color: #999;
opacity: 1;
}
.form-control:-ms-input-placeholder {
- color: #999999;
+ color: #999;
}
.form-control::-webkit-input-placeholder {
- color: #999999;
+ color: #999;
}
.form-control::-ms-expand {
border: 0;
@@ -2578,7 +2578,7 @@ output {
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
- background-color: #f2f2f2;
+ background-color: #F2F2F2;
opacity: 1;
}
.form-control[disabled],
@@ -3024,7 +3024,7 @@ select[multiple].input-lg {
.btn:hover,
.btn:focus,
.btn.focus {
- color: #333333;
+ color: #333;
text-decoration: none;
}
.btn:active,
@@ -3048,25 +3048,25 @@ fieldset[disabled] a.btn {
pointer-events: none;
}
.btn-default {
- color: #333333;
- background-color: #ffffff;
- border-color: #cccccc;
+ color: #333;
+ background-color: #fff;
+ border-color: #ccc;
}
.btn-default:focus,
.btn-default.focus {
- color: #333333;
+ color: #333;
background-color: #e6e6e6;
border-color: #8c8c8c;
}
.btn-default:hover {
- color: #333333;
+ color: #333;
background-color: #e6e6e6;
border-color: #adadad;
}
.btn-default:active,
.btn-default.active,
.open > .dropdown-toggle.btn-default {
- color: #333333;
+ color: #333;
background-color: #e6e6e6;
border-color: #adadad;
}
@@ -3079,7 +3079,7 @@ fieldset[disabled] a.btn {
.btn-default:active.focus,
.btn-default.active.focus,
.open > .dropdown-toggle.btn-default.focus {
- color: #333333;
+ color: #333;
background-color: #d4d4d4;
border-color: #8c8c8c;
}
@@ -3097,33 +3097,33 @@ fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus {
- background-color: #ffffff;
- border-color: #cccccc;
+ background-color: #fff;
+ border-color: #ccc;
}
.btn-default .badge {
- color: #ffffff;
- background-color: #333333;
+ color: #fff;
+ background-color: #333;
}
.btn-primary {
- color: #ffffff;
+ color: #fff;
background-color: #21b799;
border-color: #1da187;
}
.btn-primary:focus,
.btn-primary.focus {
- color: #ffffff;
+ color: #fff;
background-color: #198c75;
border-color: #0a352d;
}
.btn-primary:hover {
- color: #ffffff;
+ color: #fff;
background-color: #198c75;
border-color: #146e5c;
}
.btn-primary:active,
.btn-primary.active,
.open > .dropdown-toggle.btn-primary {
- color: #ffffff;
+ color: #fff;
background-color: #198c75;
border-color: #146e5c;
}
@@ -3136,7 +3136,7 @@ fieldset[disabled] .btn-default.focus {
.btn-primary:active.focus,
.btn-primary.active.focus,
.open > .dropdown-toggle.btn-primary.focus {
- color: #ffffff;
+ color: #fff;
background-color: #146e5c;
border-color: #0a352d;
}
@@ -3159,28 +3159,28 @@ fieldset[disabled] .btn-primary.focus {
}
.btn-primary .badge {
color: #21b799;
- background-color: #ffffff;
+ background-color: #fff;
}
.btn-success {
- color: #ffffff;
- background-color: #50af51;
+ color: #fff;
+ background-color: #50AF51;
border-color: #489e49;
}
.btn-success:focus,
.btn-success.focus {
- color: #ffffff;
+ color: #fff;
background-color: #408c41;
border-color: #204620;
}
.btn-success:hover {
- color: #ffffff;
+ color: #fff;
background-color: #408c41;
border-color: #357435;
}
.btn-success:active,
.btn-success.active,
.open > .dropdown-toggle.btn-success {
- color: #ffffff;
+ color: #fff;
background-color: #408c41;
border-color: #357435;
}
@@ -3193,7 +3193,7 @@ fieldset[disabled] .btn-primary.focus {
.btn-success:active.focus,
.btn-success.active.focus,
.open > .dropdown-toggle.btn-success.focus {
- color: #ffffff;
+ color: #fff;
background-color: #357335;
border-color: #204620;
}
@@ -3211,33 +3211,33 @@ fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus {
- background-color: #50af51;
+ background-color: #50AF51;
border-color: #489e49;
}
.btn-success .badge {
- color: #50af51;
- background-color: #ffffff;
+ color: #50AF51;
+ background-color: #fff;
}
.btn-info {
- color: #ffffff;
- background-color: #4b9eb6;
+ color: #fff;
+ background-color: #4B9EB6;
border-color: #438fa5;
}
.btn-info:focus,
.btn-info.focus {
- color: #ffffff;
+ color: #fff;
background-color: #3b7f93;
border-color: #1e404a;
}
.btn-info:hover {
- color: #ffffff;
+ color: #fff;
background-color: #3b7f93;
border-color: #316979;
}
.btn-info:active,
.btn-info.active,
.open > .dropdown-toggle.btn-info {
- color: #ffffff;
+ color: #fff;
background-color: #3b7f93;
border-color: #316979;
}
@@ -3250,7 +3250,7 @@ fieldset[disabled] .btn-success.focus {
.btn-info:active.focus,
.btn-info.active.focus,
.open > .dropdown-toggle.btn-info.focus {
- color: #ffffff;
+ color: #fff;
background-color: #316979;
border-color: #1e404a;
}
@@ -3268,33 +3268,33 @@ fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus {
- background-color: #4b9eb6;
+ background-color: #4B9EB6;
border-color: #438fa5;
}
.btn-info .badge {
- color: #4b9eb6;
- background-color: #ffffff;
+ color: #4B9EB6;
+ background-color: #fff;
}
.btn-warning {
- color: #ffffff;
- background-color: #f0ad4e;
+ color: #fff;
+ background-color: #F0AD4E;
border-color: #eea236;
}
.btn-warning:focus,
.btn-warning.focus {
- color: #ffffff;
+ color: #fff;
background-color: #ec971f;
border-color: #985f0d;
}
.btn-warning:hover {
- color: #ffffff;
+ color: #fff;
background-color: #ec971f;
border-color: #d58512;
}
.btn-warning:active,
.btn-warning.active,
.open > .dropdown-toggle.btn-warning {
- color: #ffffff;
+ color: #fff;
background-color: #ec971f;
border-color: #d58512;
}
@@ -3307,7 +3307,7 @@ fieldset[disabled] .btn-info.focus {
.btn-warning:active.focus,
.btn-warning.active.focus,
.open > .dropdown-toggle.btn-warning.focus {
- color: #ffffff;
+ color: #fff;
background-color: #d58512;
border-color: #985f0d;
}
@@ -3325,33 +3325,33 @@ fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus {
- background-color: #f0ad4e;
+ background-color: #F0AD4E;
border-color: #eea236;
}
.btn-warning .badge {
- color: #f0ad4e;
- background-color: #ffffff;
+ color: #F0AD4E;
+ background-color: #fff;
}
.btn-danger {
- color: #ffffff;
- background-color: #d9534f;
+ color: #fff;
+ background-color: #D9534F;
border-color: #d43f3a;
}
.btn-danger:focus,
.btn-danger.focus {
- color: #ffffff;
+ color: #fff;
background-color: #c9302c;
border-color: #761c19;
}
.btn-danger:hover {
- color: #ffffff;
+ color: #fff;
background-color: #c9302c;
border-color: #ac2925;
}
.btn-danger:active,
.btn-danger.active,
.open > .dropdown-toggle.btn-danger {
- color: #ffffff;
+ color: #fff;
background-color: #c9302c;
border-color: #ac2925;
}
@@ -3364,7 +3364,7 @@ fieldset[disabled] .btn-warning.focus {
.btn-danger:active.focus,
.btn-danger.active.focus,
.open > .dropdown-toggle.btn-danger.focus {
- color: #ffffff;
+ color: #fff;
background-color: #ac2925;
border-color: #761c19;
}
@@ -3382,15 +3382,15 @@ fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus {
- background-color: #d9534f;
+ background-color: #D9534F;
border-color: #d43f3a;
}
.btn-danger .badge {
- color: #d9534f;
- background-color: #ffffff;
+ color: #D9534F;
+ background-color: #fff;
}
.btn-link {
- color: #875a7b;
+ color: #875A7B;
font-weight: normal;
border-radius: 0;
}
@@ -3518,8 +3518,8 @@ tbody.collapse.in {
list-style: none;
font-size: 15px;
text-align: left;
- background-color: #ffffff;
- border: 1px solid #cccccc;
+ background-color: #fff;
+ border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
@@ -3542,7 +3542,7 @@ tbody.collapse.in {
clear: both;
font-weight: normal;
line-height: 1.42857143;
- color: #6b6d70;
+ color: #6B6D70;
white-space: nowrap;
}
.dropdown-menu > li > a:hover,
@@ -3554,7 +3554,7 @@ tbody.collapse.in {
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
- color: #ffffff;
+ color: #fff;
text-decoration: none;
outline: 0;
background-color: #21b799;
@@ -3889,8 +3889,8 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
line-height: 1;
color: #888888;
text-align: center;
- background-color: #f2f2f2;
- border: 1px solid #cccccc;
+ background-color: #F2F2F2;
+ border: 1px solid #ccc;
border-radius: 4px;
}
.input-group-addon.input-sm {
@@ -3975,7 +3975,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
.nav > li > a:hover,
.nav > li > a:focus {
text-decoration: none;
- background-color: #f2f2f2;
+ background-color: #F2F2F2;
}
.nav > li.disabled > a {
color: #dcddde;
@@ -3990,8 +3990,8 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
.nav .open > a,
.nav .open > a:hover,
.nav .open > a:focus {
- background-color: #f2f2f2;
- border-color: #875a7b;
+ background-color: #F2F2F2;
+ border-color: #875A7B;
}
.nav .nav-divider {
height: 1px;
@@ -4003,7 +4003,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
max-width: none;
}
.nav-tabs {
- border-bottom: 1px solid #dddddd;
+ border-bottom: 1px solid #ddd;
}
.nav-tabs > li {
float: left;
@@ -4016,14 +4016,14 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
border-radius: 4px 4px 0 0;
}
.nav-tabs > li > a:hover {
- border-color: #f2f2f2 #f2f2f2 #dddddd;
+ border-color: #F2F2F2 #F2F2F2 #ddd;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
color: #888888;
- background-color: #f9f9f9;
- border: 1px solid #dddddd;
+ background-color: #F9F9F9;
+ border: 1px solid #ddd;
border-bottom-color: transparent;
cursor: default;
}
@@ -4058,17 +4058,17 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
.nav-tabs.nav-justified > .active > a,
.nav-tabs.nav-justified > .active > a:hover,
.nav-tabs.nav-justified > .active > a:focus {
- border: 1px solid #dddddd;
+ border: 1px solid #ddd;
}
@media (min-width: 768px) {
.nav-tabs.nav-justified > li > a {
- border-bottom: 1px solid #dddddd;
+ border-bottom: 1px solid #ddd;
border-radius: 4px 4px 0 0;
}
.nav-tabs.nav-justified > .active > a,
.nav-tabs.nav-justified > .active > a:hover,
.nav-tabs.nav-justified > .active > a:focus {
- border-bottom-color: #f9f9f9;
+ border-bottom-color: #F9F9F9;
}
}
.nav-pills > li {
@@ -4083,7 +4083,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
.nav-pills > li.active > a,
.nav-pills > li.active > a:hover,
.nav-pills > li.active > a:focus {
- color: #ffffff;
+ color: #fff;
background-color: #21b799;
}
.nav-stacked > li {
@@ -4126,17 +4126,17 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
.nav-tabs-justified > .active > a,
.nav-tabs-justified > .active > a:hover,
.nav-tabs-justified > .active > a:focus {
- border: 1px solid #dddddd;
+ border: 1px solid #ddd;
}
@media (min-width: 768px) {
.nav-tabs-justified > li > a {
- border-bottom: 1px solid #dddddd;
+ border-bottom: 1px solid #ddd;
border-radius: 4px 4px 0 0;
}
.nav-tabs-justified > .active > a,
.nav-tabs-justified > .active > a:hover,
.nav-tabs-justified > .active > a:focus {
- border-bottom-color: #f9f9f9;
+ border-bottom-color: #F9F9F9;
}
}
.tab-content > .tab-pane {
@@ -4481,7 +4481,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
border-color: #e7e7e7;
}
.navbar-default .navbar-brand {
- color: #777777;
+ color: #777;
}
.navbar-default .navbar-brand:hover,
.navbar-default .navbar-brand:focus {
@@ -4489,37 +4489,37 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
background-color: transparent;
}
.navbar-default .navbar-text {
- color: #777777;
+ color: #777;
}
.navbar-default .navbar-nav > li > a {
- color: #777777;
+ color: #777;
}
.navbar-default .navbar-nav > li > a:hover,
.navbar-default .navbar-nav > li > a:focus {
- color: #333333;
+ color: #333;
background-color: transparent;
}
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
- color: #555555;
+ color: #555;
background-color: #e7e7e7;
}
.navbar-default .navbar-nav > .disabled > a,
.navbar-default .navbar-nav > .disabled > a:hover,
.navbar-default .navbar-nav > .disabled > a:focus {
- color: #cccccc;
+ color: #ccc;
background-color: transparent;
}
.navbar-default .navbar-toggle {
- border-color: #dddddd;
+ border-color: #ddd;
}
.navbar-default .navbar-toggle:hover,
.navbar-default .navbar-toggle:focus {
- background-color: #dddddd;
+ background-color: #ddd;
}
.navbar-default .navbar-toggle .icon-bar {
- background-color: #888888;
+ background-color: #888;
}
.navbar-default .navbar-collapse,
.navbar-default .navbar-form {
@@ -4529,51 +4529,51 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
.navbar-default .navbar-nav > .open > a:hover,
.navbar-default .navbar-nav > .open > a:focus {
background-color: #e7e7e7;
- color: #555555;
+ color: #555;
}
@media (max-width: 767px) {
.navbar-default .navbar-nav .open .dropdown-menu > li > a {
- color: #777777;
+ color: #777;
}
.navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
.navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
- color: #333333;
+ color: #333;
background-color: transparent;
}
.navbar-default .navbar-nav .open .dropdown-menu > .active > a,
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
- color: #555555;
+ color: #555;
background-color: #e7e7e7;
}
.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
- color: #cccccc;
+ color: #ccc;
background-color: transparent;
}
}
.navbar-default .navbar-link {
- color: #777777;
+ color: #777;
}
.navbar-default .navbar-link:hover {
- color: #333333;
+ color: #333;
}
.navbar-default .btn-link {
- color: #777777;
+ color: #777;
}
.navbar-default .btn-link:hover,
.navbar-default .btn-link:focus {
- color: #333333;
+ color: #333;
}
.navbar-default .btn-link[disabled]:hover,
fieldset[disabled] .navbar-default .btn-link:hover,
.navbar-default .btn-link[disabled]:focus,
fieldset[disabled] .navbar-default .btn-link:focus {
- color: #cccccc;
+ color: #ccc;
}
.navbar-inverse {
- background-color: #222222;
+ background-color: #222;
border-color: #080808;
}
.navbar-inverse .navbar-brand {
@@ -4581,7 +4581,7 @@ fieldset[disabled] .navbar-default .btn-link:focus {
}
.navbar-inverse .navbar-brand:hover,
.navbar-inverse .navbar-brand:focus {
- color: #ffffff;
+ color: #fff;
background-color: transparent;
}
.navbar-inverse .navbar-text {
@@ -4592,30 +4592,30 @@ fieldset[disabled] .navbar-default .btn-link:focus {
}
.navbar-inverse .navbar-nav > li > a:hover,
.navbar-inverse .navbar-nav > li > a:focus {
- color: #ffffff;
+ color: #fff;
background-color: transparent;
}
.navbar-inverse .navbar-nav > .active > a,
.navbar-inverse .navbar-nav > .active > a:hover,
.navbar-inverse .navbar-nav > .active > a:focus {
- color: #ffffff;
+ color: #fff;
background-color: #080808;
}
.navbar-inverse .navbar-nav > .disabled > a,
.navbar-inverse .navbar-nav > .disabled > a:hover,
.navbar-inverse .navbar-nav > .disabled > a:focus {
- color: #444444;
+ color: #444;
background-color: transparent;
}
.navbar-inverse .navbar-toggle {
- border-color: #333333;
+ border-color: #333;
}
.navbar-inverse .navbar-toggle:hover,
.navbar-inverse .navbar-toggle:focus {
- background-color: #333333;
+ background-color: #333;
}
.navbar-inverse .navbar-toggle .icon-bar {
- background-color: #ffffff;
+ background-color: #fff;
}
.navbar-inverse .navbar-collapse,
.navbar-inverse .navbar-form {
@@ -4625,7 +4625,7 @@ fieldset[disabled] .navbar-default .btn-link:focus {
.navbar-inverse .navbar-nav > .open > a:hover,
.navbar-inverse .navbar-nav > .open > a:focus {
background-color: #080808;
- color: #ffffff;
+ color: #fff;
}
@media (max-width: 767px) {
.navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
@@ -4639,19 +4639,19 @@ fieldset[disabled] .navbar-default .btn-link:focus {
}
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
- color: #ffffff;
+ color: #fff;
background-color: transparent;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
- color: #ffffff;
+ color: #fff;
background-color: #080808;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
.navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
- color: #444444;
+ color: #444;
background-color: transparent;
}
}
@@ -4659,20 +4659,20 @@ fieldset[disabled] .navbar-default .btn-link:focus {
color: #ffffff;
}
.navbar-inverse .navbar-link:hover {
- color: #ffffff;
+ color: #fff;
}
.navbar-inverse .btn-link {
color: #ffffff;
}
.navbar-inverse .btn-link:hover,
.navbar-inverse .btn-link:focus {
- color: #ffffff;
+ color: #fff;
}
.navbar-inverse .btn-link[disabled]:hover,
fieldset[disabled] .navbar-inverse .btn-link:hover,
.navbar-inverse .btn-link[disabled]:focus,
fieldset[disabled] .navbar-inverse .btn-link:focus {
- color: #444444;
+ color: #444;
}
.breadcrumb {
padding: 8px 15px;
@@ -4687,7 +4687,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.breadcrumb > li + li:before {
content: "/\00a0";
padding: 0 5px;
- color: #cccccc;
+ color: #ccc;
}
.breadcrumb > .active {
color: #dcddde;
@@ -4708,9 +4708,9 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
padding: 6px 12px;
line-height: 1.42857143;
text-decoration: none;
- color: #875a7b;
- background-color: #ffffff;
- border: 1px solid #dddddd;
+ color: #875A7B;
+ background-color: #fff;
+ border: 1px solid #ddd;
margin-left: -1px;
}
.pagination > li:first-child > a,
@@ -4730,8 +4730,8 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.pagination > li > span:focus {
z-index: 2;
color: #926485;
- background-color: #f2f2f2;
- border-color: #dddddd;
+ background-color: #F2F2F2;
+ border-color: #ddd;
}
.pagination > .active > a,
.pagination > .active > span,
@@ -4740,7 +4740,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.pagination > .active > a:focus,
.pagination > .active > span:focus {
z-index: 3;
- color: #ffffff;
+ color: #fff;
background-color: #21b799;
border-color: #21b799;
cursor: default;
@@ -4752,8 +4752,8 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #dcddde;
- background-color: #ffffff;
- border-color: #dddddd;
+ background-color: #fff;
+ border-color: #ddd;
cursor: not-allowed;
}
.pagination-lg > li > a,
@@ -4801,14 +4801,14 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.pager li > span {
display: inline-block;
padding: 5px 14px;
- background-color: #ffffff;
- border: 1px solid #dddddd;
+ background-color: #fff;
+ border: 1px solid #ddd;
border-radius: 15px;
}
.pager li > a:hover,
.pager li > a:focus {
text-decoration: none;
- background-color: #f2f2f2;
+ background-color: #F2F2F2;
}
.pager .next > a,
.pager .next > span {
@@ -4823,7 +4823,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
.pager .disabled > a:focus,
.pager .disabled > span {
color: #dcddde;
- background-color: #ffffff;
+ background-color: #fff;
cursor: not-allowed;
}
.label {
@@ -4832,7 +4832,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
font-size: 75%;
font-weight: bold;
line-height: 1;
- color: #ffffff;
+ color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
@@ -4840,7 +4840,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
}
a.label:hover,
a.label:focus {
- color: #ffffff;
+ color: #fff;
text-decoration: none;
cursor: pointer;
}
@@ -4866,28 +4866,28 @@ a.label:focus {
background-color: #198c75;
}
.label-success {
- background-color: #50af51;
+ background-color: #50AF51;
}
.label-success[href]:hover,
.label-success[href]:focus {
background-color: #408c41;
}
.label-info {
- background-color: #4b9eb6;
+ background-color: #4B9EB6;
}
.label-info[href]:hover,
.label-info[href]:focus {
background-color: #3b7f93;
}
.label-warning {
- background-color: #f0ad4e;
+ background-color: #F0AD4E;
}
.label-warning[href]:hover,
.label-warning[href]:focus {
background-color: #ec971f;
}
.label-danger {
- background-color: #d9534f;
+ background-color: #D9534F;
}
.label-danger[href]:hover,
.label-danger[href]:focus {
@@ -4899,7 +4899,7 @@ a.label:focus {
padding: 3px 7px;
font-size: 13px;
font-weight: bold;
- color: #ffffff;
+ color: #fff;
line-height: 1;
vertical-align: middle;
white-space: nowrap;
@@ -4921,14 +4921,14 @@ a.label:focus {
}
a.badge:hover,
a.badge:focus {
- color: #ffffff;
+ color: #fff;
text-decoration: none;
cursor: pointer;
}
.list-group-item.active > .badge,
.nav-pills > .active > a > .badge {
- color: #875a7b;
- background-color: #ffffff;
+ color: #875A7B;
+ background-color: #fff;
}
.list-group-item > .badge {
float: right;
@@ -4944,7 +4944,7 @@ a.badge:focus {
padding-bottom: 30px;
margin-bottom: 30px;
color: inherit;
- background-color: #f2f2f2;
+ background-color: #F2F2F2;
}
.jumbotron h1,
.jumbotron .h1 {
@@ -4987,8 +4987,8 @@ a.badge:focus {
padding: 4px;
margin-bottom: 21px;
line-height: 1.42857143;
- background-color: #f9f9f9;
- border: 1px solid #dddddd;
+ background-color: #F9F9F9;
+ border: 1px solid #ddd;
border-radius: 4px;
-webkit-transition: border 0.2s ease-in-out;
-o-transition: border 0.2s ease-in-out;
@@ -5002,7 +5002,7 @@ a.badge:focus {
a.thumbnail:hover,
a.thumbnail:focus,
a.thumbnail.active {
- border-color: #875a7b;
+ border-color: #875A7B;
}
.thumbnail .caption {
padding: 9px;
@@ -5114,7 +5114,7 @@ a.thumbnail.active {
height: 100%;
font-size: 13px;
line-height: 21px;
- color: #ffffff;
+ color: #fff;
text-align: center;
background-color: #21b799;
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
@@ -5137,7 +5137,7 @@ a.thumbnail.active {
animation: progress-bar-stripes 2s linear infinite;
}
.progress-bar-success {
- background-color: #50af51;
+ background-color: #50AF51;
}
.progress-striped .progress-bar-success {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
@@ -5145,7 +5145,7 @@ a.thumbnail.active {
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
}
.progress-bar-info {
- background-color: #4b9eb6;
+ background-color: #4B9EB6;
}
.progress-striped .progress-bar-info {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
@@ -5153,7 +5153,7 @@ a.thumbnail.active {
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
}
.progress-bar-warning {
- background-color: #f0ad4e;
+ background-color: #F0AD4E;
}
.progress-striped .progress-bar-warning {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
@@ -5161,7 +5161,7 @@ a.thumbnail.active {
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
}
.progress-bar-danger {
- background-color: #d9534f;
+ background-color: #D9534F;
}
.progress-striped .progress-bar-danger {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
@@ -5225,8 +5225,8 @@ a.thumbnail.active {
display: block;
padding: 10px 15px;
margin-bottom: -1px;
- background-color: #ffffff;
- border: 1px solid #dddddd;
+ background-color: #fff;
+ border: 1px solid #ddd;
}
.list-group-item:first-child {
border-top-right-radius: 4px;
@@ -5239,18 +5239,18 @@ a.thumbnail.active {
}
a.list-group-item,
button.list-group-item {
- color: #555555;
+ color: #555;
}
a.list-group-item .list-group-item-heading,
button.list-group-item .list-group-item-heading {
- color: #333333;
+ color: #333;
}
a.list-group-item:hover,
button.list-group-item:hover,
a.list-group-item:focus,
button.list-group-item:focus {
text-decoration: none;
- color: #555555;
+ color: #555;
background-color: #f5f5f5;
}
button.list-group-item {
@@ -5260,7 +5260,7 @@ button.list-group-item {
.list-group-item.disabled,
.list-group-item.disabled:hover,
.list-group-item.disabled:focus {
- background-color: #f2f2f2;
+ background-color: #F2F2F2;
color: #dcddde;
cursor: not-allowed;
}
@@ -5278,7 +5278,7 @@ button.list-group-item {
.list-group-item.active:hover,
.list-group-item.active:focus {
z-index: 2;
- color: #ffffff;
+ color: #fff;
background-color: #21b799;
border-color: #21b799;
}
@@ -5424,7 +5424,7 @@ button.list-group-item-danger.active:focus {
}
.panel {
margin-bottom: 21px;
- background-color: #ffffff;
+ background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
@@ -5458,7 +5458,7 @@ button.list-group-item-danger.active:focus {
.panel-footer {
padding: 10px 15px;
background-color: #f5f5f5;
- border-top: 1px solid #dddddd;
+ border-top: 1px solid #ddd;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
@@ -5572,7 +5572,7 @@ button.list-group-item-danger.active:focus {
.panel > .panel-body + .table-responsive,
.panel > .table + .panel-body,
.panel > .table-responsive + .panel-body {
- border-top: 1px solid #dddddd;
+ border-top: 1px solid #ddd;
}
.panel > .table > tbody:first-child > tr:first-child th,
.panel > .table > tbody:first-child > tr:first-child td {
@@ -5649,37 +5649,37 @@ button.list-group-item-danger.active:focus {
}
.panel-group .panel-heading + .panel-collapse > .panel-body,
.panel-group .panel-heading + .panel-collapse > .list-group {
- border-top: 1px solid #dddddd;
+ border-top: 1px solid #ddd;
}
.panel-group .panel-footer {
border-top: 0;
}
.panel-group .panel-footer + .panel-collapse .panel-body {
- border-bottom: 1px solid #dddddd;
+ border-bottom: 1px solid #ddd;
}
.panel-default {
- border-color: #dddddd;
+ border-color: #ddd;
}
.panel-default > .panel-heading {
- color: #6b6d70;
+ color: #6B6D70;
background-color: #f5f5f5;
- border-color: #dddddd;
+ border-color: #ddd;
}
.panel-default > .panel-heading + .panel-collapse > .panel-body {
- border-top-color: #dddddd;
+ border-top-color: #ddd;
}
.panel-default > .panel-heading .badge {
color: #f5f5f5;
- background-color: #6b6d70;
+ background-color: #6B6D70;
}
.panel-default > .panel-footer + .panel-collapse > .panel-body {
- border-bottom-color: #dddddd;
+ border-bottom-color: #ddd;
}
.panel-primary {
border-color: #21b799;
}
.panel-primary > .panel-heading {
- color: #ffffff;
+ color: #fff;
background-color: #21b799;
border-color: #21b799;
}
@@ -5688,7 +5688,7 @@ button.list-group-item-danger.active:focus {
}
.panel-primary > .panel-heading .badge {
color: #21b799;
- background-color: #ffffff;
+ background-color: #fff;
}
.panel-primary > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #21b799;
@@ -5818,14 +5818,14 @@ button.list-group-item-danger.active:focus {
font-size: 22.5px;
font-weight: bold;
line-height: 1;
- color: #000000;
- text-shadow: 0 1px 0 #ffffff;
+ color: #000;
+ text-shadow: 0 1px 0 #fff;
opacity: 0.2;
filter: alpha(opacity=20);
}
.close:hover,
.close:focus {
- color: #000000;
+ color: #000;
text-decoration: none;
cursor: pointer;
opacity: 0.5;
@@ -5880,8 +5880,8 @@ button.close {
}
.modal-content {
position: relative;
- background-color: #ffffff;
- border: 1px solid #999999;
+ background-color: #fff;
+ border: 1px solid #999;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
@@ -5896,7 +5896,7 @@ button.close {
bottom: 0;
left: 0;
z-index: 1040;
- background-color: #000000;
+ background-color: #000;
}
.modal-backdrop.fade {
opacity: 0;
@@ -6007,9 +6007,9 @@ button.close {
.tooltip-inner {
max-width: 200px;
padding: 3px 8px;
- color: #ffffff;
+ color: #fff;
text-align: center;
- background-color: #000000;
+ background-color: #000;
border-radius: 4px;
}
.tooltip-arrow {
@@ -6024,56 +6024,56 @@ button.close {
left: 50%;
margin-left: -5px;
border-width: 5px 5px 0;
- border-top-color: #000000;
+ border-top-color: #000;
}
.tooltip.top-left .tooltip-arrow {
bottom: 0;
right: 5px;
margin-bottom: -5px;
border-width: 5px 5px 0;
- border-top-color: #000000;
+ border-top-color: #000;
}
.tooltip.top-right .tooltip-arrow {
bottom: 0;
left: 5px;
margin-bottom: -5px;
border-width: 5px 5px 0;
- border-top-color: #000000;
+ border-top-color: #000;
}
.tooltip.right .tooltip-arrow {
top: 50%;
left: 0;
margin-top: -5px;
border-width: 5px 5px 5px 0;
- border-right-color: #000000;
+ border-right-color: #000;
}
.tooltip.left .tooltip-arrow {
top: 50%;
right: 0;
margin-top: -5px;
border-width: 5px 0 5px 5px;
- border-left-color: #000000;
+ border-left-color: #000;
}
.tooltip.bottom .tooltip-arrow {
top: 0;
left: 50%;
margin-left: -5px;
border-width: 0 5px 5px;
- border-bottom-color: #000000;
+ border-bottom-color: #000;
}
.tooltip.bottom-left .tooltip-arrow {
top: 0;
right: 5px;
margin-top: -5px;
border-width: 0 5px 5px;
- border-bottom-color: #000000;
+ border-bottom-color: #000;
}
.tooltip.bottom-right .tooltip-arrow {
top: 0;
left: 5px;
margin-top: -5px;
border-width: 0 5px 5px;
- border-bottom-color: #000000;
+ border-bottom-color: #000;
}
.popover {
position: absolute;
@@ -6099,9 +6099,9 @@ button.close {
word-spacing: normal;
word-wrap: normal;
font-size: 15px;
- background-color: #ffffff;
+ background-color: #fff;
background-clip: padding-box;
- border: 1px solid #cccccc;
+ border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
@@ -6159,7 +6159,7 @@ button.close {
bottom: 1px;
margin-left: -10px;
border-bottom-width: 0;
- border-top-color: #ffffff;
+ border-top-color: #fff;
}
.popover.right > .arrow {
top: 50%;
@@ -6174,7 +6174,7 @@ button.close {
left: 1px;
bottom: -10px;
border-left-width: 0;
- border-right-color: #ffffff;
+ border-right-color: #fff;
}
.popover.bottom > .arrow {
left: 50%;
@@ -6189,7 +6189,7 @@ button.close {
top: 1px;
margin-left: -10px;
border-top-width: 0;
- border-bottom-color: #ffffff;
+ border-bottom-color: #fff;
}
.popover.left > .arrow {
top: 50%;
@@ -6203,7 +6203,7 @@ button.close {
content: " ";
right: 1px;
border-right-width: 0;
- border-left-color: #ffffff;
+ border-left-color: #fff;
bottom: -10px;
}
.carousel {
@@ -6297,7 +6297,7 @@ button.close {
opacity: 0.5;
filter: alpha(opacity=50);
font-size: 20px;
- color: #ffffff;
+ color: #fff;
text-align: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
background-color: rgba(0, 0, 0, 0);
@@ -6321,7 +6321,7 @@ button.close {
.carousel-control:hover,
.carousel-control:focus {
outline: 0;
- color: #ffffff;
+ color: #fff;
text-decoration: none;
opacity: 0.9;
filter: alpha(opacity=90);
@@ -6376,7 +6376,7 @@ button.close {
height: 10px;
margin: 1px;
text-indent: -999px;
- border: 1px solid #ffffff;
+ border: 1px solid #fff;
border-radius: 10px;
cursor: pointer;
background-color: #000 \9;
@@ -6386,7 +6386,7 @@ button.close {
margin: 0;
width: 12px;
height: 12px;
- background-color: #ffffff;
+ background-color: #fff;
}
.carousel-caption {
position: absolute;
@@ -6396,7 +6396,7 @@ button.close {
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
- color: #ffffff;
+ color: #fff;
text-align: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
@@ -9203,7 +9203,7 @@ main.has_code_col:after,
}
@media screen and (min-width: 768px) {
.o-doc-dropdown.open {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
}
}
.o-doc-dropdown.open .dropdown-toggle {
@@ -9218,7 +9218,7 @@ main.has_code_col:after,
border-radius: 0;
text-align: left;
min-width: 100%;
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
padding-bottom: 12.5px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
}
@@ -9253,7 +9253,7 @@ main.has_code_col:after,
}
@media screen and (min-width: 768px) {
.o-doc-dropdown.open .dropdown-menu {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
}
}
.o-doc-dropdown.o_dropdown_mobile_nav {
@@ -9569,7 +9569,7 @@ header {
position: relative;
}
#wrap > .container.index {
- background-color: #f9f9f9;
+ background-color: #F9F9F9;
width: 100%;
position: relative;
}
@@ -9651,8 +9651,8 @@ main aside {
}
}
main article.doc-body {
- background: #ffffff;
- border-left: 1px solid #f2f2f2;
+ background: #fff;
+ border-left: 1px solid #F2F2F2;
padding: 15px 30px;
}
@media (min-width: 992px) {
@@ -9931,8 +9931,8 @@ article.doc-toc .toctree-wrapper > ul > li > span {
}
.o_main_header > .o_main_header_main > .o_primary_nav.o_sub_opened:before {
visibility: visible;
- background: #fcfcfc;
- box-shadow: 0 1px 0 #f2f2f2;
+ background: #FCFCFC;
+ box-shadow: 0 1px 0 #F2F2F2;
-webkit-transition: background 500ms cubic-bezier(0.55, 0, 0.1, 1) 0ms, visibility 0ms linear 0s, box-shadow 500ms linear 0ms;
-o-transition: background 500ms cubic-bezier(0.55, 0, 0.1, 1) 0ms, visibility 0ms linear 0s, box-shadow 500ms linear 0ms;
transition: background 500ms cubic-bezier(0.55, 0, 0.1, 1) 0ms, visibility 0ms linear 0s, box-shadow 500ms linear 0ms;
@@ -9983,12 +9983,12 @@ article.doc-toc .toctree-wrapper > ul > li > span {
transform: rotate(-45deg);
}
.o_main_header > .o_main_header_main > .o_primary_nav .dropdown.open > a {
- color: #9e588b !important;
+ color: #9E588B !important;
font-weight: 600;
}
.o_main_header > .o_main_header_main > .o_primary_nav .dropdown.open > a::before,
.o_main_header > .o_main_header_main > .o_primary_nav .dropdown.open > a::after {
- background-color: #9e588b !important;
+ background-color: #9E588B !important;
box-shadow: 0 1px 1px rgba(158, 88, 139, 0.1);
right: 3.53553391px;
}
@@ -10104,7 +10104,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
padding-top: 10px;
}
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div + div {
- border-left: 1px solid #f2f2f2;
+ border-left: 1px solid #F2F2F2;
}
}
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div ul {
@@ -10221,12 +10221,12 @@ article.doc-toc .toctree-wrapper > ul > li > span {
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_website_apps a:hover,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_website_apps a:focus,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_website_apps .o_nav_app_family {
- color: #5b899e;
+ color: #5B899E;
}
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_sale_apps a:hover,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_sale_apps a:focus,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_sale_apps .o_nav_app_family {
- color: #e46f78;
+ color: #E46F78;
}
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_sale_apps .o_nav_app_family > span {
background-position: -60px 50%;
@@ -10234,7 +10234,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_operation_apps a:hover,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_operation_apps a:focus,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_operation_apps .o_nav_app_family {
- color: #5c5b80;
+ color: #5C5B80;
}
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_operation_apps .o_nav_app_family > span {
background-position: -120px 50%;
@@ -10242,7 +10242,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_productivity_apps a:hover,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_productivity_apps a:focus,
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_productivity_apps .o_nav_app_family {
- color: #d5653e;
+ color: #D5653E;
}
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav > .container > .row > div.o_productivity_apps .o_nav_app_family > span {
background-position: -181px 50%;
@@ -10258,7 +10258,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
display: block;
height: 50px;
line-height: 50px;
- border-top: 1px solid #f2f2f2;
+ border-top: 1px solid #F2F2F2;
text-align: center;
font-size: 12px;
font-weight: 600;
@@ -10266,7 +10266,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
@media (min-width: 768px) {
.o_main_header > .o_main_header_main > .o_primary_nav .o_secondary_nav {
display: block;
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
visibility: hidden;
z-index: -2;
-webkit-transform: translate(0, -115%);
@@ -10530,11 +10530,11 @@ article.doc-toc .toctree-wrapper > ul > li > span {
box-shadow: 0 1px 1px rgba(255, 255, 255, 0.1);
}
.o_main_header > .o_main_header_main > .o_header_buttons .dropdown.open {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
color: #5a5f68;
}
.o_main_header > .o_main_header_main > .o_header_buttons .dropdown.open .dropdown-menu {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
margin-top: 0;
border: none;
border-radius: 0;
@@ -10560,7 +10560,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
}
.o_main_header.o_scrolled:not(.o_transparent_blocked),
.o_main_header.o_sub_opened {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
color: #5a5f68;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
}
@@ -10584,7 +10584,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
box-shadow: 0 1px 1px rgba(90, 95, 104, 0.1);
}
.o_main_header.o_scrolled .o_sub_nav {
- background: #fcfcfc;
+ background: #FCFCFC;
}
.o_main_header.o_scrolled .o_sub_nav > .container .navbar-header > span {
-webkit-transform: translate(0, 0);
@@ -10603,7 +10603,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
position: relative;
height: auto;
min-height: 50px;
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
color: #5a5f68;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
}
@@ -10648,7 +10648,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_header_buttons {
float: none;
- background-color: #25252d;
+ background-color: #25252D;
padding: 0 15px;
box-sizing: content-box;
margin: 50px -15px 0;
@@ -10659,7 +10659,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_header_buttons > * {
overflow: visible!important;
- color: #fcfcfc;
+ color: #FCFCFC;
float: none;
display: inline;
}
@@ -10667,7 +10667,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
display: initial!important;
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_header_buttons > * .dropdown-toggle {
- color: #fcfcfc !important;
+ color: #FCFCFC !important;
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_header_buttons > * .dropdown-menu {
position: relative;
@@ -10683,7 +10683,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
display: block;
margin-bottom: 5px;
padding: 10px;
- color: #fcfcfc !important;
+ color: #FCFCFC !important;
font-size: 0.8em;
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_header_buttons > * .dropdown-menu > li > a:hover {
@@ -10691,7 +10691,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_primary_nav {
display: block;
- background-color: #25252d;
+ background-color: #25252D;
color: white;
margin: 0 -15px 0;
padding: 0;
@@ -10704,7 +10704,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
color: white;
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_primary_nav li {
- border: 0 solid #33343d;
+ border: 0 solid #33343D;
border-width: 1px 5px 0;
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_primary_nav > li {
@@ -10750,7 +10750,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_primary_nav > li.dropdown > .o_secondary_nav > .container > .row > div > .o_nav_app_family {
height: 50px;
line-height: 50px;
- background-color: #33343d;
+ background-color: #33343D;
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_primary_nav > li.dropdown > .o_secondary_nav > .container > .row > div > .o_nav_app_family > * {
display: none;
@@ -10760,7 +10760,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
}
.o_main_header.o_mobile_menu_opened > .o_main_header_main > .o_primary_nav > li.dropdown > .o_secondary_nav > .o_store_link {
position: relative;
- border: 0 solid #33343d;
+ border: 0 solid #33343D;
border-width: 14px 0 0;
padding-top: 14px;
line-height: 1.5;
@@ -10772,7 +10772,7 @@ article.doc-toc .toctree-wrapper > ul > li > span {
width: 100%;
height: 20px;
margin-bottom: -5px;
- background-color: #33343d;
+ background-color: #33343D;
}
}
body > footer {
@@ -10781,7 +10781,7 @@ body > footer {
width: 100%;
height: auto;
min-height: 300px;
- background-color: #282f33;
+ background-color: #282F33;
color: #888888;
z-index: 1;
}
@@ -10809,14 +10809,14 @@ body > footer a:focus {
color: white;
}
body > footer .dropdown-menu a {
- color: #6b6d70;
+ color: #6B6D70;
}
body > footer .dropdown-menu a:hover,
body > footer .dropdown-menu a:focus {
color: #5f6063;
}
body > footer .dropdown-menu a:active {
- color: #ffffff;
+ color: #fff;
}
body > footer .divider {
margin: 10px 0;
@@ -11011,7 +11011,7 @@ body > footer .o_footer_bottom .social-links .fa {
}
.navbar-aside ul ul {
max-height: 0;
- background-color: #f2f2f2;
+ background-color: #F2F2F2;
padding-left: 15px;
overflow: hidden;
-webkit-transition: max-height 0.3s cubic-bezier(0.55, 0, 0.1, 1);
@@ -11021,7 +11021,7 @@ body > footer .o_footer_bottom .social-links .fa {
.navbar-aside li.active,
.navbar-aside li.active:hover,
.navbar-aside li.active:focus {
- border-color: #f2f2f2;
+ border-color: #F2F2F2;
background: transparent;
}
.navbar-aside li.active > a,
@@ -11033,7 +11033,7 @@ body > footer .o_footer_bottom .social-links .fa {
max-height: 1000px;
}
.navbar-aside > ul > li {
- border-bottom: 1px solid #f2f2f2;
+ border-bottom: 1px solid #F2F2F2;
background: white;
}
.navbar-aside > ul > li > a {
@@ -11263,8 +11263,8 @@ body > footer .o_footer_bottom .social-links .fa {
}
}
.o_main_header.o_mobile_menu_opened .mdi-navigation-menu {
- background-color: #282f33;
- color: #f2f2f2;
+ background-color: #282F33;
+ color: #F2F2F2;
}
@media screen and (min-width: 768px) {
.o_main_header > .o_sub_nav #o_sub-menu .o_breadcrumb {
@@ -11361,13 +11361,13 @@ body > footer .o_footer_bottom .social-links .fa {
padding: 0 15px;
margin: 0;
height: 38px;
- background-color: #f2f2f2;
+ background-color: #F2F2F2;
}
.o_main_header > .o_sub_nav .navbar-toggle span {
line-height: 38px;
}
.o_main_header > .o_sub_nav .navbar-toggle.collapsed {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
}
.o_main_header > .o_sub_nav .collapse .nav {
margin-right: 0;
@@ -11413,7 +11413,7 @@ body > footer .o_footer_bottom .social-links .fa {
}
@media screen and (min-width: 768px) {
.o_main_header > .o_sub_nav .collapse .nav > li.dropdown.open {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
}
}
.o_main_header > .o_sub_nav .collapse .nav > li.dropdown.open .dropdown-toggle {
@@ -11428,7 +11428,7 @@ body > footer .o_footer_bottom .social-links .fa {
border-radius: 0;
text-align: left;
min-width: 100%;
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
padding-bottom: 12.5px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
}
@@ -11463,7 +11463,7 @@ body > footer .o_footer_bottom .social-links .fa {
}
@media screen and (min-width: 768px) {
.o_main_header > .o_sub_nav .collapse .nav > li.dropdown.open .dropdown-menu {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
}
}
.o_main_header > .o_sub_nav .collapse .nav > li.dropdown.o_dropdown_mobile_nav {
@@ -11526,7 +11526,7 @@ body > footer .o_footer_bottom .social-links .fa {
}
@media screen and (min-width: 768px) {
.o_main_header > .o_sub_nav li.dropdown.open {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
}
}
.o_main_header > .o_sub_nav li.dropdown.open .dropdown-toggle {
@@ -11541,7 +11541,7 @@ body > footer .o_footer_bottom .social-links .fa {
border-radius: 0;
text-align: left;
min-width: 100%;
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
padding-bottom: 12.5px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
}
@@ -11576,7 +11576,7 @@ body > footer .o_footer_bottom .social-links .fa {
}
@media screen and (min-width: 768px) {
.o_main_header > .o_sub_nav li.dropdown.open .dropdown-menu {
- background-color: #fcfcfc;
+ background-color: #FCFCFC;
}
}
.o_main_header > .o_sub_nav li.dropdown.o_dropdown_mobile_nav {
@@ -11660,7 +11660,7 @@ body > footer .o_footer_bottom .social-links .fa {
#wrap {
z-index: 1;
position: relative;
- background: #f9f9f9;
+ background: #F9F9F9;
padding-bottom: 40px;
}
main {
@@ -11669,7 +11669,7 @@ main {
margin: -10px auto 0;
display: block;
border-radius: 2px;
- background-color: #ffffff;
+ background-color: #fff;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
}
main.deep_hover:hover {
@@ -11700,7 +11700,7 @@ main.index .card {
overflow: hidden;
margin-bottom: 20px;
min-height: 200px;
- background-color: #ffffff;
+ background-color: #fff;
will-change: transform;
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 2px 0 rgba(0, 0, 0, 0.05);
-webkit-transform: scale3d(0, 0, 0) translate3d(50px, 0, 0);
@@ -11808,11 +11808,11 @@ main.index.animating .card {
}
.card-img {
background: #75526b;
- background: -webkit-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: -moz-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: -ms-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: -o-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: linear-gradient(99deg, #875a7b 10%, #62495b 90%);
+ background: -webkit-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: -moz-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: -ms-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: -o-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: linear-gradient(99deg, #875A7B 10%, #62495B 90%);
position: absolute;
top: 0;
left: auto;
@@ -11832,11 +11832,11 @@ main.index.animating .card {
overflow: hidden;
margin: 0;
background: #75526b;
- background: -webkit-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: -moz-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: -ms-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: -o-linear-gradient(99deg, #875a7b 10%, #62495b 90%);
- background: linear-gradient(99deg, #875a7b 10%, #62495b 90%);
+ background: -webkit-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: -moz-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: -ms-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: -o-linear-gradient(99deg, #875A7B 10%, #62495B 90%);
+ background: linear-gradient(99deg, #875A7B 10%, #62495B 90%);
-webkit-animation: fadeIn 1s;
-o-animation: fadeIn 1s;
animation: fadeIn 1s;
@@ -11974,7 +11974,7 @@ main.index.animating .card {
}
}
.card.top.has_banner {
- background: #282f33;
+ background: #282F33;
}
.card.top.has_banner:before {
display: none;
@@ -12047,7 +12047,7 @@ main .alert > ul {
}
main .alert > .alert-title,
main .alert > h3 {
- color: #4b9eb6;
+ color: #4B9EB6;
}
main .alert > .alert-title:before,
main .alert > h3:before {
@@ -12095,7 +12095,7 @@ main .alert.alert-success {
}
main .alert.alert-success > .alert-title,
main .alert.alert-success > h3 {
- color: #50af51;
+ color: #50AF51;
}
main .alert.alert-success > .alert-title:before,
main .alert.alert-success > h3:before {
@@ -12111,7 +12111,7 @@ main .alert.alert-info > .alert-title,
main .alert.tip > .alert-title,
main .alert.alert-info > h3,
main .alert.tip > h3 {
- color: #4b9eb6;
+ color: #4B9EB6;
}
main .alert.alert-info > .alert-title:before,
main .alert.tip > .alert-title:before,
@@ -12129,7 +12129,7 @@ main .alert.alert-warning > .alert-title,
main .alert.warning > .alert-title,
main .alert.alert-warning > h3,
main .alert.warning > h3 {
- color: #f0ad4e;
+ color: #F0AD4E;
}
main .alert.alert-warning > .alert-title:before,
main .alert.warning > .alert-title:before,
@@ -12144,7 +12144,7 @@ main .alert.alert-danger {
}
main .alert.alert-danger > .alert-title,
main .alert.alert-danger > h3 {
- color: #d9534f;
+ color: #D9534F;
}
main .alert.alert-danger > .alert-title:before,
main .alert.alert-danger > h3:before {
@@ -12157,7 +12157,7 @@ main .alert.alert-exercise {
}
main .alert.alert-exercise > .alert-title,
main .alert.alert-exercise > h3 {
- color: #938e94;
+ color: #938E94;
}
main .alert.alert-exercise > .alert-title:before,
main .alert.alert-exercise > h3:before {
@@ -12179,7 +12179,7 @@ main .alert.alert-go_to a {
}
main .alert.alert-go_to > .alert-title,
main .alert.alert-go_to > h3 {
- color: #4b9eb6;
+ color: #4B9EB6;
}
main .alert.alert-go_to > .alert-title:before,
main .alert.alert-go_to > h3:before {
@@ -12214,6 +12214,7 @@ span.menuselection {
}
dt {
margin: .5em 0 .3em;
+ font-weight: normal;
}
blockquote {
font-family: Georgia, "Times New Roman", Times, serif;
@@ -12283,6 +12284,8 @@ table,
.table {
font-size: 0.9em;
}
+.code-namespace,
+.code-module,
.code-class,
.code-staticmethod,
.code-classmethod,
@@ -12293,6 +12296,8 @@ table,
padding-left: 20px;
margin-bottom: 2em;
}
+.code-namespace > h6,
+.code-module > h6,
.code-class > h6,
.code-staticmethod > h6,
.code-classmethod > h6,
@@ -12303,6 +12308,8 @@ table,
margin-left: -20px;
margin-bottom: 0.3em;
}
+.code-namespace > h6 .viewcode-link,
+.code-module > h6 .viewcode-link,
.code-class > h6 .viewcode-link,
.code-staticmethod > h6 .viewcode-link,
.code-classmethod > h6 .viewcode-link,
@@ -12313,6 +12320,8 @@ table,
display: none;
float: right;
}
+.code-namespace > h6:hover .viewcode-link,
+.code-module > h6:hover .viewcode-link,
.code-class > h6:hover .viewcode-link,
.code-staticmethod > h6:hover .viewcode-link,
.code-classmethod > h6:hover .viewcode-link,
@@ -12322,6 +12331,8 @@ table,
.code-data > h6:hover .viewcode-link {
display: inline;
}
+.code-namespace p,
+.code-module p,
.code-class p,
.code-staticmethod p,
.code-classmethod p,
@@ -12340,7 +12351,7 @@ table,
}
}
article.doc-body {
- background: #ffffff;
+ background: #fff;
}
article.doc-body section.doc-content:first-of-type > p:first-child {
margin-bottom: 21px;
diff --git a/doc/_extensions/odoo_ext/static/style.less b/doc/_extensions/odoo_ext/static/style.less
index 5fd9fe17804..89d287d487e 100644
--- a/doc/_extensions/odoo_ext/static/style.less
+++ b/doc/_extensions/odoo_ext/static/style.less
@@ -621,7 +621,7 @@ span.menuselection{
background:transparent;
}
-dt { margin: .5em 0 .3em;}
+dt { margin: .5em 0 .3em; font-weight: normal; }
blockquote {
font-family: @font-family-serif;
@@ -700,7 +700,7 @@ table, .table {
font-size: 0.9em;
}
-.code-class,
+.code-namespace, .code-module, .code-class,
.code-staticmethod, .code-classmethod, .code-method, .code-function,
.code-attribute, .code-data {
// indents *all* content
diff --git a/doc/_extensions/pyjsparser/LICENSE b/doc/_extensions/pyjsparser/LICENSE
new file mode 100644
index 00000000000..5f5aff9f636
--- /dev/null
+++ b/doc/_extensions/pyjsparser/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Piotr Dabkowski
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/doc/_extensions/pyjsparser/README b/doc/_extensions/pyjsparser/README
new file mode 100644
index 00000000000..574593bbf2e
--- /dev/null
+++ b/doc/_extensions/pyjsparser/README
@@ -0,0 +1,4 @@
+Vendoring of pyjsparser to add comments support as we need them for autojsdoc.
+
+Alternative: use https://github.com/Kronuz/esprima-python instead, seems to
+support comments though may be attaching them differently.
diff --git a/doc/_extensions/pyjsparser/__init__.py b/doc/_extensions/pyjsparser/__init__.py
new file mode 100644
index 00000000000..19036bddbf1
--- /dev/null
+++ b/doc/_extensions/pyjsparser/__init__.py
@@ -0,0 +1,4 @@
+s__all__ = ['PyJsParser', 'parse', 'JsSyntaxError']
+__author__ = 'Piotr Dabkowski'
+__version__ = '2.2.0'
+from .parser import PyJsParser, parse, JsSyntaxError
diff --git a/doc/_extensions/pyjsparser/parser.py b/doc/_extensions/pyjsparser/parser.py
new file mode 100644
index 00000000000..78ebe14e6ec
--- /dev/null
+++ b/doc/_extensions/pyjsparser/parser.py
@@ -0,0 +1,2928 @@
+# The MIT License
+#
+# Copyright 2014, 2015 Piotr Dabkowski
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the 'Software'),
+# to deal in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+# the Software, and to permit persons to whom the Software is furnished to do so, subject
+# to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or
+# substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+from __future__ import unicode_literals
+from .pyjsparserdata import *
+from .std_nodes import *
+from pprint import pprint
+import sys
+
+__all__ = ['PyJsParser', 'parse', 'ENABLE_JS2PY_ERRORS', 'ENABLE_PYIMPORT', 'JsSyntaxError']
+REGEXP_SPECIAL_SINGLE = ('\\', '^', '$', '*', '+', '?', '.', '[', ']', '(', ')', '{', '{', '|', '-')
+ENABLE_PYIMPORT = False
+ENABLE_JS2PY_ERRORS = False
+
+PY3 = sys.version_info >= (3,0)
+
+if PY3:
+ basestring = str
+ long = int
+ xrange = range
+ unicode = str
+
+ESPRIMA_VERSION = '2.2.0'
+DEBUG = False
+# Small naming convention changes
+# len -> leng
+# id -> d
+# type -> typ
+# str -> st
+true = True
+false = False
+null = None
+
+
+class PyJsParser:
+ """ Usage:
+ parser = PyJsParser()
+ parser.parse('var JavaScriptCode = 5.1')
+ """
+
+ def __init__(self):
+ self.clean()
+
+ def test(self, code):
+ pprint(self.parse(code))
+
+ def clean(self):
+ self.strict = None
+ self.sourceType = None
+ self.index = 0
+ self.lineNumber = 1
+ self.lineStart = 0
+ self.hasLineTerminator = None
+ self.lastIndex = None
+ self.lastLineNumber = None
+ self.lastLineStart = None
+ self.startIndex = None
+ self.startLineNumber = None
+ self.startLineStart = None
+ self.scanning = None
+ self.lookahead = None
+ self.state = None
+ self.extra = None
+ self.isBindingElement = None
+ self.isAssignmentTarget = None
+ self.firstCoverInitializedNameError = None
+
+ # 7.4 Comments
+
+ def skipSingleLineComment(self, offset):
+ start = self.index - offset;
+ while self.index < self.length:
+ ch = self.source[self.index];
+ self.index += 1
+ if isLineTerminator(ch):
+ if (ord(ch) == 13 and ord(self.source[self.index]) == 10):
+ self.index += 1
+ self.lineNumber += 1
+ self.hasLineTerminator = True
+ self.lineStart = self.index
+ return {
+ 'type': 'Line',
+ 'value': self.source[start + offset:self.index-2],
+ 'leading': True,
+ 'trailing': False,
+ 'loc': None,
+ }
+
+ def skipMultiLineComment(self):
+ start = self.index
+ while self.index < self.length:
+ ch = ord(self.source[self.index])
+ if isLineTerminator(ch):
+ if (ch == 0x0D and ord(self.source[self.index + 1]) == 0x0A):
+ self.index += 1
+ self.lineNumber += 1
+ self.index += 1
+ self.hasLineTerminator = True
+ self.lineStart = self.index
+ elif ch == 0x2A:
+ # Block comment ends with '*/'.
+ if ord(self.source[self.index + 1]) == 0x2F:
+ self.index += 2
+ return {
+ 'type': 'Block',
+ 'value': self.source[start:self.index-2],
+ 'leading': True,
+ 'trailing': False,
+ 'loc': None,
+ }
+ self.index += 1
+ else:
+ self.index += 1
+ self.tolerateUnexpectedToken()
+
+ def skipComment(self):
+ self.hasLineTerminator = False
+ startIndex = self.index
+ start = (self.index == 0)
+ comments = []
+ while self.index < self.length:
+ ch = ord(self.source[self.index])
+ if isWhiteSpace(ch):
+ self.index += 1
+ elif isLineTerminator(ch):
+ self.hasLineTerminator = True
+ self.index += 1
+ if (ch == 0x0D and ord(self.source[self.index]) == 0x0A):
+ self.index += 1
+ self.lineNumber += 1
+ self.lineStart = self.index
+ start = True
+ elif (ch == 0x2F): # U+002F is '/'
+ ch = ord(self.source[self.index + 1])
+ if (ch == 0x2F):
+ self.index += 2
+ comments.append(self.skipSingleLineComment(2))
+ start = True
+ elif (ch == 0x2A): # U+002A is '*'
+ self.index += 2
+ comments.append(self.skipMultiLineComment())
+ else:
+ break
+ elif (start and ch == 0x2D): # U+002D is '-'
+ # U+003E is '>'
+ if (ord(self.source[self.index + 1]) == 0x2D) and (ord(self.source[self.index + 2]) == 0x3E):
+ # '-->' is a single-line comment
+ self.index += 3
+ self.skipSingleLineComment(3)
+ else:
+ break
+ elif (ch == 0x3C): # U+003C is '<'
+ if self.source[self.index + 1: self.index + 4] == '!--':
+ # <!--
+ self.index += 4
+ self.skipSingleLineComment(4)
+ else:
+ break
+ else:
+ break
+ return filter(None, comments)
+
+ def scanHexEscape(self, prefix):
+ code = 0
+ leng = 4 if (prefix == 'u') else 2
+ for i in xrange(leng):
+ if self.index < self.length and isHexDigit(self.source[self.index]):
+ ch = self.source[self.index]
+ self.index += 1
+ code = code * 16 + HEX_CONV[ch]
+ else:
+ return ''
+ return unichr(code)
+
+ def scanUnicodeCodePointEscape(self):
+ ch = self.source[self.index]
+ code = 0
+ # At least, one hex digit is required.
+ if ch == '}':
+ self.throwUnexpectedToken()
+ while (self.index < self.length):
+ ch = self.source[self.index]
+ self.index += 1
+ if not isHexDigit(ch):
+ break
+ code = code * 16 + HEX_CONV[ch]
+ if code > 0x10FFFF or ch != '}':
+ self.throwUnexpectedToken()
+ # UTF-16 Encoding
+ if (code <= 0xFFFF):
+ return unichr(code)
+ cu1 = ((code - 0x10000) >> 10) + 0xD800;
+ cu2 = ((code - 0x10000) & 1023) + 0xDC00;
+ return unichr(cu1) + unichr(cu2)
+
+ def ccode(self, offset=0):
+ return ord(self.source[self.index + offset])
+
+ def log_err_case(self):
+ if not DEBUG:
+ return
+ print('INDEX', self.index)
+ print(self.source[self.index - 10:self.index + 10])
+ print('')
+
+ def at(self, loc):
+ return None if loc >= self.length else self.source[loc]
+
+ def substr(self, le, offset=0):
+ return self.source[self.index + offset:self.index + offset + le]
+
+ def getEscapedIdentifier(self):
+ d = self.source[self.index]
+ ch = ord(d)
+ self.index += 1
+ # '\u' (U+005C, U+0075) denotes an escaped character.
+ if (ch == 0x5C):
+ if (ord(self.source[self.index]) != 0x75):
+ self.throwUnexpectedToken()
+ self.index += 1
+ ch = self.scanHexEscape('u')
+ if not ch or ch == '\\' or not isIdentifierStart(ch[0]):
+ self.throwUnexpectedToken()
+ d = ch
+ while (self.index < self.length):
+ ch = self.ccode()
+ if not isIdentifierPart(ch):
+ break
+ self.index += 1
+ d += unichr(ch)
+
+ # '\u' (U+005C, U+0075) denotes an escaped character.
+ if (ch == 0x5C):
+ d = d[0: len(d) - 1]
+ if (self.ccode() != 0x75):
+ self.throwUnexpectedToken()
+ self.index += 1
+ ch = self.scanHexEscape('u');
+ if (not ch or ch == '\\' or not isIdentifierPart(ch[0])):
+ self.throwUnexpectedToken()
+ d += ch
+ return d
+
+ def getIdentifier(self):
+ start = self.index
+ self.index += 1
+ while (self.index < self.length):
+ ch = self.ccode()
+ if (ch == 0x5C):
+ # Blackslash (U+005C) marks Unicode escape sequence.
+ self.index = start
+ return self.getEscapedIdentifier()
+ if (isIdentifierPart(ch)):
+ self.index += 1
+ else:
+ break
+ return self.source[start: self.index]
+
+ def scanIdentifier(self):
+ start = self.index
+
+ # Backslash (U+005C) starts an escaped character.
+ d = self.getEscapedIdentifier() if (self.ccode() == 0x5C) else self.getIdentifier()
+
+ # There is no keyword or literal with only one character.
+ # Thus, it must be an identifier.
+ if (len(d) == 1):
+ type = Token.Identifier
+ elif (isKeyword(d)):
+ type = Token.Keyword
+ elif (d == 'null'):
+ type = Token.NullLiteral
+ elif (d == 'true' or d == 'false'):
+ type = Token.BooleanLiteral
+ else:
+ type = Token.Identifier;
+ return {
+ 'type': type,
+ 'value': d,
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': start,
+ 'end': self.index
+ }
+
+ # 7.7 Punctuators
+
+ def scanPunctuator(self):
+ token = {
+ 'type': Token.Punctuator,
+ 'value': '',
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': self.index,
+ 'end': self.index
+ }
+ # Check for most common single-character punctuators.
+ st = self.source[self.index]
+ if st == '{':
+ self.state['curlyStack'].append('{')
+ self.index += 1
+ elif st == '}':
+ self.index += 1
+ self.state['curlyStack'].pop()
+ elif st in ('.', '(', ')', ';', ',', '[', ']', ':', '?', '~'):
+ self.index += 1
+ else:
+ # 4-character punctuator.
+ st = self.substr(4)
+ if (st == '>>>='):
+ self.index += 4
+ else:
+ # 3-character punctuators.
+ st = st[0:3]
+ if st in ('===', '!==', '>>>', '<<=', '>>='):
+ self.index += 3
+ else:
+ # 2-character punctuators.
+ st = st[0:2]
+ if st in ('&&', '||', '==', '!=', '+=', '-=', '*=', '/=', '++', '--', '<<', '>>', '&=', '|=', '^=',
+ '%=', '<=', '>=', '=>'):
+ self.index += 2
+ else:
+ # 1-character punctuators.
+ st = self.source[self.index]
+ if st in ('<', '>', '=', '!', '+', '-', '*', '%', '&', '|', '^', '/'):
+ self.index += 1
+ if self.index == token['start']:
+ self.throwUnexpectedToken()
+ token['end'] = self.index;
+ token['value'] = st
+ return token
+
+ # 7.8.3 Numeric Literals
+
+ def scanHexLiteral(self, start):
+ number = ''
+ while (self.index < self.length):
+ if (not isHexDigit(self.source[self.index])):
+ break
+ number += self.source[self.index]
+ self.index += 1
+ if not number:
+ self.throwUnexpectedToken()
+ if isIdentifierStart(self.ccode()):
+ self.throwUnexpectedToken()
+ return {
+ 'type': Token.NumericLiteral,
+ 'value': int(number, 16),
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': start,
+ 'end': self.index}
+
+ def scanBinaryLiteral(self, start):
+ number = ''
+ while (self.index < self.length):
+ ch = self.source[self.index]
+ if (ch != '0' and ch != '1'):
+ break
+ number += self.source[self.index]
+ self.index += 1
+
+ if not number:
+ # only 0b or 0B
+ self.throwUnexpectedToken()
+ if (self.index < self.length):
+ ch = self.source[self.index]
+ # istanbul ignore else
+ if (isIdentifierStart(ch) or isDecimalDigit(ch)):
+ self.throwUnexpectedToken();
+ return {
+ 'type': Token.NumericLiteral,
+ 'value': int(number, 2),
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': start,
+ 'end': self.index}
+
+ def scanOctalLiteral(self, prefix, start):
+ if isOctalDigit(prefix):
+ octal = True
+ number = '0' + self.source[self.index]
+ self.index += 1
+ else:
+ octal = False
+ self.index += 1
+ number = ''
+ while (self.index < self.length):
+ if (not isOctalDigit(self.source[self.index])):
+ break
+ number += self.source[self.index]
+ self.index += 1
+ if (not octal and not number):
+ # only 0o or 0O
+ self.throwUnexpectedToken()
+ if (isIdentifierStart(self.ccode()) or isDecimalDigit(self.ccode())):
+ self.throwUnexpectedToken()
+ return {
+ 'type': Token.NumericLiteral,
+ 'value': int(number, 8),
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': start,
+ 'end': self.index}
+
+ def octalToDecimal(self, ch):
+ # \0 is not octal escape sequence
+ octal = (ch != '0')
+ code = int(ch, 8)
+
+ if (self.index < self.length and isOctalDigit(self.source[self.index])):
+ octal = True
+ code = code * 8 + int(self.source[self.index], 8)
+ self.index += 1
+
+ # 3 digits are only allowed when string starts
+ # with 0, 1, 2, 3
+ if (ch in '0123' and self.index < self.length and isOctalDigit(self.source[self.index])):
+ code = code * 8 + int((self.source[self.index]), 8)
+ self.index += 1
+ return {
+ 'code': code,
+ 'octal': octal}
+
+ def isImplicitOctalLiteral(self):
+ # Implicit octal, unless there is a non-octal digit.
+ # (Annex B.1.1 on Numeric Literals)
+ for i in xrange(self.index + 1, self.length):
+ ch = self.source[i];
+ if (ch == '8' or ch == '9'):
+ return False;
+ if (not isOctalDigit(ch)):
+ return True
+ return True
+
+ def scanNumericLiteral(self):
+ ch = self.source[self.index]
+ assert isDecimalDigit(ch) or (ch == '.'), 'Numeric literal must start with a decimal digit or a decimal point'
+ start = self.index
+ number = ''
+ if ch != '.':
+ number = self.source[self.index]
+ self.index += 1
+ ch = self.source[self.index]
+ # Hex number starts with '0x'.
+ # Octal number starts with '0'.
+ # Octal number in ES6 starts with '0o'.
+ # Binary number in ES6 starts with '0b'.
+ if (number == '0'):
+ if (ch == 'x' or ch == 'X'):
+ self.index += 1
+ return self.scanHexLiteral(start);
+ if (ch == 'b' or ch == 'B'):
+ self.index += 1
+ return self.scanBinaryLiteral(start)
+ if (ch == 'o' or ch == 'O'):
+ return self.scanOctalLiteral(ch, start)
+ if (isOctalDigit(ch)):
+ if (self.isImplicitOctalLiteral()):
+ return self.scanOctalLiteral(ch, start);
+ while (isDecimalDigit(self.ccode())):
+ number += self.source[self.index]
+ self.index += 1
+ ch = self.source[self.index];
+ if (ch == '.'):
+ number += self.source[self.index]
+ self.index += 1
+ while (isDecimalDigit(self.source[self.index])):
+ number += self.source[self.index]
+ self.index += 1
+ ch = self.source[self.index]
+ if (ch == 'e' or ch == 'E'):
+ number += self.source[self.index]
+ self.index += 1
+ ch = self.source[self.index]
+ if (ch == '+' or ch == '-'):
+ number += self.source[self.index]
+ self.index += 1
+ if (isDecimalDigit(self.source[self.index])):
+ while (isDecimalDigit(self.source[self.index])):
+ number += self.source[self.index]
+ self.index += 1
+ else:
+ self.throwUnexpectedToken()
+ if (isIdentifierStart(self.source[self.index])):
+ self.throwUnexpectedToken();
+ return {
+ 'type': Token.NumericLiteral,
+ 'value': float(number),
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': start,
+ 'end': self.index}
+
+ # 7.8.4 String Literals
+
+ def _interpret_regexp(self, string, flags):
+ '''Perform sctring escape - for regexp literals'''
+ self.index = 0
+ self.length = len(string)
+ self.source = string
+ self.lineNumber = 0
+ self.lineStart = 0
+ octal = False
+ st = ''
+ inside_square = 0
+ while (self.index < self.length):
+ template = '[%s]' if not inside_square else '%s'
+ ch = self.source[self.index]
+ self.index += 1
+ if ch == '\\':
+ ch = self.source[self.index]
+ self.index += 1
+ if (not isLineTerminator(ch)):
+ if ch == 'u':
+ digs = self.source[self.index:self.index + 4]
+ if len(digs) == 4 and all(isHexDigit(d) for d in digs):
+ st += template % unichr(int(digs, 16))
+ self.index += 4
+ else:
+ st += 'u'
+ elif ch == 'x':
+ digs = self.source[self.index:self.index + 2]
+ if len(digs) == 2 and all(isHexDigit(d) for d in digs):
+ st += template % unichr(int(digs, 16))
+ self.index += 2
+ else:
+ st += 'x'
+ # special meaning - single char.
+ elif ch == '0':
+ st += '\\0'
+ elif ch == 'n':
+ st += '\\n'
+ elif ch == 'r':
+ st += '\\r'
+ elif ch == 't':
+ st += '\\t'
+ elif ch == 'f':
+ st += '\\f'
+ elif ch == 'v':
+ st += '\\v'
+
+ # unescape special single characters like . so that they are interpreted literally
+ elif ch in REGEXP_SPECIAL_SINGLE:
+ st += '\\' + ch
+
+ # character groups
+ elif ch == 'b':
+ st += '\\b'
+ elif ch == 'B':
+ st += '\\B'
+ elif ch == 'w':
+ st += '\\w'
+ elif ch == 'W':
+ st += '\\W'
+ elif ch == 'd':
+ st += '\\d'
+ elif ch == 'D':
+ st += '\\D'
+ elif ch == 's':
+ st += template % u' \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff'
+ elif ch == 'S':
+ st += template % u'\u0000-\u0008\u000e-\u001f\u0021-\u009f\u00a1-\u167f\u1681-\u180d\u180f-\u1fff\u200b-\u2027\u202a-\u202e\u2030-\u205e\u2060-\u2fff\u3001-\ufefe\uff00-\uffff'
+ else:
+ if isDecimalDigit(ch):
+ num = ch
+ while self.index < self.length and isDecimalDigit(self.source[self.index]):
+ num += self.source[self.index]
+ self.index += 1
+ st += '\\' + num
+
+ else:
+ st += ch # DONT ESCAPE!!!
+ else:
+ self.lineNumber += 1
+ if (ch == '\r' and self.source[self.index] == '\n'):
+ self.index += 1
+ self.lineStart = self.index
+ else:
+ if ch == '[':
+ inside_square = True
+ elif ch == ']':
+ inside_square = False
+ st += ch
+ # print string, 'was transformed to', st
+ return st
+
+ def scanStringLiteral(self):
+ st = ''
+ octal = False
+
+ quote = self.source[self.index]
+ assert quote == '\'' or quote == '"', 'String literal must starts with a quote'
+ start = self.index;
+ self.index += 1
+
+ while (self.index < self.length):
+ ch = self.source[self.index]
+ self.index += 1
+ if (ch == quote):
+ quote = ''
+ break
+ elif (ch == '\\'):
+ ch = self.source[self.index]
+ self.index += 1
+ if (not isLineTerminator(ch)):
+ if ch in 'ux':
+ if (self.source[self.index] == '{'):
+ self.index += 1
+ st += self.scanUnicodeCodePointEscape()
+ else:
+ unescaped = self.scanHexEscape(ch)
+ if (not unescaped):
+ self.throwUnexpectedToken() # with throw I don't know whats the difference
+ st += unescaped
+ elif ch == 'n':
+ st += '\n';
+ elif ch == 'r':
+ st += '\r';
+ elif ch == 't':
+ st += '\t';
+ elif ch == 'b':
+ st += '\b';
+ elif ch == 'f':
+ st += '\f';
+ elif ch == 'v':
+ st += '\x0B'
+ # elif ch in '89':
+ # self.throwUnexpectedToken() # again with throw....
+ else:
+ if isOctalDigit(ch):
+ octToDec = self.octalToDecimal(ch)
+ octal = octToDec.get('octal') or octal
+ st += unichr(octToDec['code'])
+ else:
+ st += ch
+ else:
+ self.lineNumber += 1
+ if (ch == '\r' and self.source[self.index] == '\n'):
+ self.index += 1
+ self.lineStart = self.index
+ elif isLineTerminator(ch):
+ break
+ else:
+ st += ch;
+ if (quote != ''):
+ self.throwUnexpectedToken()
+ return {
+ 'type': Token.StringLiteral,
+ 'value': st,
+ 'octal': octal,
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.startLineStart,
+ 'start': start,
+ 'end': self.index}
+
+ def scanTemplate(self):
+ cooked = ''
+ terminated = False
+ tail = False
+ start = self.index
+ head = (self.source[self.index] == '`')
+ rawOffset = 2
+
+ self.index += 1
+
+ while (self.index < self.length):
+ ch = self.source[self.index]
+ self.index += 1
+ if (ch == '`'):
+ rawOffset = 1;
+ tail = True
+ terminated = True
+ break
+ elif (ch == '$'):
+ if (self.source[self.index] == '{'):
+ self.state['curlyStack'].append('${')
+ self.index += 1
+ terminated = True
+ break;
+ cooked += ch
+ elif (ch == '\\'):
+ ch = self.source[self.index]
+ self.index += 1
+ if (not isLineTerminator(ch)):
+ if ch == 'n':
+ cooked += '\n'
+ elif ch == 'r':
+ cooked += '\r'
+ elif ch == 't':
+ cooked += '\t'
+ elif ch in 'ux':
+ if (self.source[self.index] == '{'):
+ self.index += 1
+ cooked += self.scanUnicodeCodePointEscape()
+ else:
+ restore = self.index
+ unescaped = self.scanHexEscape(ch)
+ if (unescaped):
+ cooked += unescaped
+ else:
+ self.index = restore
+ cooked += ch
+ elif ch == 'b':
+ cooked += '\b'
+ elif ch == 'f':
+ cooked += '\f'
+ elif ch == 'v':
+ cooked += '\v'
+ else:
+ if (ch == '0'):
+ if isDecimalDigit(self.ccode()):
+ # Illegal: \01 \02 and so on
+ self.throwError(Messages.TemplateOctalLiteral)
+ cooked += '\0'
+ elif (isOctalDigit(ch)):
+ # Illegal: \1 \2
+ self.throwError(Messages.TemplateOctalLiteral)
+ else:
+ cooked += ch
+ else:
+ self.lineNumber += 1
+ if (ch == '\r' and self.source[self.index] == '\n'):
+ self.index += 1
+ self.lineStart = self.index
+ elif (isLineTerminator(ch)):
+ self.lineNumber += 1
+ if (ch == '\r' and self.source[self.index] == '\n'):
+ self.index += 1
+ self.lineStart = self.index
+ cooked += '\n'
+ else:
+ cooked += ch;
+ if (not terminated):
+ self.throwUnexpectedToken()
+
+ if (not head):
+ self.state['curlyStack'].pop();
+
+ return {
+ 'type': Token.Template,
+ 'value': {
+ 'cooked': cooked,
+ 'raw': self.source[start + 1:self.index - rawOffset]},
+ 'head': head,
+ 'tail': tail,
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': start,
+ 'end': self.index}
+
+ def testRegExp(self, pattern, flags):
+ # todo: you should return python regexp object
+ return (pattern, flags)
+
+ def scanRegExpBody(self):
+ ch = self.source[self.index]
+ assert ch == '/', 'Regular expression literal must start with a slash'
+ st = ch
+ self.index += 1
+
+ classMarker = False
+ terminated = False
+ while (self.index < self.length):
+ ch = self.source[self.index]
+ self.index += 1
+ st += ch
+ if (ch == '\\'):
+ ch = self.source[self.index]
+ self.index += 1
+ # ECMA-262 7.8.5
+ if (isLineTerminator(ch)):
+ self.throwUnexpectedToken(None, Messages.UnterminatedRegExp)
+ st += ch
+ elif (isLineTerminator(ch)):
+ self.throwUnexpectedToken(None, Messages.UnterminatedRegExp)
+ elif (classMarker):
+ if (ch == ']'):
+ classMarker = False
+ else:
+ if (ch == '/'):
+ terminated = True
+ break
+ elif (ch == '['):
+ classMarker = True;
+ if (not terminated):
+ self.throwUnexpectedToken(None, Messages.UnterminatedRegExp)
+
+ # Exclude leading and trailing slash.
+ body = st[1:-1]
+ return {
+ 'value': body,
+ 'literal': st}
+
+ def scanRegExpFlags(self):
+ st = ''
+ flags = ''
+ while (self.index < self.length):
+ ch = self.source[self.index]
+ if (not isIdentifierPart(ch)):
+ break
+ self.index += 1
+ if (ch == '\\' and self.index < self.length):
+ ch = self.source[self.index]
+ if (ch == 'u'):
+ self.index += 1
+ restore = self.index
+ ch = self.scanHexEscape('u')
+ if (ch):
+ flags += ch
+ st += '\\u'
+ while restore < self.index:
+ st += self.source[restore]
+ restore += 1
+ else:
+ self.index = restore
+ flags += 'u'
+ st += '\\u'
+ self.tolerateUnexpectedToken()
+ else:
+ st += '\\'
+ self.tolerateUnexpectedToken()
+ else:
+ flags += ch
+ st += ch
+ return {
+ 'value': flags,
+ 'literal': st}
+
+ def scanRegExp(self, comments):
+ self.scanning = True
+ self.lookahead = None
+ comments.extend(self.skipComment())
+ start = self.index
+
+ body = self.scanRegExpBody()
+ flags = self.scanRegExpFlags()
+ value = self.testRegExp(body['value'], flags['value'])
+ scanning = False
+ return {
+ 'literal': body['literal'] + flags['literal'],
+ 'value': value,
+ 'regex': {
+ 'pattern': body['value'],
+ 'flags': flags['value']
+ },
+ 'start': start,
+ 'end': self.index,
+ 'comments': comments}
+
+ def collectRegex(self):
+ return self.scanRegExp(self.skipComment())
+
+ def isIdentifierName(self, token):
+ return token['type'] in (1, 3, 4, 5)
+
+ # def advanceSlash(self): ???
+
+ def advanceWithComments(self, comments):
+ token = self.advance()
+ token['comments'] = comments
+ return token
+
+ def advance(self):
+ if (self.index >= self.length):
+ return {
+ 'type': Token.EOF,
+ 'lineNumber': self.lineNumber,
+ 'lineStart': self.lineStart,
+ 'start': self.index,
+ 'end': self.index}
+ ch = self.ccode()
+
+ if isIdentifierStart(ch):
+ token = self.scanIdentifier()
+ if (self.strict and isStrictModeReservedWord(token['value'])):
+ token['type'] = Token.Keyword
+ return token
+ # Very common: ( and ) and ;
+ if (ch == 0x28 or ch == 0x29 or ch == 0x3B):
+ return self.scanPunctuator()
+
+ # String literal starts with single quote (U+0027) or double quote (U+0022).
+ if (ch == 0x27 or ch == 0x22):
+ return self.scanStringLiteral()
+
+ # Dot (.) U+002E can also start a floating-point number, hence the need
+ # to check the next character.
+ if (ch == 0x2E):
+ if (isDecimalDigit(self.ccode(1))):
+ return self.scanNumericLiteral()
+ return self.scanPunctuator();
+
+ if (isDecimalDigit(ch)):
+ return self.scanNumericLiteral()
+
+ # Slash (/) U+002F can also start a regex.
+ # if (extra.tokenize && ch == 0x2F):
+ # return advanceSlash();
+
+ # Template literals start with ` (U+0060) for template head
+ # or } (U+007D) for template middle or template tail.
+ if (ch == 0x60 or (ch == 0x7D and self.state['curlyStack'][len(self.state['curlyStack']) - 1] == '${')):
+ return self.scanTemplate()
+ return self.scanPunctuator();
+
+ # def collectToken(self):
+ # loc = {
+ # 'start': {
+ # 'line': self.lineNumber,
+ # 'column': self.index - self.lineStart}}
+ #
+ # token = self.advance()
+ #
+ # loc['end'] = {
+ # 'line': self.lineNumber,
+ # 'column': self.index - self.lineStart}
+ # if (token['type'] != Token.EOF):
+ # value = self.source[token['start']: token['end']]
+ # entry = {
+ # 'type': TokenName[token['type']],
+ # 'value': value,
+ # 'range': [token['start'], token['end']],
+ # 'loc': loc}
+ # if (token.get('regex')):
+ # entry['regex'] = {
+ # 'pattern': token['regex']['pattern'],
+ # 'flags': token['regex']['flags']}
+ # self.extra['tokens'].append(entry)
+ # return token;
+
+
+ def lex(self):
+ self.scanning = True
+
+ self.lastIndex = self.index
+ self.lastLineNumber = self.lineNumber
+ self.lastLineStart = self.lineStart
+
+ comments = self.skipComment()
+
+ token = self.lookahead
+
+ self.startIndex = self.index
+ self.startLineNumber = self.lineNumber
+ self.startLineStart = self.lineStart
+
+ self.lookahead = self.advanceWithComments(comments)
+ self.scanning = False
+ return token
+
+ def peek(self):
+ self.scanning = True
+
+ comments = self.skipComment()
+
+ self.lastIndex = self.index
+ self.lastLineNumber = self.lineNumber
+ self.lastLineStart = self.lineStart
+
+ self.startIndex = self.index
+ self.startLineNumber = self.lineNumber
+ self.startLineStart = self.lineStart
+
+ self.lookahead = self.advanceWithComments(comments)
+ self.scanning = False
+
+ def createError(self, line, pos, description):
+ global ENABLE_PYIMPORT
+ if ENABLE_JS2PY_ERRORS:
+ old_pyimport = ENABLE_PYIMPORT # ENABLE_PYIMPORT will be affected by js2py import
+ self.log_err_case()
+ try:
+ from js2py.base import ERRORS, Js, JsToPyException
+ except:
+ raise Exception("ENABLE_JS2PY_ERRORS was set to True, but Js2Py was not found!")
+ ENABLE_PYIMPORT = old_pyimport
+ error = ERRORS['SyntaxError']('Line ' + unicode(line) + ': ' + unicode(description))
+ error.put('index', Js(pos))
+ error.put('lineNumber', Js(line))
+ error.put('column', Js(pos - (self.lineStart if self.scanning else self.lastLineStart) + 1))
+ error.put('description', Js(description))
+ return JsToPyException(error)
+ else:
+ return JsSyntaxError('Line ' + unicode(line) + ': ' + unicode(description))
+
+
+ # Throw an exception
+
+ def throwError(self, messageFormat, *args):
+ msg = messageFormat % tuple(unicode(e) for e in args)
+ raise self.createError(self.lastLineNumber, self.lastIndex, msg);
+
+ def tolerateError(self, messageFormat, *args):
+ return self.throwError(messageFormat, *args)
+
+ # Throw an exception because of the token.
+
+ def unexpectedTokenError(self, token={}, message=''):
+ msg = message or Messages.UnexpectedToken
+ if (token):
+ typ = token['type']
+ if (not message):
+ if typ == Token.EOF:
+ msg = Messages.UnexpectedEOS
+ elif (typ == Token.Identifier):
+ msg = Messages.UnexpectedIdentifier
+ elif (typ == Token.NumericLiteral):
+ msg = Messages.UnexpectedNumber
+ elif (typ == Token.StringLiteral):
+ msg = Messages.UnexpectedString
+ elif (typ == Token.Template):
+ msg = Messages.UnexpectedTemplate
+ else:
+ msg = Messages.UnexpectedToken;
+ if (typ == Token.Keyword):
+ if (isFutureReservedWord(token['value'])):
+ msg = Messages.UnexpectedReserved
+ elif (self.strict and isStrictModeReservedWord(token['value'])):
+ msg = Messages.StrictReservedWord
+ value = token['value']['raw'] if (typ == Token.Template) else token.get('value')
+ else:
+ value = 'ILLEGAL'
+ msg = msg.replace('%s', unicode(value))
+
+ return (self.createError(token['lineNumber'], token['start'], msg) if (token and token.get('lineNumber')) else
+ self.createError(self.lineNumber if self.scanning else self.lastLineNumber,
+ self.index if self.scanning else self.lastIndex, msg))
+
+ def throwUnexpectedToken(self, token={}, message=''):
+ raise self.unexpectedTokenError(token, message)
+
+ def tolerateUnexpectedToken(self, token={}, message=''):
+ self.throwUnexpectedToken(token, message)
+
+ # Expect the next token to match the specified punctuator.
+ # If not, an exception will be thrown.
+
+ def expect(self, value):
+ token = self.lex()
+ if (token['type'] != Token.Punctuator or token['value'] != value):
+ self.throwUnexpectedToken(token)
+
+ # /**
+ # * @name expectCommaSeparator
+ # * @description Quietly expect a comma when in tolerant mode, otherwise delegates
+ # * to <code>expect(value)</code>
+ # * @since 2.0
+ # */
+ def expectCommaSeparator(self):
+ self.expect(',')
+
+ # Expect the next token to match the specified keyword.
+ # If not, an exception will be thrown.
+
+ def expectKeyword(self, keyword):
+ token = self.lex();
+ if (token['type'] != Token.Keyword or token['value'] != keyword):
+ self.throwUnexpectedToken(token)
+
+ # Return true if the next token matches the specified punctuator.
+
+ def match(self, value):
+ return self.lookahead['type'] == Token.Punctuator and self.lookahead['value'] == value
+
+ # Return true if the next token matches the specified keyword
+
+ def matchKeyword(self, keyword):
+ return self.lookahead['type'] == Token.Keyword and self.lookahead['value'] == keyword
+
+ # Return true if the next token matches the specified contextual keyword
+ # (where an identifier is sometimes a keyword depending on the context)
+
+ def matchContextualKeyword(self, keyword):
+ return self.lookahead['type'] == Token.Identifier and self.lookahead['value'] == keyword
+
+ # Return true if the next token is an assignment operator
+
+ def matchAssign(self):
+ if (self.lookahead['type'] != Token.Punctuator):
+ return False;
+ op = self.lookahead['value']
+ return op in ('=', '*=', '/=', '%=', '+=', '-=', '<<=', '>>=', '>>>=', '&=', '^=', '|=')
+
+ def consumeSemicolon(self):
+ # Catch the very common case first: immediately a semicolon (U+003B).
+
+ if (self.at(self.startIndex) == ';' or self.match(';')):
+ self.lex()
+ return
+
+ if (self.hasLineTerminator):
+ return
+
+ # TODO: FIXME(ikarienator): this is seemingly an issue in the previous location info convention.
+ self.lastIndex = self.startIndex
+ self.lastLineNumber = self.startLineNumber
+ self.lastLineStart = self.startLineStart
+
+ if (self.lookahead['type'] != Token.EOF and not self.match('}')):
+ self.throwUnexpectedToken(self.lookahead)
+
+ # // Cover grammar support.
+ # //
+ # // When an assignment expression position starts with an left parenthesis, the determination of the type
+ # // of the syntax is to be deferred arbitrarily long until the end of the parentheses pair (plus a lookahead)
+ # // or the first comma. This situation also defers the determination of all the expressions nested in the pair.
+ # //
+ # // There are three productions that can be parsed in a parentheses pair that needs to be determined
+ # // after the outermost pair is closed. They are:
+ # //
+ # // 1. AssignmentExpression
+ # // 2. BindingElements
+ # // 3. AssignmentTargets
+ # //
+ # // In order to avoid exponential backtracking, we use two flags to denote if the production can be
+ # // binding element or assignment target.
+ # //
+ # // The three productions have the relationship:
+ # //
+ # // BindingElements <= AssignmentTargets <= AssignmentExpression
+ # //
+ # // with a single exception that CoverInitializedName when used directly in an Expression, generates
+ # // an early error. Therefore, we need the third state, firstCoverInitializedNameError, to track the
+ # // first usage of CoverInitializedName and report it when we reached the end of the parentheses pair.
+ # //
+ # // isolateCoverGrammar function runs the given parser function with a new cover grammar context, and it does not
+ # // effect the current flags. This means the production the parser parses is only used as an expression. Therefore
+ # // the CoverInitializedName check is conducted.
+ # //
+ # // inheritCoverGrammar function runs the given parse function with a new cover grammar context, and it propagates
+ # // the flags outside of the parser. This means the production the parser parses is used as a part of a potential
+ # // pattern. The CoverInitializedName check is deferred.
+
+ def isolateCoverGrammar(self, parser):
+ oldIsBindingElement = self.isBindingElement
+ oldIsAssignmentTarget = self.isAssignmentTarget
+ oldFirstCoverInitializedNameError = self.firstCoverInitializedNameError
+ self.isBindingElement = true
+ self.isAssignmentTarget = true
+ self.firstCoverInitializedNameError = null
+ result = parser()
+ if (self.firstCoverInitializedNameError != null):
+ self.throwUnexpectedToken(self.firstCoverInitializedNameError)
+ self.isBindingElement = oldIsBindingElement
+ self.isAssignmentTarget = oldIsAssignmentTarget
+ self.firstCoverInitializedNameError = oldFirstCoverInitializedNameError
+ return result
+
+ def inheritCoverGrammar(self, parser):
+ oldIsBindingElement = self.isBindingElement
+ oldIsAssignmentTarget = self.isAssignmentTarget
+ oldFirstCoverInitializedNameError = self.firstCoverInitializedNameError
+ self.isBindingElement = true
+ self.isAssignmentTarget = true
+ self.firstCoverInitializedNameError = null
+ result = parser()
+ self.isBindingElement = self.isBindingElement and oldIsBindingElement
+ self.isAssignmentTarget = self.isAssignmentTarget and oldIsAssignmentTarget
+ self.firstCoverInitializedNameError = oldFirstCoverInitializedNameError or self.firstCoverInitializedNameError
+ return result
+
+ def parseArrayPattern(self):
+ node = Node()
+ elements = []
+ self.expect('[');
+ while (not self.match(']')):
+ if (self.match(',')):
+ self.lex()
+ elements.append(null)
+ else:
+ if (self.match('...')):
+ restNode = Node()
+ self.lex()
+ rest = self.parseVariableIdentifier()
+ elements.append(restNode.finishRestElement(rest))
+ break
+ else:
+ elements.append(self.parsePatternWithDefault())
+ if (not self.match(']')):
+ self.expect(',')
+ self.expect(']')
+ return node.finishArrayPattern(elements)
+
+ def parsePropertyPattern(self):
+ node = Node()
+ computed = self.match('[')
+ if (self.lookahead['type'] == Token.Identifier):
+ key = self.parseVariableIdentifier()
+ if (self.match('=')):
+ self.lex();
+ init = self.parseAssignmentExpression()
+ return node.finishProperty(
+ 'init', key, false, WrappingNode(key).finishAssignmentPattern(key, init), false, false)
+ elif (not self.match(':')):
+ return node.finishProperty('init', key, false, key, false, true)
+ else:
+ key = self.parseObjectPropertyKey()
+ self.expect(':')
+ init = self.parsePatternWithDefault()
+ return node.finishProperty('init', key, computed, init, false, false)
+
+ def parseObjectPattern(self):
+ node = Node()
+ properties = []
+ self.expect('{')
+ while (not self.match('}')):
+ properties.append(self.parsePropertyPattern())
+ if (not self.match('}')):
+ self.expect(',')
+ self.lex()
+ return node.finishObjectPattern(properties)
+
+ def parsePattern(self):
+ if (self.lookahead['type'] == Token.Identifier):
+ return self.parseVariableIdentifier()
+ elif (self.match('[')):
+ return self.parseArrayPattern()
+ elif (self.match('{')):
+ return self.parseObjectPattern()
+ self.throwUnexpectedToken(self.lookahead)
+
+ def parsePatternWithDefault(self):
+ startToken = self.lookahead
+
+ pattern = self.parsePattern()
+ if (self.match('=')):
+ self.lex()
+ right = self.isolateCoverGrammar(self.parseAssignmentExpression)
+ pattern = WrappingNode(startToken).finishAssignmentPattern(pattern, right)
+ return pattern
+
+ # 11.1.4 Array Initialiser
+
+ def parseArrayInitialiser(self):
+ elements = []
+ node = Node()
+
+ self.expect('[')
+
+ while (not self.match(']')):
+ if (self.match(',')):
+ self.lex()
+ elements.append(null)
+ elif (self.match('...')):
+ restSpread = Node()
+ self.lex()
+ restSpread.finishSpreadElement(self.inheritCoverGrammar(self.parseAssignmentExpression))
+ if (not self.match(']')):
+ self.isAssignmentTarget = self.isBindingElement = false
+ self.expect(',')
+ elements.append(restSpread)
+ else:
+ elements.append(self.inheritCoverGrammar(self.parseAssignmentExpression))
+ if (not self.match(']')):
+ self.expect(',')
+ self.lex();
+
+ return node.finishArrayExpression(elements)
+
+ # 11.1.5 Object Initialiser
+
+ def parsePropertyFunction(self, node, paramInfo):
+
+ self.isAssignmentTarget = self.isBindingElement = false;
+
+ previousStrict = self.strict;
+ body = self.isolateCoverGrammar(self.parseFunctionSourceElements);
+
+ if (self.strict and paramInfo['firstRestricted']):
+ self.tolerateUnexpectedToken(paramInfo['firstRestricted'], paramInfo.get('message'))
+ if (self.strict and paramInfo.get('stricted')):
+ self.tolerateUnexpectedToken(paramInfo.get('stricted'), paramInfo.get('message'));
+
+ self.strict = previousStrict;
+ return node.finishFunctionExpression(null, paramInfo.get('params'), paramInfo.get('defaults'), body)
+
+ def parsePropertyMethodFunction(self):
+ node = Node();
+
+ params = self.parseParams(null);
+ method = self.parsePropertyFunction(node, params);
+ return method;
+
+ def parseObjectPropertyKey(self):
+ node = Node()
+
+ token = self.lex();
+
+ # // Note: This function is called only from parseObjectProperty(), where
+ # // EOF and Punctuator tokens are already filtered out.
+
+ typ = token['type']
+
+ if typ in [Token.StringLiteral, Token.NumericLiteral]:
+ if self.strict and token.get('octal'):
+ self.tolerateUnexpectedToken(token, Messages.StrictOctalLiteral);
+ return node.finishLiteral(token);
+ elif typ in (Token.Identifier, Token.BooleanLiteral, Token.NullLiteral, Token.Keyword):
+ return node.finishIdentifier(token['value']);
+ elif typ == Token.Punctuator:
+ if (token['value'] == '['):
+ expr = self.isolateCoverGrammar(self.parseAssignmentExpression)
+ self.expect(']')
+ return expr
+ self.throwUnexpectedToken(token)
+
+ def lookaheadPropertyName(self):
+ typ = self.lookahead['type']
+ if typ in (Token.Identifier, Token.StringLiteral, Token.BooleanLiteral, Token.NullLiteral, Token.NumericLiteral,
+ Token.Keyword):
+ return true
+ if typ == Token.Punctuator:
+ return self.lookahead['value'] == '['
+ return false
+
+ # // This function is to try to parse a MethodDefinition as defined in 14.3. But in the case of object literals,
+ # // it might be called at a position where there is in fact a short hand identifier pattern or a data property.
+ # // This can only be determined after we consumed up to the left parentheses.
+ # //
+ # // In order to avoid back tracking, it returns `null` if the position is not a MethodDefinition and the caller
+ # // is responsible to visit other options.
+ def tryParseMethodDefinition(self, token, key, computed, node):
+ if (token['type'] == Token.Identifier):
+ # check for `get` and `set`;
+
+ if (token['value'] == 'get' and self.lookaheadPropertyName()):
+ computed = self.match('[');
+ key = self.parseObjectPropertyKey()
+ methodNode = Node()
+ self.expect('(')
+ self.expect(')')
+ value = self.parsePropertyFunction(methodNode, {
+ 'params': [],
+ 'defaults': [],
+ 'stricted': null,
+ 'firstRestricted': null,
+ 'message': null
+ })
+ return node.finishProperty('get', key, computed, value, false, false)
+ elif (token['value'] == 'set' and self.lookaheadPropertyName()):
+ computed = self.match('[')
+ key = self.parseObjectPropertyKey()
+ methodNode = Node()
+ self.expect('(')
+
+ options = {
+ 'params': [],
+ 'defaultCount': 0,
+ 'defaults': [],
+ 'firstRestricted': null,
+ 'paramSet': {}
+ }
+ if (self.match(')')):
+ self.tolerateUnexpectedToken(self.lookahead);
+ else:
+ self.parseParam(options);
+ if (options['defaultCount'] == 0):
+ options['defaults'] = []
+ self.expect(')')
+
+ value = self.parsePropertyFunction(methodNode, options);
+ return node.finishProperty('set', key, computed, value, false, false);
+ if (self.match('(')):
+ value = self.parsePropertyMethodFunction();
+ return node.finishProperty('init', key, computed, value, true, false)
+ return null;
+
+ def checkProto(self, key, computed, hasProto):
+ if (computed == false and (key['type'] == Syntax.Identifier and key['name'] == '__proto__' or
+ key['type'] == Syntax.Literal and key['value'] == '__proto__')):
+ if (hasProto['value']):
+ self.tolerateError(Messages.DuplicateProtoProperty);
+ else:
+ hasProto['value'] = true;
+
+ def parseObjectProperty(self, hasProto):
+ token = self.lookahead
+ node = Node()
+ node.comments = self.lookahead.get('comments', [])
+
+ computed = self.match('[');
+ key = self.parseObjectPropertyKey();
+ maybeMethod = self.tryParseMethodDefinition(token, key, computed, node)
+
+ if (maybeMethod):
+ self.checkProto(maybeMethod['key'], maybeMethod['computed'], hasProto);
+ return maybeMethod;
+
+ # // init property or short hand property.
+ self.checkProto(key, computed, hasProto);
+
+ if (self.match(':')):
+ self.lex();
+ value = self.inheritCoverGrammar(self.parseAssignmentExpression)
+ return node.finishProperty('init', key, computed, value, false, false)
+
+ if (token['type'] == Token.Identifier):
+ if (self.match('=')):
+ self.firstCoverInitializedNameError = self.lookahead;
+ self.lex();
+ value = self.isolateCoverGrammar(self.parseAssignmentExpression);
+ return node.finishProperty('init', key, computed,
+ WrappingNode(token).finishAssignmentPattern(key, value), false, true)
+ return node.finishProperty('init', key, computed, key, false, true)
+ self.throwUnexpectedToken(self.lookahead)
+
+ def parseObjectInitialiser(self):
+ properties = []
+ hasProto = {'value': false}
+ node = Node();
+ node.comments = self.lookahead.get('comments', [])
+ self.expect('{');
+ while (not self.match('}')):
+ properties.append(self.parseObjectProperty(hasProto));
+
+ if (not self.match('}')):
+ self.expectCommaSeparator()
+ self.expect('}');
+ return node.finishObjectExpression(properties)
+
+ def reinterpretExpressionAsPattern(self, expr):
+ typ = (expr['type'])
+ if typ in (Syntax.Identifier, Syntax.MemberExpression, Syntax.RestElement, Syntax.AssignmentPattern):
+ pass
+ elif typ == Syntax.SpreadElement:
+ expr['type'] = Syntax.RestElement
+ self.reinterpretExpressionAsPattern(expr.argument)
+ elif typ == Syntax.ArrayExpression:
+ expr['type'] = Syntax.ArrayPattern
+ for i in xrange(len(expr['elements'])):
+ if (expr['elements'][i] != null):
+ self.reinterpretExpressionAsPattern(expr['elements'][i])
+ elif typ == Syntax.ObjectExpression:
+ expr['type'] = Syntax.ObjectPattern
+ for i in xrange(len(expr['properties'])):
+ self.reinterpretExpressionAsPattern(expr['properties'][i]['value']);
+ elif Syntax.AssignmentExpression:
+ expr['type'] = Syntax.AssignmentPattern;
+ self.reinterpretExpressionAsPattern(expr['left'])
+ else:
+ # // Allow other node type for tolerant parsing.
+ return
+
+ def parseTemplateElement(self, option):
+
+ if (self.lookahead['type'] != Token.Template or (option['head'] and not self.lookahead['head'])):
+ self.throwUnexpectedToken()
+
+ node = Node();
+ token = self.lex();
+
+ return node.finishTemplateElement({'raw': token['value']['raw'], 'cooked': token['value']['cooked']},
+ token['tail'])
+
+ def parseTemplateLiteral(self):
+ node = Node()
+
+ quasi = self.parseTemplateElement({'head': true})
+ quasis = [quasi]
+ expressions = []
+
+ while (not quasi['tail']):
+ expressions.append(self.parseExpression());
+ quasi = self.parseTemplateElement({'head': false});
+ quasis.append(quasi)
+ return node.finishTemplateLiteral(quasis, expressions)
+
+ # 11.1.6 The Grouping Operator
+
+ def parseGroupExpression(self):
+ self.expect('(');
+
+ if (self.match(')')):
+ self.lex();
+ if (not self.match('=>')):
+ self.expect('=>')
+ return {
+ 'type': PlaceHolders.ArrowParameterPlaceHolder,
+ 'params': []}
+
+ startToken = self.lookahead
+ if (self.match('...')):
+ expr = self.parseRestElement();
+ self.expect(')');
+ if (not self.match('=>')):
+ self.expect('=>')
+ return {
+ 'type': PlaceHolders.ArrowParameterPlaceHolder,
+ 'params': [expr]}
+
+ self.isBindingElement = true;
+ expr = self.inheritCoverGrammar(self.parseAssignmentExpression);
+
+ if (self.match(',')):
+ self.isAssignmentTarget = false;
+ expressions = [expr]
+
+ while (self.startIndex < self.length):
+ if (not self.match(',')):
+ break
+ self.lex();
+
+ if (self.match('...')):
+ if (not self.isBindingElement):
+ self.throwUnexpectedToken(self.lookahead)
+ expressions.append(self.parseRestElement())
+ self.expect(')');
+ if (not self.match('=>')):
+ self.expect('=>');
+ self.isBindingElement = false
+ for i in xrange(len(expressions)):
+ self.reinterpretExpressionAsPattern(expressions[i])
+ return {
+ 'type': PlaceHolders.ArrowParameterPlaceHolder,
+ 'params': expressions}
+ expressions.append(self.inheritCoverGrammar(self.parseAssignmentExpression))
+ expr = WrappingNode(startToken).finishSequenceExpression(expressions);
+ self.expect(')')
+
+ if (self.match('=>')):
+ if (not self.isBindingElement):
+ self.throwUnexpectedToken(self.lookahead);
+ if (expr['type'] == Syntax.SequenceExpression):
+ for i in xrange(len(expr.expressions)):
+ self.reinterpretExpressionAsPattern(expr['expressions'][i])
+ else:
+ self.reinterpretExpressionAsPattern(expr);
+ expr = {
+ 'type': PlaceHolders.ArrowParameterPlaceHolder,
+ 'params': expr['expressions'] if expr['type'] == Syntax.SequenceExpression else [expr]}
+ self.isBindingElement = false
+ return expr
+
+ # 11.1 Primary Expressions
+
+ def parsePrimaryExpression(self):
+ if (self.match('(')):
+ self.isBindingElement = false;
+ return self.inheritCoverGrammar(self.parseGroupExpression)
+ if (self.match('[')):
+ return self.inheritCoverGrammar(self.parseArrayInitialiser)
+
+ if (self.match('{')):
+ return self.inheritCoverGrammar(self.parseObjectInitialiser)
+
+ typ = self.lookahead['type']
+ node = Node();
+ node.comments = self.lookahead.get('comments', [])
+
+ if (typ == Token.Identifier):
+ expr = node.finishIdentifier(self.lex()['value']);
+ elif (typ == Token.StringLiteral or typ == Token.NumericLiteral):
+ self.isAssignmentTarget = self.isBindingElement = false
+ if (self.strict and self.lookahead.get('octal')):
+ self.tolerateUnexpectedToken(self.lookahead, Messages.StrictOctalLiteral)
+ expr = node.finishLiteral(self.lex())
+ elif (typ == Token.Keyword):
+ self.isAssignmentTarget = self.isBindingElement = false
+ if (self.matchKeyword('function')):
+ return self.parseFunctionExpression()
+ if (self.matchKeyword('this')):
+ self.lex()
+ return node.finishThisExpression()
+ if (self.matchKeyword('class')):
+ return self.parseClassExpression()
+ self.throwUnexpectedToken(self.lex())
+ elif (typ == Token.BooleanLiteral):
+ isAssignmentTarget = self.isBindingElement = false
+ token = self.lex();
+ token['value'] = (token['value'] == 'true')
+ expr = node.finishLiteral(token)
+ elif (typ == Token.NullLiteral):
+ self.isAssignmentTarget = self.isBindingElement = false
+ token = self.lex()
+ token['value'] = null;
+ expr = node.finishLiteral(token)
+ elif (self.match('/') or self.match('/=')):
+ self.isAssignmentTarget = self.isBindingElement = false;
+ self.index = self.startIndex;
+ token = self.scanRegExp([]); # hehe, here you are!
+ self.lex();
+ expr = node.finishLiteral(token);
+ elif (typ == Token.Template):
+ expr = self.parseTemplateLiteral()
+ else:
+ self.throwUnexpectedToken(self.lex());
+ return expr;
+
+ # 11.2 Left-Hand-Side Expressions
+
+ def parseArguments(self):
+ args = [];
+
+ self.expect('(');
+ if (not self.match(')')):
+ while (self.startIndex < self.length):
+ args.append(self.isolateCoverGrammar(self.parseAssignmentExpression))
+ if (self.match(')')):
+ break
+ self.expectCommaSeparator()
+ self.expect(')')
+ return args;
+
+ def parseNonComputedProperty(self):
+ node = Node()
+
+ token = self.lex();
+
+ if (not self.isIdentifierName(token)):
+ self.throwUnexpectedToken(token)
+ return node.finishIdentifier(token['value'])
+
+ def parseNonComputedMember(self):
+ self.expect('.')
+ return self.parseNonComputedProperty();
+
+ def parseComputedMember(self):
+ self.expect('[')
+
+ expr = self.isolateCoverGrammar(self.parseExpression)
+ self.expect(']')
+
+ return expr
+
+ def parseNewExpression(self):
+ node = Node()
+ self.expectKeyword('new')
+ callee = self.isolateCoverGrammar(self.parseLeftHandSideExpression)
+ args = self.parseArguments() if self.match('(') else []
+
+ self.isAssignmentTarget = self.isBindingElement = false
+
+ return node.finishNewExpression(callee, args)
+
+ def parseLeftHandSideExpressionAllowCall(self):
+ previousAllowIn = self.state['allowIn']
+
+ startToken = self.lookahead;
+ self.state['allowIn'] = true;
+
+ if (self.matchKeyword('super') and self.state['inFunctionBody']):
+ expr = Node();
+ self.lex();
+ expr = expr.finishSuper()
+ if (not self.match('(') and not self.match('.') and not self.match('[')):
+ self.throwUnexpectedToken(self.lookahead);
+ else:
+ expr = self.inheritCoverGrammar(
+ self.parseNewExpression if self.matchKeyword('new') else self.parsePrimaryExpression)
+ while True:
+ if (self.match('.')):
+ self.isBindingElement = false;
+ self.isAssignmentTarget = true;
+ property = self.parseNonComputedMember();
+ expr = WrappingNode(startToken).finishMemberExpression('.', expr, property)
+ elif (self.match('(')):
+ self.isBindingElement = false;
+ self.isAssignmentTarget = false;
+ args = self.parseArguments();
+ expr = WrappingNode(startToken).finishCallExpression(expr, args)
+ elif (self.match('[')):
+ self.isBindingElement = false;
+ self.isAssignmentTarget = true;
+ property = self.parseComputedMember();
+ expr = WrappingNode(startToken).finishMemberExpression('[', expr, property)
+ elif (self.lookahead['type'] == Token.Template and self.lookahead['head']):
+ quasi = self.parseTemplateLiteral()
+ expr = WrappingNode(startToken).finishTaggedTemplateExpression(expr, quasi)
+ else:
+ break
+ self.state['allowIn'] = previousAllowIn
+
+ return expr
+
+ def parseLeftHandSideExpression(self):
+ assert self.state['allowIn'], 'callee of new expression always allow in keyword.'
+
+ startToken = self.lookahead
+
+ if (self.matchKeyword('super') and self.state['inFunctionBody']):
+ expr = Node();
+ self.lex();
+ expr = expr.finishSuper();
+ if (not self.match('[') and not self.match('.')):
+ self.throwUnexpectedToken(self.lookahead)
+ else:
+ expr = self.inheritCoverGrammar(
+ self.parseNewExpression if self.matchKeyword('new') else self.parsePrimaryExpression);
+
+ while True:
+ if (self.match('[')):
+ self.isBindingElement = false;
+ self.isAssignmentTarget = true;
+ property = self.parseComputedMember();
+ expr = WrappingNode(startToken).finishMemberExpression('[', expr, property)
+ elif (self.match('.')):
+ self.isBindingElement = false;
+ self.isAssignmentTarget = true;
+ property = self.parseNonComputedMember();
+ expr = WrappingNode(startToken).finishMemberExpression('.', expr, property);
+ elif (self.lookahead['type'] == Token.Template and self.lookahead['head']):
+ quasi = self.parseTemplateLiteral();
+ expr = WrappingNode(startToken).finishTaggedTemplateExpression(expr, quasi)
+ else:
+ break
+ return expr
+
+ # 11.3 Postfix Expressions
+
+ def parsePostfixExpression(self):
+ startToken = self.lookahead
+
+ expr = self.inheritCoverGrammar(self.parseLeftHandSideExpressionAllowCall)
+
+ if (not self.hasLineTerminator and self.lookahead['type'] == Token.Punctuator):
+ if (self.match('++') or self.match('--')):
+ # 11.3.1, 11.3.2
+ if (self.strict and expr.type == Syntax.Identifier and isRestrictedWord(expr.name)):
+ self.tolerateError(Messages.StrictLHSPostfix)
+ if (not self.isAssignmentTarget):
+ self.tolerateError(Messages.InvalidLHSInAssignment);
+ self.isAssignmentTarget = self.isBindingElement = false;
+
+ token = self.lex();
+ expr = WrappingNode(startToken).finishPostfixExpression(token['value'], expr);
+ return expr;
+
+ # 11.4 Unary Operators
+
+ def parseUnaryExpression(self):
+
+ if (self.lookahead['type'] != Token.Punctuator and self.lookahead['type'] != Token.Keyword):
+ expr = self.parsePostfixExpression();
+ elif (self.match('++') or self.match('--')):
+ startToken = self.lookahead;
+ token = self.lex();
+ expr = self.inheritCoverGrammar(self.parseUnaryExpression);
+ # 11.4.4, 11.4.5
+ if (self.strict and expr.type == Syntax.Identifier and isRestrictedWord(expr.name)):
+ self.tolerateError(Messages.StrictLHSPrefix)
+ if (not self.isAssignmentTarget):
+ self.tolerateError(Messages.InvalidLHSInAssignment)
+ expr = WrappingNode(startToken).finishUnaryExpression(token['value'], expr)
+ self.isAssignmentTarget = self.isBindingElement = false
+ elif (self.match('+') or self.match('-') or self.match('~') or self.match('!')):
+ startToken = self.lookahead;
+ token = self.lex();
+ expr = self.inheritCoverGrammar(self.parseUnaryExpression);
+ expr = WrappingNode(startToken).finishUnaryExpression(token['value'], expr)
+ self.isAssignmentTarget = self.isBindingElement = false;
+ elif (self.matchKeyword('delete') or self.matchKeyword('void') or self.matchKeyword('typeof')):
+ startToken = self.lookahead;
+ token = self.lex();
+ expr = self.inheritCoverGrammar(self.parseUnaryExpression);
+ expr = WrappingNode(startToken).finishUnaryExpression(token['value'], expr);
+ if (self.strict and expr.operator == 'delete' and expr.argument.type == Syntax.Identifier):
+ self.tolerateError(Messages.StrictDelete)
+ self.isAssignmentTarget = self.isBindingElement = false;
+ else:
+ expr = self.parsePostfixExpression()
+ return expr
+
+ def binaryPrecedence(self, token, allowIn):
+ prec = 0;
+ typ = token['type']
+ if (typ != Token.Punctuator and typ != Token.Keyword):
+ return 0;
+ val = token['value']
+ if val == 'in' and not allowIn:
+ return 0
+ return PRECEDENCE.get(val, 0)
+
+ # 11.5 Multiplicative Operators
+ # 11.6 Additive Operators
+ # 11.7 Bitwise Shift Operators
+ # 11.8 Relational Operators
+ # 11.9 Equality Operators
+ # 11.10 Binary Bitwise Operators
+ # 11.11 Binary Logical Operators
+
+ def parseBinaryExpression(self):
+
+ marker = self.lookahead;
+ left = self.inheritCoverGrammar(self.parseUnaryExpression);
+
+ token = self.lookahead;
+ prec = self.binaryPrecedence(token, self.state['allowIn']);
+ if (prec == 0):
+ return left
+ self.isAssignmentTarget = self.isBindingElement = false;
+ token['prec'] = prec
+ self.lex()
+
+ markers = [marker, self.lookahead];
+ right = self.isolateCoverGrammar(self.parseUnaryExpression);
+
+ stack = [left, token, right];
+
+ while True:
+ prec = self.binaryPrecedence(self.lookahead, self.state['allowIn'])
+ if not prec > 0:
+ break
+ # Reduce: make a binary expression from the three topmost entries.
+ while ((len(stack) > 2) and (prec <= stack[len(stack) - 2]['prec'])):
+ right = stack.pop();
+ operator = stack.pop()['value']
+ left = stack.pop()
+ markers.pop()
+ expr = WrappingNode(markers[len(markers) - 1]).finishBinaryExpression(operator, left, right)
+ stack.append(expr)
+
+ # Shift
+ token = self.lex();
+ token['prec'] = prec;
+ stack.append(token);
+ markers.append(self.lookahead);
+ expr = self.isolateCoverGrammar(self.parseUnaryExpression);
+ stack.append(expr);
+
+ # Final reduce to clean-up the stack.
+ i = len(stack) - 1;
+ expr = stack[i]
+ markers.pop()
+ while (i > 1):
+ expr = WrappingNode(markers.pop()).finishBinaryExpression(stack[i - 1]['value'], stack[i - 2], expr);
+ i -= 2
+ return expr
+
+ # 11.12 Conditional Operator
+
+ def parseConditionalExpression(self):
+
+ startToken = self.lookahead
+
+ expr = self.inheritCoverGrammar(self.parseBinaryExpression);
+ if (self.match('?')):
+ self.lex()
+ previousAllowIn = self.state['allowIn']
+ self.state['allowIn'] = true;
+ consequent = self.isolateCoverGrammar(self.parseAssignmentExpression);
+ self.state['allowIn'] = previousAllowIn;
+ self.expect(':');
+ alternate = self.isolateCoverGrammar(self.parseAssignmentExpression)
+
+ expr = WrappingNode(startToken).finishConditionalExpression(expr, consequent, alternate);
+ self.isAssignmentTarget = self.isBindingElement = false;
+ return expr
+
+ # [ES6] 14.2 Arrow Function
+
+ def parseConciseBody(self):
+ if (self.match('{')):
+ return self.parseFunctionSourceElements()
+ return self.isolateCoverGrammar(self.parseAssignmentExpression)
+
+ def checkPatternParam(self, options, param):
+ typ = param.type
+ if typ == Syntax.Identifier:
+ self.validateParam(options, param, param.name);
+ elif typ == Syntax.RestElement:
+ self.checkPatternParam(options, param.argument)
+ elif typ == Syntax.AssignmentPattern:
+ self.checkPatternParam(options, param.left)
+ elif typ == Syntax.ArrayPattern:
+ for i in xrange(len(param.elements)):
+ if (param.elements[i] != null):
+ self.checkPatternParam(options, param.elements[i]);
+ else:
+ assert typ == Syntax.ObjectPattern, 'Invalid type'
+ for i in xrange(len(param.properties)):
+ self.checkPatternParam(options, param.properties[i]['value']);
+
+ def reinterpretAsCoverFormalsList(self, expr):
+ defaults = [];
+ defaultCount = 0;
+ params = [expr];
+ typ = expr.type
+ if typ == Syntax.Identifier:
+ pass
+ elif typ == PlaceHolders.ArrowParameterPlaceHolder:
+ params = expr.params
+ else:
+ return null
+ options = {
+ 'paramSet': {}}
+ le = len(params)
+ for i in xrange(le):
+ param = params[i]
+ if param.type == Syntax.AssignmentPattern:
+ params[i] = param.left;
+ defaults.append(param.right);
+ defaultCount += 1
+ self.checkPatternParam(options, param.left);
+ else:
+ self.checkPatternParam(options, param);
+ params[i] = param;
+ defaults.append(null);
+ if (options.get('message') == Messages.StrictParamDupe):
+ token = options.get('stricted') if self.strict else options['firstRestricted']
+ self.throwUnexpectedToken(token, options.get('message'));
+ if (defaultCount == 0):
+ defaults = []
+ return {
+ 'params': params,
+ 'defaults': defaults,
+ 'stricted': options['stricted'],
+ 'firstRestricted': options['firstRestricted'],
+ 'message': options.get('message')}
+
+ def parseArrowFunctionExpression(self, options, node):
+ if (self.hasLineTerminator):
+ self.tolerateUnexpectedToken(self.lookahead)
+ self.expect('=>')
+ previousStrict = self.strict;
+
+ body = self.parseConciseBody();
+
+ if (self.strict and options['firstRestricted']):
+ self.throwUnexpectedToken(options['firstRestricted'], options.get('message'));
+ if (self.strict and options['stricted']):
+ self.tolerateUnexpectedToken(options['stricted'], options['message']);
+
+ self.strict = previousStrict
+
+ return node.finishArrowFunctionExpression(options['params'], options['defaults'], body,
+ body.type != Syntax.BlockStatement)
+
+ # 11.13 Assignment Operators
+
+ def parseAssignmentExpression(self):
+ startToken = self.lookahead;
+ token = self.lookahead;
+
+ expr = self.parseConditionalExpression();
+
+ if (expr.type == PlaceHolders.ArrowParameterPlaceHolder or self.match('=>')):
+ self.isAssignmentTarget = self.isBindingElement = false;
+ lis = self.reinterpretAsCoverFormalsList(expr)
+
+ if (lis):
+ self.firstCoverInitializedNameError = null;
+ return self.parseArrowFunctionExpression(lis, WrappingNode(startToken))
+ return expr
+
+ if (self.matchAssign()):
+ if (not self.isAssignmentTarget):
+ self.tolerateError(Messages.InvalidLHSInAssignment)
+ # 11.13.1
+
+ if (self.strict and expr.type == Syntax.Identifier and isRestrictedWord(expr.name)):
+ self.tolerateUnexpectedToken(token, Messages.StrictLHSAssignment);
+ if (not self.match('=')):
+ self.isAssignmentTarget = self.isBindingElement = false;
+ else:
+ self.reinterpretExpressionAsPattern(expr)
+ token = self.lex();
+ right = self.isolateCoverGrammar(self.parseAssignmentExpression)
+ expr = WrappingNode(startToken).finishAssignmentExpression(token['value'], expr, right);
+ self.firstCoverInitializedNameError = null
+ return expr
+
+ # 11.14 Comma Operator
+
+ def parseExpression(self):
+ startToken = self.lookahead
+ expr = self.isolateCoverGrammar(self.parseAssignmentExpression)
+
+ if (self.match(',')):
+ expressions = [expr];
+
+ while (self.startIndex < self.length):
+ if (not self.match(',')):
+ break
+ self.lex();
+ expressions.append(self.isolateCoverGrammar(self.parseAssignmentExpression))
+ expr = WrappingNode(startToken).finishSequenceExpression(expressions);
+ return expr
+
+ # 12.1 Block
+
+ def parseStatementListItem(self):
+ if (self.lookahead['type'] == Token.Keyword):
+ val = (self.lookahead['value'])
+ if val == 'export':
+ if (self.sourceType != 'module'):
+ self.tolerateUnexpectedToken(self.lookahead, Messages.IllegalExportDeclaration)
+ return self.parseExportDeclaration();
+ elif val == 'import':
+ if (self.sourceType != 'module'):
+ self.tolerateUnexpectedToken(self.lookahead, Messages.IllegalImportDeclaration);
+ return self.parseImportDeclaration();
+ elif val == 'const' or val == 'let':
+ return self.parseLexicalDeclaration({'inFor': false});
+ elif val == 'function':
+ return self.parseFunctionDeclaration(Node());
+ elif val == 'class':
+ return self.parseClassDeclaration();
+ elif ENABLE_PYIMPORT and val == 'pyimport': # <<<<< MODIFIED HERE
+ return self.parsePyimportStatement()
+ return self.parseStatement();
+
+ def parsePyimportStatement(self):
+ print(ENABLE_PYIMPORT)
+ assert ENABLE_PYIMPORT
+ n = Node()
+ self.lex()
+ n.finishPyimport(self.parseVariableIdentifier())
+ self.consumeSemicolon()
+ return n
+
+ def parseStatementList(self):
+ list = [];
+ while (self.startIndex < self.length):
+ if (self.match('}')):
+ break
+ list.append(self.parseStatementListItem())
+ return list
+
+ def parseBlock(self):
+ node = Node();
+
+ self.expect('{');
+
+ block = self.parseStatementList()
+
+ self.expect('}');
+
+ return node.finishBlockStatement(block);
+
+ # 12.2 Variable Statement
+
+ def parseVariableIdentifier(self):
+ node = Node()
+
+ token = self.lex()
+
+ if (token['type'] != Token.Identifier):
+ if (self.strict and token['type'] == Token.Keyword and isStrictModeReservedWord(token['value'])):
+ self.tolerateUnexpectedToken(token, Messages.StrictReservedWord);
+ else:
+ self.throwUnexpectedToken(token)
+ return node.finishIdentifier(token['value'])
+
+ def parseVariableDeclaration(self):
+ init = null
+ node = Node();
+ d = self.parsePattern();
+
+ # 12.2.1
+ if (self.strict and isRestrictedWord(d.name)):
+ self.tolerateError(Messages.StrictVarName);
+
+ if (self.match('=')):
+ self.lex();
+ init = self.isolateCoverGrammar(self.parseAssignmentExpression);
+ elif (d.type != Syntax.Identifier):
+ self.expect('=')
+ return node.finishVariableDeclarator(d, init)
+
+ def parseVariableDeclarationList(self):
+ lis = []
+
+ while True:
+ lis.append(self.parseVariableDeclaration())
+ if (not self.match(',')):
+ break
+ self.lex();
+ if not (self.startIndex < self.length):
+ break
+
+ return lis;
+
+ def parseVariableStatement(self, node):
+ self.expectKeyword('var')
+ declarations = self.parseVariableDeclarationList()
+
+ self.consumeSemicolon()
+
+ return node.finishVariableDeclaration(declarations)
+
+ def parseLexicalBinding(self, kind, options):
+ init = null
+ node = Node()
+
+ d = self.parsePattern();
+
+ # 12.2.1
+ if (self.strict and d.type == Syntax.Identifier and isRestrictedWord(d.name)):
+ self.tolerateError(Messages.StrictVarName);
+
+ if (kind == 'const'):
+ if (not self.matchKeyword('in')):
+ self.expect('=')
+ init = self.isolateCoverGrammar(self.parseAssignmentExpression)
+ elif ((not options['inFor'] and d.type != Syntax.Identifier) or self.match('=')):
+ self.expect('=');
+ init = self.isolateCoverGrammar(self.parseAssignmentExpression);
+ return node.finishVariableDeclarator(d, init)
+
+ def parseBindingList(self, kind, options):
+ list = [];
+
+ while True:
+ list.append(self.parseLexicalBinding(kind, options));
+ if (not self.match(',')):
+ break
+ self.lex();
+ if not (self.startIndex < self.length):
+ break
+ return list;
+
+ def parseLexicalDeclaration(self, options):
+ node = Node();
+
+ kind = self.lex()['value']
+ assert kind == 'let' or kind == 'const', 'Lexical declaration must be either let or const'
+ declarations = self.parseBindingList(kind, options);
+ self.consumeSemicolon();
+ return node.finishLexicalDeclaration(declarations, kind);
+
+ def parseRestElement(self):
+ node = Node();
+
+ self.lex();
+
+ if (self.match('{')):
+ self.throwError(Messages.ObjectPatternAsRestParameter)
+ param = self.parseVariableIdentifier();
+ if (self.match('=')):
+ self.throwError(Messages.DefaultRestParameter);
+
+ if (not self.match(')')):
+ self.throwError(Messages.ParameterAfterRestParameter);
+ return node.finishRestElement(param);
+
+ # 12.3 Empty Statement
+
+ def parseEmptyStatement(self, node):
+ self.expect(';');
+ return node.finishEmptyStatement()
+
+ # 12.4 Expression Statement
+
+ def parseExpressionStatement(self, node):
+ expr = self.parseExpression();
+ self.consumeSemicolon();
+ return node.finishExpressionStatement(expr);
+
+ # 12.5 If statement
+
+ def parseIfStatement(self, node):
+ self.expectKeyword('if');
+
+ self.expect('(');
+
+ test = self.parseExpression();
+
+ self.expect(')');
+
+ consequent = self.parseStatement();
+
+ if (self.matchKeyword('else')):
+ self.lex();
+ alternate = self.parseStatement();
+ else:
+ alternate = null;
+ return node.finishIfStatement(test, consequent, alternate)
+
+ # 12.6 Iteration Statements
+
+ def parseDoWhileStatement(self, node):
+
+ self.expectKeyword('do')
+
+ oldInIteration = self.state['inIteration']
+ self.state['inIteration'] = true
+
+ body = self.parseStatement();
+
+ self.state['inIteration'] = oldInIteration;
+
+ self.expectKeyword('while');
+
+ self.expect('(');
+
+ test = self.parseExpression();
+
+ self.expect(')')
+
+ if (self.match(';')):
+ self.lex()
+ return node.finishDoWhileStatement(body, test)
+
+ def parseWhileStatement(self, node):
+
+ self.expectKeyword('while')
+
+ self.expect('(')
+
+ test = self.parseExpression()
+
+ self.expect(')')
+
+ oldInIteration = self.state['inIteration']
+ self.state['inIteration'] = true
+
+ body = self.parseStatement()
+
+ self.state['inIteration'] = oldInIteration
+
+ return node.finishWhileStatement(test, body)
+
+ def parseForStatement(self, node):
+ previousAllowIn = self.state['allowIn']
+
+ init = test = update = null
+
+ self.expectKeyword('for')
+
+ self.expect('(')
+
+ if (self.match(';')):
+ self.lex()
+ else:
+ if (self.matchKeyword('var')):
+ init = Node()
+ self.lex()
+
+ self.state['allowIn'] = false;
+ init = init.finishVariableDeclaration(self.parseVariableDeclarationList())
+ self.state['allowIn'] = previousAllowIn
+
+ if (len(init.declarations) == 1 and self.matchKeyword('in')):
+ self.lex()
+ left = init
+ right = self.parseExpression()
+ init = null
+ else:
+ self.expect(';')
+ elif (self.matchKeyword('const') or self.matchKeyword('let')):
+ init = Node()
+ kind = self.lex()['value']
+
+ self.state['allowIn'] = false
+ declarations = self.parseBindingList(kind, {'inFor': true})
+ self.state['allowIn'] = previousAllowIn
+
+ if (len(declarations) == 1 and declarations[0].init == null and self.matchKeyword('in')):
+ init = init.finishLexicalDeclaration(declarations, kind);
+ self.lex();
+ left = init;
+ right = self.parseExpression();
+ init = null;
+ else:
+ self.consumeSemicolon();
+ init = init.finishLexicalDeclaration(declarations, kind);
+ else:
+ initStartToken = self.lookahead
+ self.state['allowIn'] = false
+ init = self.inheritCoverGrammar(self.parseAssignmentExpression);
+ self.state['allowIn'] = previousAllowIn;
+
+ if (self.matchKeyword('in')):
+ if (not self.isAssignmentTarget):
+ self.tolerateError(Messages.InvalidLHSInForIn)
+ self.lex();
+ self.reinterpretExpressionAsPattern(init);
+ left = init;
+ right = self.parseExpression();
+ init = null;
+ else:
+ if (self.match(',')):
+ initSeq = [init];
+ while (self.match(',')):
+ self.lex();
+ initSeq.append(self.isolateCoverGrammar(self.parseAssignmentExpression))
+ init = WrappingNode(initStartToken).finishSequenceExpression(initSeq)
+ self.expect(';');
+
+ if ('left' not in locals()):
+ if (not self.match(';')):
+ test = self.parseExpression();
+
+ self.expect(';');
+
+ if (not self.match(')')):
+ update = self.parseExpression();
+
+ self.expect(')');
+
+ oldInIteration = self.state['inIteration']
+ self.state['inIteration'] = true;
+
+ body = self.isolateCoverGrammar(self.parseStatement)
+
+ self.state['inIteration'] = oldInIteration;
+
+ return node.finishForStatement(init, test, update, body) if (
+ 'left' not in locals()) else node.finishForInStatement(left, right, body);
+
+ # 12.7 The continue statement
+
+ def parseContinueStatement(self, node):
+ label = null
+
+ self.expectKeyword('continue');
+
+ # Optimize the most common form: 'continue;'.
+ if ord(self.source[self.startIndex]) == 0x3B:
+ self.lex();
+ if (not self.state['inIteration']):
+ self.throwError(Messages.IllegalContinue)
+ return node.finishContinueStatement(null)
+ if (self.hasLineTerminator):
+ if (not self.state['inIteration']):
+ self.throwError(Messages.IllegalContinue);
+ return node.finishContinueStatement(null);
+
+ if (self.lookahead['type'] == Token.Identifier):
+ label = self.parseVariableIdentifier();
+
+ key = '$' + label.name;
+ if not key in self.state['labelSet']: # todo make sure its correct!
+ self.throwError(Messages.UnknownLabel, label.name);
+ self.consumeSemicolon()
+
+ if (label == null and not self.state['inIteration']):
+ self.throwError(Messages.IllegalContinue)
+ return node.finishContinueStatement(label)
+
+ # 12.8 The break statement
+
+ def parseBreakStatement(self, node):
+ label = null
+
+ self.expectKeyword('break');
+
+ # Catch the very common case first: immediately a semicolon (U+003B).
+ if (ord(self.source[self.lastIndex]) == 0x3B):
+ self.lex();
+
+ if (not (self.state['inIteration'] or self.state['inSwitch'])):
+ self.throwError(Messages.IllegalBreak)
+ return node.finishBreakStatement(null)
+ if (self.hasLineTerminator):
+ if (not (self.state['inIteration'] or self.state['inSwitch'])):
+ self.throwError(Messages.IllegalBreak);
+ return node.finishBreakStatement(null);
+ if (self.lookahead['type'] == Token.Identifier):
+ label = self.parseVariableIdentifier();
+
+ key = '$' + label.name;
+ if not (key in self.state['labelSet']):
+ self.throwError(Messages.UnknownLabel, label.name);
+ self.consumeSemicolon();
+
+ if (label == null and not (self.state['inIteration'] or self.state['inSwitch'])):
+ self.throwError(Messages.IllegalBreak)
+ return node.finishBreakStatement(label);
+
+ # 12.9 The return statement
+
+ def parseReturnStatement(self, node):
+ argument = null;
+
+ self.expectKeyword('return');
+
+ if (not self.state['inFunctionBody']):
+ self.tolerateError(Messages.IllegalReturn);
+
+ # 'return' followed by a space and an identifier is very common.
+ if (ord(self.source[self.lastIndex]) == 0x20):
+ if (isIdentifierStart(self.source[self.lastIndex + 1])):
+ argument = self.parseExpression();
+ self.consumeSemicolon();
+ return node.finishReturnStatement(argument)
+ if (self.hasLineTerminator):
+ # HACK
+ return node.finishReturnStatement(null)
+
+ if (not self.match(';')):
+ if (not self.match('}') and self.lookahead['type'] != Token.EOF):
+ argument = self.parseExpression();
+ self.consumeSemicolon();
+
+ return node.finishReturnStatement(argument);
+
+ # 12.10 The with statement
+
+ def parseWithStatement(self, node):
+ if (self.strict):
+ self.tolerateError(Messages.StrictModeWith)
+
+ self.expectKeyword('with');
+
+ self.expect('(');
+
+ obj = self.parseExpression();
+
+ self.expect(')');
+
+ body = self.parseStatement();
+
+ return node.finishWithStatement(obj, body);
+
+ # 12.10 The swith statement
+
+ def parseSwitchCase(self):
+ consequent = []
+ node = Node();
+
+ if (self.matchKeyword('default')):
+ self.lex();
+ test = null;
+ else:
+ self.expectKeyword('case');
+ test = self.parseExpression();
+
+ self.expect(':');
+
+ while (self.startIndex < self.length):
+ if (self.match('}') or self.matchKeyword('default') or self.matchKeyword('case')):
+ break
+ statement = self.parseStatementListItem()
+ consequent.append(statement)
+ return node.finishSwitchCase(test, consequent)
+
+ def parseSwitchStatement(self, node):
+
+ self.expectKeyword('switch');
+
+ self.expect('(');
+
+ discriminant = self.parseExpression();
+
+ self.expect(')');
+
+ self.expect('{');
+
+ cases = [];
+
+ if (self.match('}')):
+ self.lex();
+ return node.finishSwitchStatement(discriminant, cases);
+
+ oldInSwitch = self.state['inSwitch'];
+ self.state['inSwitch'] = true;
+ defaultFound = false;
+
+ while (self.startIndex < self.length):
+ if (self.match('}')):
+ break;
+ clause = self.parseSwitchCase();
+ if (clause.test == null):
+ if (defaultFound):
+ self.throwError(Messages.MultipleDefaultsInSwitch);
+ defaultFound = true;
+ cases.append(clause);
+
+ self.state['inSwitch'] = oldInSwitch;
+
+ self.expect('}');
+
+ return node.finishSwitchStatement(discriminant, cases);
+
+ # 12.13 The throw statement
+
+ def parseThrowStatement(self, node):
+
+ self.expectKeyword('throw');
+
+ if (self.hasLineTerminator):
+ self.throwError(Messages.NewlineAfterThrow);
+
+ argument = self.parseExpression();
+
+ self.consumeSemicolon();
+
+ return node.finishThrowStatement(argument);
+
+ # 12.14 The try statement
+
+ def parseCatchClause(self):
+ node = Node();
+
+ self.expectKeyword('catch');
+
+ self.expect('(');
+ if (self.match(')')):
+ self.throwUnexpectedToken(self.lookahead);
+ param = self.parsePattern();
+
+ # 12.14.1
+ if (self.strict and isRestrictedWord(param.name)):
+ self.tolerateError(Messages.StrictCatchVariable);
+
+ self.expect(')');
+ body = self.parseBlock();
+ return node.finishCatchClause(param, body);
+
+ def parseTryStatement(self, node):
+ handler = null
+ finalizer = null;
+
+ self.expectKeyword('try');
+
+ block = self.parseBlock();
+
+ if (self.matchKeyword('catch')):
+ handler = self.parseCatchClause()
+
+ if (self.matchKeyword('finally')):
+ self.lex();
+ finalizer = self.parseBlock();
+
+ if (not handler and not finalizer):
+ self.throwError(Messages.NoCatchOrFinally)
+
+ return node.finishTryStatement(block, handler, finalizer)
+
+ # 12.15 The debugger statement
+
+ def parseDebuggerStatement(self, node):
+ self.expectKeyword('debugger');
+
+ self.consumeSemicolon();
+
+ return node.finishDebuggerStatement();
+
+ # 12 Statements
+
+ def parseStatement(self):
+ typ = self.lookahead['type']
+
+ if (typ == Token.EOF):
+ self.throwUnexpectedToken(self.lookahead)
+
+ if (typ == Token.Punctuator and self.lookahead['value'] == '{'):
+ return self.parseBlock()
+
+ self.isAssignmentTarget = self.isBindingElement = true;
+ node = Node();
+ node.comments = self.lookahead.get('comments', [])
+ val = self.lookahead['value']
+
+ if (typ == Token.Punctuator):
+ if val == ';':
+ return self.parseEmptyStatement(node);
+ elif val == '(':
+ return self.parseExpressionStatement(node);
+ elif (typ == Token.Keyword):
+ if val == 'break':
+ return self.parseBreakStatement(node);
+ elif val == 'continue':
+ return self.parseContinueStatement(node);
+ elif val == 'debugger':
+ return self.parseDebuggerStatement(node);
+ elif val == 'do':
+ return self.parseDoWhileStatement(node);
+ elif val == 'for':
+ return self.parseForStatement(node);
+ elif val == 'function':
+ return self.parseFunctionDeclaration(node);
+ elif val == 'if':
+ return self.parseIfStatement(node);
+ elif val == 'return':
+ return self.parseReturnStatement(node);
+ elif val == 'switch':
+ return self.parseSwitchStatement(node);
+ elif val == 'throw':
+ return self.parseThrowStatement(node);
+ elif val == 'try':
+ return self.parseTryStatement(node);
+ elif val == 'var':
+ return self.parseVariableStatement(node);
+ elif val == 'while':
+ return self.parseWhileStatement(node);
+ elif val == 'with':
+ return self.parseWithStatement(node);
+
+ expr = self.parseExpression();
+
+ # 12.12 Labelled Statements
+ if ((expr.type == Syntax.Identifier) and self.match(':')):
+ self.lex();
+
+ key = '$' + expr.name
+ if key in self.state['labelSet']:
+ self.throwError(Messages.Redeclaration, 'Label', expr.name);
+ self.state['labelSet'][key] = true
+ labeledBody = self.parseStatement()
+ del self.state['labelSet'][key]
+ return node.finishLabeledStatement(expr, labeledBody)
+ self.consumeSemicolon();
+ return node.finishExpressionStatement(expr)
+
+ # 13 Function Definition
+
+ def parseFunctionSourceElements(self):
+ body = []
+ node = Node()
+ firstRestricted = None
+
+ self.expect('{')
+
+ while (self.startIndex < self.length):
+ if (self.lookahead['type'] != Token.StringLiteral):
+ break
+ token = self.lookahead;
+
+ statement = self.parseStatementListItem()
+ body.append(statement)
+ if (statement.expression.type != Syntax.Literal):
+ # this is not directive
+ break
+ directive = self.source[token['start'] + 1: token['end'] - 1]
+ if (directive == 'use strict'):
+ self.strict = true;
+ if (firstRestricted):
+ self.tolerateUnexpectedToken(firstRestricted, Messages.StrictOctalLiteral);
+ else:
+ if (not firstRestricted and token.get('octal')):
+ firstRestricted = token;
+
+ oldLabelSet = self.state['labelSet']
+ oldInIteration = self.state['inIteration']
+ oldInSwitch = self.state['inSwitch']
+ oldInFunctionBody = self.state['inFunctionBody']
+ oldParenthesisCount = self.state['parenthesizedCount']
+
+ self.state['labelSet'] = {}
+ self.state['inIteration'] = false
+ self.state['inSwitch'] = false
+ self.state['inFunctionBody'] = true
+ self.state['parenthesizedCount'] = 0
+
+ while (self.startIndex < self.length):
+ if (self.match('}')):
+ break
+ body.append(self.parseStatementListItem())
+ self.expect('}')
+
+ self.state['labelSet'] = oldLabelSet;
+ self.state['inIteration'] = oldInIteration;
+ self.state['inSwitch'] = oldInSwitch;
+ self.state['inFunctionBody'] = oldInFunctionBody;
+ self.state['parenthesizedCount'] = oldParenthesisCount;
+
+ return node.finishBlockStatement(body)
+
+ def validateParam(self, options, param, name):
+ key = '$' + name
+ if (self.strict):
+ if (isRestrictedWord(name)):
+ options['stricted'] = param;
+ options['message'] = Messages.StrictParamName
+ if key in options['paramSet']:
+ options['stricted'] = param;
+ options['message'] = Messages.StrictParamDupe;
+ elif (not options['firstRestricted']):
+ if (isRestrictedWord(name)):
+ options['firstRestricted'] = param;
+ options['message'] = Messages.StrictParamName;
+ elif (isStrictModeReservedWord(name)):
+ options['firstRestricted'] = param;
+ options['message'] = Messages.StrictReservedWord;
+ elif key in options['paramSet']:
+ options['firstRestricted'] = param
+ options['message'] = Messages.StrictParamDupe;
+ options['paramSet'][key] = true
+
+ def parseParam(self, options):
+ token = self.lookahead
+ de = None
+ if (token['value'] == '...'):
+ param = self.parseRestElement();
+ self.validateParam(options, param.argument, param.argument.name);
+ options['params'].append(param);
+ options['defaults'].append(null);
+ return false
+ param = self.parsePatternWithDefault();
+ self.validateParam(options, token, token['value']);
+
+ if (param.type == Syntax.AssignmentPattern):
+ de = param.right;
+ param = param.left;
+ options['defaultCount'] += 1
+ options['params'].append(param);
+ options['defaults'].append(de)
+ return not self.match(')')
+
+ def parseParams(self, firstRestricted):
+ options = {
+ 'params': [],
+ 'defaultCount': 0,
+ 'defaults': [],
+ 'firstRestricted': firstRestricted}
+
+ self.expect('(');
+
+ if (not self.match(')')):
+ options['paramSet'] = {};
+ while (self.startIndex < self.length):
+ if (not self.parseParam(options)):
+ break
+ self.expect(',');
+ self.expect(')');
+
+ if (options['defaultCount'] == 0):
+ options['defaults'] = [];
+
+ return {
+ 'params': options['params'],
+ 'defaults': options['defaults'],
+ 'stricted': options.get('stricted'),
+ 'firstRestricted': options.get('firstRestricted'),
+ 'message': options.get('message')}
+
+ def parseFunctionDeclaration(self, node, identifierIsOptional=None):
+ node.comments = self.lookahead.get('comments', [])
+ d = null
+ params = []
+ defaults = []
+ message = None
+ firstRestricted = None
+
+ self.expectKeyword('function');
+ if (identifierIsOptional or not self.match('(')):
+ token = self.lookahead;
+ d = self.parseVariableIdentifier();
+ if (self.strict):
+ if (isRestrictedWord(token['value'])):
+ self.tolerateUnexpectedToken(token, Messages.StrictFunctionName);
+ else:
+ if (isRestrictedWord(token['value'])):
+ firstRestricted = token;
+ message = Messages.StrictFunctionName;
+ elif (isStrictModeReservedWord(token['value'])):
+ firstRestricted = token;
+ message = Messages.StrictReservedWord;
+
+ tmp = self.parseParams(firstRestricted);
+ params = tmp['params']
+ defaults = tmp['defaults']
+ stricted = tmp.get('stricted')
+ firstRestricted = tmp['firstRestricted']
+ if (tmp.get('message')):
+ message = tmp['message'];
+
+ previousStrict = self.strict;
+ body = self.parseFunctionSourceElements();
+ if (self.strict and firstRestricted):
+ self.throwUnexpectedToken(firstRestricted, message);
+
+ if (self.strict and stricted):
+ self.tolerateUnexpectedToken(stricted, message);
+ self.strict = previousStrict;
+
+ return node.finishFunctionDeclaration(d, params, defaults, body);
+
+ def parseFunctionExpression(self):
+ id = null
+ params = []
+ defaults = []
+ node = Node();
+ node.comments = self.lookahead.get('comments', [])
+ firstRestricted = None
+ message = None
+
+ self.expectKeyword('function');
+
+ if (not self.match('(')):
+ token = self.lookahead;
+ id = self.parseVariableIdentifier();
+ if (self.strict):
+ if (isRestrictedWord(token['value'])):
+ self.tolerateUnexpectedToken(token, Messages.StrictFunctionName);
+ else:
+ if (isRestrictedWord(token['value'])):
+ firstRestricted = token;
+ message = Messages.StrictFunctionName;
+ elif (isStrictModeReservedWord(token['value'])):
+ firstRestricted = token;
+ message = Messages.StrictReservedWord;
+ tmp = self.parseParams(firstRestricted);
+ params = tmp['params']
+ defaults = tmp['defaults']
+ stricted = tmp.get('stricted')
+ firstRestricted = tmp['firstRestricted']
+ if (tmp.get('message')):
+ message = tmp['message']
+
+ previousStrict = self.strict;
+ body = self.parseFunctionSourceElements();
+ if (self.strict and firstRestricted):
+ self.throwUnexpectedToken(firstRestricted, message);
+ if (self.strict and stricted):
+ self.tolerateUnexpectedToken(stricted, message);
+ self.strict = previousStrict;
+
+ return node.finishFunctionExpression(id, params, defaults, body);
+
+ # todo Translate parse class functions!
+
+ def parseClassExpression(self):
+ raise NotImplementedError()
+
+ def parseClassDeclaration(self):
+ raise NotImplementedError()
+
+ # 14 Program
+
+ def parseScriptBody(self):
+ body = []
+ firstRestricted = None
+
+ while (self.startIndex < self.length):
+ token = self.lookahead;
+ if (token['type'] != Token.StringLiteral):
+ break
+ statement = self.parseStatementListItem();
+ body.append(statement);
+ if (statement.expression.type != Syntax.Literal):
+ # this is not directive
+ break
+ directive = self.source[token['start'] + 1: token['end'] - 1]
+ if (directive == 'use strict'):
+ self.strict = true;
+ if (firstRestricted):
+ self.tolerateUnexpectedToken(firstRestricted, Messages.StrictOctalLiteral)
+ else:
+ if (not firstRestricted and token.get('octal')):
+ firstRestricted = token;
+ while (self.startIndex < self.length):
+ statement = self.parseStatementListItem();
+ # istanbul ignore if
+ if (statement is None):
+ break
+ body.append(statement);
+ return body;
+
+ def parseProgram(self):
+ self.peek()
+ node = Node()
+
+ body = self.parseScriptBody()
+ return node.finishProgram(body)
+
+ # DONE!!!
+ def parse(self, code, options={}):
+ if options:
+ raise NotImplementedError('Options not implemented! You can only use default settings.')
+
+ self.clean()
+ self.source = unicode(code) + ' \n ; //END' # I have to add it in order not to check for EOF every time
+ self.index = 0
+ self.lineNumber = 1 if len(self.source) > 0 else 0
+ self.lineStart = 0
+ self.startIndex = self.index
+ self.startLineNumber = self.lineNumber;
+ self.startLineStart = self.lineStart;
+ self.length = len(self.source)
+ self.lookahead = null;
+ self.state = {
+ 'allowIn': true,
+ 'labelSet': {},
+ 'inFunctionBody': false,
+ 'inIteration': false,
+ 'inSwitch': false,
+ 'lastCommentStart': -1,
+ 'curlyStack': [],
+ 'parenthesizedCount': None}
+ self.sourceType = 'script';
+ self.strict = false;
+ program = self.parseProgram();
+ return node_to_dict(program)
+
+
+
+def parse(javascript_code):
+ """Returns syntax tree of javascript_code.
+ Same as PyJsParser().parse For your convenience :) """
+ p = PyJsParser()
+ return p.parse(javascript_code)
+
+
+if __name__ == '__main__':
+ import time
+
+ test_path = None
+ if test_path:
+ f = open(test_path, 'rb')
+ x = f.read()
+ f.close()
+ else:
+ x = 'var $ = "Hello!"'
+ p = PyJsParser()
+ t = time.time()
+ res = p.parse(x)
+ dt = time.time() - t + 0.000000001
+ if test_path:
+ print(len(res))
+ else:
+ pprint(res)
+ print()
+ print('Parsed everyting in', round(dt, 5), 'seconds.')
+ print('Thats %d characters per second' % int(len(x) / dt))
+
+
+
diff --git a/doc/_extensions/pyjsparser/pyjsparserdata.py b/doc/_extensions/pyjsparser/pyjsparserdata.py
new file mode 100644
index 00000000000..1d6ab2ee2ca
--- /dev/null
+++ b/doc/_extensions/pyjsparser/pyjsparserdata.py
@@ -0,0 +1,303 @@
+# The MIT License
+#
+# Copyright 2014, 2015 Piotr Dabkowski
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the 'Software'),
+# to deal in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+# the Software, and to permit persons to whom the Software is furnished to do so, subject
+# to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or
+# substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+from __future__ import unicode_literals
+
+import sys
+import unicodedata
+from collections import defaultdict
+
+PY3 = sys.version_info >= (3,0)
+
+if PY3:
+ unichr = chr
+ xrange = range
+ unicode = str
+
+token = {
+ 'BooleanLiteral': 1,
+ 'EOF': 2,
+ 'Identifier': 3,
+ 'Keyword': 4,
+ 'NullLiteral': 5,
+ 'NumericLiteral': 6,
+ 'Punctuator': 7,
+ 'StringLiteral': 8,
+ 'RegularExpression': 9,
+ 'Template': 10
+ }
+
+
+TokenName = dict((v,k) for k,v in token.items())
+
+FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
+ 'return', 'case', 'delete', 'throw', 'void',
+ # assignment operators
+ '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=',
+ '&=', '|=', '^=', ',',
+ # binary/unary operators
+ '+', '-', '*', '/', '%', '++', '--', '<<', '>>', '>>>', '&',
+ '|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
+ '<=', '<', '>', '!=', '!==']
+
+syntax= set(('AssignmentExpression',
+ 'AssignmentPattern',
+ 'ArrayExpression',
+ 'ArrayPattern',
+ 'ArrowFunctionExpression',
+ 'BlockStatement',
+ 'BinaryExpression',
+ 'BreakStatement',
+ 'CallExpression',
+ 'CatchClause',
+ 'ClassBody',
+ 'ClassDeclaration',
+ 'ClassExpression',
+ 'ConditionalExpression',
+ 'ContinueStatement',
+ 'DoWhileStatement',
+ 'DebuggerStatement',
+ 'EmptyStatement',
+ 'ExportAllDeclaration',
+ 'ExportDefaultDeclaration',
+ 'ExportNamedDeclaration',
+ 'ExportSpecifier',
+ 'ExpressionStatement',
+ 'ForStatement',
+ 'ForInStatement',
+ 'FunctionDeclaration',
+ 'FunctionExpression',
+ 'Identifier',
+ 'IfStatement',
+ 'ImportDeclaration',
+ 'ImportDefaultSpecifier',
+ 'ImportNamespaceSpecifier',
+ 'ImportSpecifier',
+ 'Literal',
+ 'LabeledStatement',
+ 'LogicalExpression',
+ 'MemberExpression',
+ 'MethodDefinition',
+ 'NewExpression',
+ 'ObjectExpression',
+ 'ObjectPattern',
+ 'Program',
+ 'Property',
+ 'RestElement',
+ 'ReturnStatement',
+ 'SequenceExpression',
+ 'SpreadElement',
+ 'Super',
+ 'SwitchCase',
+ 'SwitchStatement',
+ 'TaggedTemplateExpression',
+ 'TemplateElement',
+ 'TemplateLiteral',
+ 'ThisExpression',
+ 'ThrowStatement',
+ 'TryStatement',
+ 'UnaryExpression',
+ 'UpdateExpression',
+ 'VariableDeclaration',
+ 'VariableDeclarator',
+ 'WhileStatement',
+ 'WithStatement'))
+
+
+# Error messages should be identical to V8.
+messages = {
+ 'UnexpectedToken': 'Unexpected token %s',
+ 'UnexpectedNumber': 'Unexpected number',
+ 'UnexpectedString': 'Unexpected string',
+ 'UnexpectedIdentifier': 'Unexpected identifier',
+ 'UnexpectedReserved': 'Unexpected reserved word',
+ 'UnexpectedTemplate': 'Unexpected quasi %s',
+ 'UnexpectedEOS': 'Unexpected end of input',
+ 'NewlineAfterThrow': 'Illegal newline after throw',
+ 'InvalidRegExp': 'Invalid regular expression',
+ 'UnterminatedRegExp': 'Invalid regular expression: missing /',
+ 'InvalidLHSInAssignment': 'Invalid left-hand side in assignment',
+ 'InvalidLHSInForIn': 'Invalid left-hand side in for-in',
+ 'MultipleDefaultsInSwitch': 'More than one default clause in switch statement',
+ 'NoCatchOrFinally': 'Missing catch or finally after try',
+ 'UnknownLabel': 'Undefined label \'%s\'',
+ 'Redeclaration': '%s \'%s\' has already been declared',
+ 'IllegalContinue': 'Illegal continue statement',
+ 'IllegalBreak': 'Illegal break statement',
+ 'IllegalReturn': 'Illegal return statement',
+ 'StrictModeWith': 'Strict mode code may not include a with statement',
+ 'StrictCatchVariable': 'Catch variable may not be eval or arguments in strict mode',
+ 'StrictVarName': 'Variable name may not be eval or arguments in strict mode',
+ 'StrictParamName': 'Parameter name eval or arguments is not allowed in strict mode',
+ 'StrictParamDupe': 'Strict mode function may not have duplicate parameter names',
+ 'StrictFunctionName': 'Function name may not be eval or arguments in strict mode',
+ 'StrictOctalLiteral': 'Octal literals are not allowed in strict mode.',
+ 'StrictDelete': 'Delete of an unqualified identifier in strict mode.',
+ 'StrictLHSAssignment': 'Assignment to eval or arguments is not allowed in strict mode',
+ 'StrictLHSPostfix': 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
+ 'StrictLHSPrefix': 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
+ 'StrictReservedWord': 'Use of future reserved word in strict mode',
+ 'TemplateOctalLiteral': 'Octal literals are not allowed in template strings.',
+ 'ParameterAfterRestParameter': 'Rest parameter must be last formal parameter',
+ 'DefaultRestParameter': 'Unexpected token =',
+ 'ObjectPatternAsRestParameter': 'Unexpected token {',
+ 'DuplicateProtoProperty': 'Duplicate __proto__ fields are not allowed in object literals',
+ 'ConstructorSpecialMethod': 'Class constructor may not be an accessor',
+ 'DuplicateConstructor': 'A class may only have one constructor',
+ 'StaticPrototype': 'Classes may not have static property named prototype',
+ 'MissingFromClause': 'Unexpected token',
+ 'NoAsAfterImportNamespace': 'Unexpected token',
+ 'InvalidModuleSpecifier': 'Unexpected token',
+ 'IllegalImportDeclaration': 'Unexpected token',
+ 'IllegalExportDeclaration': 'Unexpected token'}
+
+PRECEDENCE = {'||':1,
+ '&&':2,
+ '|':3,
+ '^':4,
+ '&':5,
+ '==':6,
+ '!=':6,
+ '===':6,
+ '!==':6,
+ '<':7,
+ '>':7,
+ '<=':7,
+ '>=':7,
+ 'instanceof':7,
+ 'in':7,
+ '<<':8,
+ '>>':8,
+ '>>>':8,
+ '+':9,
+ '-':9,
+ '*':11,
+ '/':11,
+ '%':11}
+
+class Token: pass
+class Syntax: pass
+class Messages: pass
+class PlaceHolders:
+ ArrowParameterPlaceHolder = 'ArrowParameterPlaceHolder'
+
+for k,v in token.items():
+ setattr(Token, k, v)
+
+for e in syntax:
+ setattr(Syntax, e, e)
+
+for k,v in messages.items():
+ setattr(Messages, k, v)
+
+#http://stackoverflow.com/questions/14245893/efficiently-list-all-characters-in-a-given-unicode-category
+BOM = u'\uFEFF'
+ZWJ = u'\u200D'
+ZWNJ = u'\u200C'
+TAB = u'\u0009'
+VT = u'\u000B'
+FF = u'\u000C'
+SP = u'\u0020'
+NBSP = u'\u00A0'
+LF = u'\u000A'
+CR = u'\u000D'
+LS = u'\u2028'
+PS = u'\u2029'
+
+U_CATEGORIES = defaultdict(list)
+for c in map(unichr, range(sys.maxunicode + 1)):
+ U_CATEGORIES[unicodedata.category(c)].append(c)
+UNICODE_LETTER = set(U_CATEGORIES['Lu']+U_CATEGORIES['Ll']+
+ U_CATEGORIES['Lt']+U_CATEGORIES['Lm']+
+ U_CATEGORIES['Lo']+U_CATEGORIES['Nl'])
+UNICODE_COMBINING_MARK = set(U_CATEGORIES['Mn']+U_CATEGORIES['Mc'])
+UNICODE_DIGIT = set(U_CATEGORIES['Nd'])
+UNICODE_CONNECTOR_PUNCTUATION = set(U_CATEGORIES['Pc'])
+IDENTIFIER_START = UNICODE_LETTER.union(set(('$','_', '\\'))) # and some fucking unicode escape sequence
+IDENTIFIER_PART = IDENTIFIER_START.union(UNICODE_COMBINING_MARK).union(UNICODE_DIGIT)\
+ .union(UNICODE_CONNECTOR_PUNCTUATION).union(set((ZWJ, ZWNJ)))
+
+WHITE_SPACE = set((0x20, 0x09, 0x0B, 0x0C, 0xA0, 0x1680,
+ 0x180E, 0x2000, 0x2001, 0x2002, 0x2003,
+ 0x2004, 0x2005, 0x2006, 0x2007, 0x2008,
+ 0x2009, 0x200A, 0x202F, 0x205F, 0x3000,
+ 0xFEFF))
+
+LINE_TERMINATORS = set((0x0A, 0x0D, 0x2028, 0x2029))
+
+def isIdentifierStart(ch):
+ return (ch if isinstance(ch, unicode) else unichr(ch)) in IDENTIFIER_START
+
+def isIdentifierPart(ch):
+ return (ch if isinstance(ch, unicode) else unichr(ch)) in IDENTIFIER_PART
+
+def isWhiteSpace(ch):
+ return (ord(ch) if isinstance(ch, unicode) else ch) in WHITE_SPACE
+
+def isLineTerminator(ch):
+ return (ord(ch) if isinstance(ch, unicode) else ch) in LINE_TERMINATORS
+
+OCTAL = set(('0', '1', '2', '3', '4', '5', '6', '7'))
+DEC = set(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'))
+HEX = set('0123456789abcdefABCDEF')
+HEX_CONV = dict(('0123456789abcdef'[n],n) for n in xrange(16))
+for i,e in enumerate('ABCDEF', 10):
+ HEX_CONV[e] = i
+
+
+def isDecimalDigit(ch):
+ return (ch if isinstance(ch, unicode) else unichr(ch)) in DEC
+
+def isHexDigit(ch):
+ return (ch if isinstance(ch, unicode) else unichr(ch)) in HEX
+
+def isOctalDigit(ch):
+ return (ch if isinstance(ch, unicode) else unichr(ch)) in OCTAL
+
+def isFutureReservedWord(w):
+ return w in ('enum', 'export', 'import', 'super')
+
+
+RESERVED_WORD = set(('implements', 'interface', 'package', 'private', 'protected', 'public', 'static', 'yield', 'let'))
+def isStrictModeReservedWord(w):
+ return w in RESERVED_WORD
+
+def isRestrictedWord(w):
+ return w in ('eval', 'arguments')
+
+
+KEYWORDS = set(('if', 'in', 'do', 'var', 'for', 'new', 'try', 'let', 'this', 'else', 'case',
+ 'void', 'with', 'enum', 'while', 'break', 'catch', 'throw', 'const', 'yield',
+ 'class', 'super', 'return', 'typeof', 'delete', 'switch', 'export', 'import',
+ 'default', 'finally', 'extends', 'function', 'continue', 'debugger', 'instanceof', 'pyimport'))
+def isKeyword(w):
+ # 'const' is specialized as Keyword in V8.
+ # 'yield' and 'let' are for compatibility with SpiderMonkey and ES.next.
+ # Some others are from future reserved words.
+ return w in KEYWORDS
+
+
+class JsSyntaxError(Exception): pass
+
+if __name__=='__main__':
+ assert isLineTerminator('\n')
+ assert isLineTerminator(0x0A)
+ assert isIdentifierStart('$')
+ assert isIdentifierStart(100)
+ assert isWhiteSpace(' ') \ No newline at end of file
diff --git a/doc/_extensions/pyjsparser/std_nodes.py b/doc/_extensions/pyjsparser/std_nodes.py
new file mode 100644
index 00000000000..bee67b86c6c
--- /dev/null
+++ b/doc/_extensions/pyjsparser/std_nodes.py
@@ -0,0 +1,470 @@
+from .pyjsparserdata import *
+
+
+class BaseNode:
+ def finish(self):
+ pass
+
+ def finishArrayExpression(self, elements):
+ self.type = Syntax.ArrayExpression
+ self.elements = elements
+ self.finish()
+ return self
+
+ def finishArrayPattern(self, elements):
+ self.type = Syntax.ArrayPattern
+ self.elements = elements
+ self.finish()
+ return self
+
+ def finishArrowFunctionExpression(self, params, defaults, body, expression):
+ self.type = Syntax.ArrowFunctionExpression
+ self.id = None
+ self.params = params
+ self.defaults = defaults
+ self.body = body
+ self.generator = False
+ self.expression = expression
+ self.finish()
+ return self
+
+ def finishAssignmentExpression(self, operator, left, right):
+ self.type = Syntax.AssignmentExpression
+ self.operator = operator
+ self.left = left
+ self.right = right
+ self.finish()
+ return self
+
+ def finishAssignmentPattern(self, left, right):
+ self.type = Syntax.AssignmentPattern
+ self.left = left
+ self.right = right
+ self.finish()
+ return self
+
+ def finishBinaryExpression(self, operator, left, right):
+ self.type = Syntax.LogicalExpression if (operator == '||' or operator == '&&') else Syntax.BinaryExpression
+ self.operator = operator
+ self.left = left
+ self.right = right
+ self.finish()
+ return self
+
+ def finishBlockStatement(self, body):
+ self.type = Syntax.BlockStatement
+ self.body = body
+ self.finish()
+ return self
+
+ def finishBreakStatement(self, label):
+ self.type = Syntax.BreakStatement
+ self.label = label
+ self.finish()
+ return self
+
+ def finishCallExpression(self, callee, args):
+ self.type = Syntax.CallExpression
+ self.callee = callee
+ self.arguments = args
+ self.finish()
+ return self
+
+ def finishCatchClause(self, param, body):
+ self.type = Syntax.CatchClause
+ self.param = param
+ self.body = body
+ self.finish()
+ return self
+
+ def finishClassBody(self, body):
+ self.type = Syntax.ClassBody
+ self.body = body
+ self.finish()
+ return self
+
+ def finishClassDeclaration(self, id, superClass, body):
+ self.type = Syntax.ClassDeclaration
+ self.id = id
+ self.superClass = superClass
+ self.body = body
+ self.finish()
+ return self
+
+ def finishClassExpression(self, id, superClass, body):
+ self.type = Syntax.ClassExpression
+ self.id = id
+ self.superClass = superClass
+ self.body = body
+ self.finish()
+ return self
+
+ def finishConditionalExpression(self, test, consequent, alternate):
+ self.type = Syntax.ConditionalExpression
+ self.test = test
+ self.consequent = consequent
+ self.alternate = alternate
+ self.finish()
+ return self
+
+ def finishContinueStatement(self, label):
+ self.type = Syntax.ContinueStatement
+ self.label = label
+ self.finish()
+ return self
+
+ def finishDebuggerStatement(self, ):
+ self.type = Syntax.DebuggerStatement
+ self.finish()
+ return self
+
+ def finishDoWhileStatement(self, body, test):
+ self.type = Syntax.DoWhileStatement
+ self.body = body
+ self.test = test
+ self.finish()
+ return self
+
+ def finishEmptyStatement(self, ):
+ self.type = Syntax.EmptyStatement
+ self.finish()
+ return self
+
+ def finishExpressionStatement(self, expression):
+ self.type = Syntax.ExpressionStatement
+ self.expression = expression
+ self.finish()
+ return self
+
+ def finishForStatement(self, init, test, update, body):
+ self.type = Syntax.ForStatement
+ self.init = init
+ self.test = test
+ self.update = update
+ self.body = body
+ self.finish()
+ return self
+
+ def finishForInStatement(self, left, right, body):
+ self.type = Syntax.ForInStatement
+ self.left = left
+ self.right = right
+ self.body = body
+ self.each = False
+ self.finish()
+ return self
+
+ def finishFunctionDeclaration(self, id, params, defaults, body):
+ self.type = Syntax.FunctionDeclaration
+ self.id = id
+ self.params = params
+ self.defaults = defaults
+ self.body = body
+ self.generator = False
+ self.expression = False
+ self.finish()
+ return self
+
+ def finishFunctionExpression(self, id, params, defaults, body):
+ self.type = Syntax.FunctionExpression
+ self.id = id
+ self.params = params
+ self.defaults = defaults
+ self.body = body
+ self.generator = False
+ self.expression = False
+ self.finish()
+ return self
+
+ def finishIdentifier(self, name):
+ self.type = Syntax.Identifier
+ self.name = name
+ self.finish()
+ return self
+
+ def finishIfStatement(self, test, consequent, alternate):
+ self.type = Syntax.IfStatement
+ self.test = test
+ self.consequent = consequent
+ self.alternate = alternate
+ self.finish()
+ return self
+
+ def finishLabeledStatement(self, label, body):
+ self.type = Syntax.LabeledStatement
+ self.label = label
+ self.body = body
+ self.finish()
+ return self
+
+ def finishLiteral(self, token):
+ self.type = Syntax.Literal
+ self.value = token['value']
+ self.raw = None # todo fix it?
+ if token.get('regex'):
+ self.regex = token['regex']
+ self.finish()
+ return self
+
+ def finishMemberExpression(self, accessor, object, property):
+ self.type = Syntax.MemberExpression
+ self.computed = accessor == '['
+ self.object = object
+ self.property = property
+ self.finish()
+ return self
+
+ def finishNewExpression(self, callee, args):
+ self.type = Syntax.NewExpression
+ self.callee = callee
+ self.arguments = args
+ self.finish()
+ return self
+
+ def finishObjectExpression(self, properties):
+ self.type = Syntax.ObjectExpression
+ self.properties = properties
+ self.finish()
+ return self
+
+ def finishObjectPattern(self, properties):
+ self.type = Syntax.ObjectPattern
+ self.properties = properties
+ self.finish()
+ return self
+
+ def finishPostfixExpression(self, operator, argument):
+ self.type = Syntax.UpdateExpression
+ self.operator = operator
+ self.argument = argument
+ self.prefix = False
+ self.finish()
+ return self
+
+ def finishProgram(self, body):
+ self.type = Syntax.Program
+ self.body = body
+ self.finish()
+ return self
+
+ def finishPyimport(self, imp):
+ self.type = 'PyimportStatement'
+ self.imp = imp
+ self.finish()
+ return self
+
+ def finishProperty(self, kind, key, computed, value, method, shorthand):
+ self.type = Syntax.Property
+ self.key = key
+ self.computed = computed
+ self.value = value
+ self.kind = kind
+ self.method = method
+ self.shorthand = shorthand
+ self.finish()
+ return self
+
+ def finishRestElement(self, argument):
+ self.type = Syntax.RestElement
+ self.argument = argument
+ self.finish()
+ return self
+
+ def finishReturnStatement(self, argument):
+ self.type = Syntax.ReturnStatement
+ self.argument = argument
+ self.finish()
+ return self
+
+ def finishSequenceExpression(self, expressions):
+ self.type = Syntax.SequenceExpression
+ self.expressions = expressions
+ self.finish()
+ return self
+
+ def finishSpreadElement(self, argument):
+ self.type = Syntax.SpreadElement
+ self.argument = argument
+ self.finish()
+ return self
+
+ def finishSwitchCase(self, test, consequent):
+ self.type = Syntax.SwitchCase
+ self.test = test
+ self.consequent = consequent
+ self.finish()
+ return self
+
+ def finishSuper(self, ):
+ self.type = Syntax.Super
+ self.finish()
+ return self
+
+ def finishSwitchStatement(self, discriminant, cases):
+ self.type = Syntax.SwitchStatement
+ self.discriminant = discriminant
+ self.cases = cases
+ self.finish()
+ return self
+
+ def finishTaggedTemplateExpression(self, tag, quasi):
+ self.type = Syntax.TaggedTemplateExpression
+ self.tag = tag
+ self.quasi = quasi
+ self.finish()
+ return self
+
+ def finishTemplateElement(self, value, tail):
+ self.type = Syntax.TemplateElement
+ self.value = value
+ self.tail = tail
+ self.finish()
+ return self
+
+ def finishTemplateLiteral(self, quasis, expressions):
+ self.type = Syntax.TemplateLiteral
+ self.quasis = quasis
+ self.expressions = expressions
+ self.finish()
+ return self
+
+ def finishThisExpression(self, ):
+ self.type = Syntax.ThisExpression
+ self.finish()
+ return self
+
+ def finishThrowStatement(self, argument):
+ self.type = Syntax.ThrowStatement
+ self.argument = argument
+ self.finish()
+ return self
+
+ def finishTryStatement(self, block, handler, finalizer):
+ self.type = Syntax.TryStatement
+ self.block = block
+ self.guardedHandlers = []
+ self.handlers = [handler] if handler else []
+ self.handler = handler
+ self.finalizer = finalizer
+ self.finish()
+ return self
+
+ def finishUnaryExpression(self, operator, argument):
+ self.type = Syntax.UpdateExpression if (operator == '++' or operator == '--') else Syntax.UnaryExpression
+ self.operator = operator
+ self.argument = argument
+ self.prefix = True
+ self.finish()
+ return self
+
+ def finishVariableDeclaration(self, declarations):
+ self.type = Syntax.VariableDeclaration
+ self.declarations = declarations
+ self.kind = 'var'
+ self.finish()
+ return self
+
+ def finishLexicalDeclaration(self, declarations, kind):
+ self.type = Syntax.VariableDeclaration
+ self.declarations = declarations
+ self.kind = kind
+ self.finish()
+ return self
+
+ def finishVariableDeclarator(self, id, init):
+ self.type = Syntax.VariableDeclarator
+ self.id = id
+ self.init = init
+ self.finish()
+ return self
+
+ def finishWhileStatement(self, test, body):
+ self.type = Syntax.WhileStatement
+ self.test = test
+ self.body = body
+ self.finish()
+ return self
+
+ def finishWithStatement(self, object, body):
+ self.type = Syntax.WithStatement
+ self.object = object
+ self.body = body
+ self.finish()
+ return self
+
+ def finishExportSpecifier(self, local, exported):
+ self.type = Syntax.ExportSpecifier
+ self.exported = exported or local
+ self.local = local
+ self.finish()
+ return self
+
+ def finishImportDefaultSpecifier(self, local):
+ self.type = Syntax.ImportDefaultSpecifier
+ self.local = local
+ self.finish()
+ return self
+
+ def finishImportNamespaceSpecifier(self, local):
+ self.type = Syntax.ImportNamespaceSpecifier
+ self.local = local
+ self.finish()
+ return self
+
+ def finishExportNamedDeclaration(self, declaration, specifiers, src):
+ self.type = Syntax.ExportNamedDeclaration
+ self.declaration = declaration
+ self.specifiers = specifiers
+ self.source = src
+ self.finish()
+ return self
+
+ def finishExportDefaultDeclaration(self, declaration):
+ self.type = Syntax.ExportDefaultDeclaration
+ self.declaration = declaration
+ self.finish()
+ return self
+
+ def finishExportAllDeclaration(self, src):
+ self.type = Syntax.ExportAllDeclaration
+ self.source = src
+ self.finish()
+ return self
+
+ def finishImportSpecifier(self, local, imported):
+ self.type = Syntax.ImportSpecifier
+ self.local = local or imported
+ self.imported = imported
+ self.finish()
+ return self
+
+ def finishImportDeclaration(self, specifiers, src):
+ self.type = Syntax.ImportDeclaration
+ self.specifiers = specifiers
+ self.source = src
+ self.finish()
+ return self
+
+ def __getitem__(self, item):
+ return getattr(self, item)
+
+ def __setitem__(self, key, value):
+ setattr(self, key, value)
+
+class Node(BaseNode):
+ pass
+
+
+class WrappingNode(BaseNode):
+ def __init__(self, startToken=None):
+ pass
+
+
+def node_to_dict(node): # extremely important for translation speed
+ if isinstance(node, list):
+ return [node_to_dict(e) for e in node]
+ elif isinstance(node, dict):
+ return dict((k, node_to_dict(v)) for k, v in node.items())
+ elif not isinstance(node, BaseNode):
+ return node
+ return dict((k, node_to_dict(v)) for k, v in node.__dict__.items())
diff --git a/doc/conf.py b/doc/conf.py
index ab71691afc7..e6108f92f95 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -25,6 +25,7 @@ extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.linkcode',
+ 'autojsdoc.ext',
'github_link',
'odoo_ext',
'html_domain',
@@ -67,8 +68,9 @@ today_fmt = '%B %d, %Y'
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
+# markdown compatibility: make `foo` behave like ``foo``, the rst default is
+# title-reference which is never what people are looking for
+default_role = 'literal'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
diff --git a/doc/reference/javascript.rst b/doc/reference/javascript.rst
index 25b64bc8b05..b7696a2e983 100644
--- a/doc/reference/javascript.rst
+++ b/doc/reference/javascript.rst
@@ -8,1402 +8,5 @@
Javascript
==========
-Widgets
-=======
-
-.. class:: Widget
-
- Exported in ``web.Widget``, the base class for all visual components.
- It corresponds to an MVC view, and provides a number of service to
- simplify handling of a section of a page:
-
- * Handles parent/child relationships between widgets
- * Provides extensive lifecycle management with safety features (e.g.
- automatically destroying children widgets during the destruction of a
- parent)
- * Automatic rendering with :ref:`qweb <reference/qweb>`
- * Backbone-compatible shortcuts
-
-DOM Root
---------
-
-A :class:`~Widget` is responsible for a section of the page
-materialized by the DOM root of the widget.
-
-A widget's DOM root is available via two attributes:
-
-.. attribute:: Widget.el
-
- raw DOM element set as root to the widget
-
-.. attribute:: Widget.$el
-
- jQuery wrapper around :attr:`~Widget.el`
-
-There are two main ways to define and generate this DOM root:
-
-.. attribute:: Widget.template
-
- Should be set to the name of a :ref:`QWeb template <reference/qweb>`.
- If set, the template will be rendered after the widget has been
- initialized but before it has been started. The root element generated by
- the template will be set as the DOM root of the widget.
-
-.. attribute:: Widget.tagName
-
- Used if the widget has no template defined. Defaults to ``div``,
- will be used as the tag name to create the DOM element to set as
- the widget's DOM root. It is possible to further customize this
- generated DOM root with the following attributes:
-
- .. attribute:: Widget.id
-
- Used to generate an ``id`` attribute on the generated DOM
- root.
-
- .. attribute:: Widget.className
-
- Used to generate a ``class`` attribute on the generated DOM root.
-
- .. attribute:: Widget.attributes
-
- Mapping (object literal) of attribute names to attribute
- values. Each of these k:v pairs will be set as a DOM attribute
- on the generated DOM root.
-
- None of these is used in case a template is specified on the widget.
-
-The DOM root can also be defined programmatically by overridding
-
-.. function:: Widget.renderElement
-
- Renders the widget's DOM root and sets it. The default
- implementation will render a set template or generate an element
- as described above, and will call
- :func:`~Widget.setElement` on the result.
-
- Any override to :func:`~Widget.renderElement` which
- does not call its ``_super`` **must** call
- :func:`~Widget.setElement` with whatever it
- generated or the widget's behavior is undefined.
-
- .. note::
-
- The default :func:`~Widget.renderElement` can
- be called repeatedly, it will *replace* the previous DOM root
- (using ``replaceWith``). However, this requires that the
- widget correctly sets and unsets its events (and children
- widgets). Generally, :func:`~Widget.renderElement` should
- not be called repeatedly unless the widget advertizes this feature.
-
-Using a widget
-''''''''''''''
-
-A widget's lifecycle has 3 main phases:
-
-* creation and initialization of the widget instance
-
- .. function:: Widget.init(parent)
-
- initialization method of widgets, synchronous, can be overridden to
- take more parameters from the widget's creator/parent
-
- :param parent: the new widget's parent, used to handle automatic
- destruction and event propagation. Can be ``null`` for
- the widget to have no parent.
- :type parent: :class:`~Widget`
-
-* DOM injection and startup, this is done by calling one of:
-
- .. function:: Widget.appendTo(element)
-
- Renders the widget and inserts it as the last child of the target, uses
- `.appendTo()`_
-
- .. function:: Widget.prependTo(element)
-
- Renders the widget and inserts it as the first child of the target, uses
- `.prependTo()`_
-
- .. function:: Widget.insertAfter(element)
-
- Renders the widget and inserts it as the preceding sibling of the target,
- uses `.insertAfter()`_
-
- .. function:: Widget.insertBefore(element)
-
- Renders the widget and inserts it as the following sibling of the target,
- uses `.insertBefore()`_
-
- All of these methods accept whatever the corresponding jQuery method accepts
- (CSS selectors, DOM nodes or jQuery objects). They all return a deferred_
- and are charged with three tasks:
-
- * rendering the widget's root element via
- :func:`~Widget.renderElement`
- * inserting the widget's root element in the DOM using whichever jQuery
- method they match
- * starting the widget, and returning the result of starting it
-
- .. function:: Widget.start()
-
- asynchronous startup of the widget once it's been injected in the DOM,
- generally used to perform asynchronous RPC calls to fetch whatever
- remote data is necessary for the widget to do its work.
-
- Must return a deferred_ to indicate when its work is done.
-
- A widget is *not guaranteed* to work correctly until its
- :func:`~Widget.start` method has finished executing. The
- widget's parent/creator must wait for a widget to be fully started
- before interacting with it
-
- :returns: deferred_ object
-
-* widget destruction and cleanup
-
- .. function:: Widget.destroy()
-
- destroys the widget's children, unbinds its events and removes its root
- from the DOM. Automatically called when the widget's parent is destroyed,
- must be called explicitly if the widget has no parents or if it is
- removed but its parent remains.
-
- A widget being destroyed is automatically unlinked from its parent.
-
-Related to widget destruction is an important utility method:
-
-.. function:: Widget.alive(deferred[, reject=false])
-
- A significant issue with RPC and destruction is that an RPC call may take
- a long time to execute and return while a widget is being destroyed or
- after it has been destroyed, trying to execute its operations on a widget
- in a broken/invalid state.
-
- This is a frequent source of errors or strange behaviors.
-
- :func:`~Widget.alive` can be used to wrap an RPC call,
- ensuring that whatever operations should be executed when the call ends
- are only executed if the widget is still alive::
-
- this.alive(this.model.query().all()).then(function (records) {
- // would break if executed after the widget is destroyed, wrapping
- // rpc in alive() prevents execution
- _.each(records, function (record) {
- self.$el.append(self.format(record));
- });
- });
-
- :param deferred: a deferred_ object to wrap
- :param reject: by default, if the RPC call returns after the widget has
- been destroyed the returned deferred_ is left in limbo
- (neither resolved nor rejected). If ``reject`` is set to
- ``true``, the deferred_ will be rejected instead.
- :returns: deferred_ object
-
-.. function:: Widget.isDestroyed()
-
- :returns: ``true`` if the widget is being or has been destroyed, ``false``
- otherwise
-
-Accessing DOM content
-'''''''''''''''''''''
-
-Because a widget is only responsible for the content below its DOM root, there
- is a shortcut for selecting sub-sections of a widget's DOM:
-
-.. function:: Widget.$(selector)
-
- Applies the CSS selector specified as parameter to the widget's
- DOM root::
-
- this.$(selector);
-
- is functionally identical to::
-
- this.$el.find(selector);
-
- :param String selector: CSS selector
- :returns: jQuery object
-
- .. note:: this helper method is similar to ``Backbone.View.$``
-
-Resetting the DOM root
-''''''''''''''''''''''
-
-.. function:: Widget.setElement(element)
-
- Re-sets the widget's DOM root to the provided element, also
- handles re-setting the various aliases of the DOM root as well as
- unsetting and re-setting delegated events.
-
- :param Element element: a DOM element or jQuery object to set as
- the widget's DOM root
-
- .. note:: should be mostly compatible with `Backbone's
- setElement`_
-
-DOM events handling
--------------------
-
-A widget will generally need to respond to user action within its
-section of the page. This entails binding events to DOM elements.
-
-To this end, :class:`~Widget` provides a shortcut:
-
-.. attribute:: Widget.events
-
- Events are a mapping of an event selector (an event name and an optional
- CSS selector separated by a space) to a callback. The callback can
- be the name of a widget's method or a function object. In either case, the
- ``this`` will be set to the widget::
-
- events: {
- 'click p.oe_some_class a': 'some_method',
- 'change input': function (e) {
- e.stopPropagation();
- }
- },
-
- The selector is used for jQuery's `event delegation`_, the
- callback will only be triggered for descendants of the DOM root
- matching the selector\ [#eventsdelegation]_. If the selector is left out
- (only an event name is specified), the event will be set directly on the
- widget's DOM root.
-
-.. function:: Widget.delegateEvents
-
- This method is in charge of binding :attr:`~Widget.events` to the
- DOM. It is automatically called after setting the widget's DOM root.
-
- It can be overridden to set up more complex events than the
- :attr:`~Widget.events` map allows, but the parent should always be
- called (or :attr:`~Widget.events` won't be handled correctly).
-
-.. function:: Widget.undelegateEvents
-
- This method is in charge of unbinding :attr:`~Widget.events` from
- the DOM root when the widget is destroyed or the DOM root is reset, in
- order to avoid leaving "phantom" events.
-
- It should be overridden to un-set any event set in an override of
- :func:`~Widget.delegateEvents`.
-
-.. note:: this behavior should be compatible with `Backbone's
- delegateEvents`_, apart from not accepting any argument.
-
-Subclassing Widget
-------------------
-
-:class:`~Widget` is subclassed in the standard manner (via the
-:func:`~Class.extend` method), and provides a number of
-abstract properties and concrete methods (which you may or may not want to
-override). Creating a subclass looks like this::
-
- var MyWidget = Widget.extend({
- // QWeb template to use when rendering the object
- template: "MyQWebTemplate",
- events: {
- // events binding example
- 'click .my-button': 'handle_click',
- },
-
- init: function(parent) {
- this._super(parent);
- // insert code to execute before rendering, for object
- // initialization
- },
- start: function() {
- var sup = this._super();
- // post-rendering initialization code, at this point
-
- // allows multiplexing deferred objects
- return $.when(
- // propagate asynchronous signal from parent class
- sup,
- // return own's asynchronous signal
- this.rpc(/* … */))
- }
- });
-
-The new class can then be used in the following manner::
-
- // Create the instance
- var my_widget = new MyWidget(this);
- // Render and insert into DOM
- my_widget.appendTo(".some-div");
-
-After these two lines have executed (and any promise returned by
-:func:`~Widget.appendTo` has been resolved if needed), the widget is
-ready to be used.
-
-.. note:: the insertion methods will start the widget themselves, and will
- return the result of :func:`~Widget.start()`.
-
- If for some reason you do not want to call these methods, you will
- have to first call :func:`~Widget.render()` on the
- widget, then insert it into your DOM and start it.
-
-If the widget is not needed anymore (because it's transient), simply terminate
-it::
-
- my_widget.destroy();
-
-will unbind all DOM events, remove the widget's content from the DOM and
-destroy all widget data.
-
-Development Guidelines
-----------------------
-
-* Identifiers (``id`` attribute) should be avoided. In generic applications
- and modules, ``id`` limits the re-usability of components and tends to make
- code more brittle. Most of the time, they can be replaced with nothing,
- classes or keeping a reference to a DOM node or jQuery element.
-
- If an ``id`` is absolutely necessary (because a third-party library requires
- one), the id should be partially generated using ``_.uniqueId()`` e.g.::
-
- this.id = _.uniqueId('my-widget-')
-* Avoid predictable/common CSS class names. Class names such as "content" or
- "navigation" might match the desired meaning/semantics, but it is likely an
- other developer will have the same need, creating a naming conflict and
- unintended behavior. Generic class names should be prefixed with e.g. the
- name of the component they belong to (creating "informal" namespaces, much
- as in C or Objective-C).
-* Global selectors should be avoided. Because a component may be used several
- times in a single page (an example in Odoo is dashboards), queries should be
- restricted to a given component's scope. Unfiltered selections such as
- ``$(selector)`` or ``document.querySelectorAll(selector)`` will generally
- lead to unintended or incorrect behavior. Odoo Web's
- :class:`~Widget` has an attribute providing its DOM root
- (:attr:`~Widget.$el`), and a shortcut to select nodes directly
- (:func:`~Widget.$`).
-* More generally, never assume your components own or controls anything beyond
- its own personal :attr:`~Widget.$el`
-* html templating/rendering should use QWeb unless absolutely trivial.
-* All interactive components (components displaying information to the screen
- or intercepting DOM events) must inherit from :class:`~Widget`
- and correctly implement and use its API and life cycle.
-
-.. _.appendTo():
- http://api.jquery.com/appendTo/
-
-.. _.prependTo():
- http://api.jquery.com/prependTo/
-
-.. _.insertAfter():
- http://api.jquery.com/insertAfter/
-
-.. _.insertBefore():
- http://api.jquery.com/insertBefore/
-
-.. _event delegation:
- http://api.jquery.com/delegate/
-
-.. _Backbone's setElement:
- http://backbonejs.org/#View-setElement
-
-.. _Backbone's delegateEvents:
- http://backbonejs.org/#View-delegateEvents
-
-.. _deferred: http://api.jquery.com/category/deferred-object/
-
-RPC
-===
-
-To display and interact with data, calls to the Odoo server are necessary.
-This is performed using :abbr:`RPC <Remote Procedure Call>`.
-
-Odoo Web provides two primary APIs to handle this: a low-level
-JSON-RPC based API communicating with the Python section of Odoo
-Web (and of your module, if you have a Python part) and a high-level
-API above that allowing your code to talk directly to high-level Odoo models.
-
-All networking APIs are :ref:`asynchronous <reference/async>`. As a result,
-all of them will return Deferred_ objects (whether they resolve those with
-values or not). Understanding how those work before before moving on is
-probably necessary.
-
-High-level API: calling into Odoo models
--------------------------------------------
-
-Access to Odoo object methods (made available through XML-RPC from the server)
-is done via :class:`Model`. It maps onto the Odoo server objects via two primary
-methods, :func:`~Model.call` (exported in ``web.Model``) and :func:`~Model.query`
-(exported in ``web.DataModel``, only available in the backend client).
-
-:func:`~Model.call` is a direct mapping to the corresponding method of
-the Odoo server object. Its usage is similar to that of the Odoo Model API,
-with three differences:
-
-* The interface is :ref:`asynchronous <reference/async>`, so instead of
- returning results directly RPC method calls will return
- Deferred_ instances, which will themselves resolve to the
- result of the matching RPC call.
-
-* Because ECMAScript 3/Javascript 1.5 doesnt feature any equivalent to
- ``__getattr__`` or ``method_missing``, there needs to be an explicit
- method to dispatch RPC methods.
-
-* No notion of pooler, the model proxy is instantiated where needed,
- not fetched from an other (somewhat global) object::
-
- var Users = new Model('res.users');
-
- Users.call('change_password', ['oldpassword', 'newpassword'],
- {context: some_context}).then(function (result) {
- // do something with change_password result
- });
-
-:func:`~Model.query` is a shortcut for a builder-style
-interface to searches (``search`` + ``read`` in Odoo RPC terms). It
-returns a :class:`~odoo.web.Query` object which is immutable but
-allows building new :class:`~odoo.web.Query` instances from the
-first one, adding new properties or modifiying the parent object's::
-
- Users.query(['name', 'login', 'user_email', 'signature'])
- .filter([['active', '=', true], ['company_id', '=', main_company]])
- .limit(15)
- .all().then(function (users) {
- // do work with users records
- });
-
-The query is only actually performed when calling one of the query
-serialization methods, :func:`~odoo.web.Query.all` and
-:func:`~odoo.web.Query.first`. These methods will perform a new
-RPC call every time they are called.
-
-For that reason, it's actually possible to keep "intermediate" queries
-around and use them differently/add new specifications on them.
-
-.. class:: Model(name)
-
- .. attribute:: Model.name
-
- name of the model this object is bound to
-
- .. function:: Model.call(method[, args][, kwargs])
-
- Calls the ``method`` method of the current model, with the
- provided positional and keyword arguments.
-
- :param String method: method to call over rpc on the
- :attr:`~Model.name`
- :param Array<> args: positional arguments to pass to the
- method, optional
- :param Object<> kwargs: keyword arguments to pass to the
- method, optional
- :rtype: Deferred<>
-
- .. function:: Model.query(fields)
-
- :param Array<String> fields: list of fields to fetch during
- the search
- :returns: a :class:`~odoo.web.Query` object
- representing the search to perform
-
-.. class:: odoo.web.Query(fields)
-
- The first set of methods is the "fetching" methods. They perform
- RPC queries using the internal data of the object they're called
- on.
-
- .. function:: odoo.web.Query.all()
-
- Fetches the result of the current :class:`~odoo.web.Query` object's
- search.
-
- :rtype: Deferred<Array<>>
-
- .. function:: odoo.web.Query.first()
-
- Fetches the **first** result of the current
- :class:`~odoo.web.Query`, or ``null`` if the current
- :class:`~odoo.web.Query` does have any result.
-
- :rtype: Deferred<Object | null>
-
- .. function:: odoo.web.Query.count()
-
- Fetches the number of records the current
- :class:`~odoo.web.Query` would retrieve.
-
- :rtype: Deferred<Number>
-
- .. function:: odoo.web.Query.group_by(grouping...)
-
- Fetches the groups for the query, using the first specified
- grouping parameter
-
- :param Array<String> grouping: Lists the levels of grouping
- asked of the server. Grouping
- can actually be an array or
- varargs.
- :rtype: Deferred<Array<odoo.web.QueryGroup>> | null
-
- The second set of methods is the "mutator" methods, they create a
- **new** :class:`~odoo.web.Query` object with the relevant
- (internal) attribute either augmented or replaced.
-
- .. function:: odoo.web.Query.context(ctx)
-
- Adds the provided ``ctx`` to the query, on top of any existing
- context
-
- .. function:: odoo.web.Query.filter(domain)
-
- Adds the provided domain to the query, this domain is
- ``AND``-ed to the existing query domain.
-
- .. function:: opeenrp.web.Query.offset(offset)
-
- Sets the provided offset on the query. The new offset
- *replaces* the old one.
-
- .. function:: odoo.web.Query.limit(limit)
-
- Sets the provided limit on the query. The new limit *replaces*
- the old one.
-
- .. function:: odoo.web.Query.order_by(fields…)
-
- Overrides the model's natural order with the provided field
- specifications. Behaves much like Django's :py:meth:`QuerySet.order_by
- <django.db.models.query.QuerySet.order_by>`:
-
- * Takes 1..n field names, in order of most to least importance
- (the first field is the first sorting key). Fields are
- provided as strings.
-
- * A field specifies an ascending order, unless it is prefixed
- with the minus sign "``-``" in which case the field is used
- in the descending order
-
- Divergences from Django's sorting include a lack of random sort
- (``?`` field) and the inability to "drill down" into relations
- for sorting.
-
-Aggregation (grouping)
-''''''''''''''''''''''
-
-Odoo has powerful grouping capacities, but they are kind-of strange
-in that they're recursive, and level n+1 relies on data provided
-directly by the grouping at level n. As a result, while
-:py:meth:`odoo.models.Model.read_group` works it's not a very intuitive
-API.
-
-Odoo Web eschews direct calls to :py:meth:`~odoo.models.Model.read_group`
-in favor of calling a method of :class:`~odoo.web.Query`, :py:meth:`much
-in the way it is one in SQLAlchemy <sqlalchemy.orm.query.Query.group_by>`
-[#terminal]_::
-
- some_query.group_by(['field1', 'field2']).then(function (groups) {
- // do things with the fetched groups
- });
-
-This method is asynchronous when provided with 1..n fields (to group
-on) as argument, but it can also be called without any field (empty
-fields collection or nothing at all). In this case, instead of
-returning a Deferred object it will return ``null``.
-
-When grouping criterion come from a third-party and may or may not
-list fields (e.g. could be an empty list), this provides two ways to
-test the presence of actual subgroups (versus the need to perform a
-regular query for records):
-
-* A check on ``group_by``'s result and two completely separate code
- paths::
-
- var groups;
- if (groups = some_query.group_by(gby)) {
- groups.then(function (gs) {
- // groups
- });
- }
- // no groups
-
-* Or a more coherent code path using :func:`when`'s ability to
- coerce values into deferreds::
-
- $.when(some_query.group_by(gby)).then(function (groups) {
- if (!groups) {
- // No grouping
- } else {
- // grouping, even if there are no groups (groups
- // itself could be an empty array)
- }
- });
-
-The result of a (successful) :func:`~odoo.web.Query.group_by` is
-an array of :class:`~odoo.web.QueryGroup`:
-
-.. class:: odoo.web.QueryGroup
-
- .. function:: odoo.web.QueryGroup.get(key)
-
- returns the group's attribute ``key``. Known attributes are:
-
- ``grouped_on``
- which grouping field resulted from this group
- ``value``
- ``grouped_on``'s value for this group
- ``length``
- the number of records in the group
- ``aggregates``
- a {field: value} mapping of aggregations for the group
-
- .. function:: odoo.web.QueryGroup.query([fields...])
-
- equivalent to :func:`Model.query` but pre-filtered to
- only include the records within this group. Returns a
- :class:`~odoo.web.Query` which can be further manipulated as
- usual.
-
- .. function:: odoo.web.QueryGroup.subgroups()
-
- returns a deferred to an array of :class:`~odoo.web.QueryGroup`
- below this one
-
-Low-level API: RPC calls to Python side
----------------------------------------
-
-While the previous section is great for calling core OpenERP code
-(models code), it does not work if you want to call the Python side of
-Odoo Web.
-
-For this, a lower-level API exists on on
-:class:`~Session` objects (the class is exported in ``web.Session``, but
-an instance isusually available through ``web.session``): the ``rpc`` method.
-
-This method simply takes an absolute path (the absolute URL of the JSON
-:ref:`route <reference/http/routing>` to call) and a mapping of attributes to
-values (passed as keyword arguments to the Python method). This function
-fetches the return value of the Python methods, converted to JSON.
-
-For instance, to call the ``resequence`` of the
-:class:`~web.controllers.main.DataSet` controller::
-
- session.rpc('/web/dataset/resequence', {
- model: some_model,
- ids: array_of_ids,
- offset: 42
- }).then(function (result) {
- // resequence didn't error out
- }, function () {
- // an error occured during during call
- });
-
-.. _reference/javascript/client:
-
-Web Client
-==========
-
-Javascript module system overview
----------------------------------
-
-A new module system (inspired from requirejs) has now been deployed.
-It has many advantages over the Odoo version 8 system.
-
-* loading order: dependencies are guaranteed to be loaded first, even if
- files are not loaded in the correct order in the bundle files.
-* easier to split a file into smaller logical units.
-* no global variables: easier to reason.
-* it is possible to examine every dependencies and dependants. This
- makes refactoring much simpler, and less risky.
-
-It has also some disadvantages:
-
-* files are required to use the module system if they want to interact
- with odoo, since the various objects are only available in the module
- system, and not in global variables
-* circular dependencies are not supported. It makes sense, but it means
- that one needs to be careful.
-
-This is obviously a very large change and will require everyone to
-adopt new habits. For example, the variable odoo does not exist
-anymore. The new way of doing things is to import explicitely the module
-you need, and declaring explicitely the objects you export. Here is a
-simple example::
-
- odoo.define('addon_name.service', function (require) {
- var utils = require('web.utils');
- var Model = require('web.Model');
-
- // do things with utils and Model
- var something_useful = 15;
- return {
- something_useful: something_useful,
- };
- });
-
-This snippet shows a module named ``addon_name.service``. It is defined
-with the ``odoo.define`` function. ``odoo.define`` takes a name and a
-function for arguments:
-
-* The name is the concatenation of the name of the addon it is defined in
- and a name describing its purpose.
-* The function is the place where the javascript module is actually
- defined. It takes a function ``require`` as first argument, and
- returns something (or not, depending if it needs to export something).
- The ``require`` function is used to get a handle on the dependencies.
- In this case, it gives a handle on two javascript modules from the
- ``web`` addon, namely ``web.utils`` and ``web.Model``.
-
-The idea is that you define what you need to import (by using the
-``require`` function) and declare what you export (by returning
-something). The web client will then make sure that your code is loaded
-properly.
-
-Modules are contained in a file, but a file can define several modules
-(however, it is better to keep them in separate files).
-
-Each module can return a deferred. In that case, the module is marked as loaded
-only when the deferred is resolved, and its value is equal to the resolved value.
-The module can be rejected (unloaded). This will be logged in the console as info.
-
-* ``Missing dependencies``:
- These modules do not appear in the page. It is possible that the JavaScript
- file is not in the page or that the module name is wrong
-* ``Failed modules``:
- A javascript error is detected
-* ``Rejected modules``:
- The module returns a rejected deferred. It (and its dependent modules) is not
- loaded.
-* ``Rejected linked modules``:
- Modules who depend on a rejected module
-* ``Non loaded modules``:
- Modules who depend on a missing or a failed module
-
-
-Web client structure
---------------------
-
-The web client files have been refactored into smaller and simpler files.
-Here is a description of the current file structure:
-
-* the ``framework/`` folder contains all basic low level modules. The
- modules here are supposed to be generic. Some of them are:
-
- * ``web.ajax`` implements rpc calls
- * ``web.core`` is a generic modules. It exports various useful
- objects and functions, such as ``qweb``, ``_t`` or the main bus.
- * ``web.Widget`` contains the widget class
- * ``web.Model`` is an abstraction over ``web.ajax`` to make
- calls to the server model methods
- * ``web.session`` is the former ``odoo.session``
- * ``web.utils`` for useful code snippets
- * ``web.time`` for every time-related generic functions
-* the ``views/`` folder contains all view definitions
-* ``widgets/`` is for standalone widgets
-
-The ``js/`` folder also contains some important files:
-
-* ``action_manager.js`` is the ActionManager class
-* ``boot.js`` is the file actually implementing the module system
-* ``menu.js`` is the definition of the top menu
-* ``web_client.js`` is for the root widget WebClient
-* ``view_manager.js`` contains the ViewManager
-
-The two other files are ``tour.js`` for the tours and ``compatibility.js``.
-The latter file is a compatibility layer bridging the old system to the
-new module system. This is where every module names are exported to the
-global variable ``odoo``. In theory, our addons should work without
-ever using the variable ``odoo``, and the compatibility module can be
-disabled safely.
-
-Javascript conventions
-----------------------
-
-Here are some basic conventions for the javascript code:
-
-* declare all your dependencies at the top of the module. Also, they
- should be sorted alphabetically by module name. This makes it
- easier to understand your module.
-* declare all exports at the end.
-* add the ``use strict`` statement at the beginning of every module
-* always name your module properly: ``addon_name.description``.
-* use capital letters for classes (for example, ``ActionManager`` is
- defined in the module ``web.ActionManager``), and lowercase for
- everything else (for example, ``ajax`` is defined in ``web.ajax``).
-* declare one module per file
-
-
-
-
-
-Testing in Odoo Web Client
-==========================
-
-Javascript Unit Testing
------------------------
-
-Odoo Web includes means to unit-test both the core code of
-Odoo Web and your own javascript modules. On the javascript side,
-unit-testing is based on QUnit_ with a number of helpers and
-extensions for better integration with Odoo.
-
-To see what the runner looks like, find (or start) an Odoo server
-with the web client enabled, and navigate to ``/web/tests``
-This will show the runner selector, which lists all modules with javascript
-unit tests, and allows starting any of them (or all javascript tests in all
-modules at once).
-
-.. image:: ./images/runner.png
- :align: center
-
-Clicking any runner button will launch the corresponding tests in the
-bundled QUnit_ runner:
-
-.. image:: ./images/tests.png
- :align: center
-
-Writing a test case
--------------------
-
-The first step is to list the test file(s). This is done through the
-``test`` key of the Odoo manifest, by adding javascript files to it:
-
-.. code-block:: python
-
- {
- 'name': "Demonstration of web/javascript tests",
- 'category': 'Hidden',
- 'depends': ['web'],
- 'test': ['static/tests/demo.js'],
- }
-
-and to create the corresponding test file(s)
-
-.. note::
-
- Test files which do not exist will be ignored, if all test files
- of a module are ignored (can not be found), the test runner will
- consider that the module has no javascript tests.
-
-After that, refreshing the runner selector will display the new module
-and allow running all of its (0 so far) tests:
-
-.. image:: ./images/runner2.png
- :align: center
-
-The next step is to create a test case::
-
- odoo.testing.section('basic section', function (test) {
- test('my first test', function () {
- ok(false, "this test has run");
- });
- });
-
-All testing helpers and structures live in the ``odoo.testing``
-module. Odoo tests live in a :func:`~odoo.testing.section`,
-which is itself part of a module. The first argument to a section is
-the name of the section, the second one is the section body.
-
-:func:`test <odoo.testing.case>`, provided by the
-:func:`~odoo.testing.section` to the callback, is used to
-register a given test case which will be run whenever the test runner
-actually does its job. Odoo Web test case use standard `QUnit
-assertions`_ within them.
-
-Launching the test runner at this point will run the test and display
-the corresponding assertion message, with red colors indicating the
-test failed:
-
-.. image:: ./images/tests2.png
- :align: center
-
-Fixing the test (by replacing ``false`` to ``true`` in the assertion)
-will make it pass:
-
-.. image:: ./images/tests3.png
- :align: center
-
-Assertions
-----------
-
-As noted above, Odoo Web's tests use `qunit assertions`_. They are
-available globally (so they can just be called without references to
-anything). The following list is available:
-
-.. function:: ok(state[, message])
-
- checks that ``state`` is truthy (in the javascript sense)
-
-.. function:: strictEqual(actual, expected[, message])
-
- checks that the actual (produced by a method being tested) and
- expected values are identical (roughly equivalent to ``ok(actual
- === expected, message)``)
-
-.. function:: notStrictEqual(actual, expected[, message])
-
- checks that the actual and expected values are *not* identical
- (roughly equivalent to ``ok(actual !== expected, message)``)
-
-.. function:: deepEqual(actual, expected[, message])
-
- deep comparison between actual and expected: recurse into
- containers (objects and arrays) to ensure that they have the same
- keys/number of elements, and the values match.
-
-.. function:: notDeepEqual(actual, expected[, message])
-
- inverse operation to :func:`deepEqual`
-
-.. function:: throws(block[, expected][, message])
-
- checks that, when called, the ``block`` throws an
- error. Optionally validates that error against ``expected``.
-
- :param Function block:
- :param expected: if a regexp, checks that the thrown error's
- message matches the regular expression. If an
- error type, checks that the thrown error is of
- that type.
- :type expected: Error | RegExp
-
-.. function:: equal(actual, expected[, message])
-
- checks that ``actual`` and ``expected`` are loosely equal, using
- the ``==`` operator and its coercion rules.
-
-.. function:: notEqual(actual, expected[, message])
-
- inverse operation to :func:`equal`
-
-Getting an Odoo instance
-------------------------
-
-The Odoo instance is the base through which most Odoo Web
-modules behaviors (functions, objects, …) are accessed. As a result,
-the test framework automatically builds one, and loads the module
-being tested and all of its dependencies inside it. This new instance
-is provided as the first positional parameter to your test
-cases. Let's observe by adding javascript code (not test code) to the
-test module:
-
-.. code-block:: python
-
- {
- 'name': "Demonstration of web/javascript tests",
- 'category': 'Hidden',
- 'depends': ['web'],
- 'js': ['static/src/js/demo.js'],
- 'test': ['static/tests/demo.js'],
- }
-
-::
-
- // src/js/demo.js
- odoo.web_tests_demo = function (instance) {
- instance.web_tests_demo = {
- value_true: true,
- SomeType: instance.web.Class.extend({
- init: function (value) {
- this.value = value;
- }
- })
- };
- };
-
-and then adding a new test case, which simply checks that the
-``instance`` contains all the expected stuff we created in the
-module::
-
- // test/demo.js
- test('module content', function (instance) {
- ok(instance.web_tests_demo.value_true, "should have a true value");
- var type_instance = new instance.web_tests_demo.SomeType(42);
- strictEqual(type_instance.value, 42, "should have provided value");
- });
-
-DOM Scratchpad
---------------
-
-As in the wider client, arbitrarily accessing document content is
-strongly discouraged during tests. But DOM access is still needed to
-e.g. fully initialize :class:`widgets <~odoo.Widget>` before
-testing them.
-
-Thus, a test case gets a DOM scratchpad as its second positional
-parameter, in a jQuery instance. That scratchpad is fully cleaned up
-before each test, and as long as it doesn't do anything outside the
-scratchpad your code can do whatever it wants::
-
- // test/demo.js
- test('DOM content', function (instance, $scratchpad) {
- $scratchpad.html('<div><span class="foo bar">ok</span></div>');
- ok($scratchpad.find('span').hasClass('foo'),
- "should have provided class");
- });
- test('clean scratchpad', function (instance, $scratchpad) {
- ok(!$scratchpad.children().length, "should have no content");
- ok(!$scratchpad.text(), "should have no text");
- });
-
-.. note::
-
- The top-level element of the scratchpad is not cleaned up, test
- cases can add text or DOM children but shoud not alter
- ``$scratchpad`` itself.
-
-Loading templates
------------------
-
-To avoid the corresponding processing costs, by default templates are
-not loaded into QWeb. If you need to render e.g. widgets making use of
-QWeb templates, you can request their loading through the
-:attr:`~TestOptions.templates` option to the :func:`test case
-function <odoo.testing.case>`.
-
-This will automatically load all relevant templates in the instance's
-qweb before running the test case:
-
-.. code-block:: python
-
- {
- 'name': "Demonstration of web/javascript tests",
- 'category': 'Hidden',
- 'depends': ['web'],
- 'js': ['static/src/js/demo.js'],
- 'test': ['static/tests/demo.js'],
- 'qweb': ['static/src/xml/demo.xml'],
- }
-
-.. code-block:: xml
-
- <!-- src/xml/demo.xml -->
- <templates id="template" xml:space="preserve">
- <t t-name="DemoTemplate">
- <t t-foreach="5" t-as="value">
- <p><t t-esc="value"/></p>
- </t>
- </t>
- </templates>
-
-::
-
- // test/demo.js
- test('templates', {templates: true}, function (instance) {
- var s = instance.web.qweb.render('DemoTemplate');
- var texts = $(s).find('p').map(function () {
- return $(this).text();
- }).get();
-
- deepEqual(texts, ['0', '1', '2', '3', '4']);
- });
-
-Asynchronous cases
-------------------
-
-The test case examples so far are all synchronous, they execute from
-the first to the last line and once the last line has executed the
-test is done. But the web client is full of :ref:`asynchronous code
-<reference/async>`, and thus test cases need to be async-aware.
-
-This is done by returning a :class:`deferred <Deferred>` from the
-case callback::
-
- // test/demo.js
- test('asynchronous', {
- asserts: 1
- }, function () {
- var d = $.Deferred();
- setTimeout(function () {
- ok(true);
- d.resolve();
- }, 100);
- return d;
- });
-
-This example also uses the :class:`options parameter <TestOptions>`
-to specify the number of assertions the case should expect, if less or
-more assertions are specified the case will count as failed.
-
-Asynchronous test cases *must* specify the number of assertions they
-will run. This allows more easily catching situations where e.g. the
-test architecture was not warned about asynchronous operations.
-
-.. note::
-
- Asynchronous test cases also have a 2 seconds timeout: if the test
- does not finish within 2 seconds, it will be considered
- failed. This pretty much always means the test will not
- resolve. This timeout *only* applies to the test itself, not to
- the setup and teardown processes.
-
-.. note::
-
- If the returned deferred is rejected, the test will be failed
- unless :attr:`~TestOptions.fail_on_rejection` is set to
- ``false``.
-
-RPC
----
-
-An important subset of asynchronous test cases is test cases which
-need to perform (and chain, to an extent) RPC calls.
-
-.. note::
-
- Because they are a subset of asynchronous cases, RPC cases must
- also provide a valid :attr:`assertions count
- <TestOptions.asserts>`.
-
-To enable mock RPC, set the :attr:`rpc option <TestOptions.rpc>` to
-``mock``. This will add a third parameter to the test case callback:
-
-.. function:: mock(rpc_spec, handler)
-
- Can be used in two different ways depending on the shape of the
- first parameter:
-
- * If it matches the pattern ``model:method`` (if it contains a
- colon, essentially) the call will set up the mocking of an RPC
- call straight to the Odoo server (through XMLRPC) as
- performed via e.g. :func:`odoo.web.Model.call`.
-
- In that case, ``handler`` should be a function taking two
- arguments ``args`` and ``kwargs``, matching the corresponding
- arguments on the server side and should simply return the value
- as if it were returned by the Python XMLRPC handler::
-
- test('XML-RPC', {rpc: 'mock', asserts: 3}, function (instance, $s, mock) {
- // set up mocking
- mock('people.famous:name_search', function (args, kwargs) {
- strictEqual(kwargs.name, 'bob');
- return [
- [1, "Microsoft Bob"],
- [2, "Bob the Builder"],
- [3, "Silent Bob"]
- ];
- });
-
- // actual test code
- return new instance.web.Model('people.famous')
- .call('name_search', {name: 'bob'}).then(function (result) {
- strictEqual(result.length, 3, "shoud return 3 people");
- strictEqual(result[0][1], "Microsoft Bob",
- "the most famous bob should be Microsoft Bob");
- });
- });
-
- * Otherwise, if it matches an absolute path (e.g. ``/a/b/c``) it
- will mock a JSON-RPC call to a web client controller, such as
- ``/web/webclient/translations``. In that case, the handler takes
- a single ``params`` argument holding all of the parameters
- provided over JSON-RPC.
-
- As previously, the handler should simply return the result value
- as if returned by the original JSON-RPC handler::
-
- test('JSON-RPC', {rpc: 'mock', asserts: 3, templates: true}, function (instance, $s, mock) {
- var fetched_dbs = false, fetched_langs = false;
- mock('/web/database/get_list', function () {
- fetched_dbs = true;
- return ['foo', 'bar', 'baz'];
- });
- mock('/web/session/get_lang_list', function () {
- fetched_langs = true;
- return [['vo_IS', 'Hopelandic / Vonlenska']];
- });
-
- // widget needs that or it blows up
- instance.webclient = {toggle_bars: odoo.testing.noop};
- var dbm = new instance.web.DatabaseManager({});
- return dbm.appendTo($s).then(function () {
- ok(fetched_dbs, "should have fetched databases");
- ok(fetched_langs, "should have fetched languages");
- deepEqual(dbm.db_list, ['foo', 'bar', 'baz']);
- });
- });
-
-.. note::
-
- Mock handlers can contain assertions, these assertions should be
- part of the assertions count (and if multiple calls are made to a
- handler containing assertions, it multiplies the effective number
- of assertions).
-
-Testing API
------------
-
-.. function:: odoo.testing.section(name[, options], body)
-
- A test section, serves as shared namespace for related tests (for
- constants or values to only set up once). The ``body`` function
- should contain the tests themselves.
-
- Note that the order in which tests are run is essentially
- undefined, do *not* rely on it.
-
- :param String name:
- :param TestOptions options:
- :param body:
- :type body: Function<:func:`~odoo.testing.case`, void>
-
-.. function:: odoo.testing.case(name[, options], callback)
-
- Registers a test case callback in the test runner, the callback
- will only be run once the runner is started (or maybe not at all,
- if the test is filtered out).
-
- :param String name:
- :param TestOptions options:
- :param callback:
- :type callback: Function<instance, $, Function<String, Function, void>>
-
-.. class:: TestOptions
-
- the various options which can be passed to
- :func:`~odoo.testing.section` or
- :func:`~odoo.testing.case`. Except for
- :attr:`~TestOptions.setup` and
- :attr:`~TestOptions.teardown`, an option on
- :func:`~odoo.testing.case` will overwrite the corresponding
- option on :func:`~odoo.testing.section` so
- e.g. :attr:`~TestOptions.rpc` can be set for a
- :func:`~odoo.testing.section` and then differently set for
- some :func:`~odoo.testing.case` of that
- :func:`~odoo.testing.section`
-
- .. attribute:: TestOptions.asserts
-
- An integer, the number of assertions which should run during a
- normal execution of the test. Mandatory for asynchronous tests.
-
- .. attribute:: TestOptions.setup
-
- Test case setup, run right before each test case. A section's
- :func:`~TestOptions.setup` is run before the case's own, if
- both are specified.
-
- .. attribute:: TestOptions.teardown
-
- Test case teardown, a case's :func:`~TestOptions.teardown`
- is run before the corresponding section if both are present.
-
- .. attribute:: TestOptions.fail_on_rejection
-
- If the test is asynchronous and its resulting promise is
- rejected, fail the test. Defaults to ``true``, set to
- ``false`` to not fail the test in case of rejection::
-
- // test/demo.js
- test('unfail rejection', {
- asserts: 1,
- fail_on_rejection: false
- }, function () {
- var d = $.Deferred();
- setTimeout(function () {
- ok(true);
- d.reject();
- }, 100);
- return d;
- });
-
- .. attribute:: TestOptions.rpc
-
- RPC method to use during tests, one of ``"mock"`` or
- ``"rpc"``. Any other value will disable RPC for the test (if
- they were enabled by the suite for instance).
-
- .. attribute:: TestOptions.templates
-
- Whether the current module (and its dependencies)'s templates
- should be loaded into QWeb before starting the test. A
- boolean, ``false`` by default.
-
-The test runner can also use two global configuration values set
-directly on the ``window`` object:
-
-* ``oe_all_dependencies`` is an ``Array`` of all modules with a web
- component, ordered by dependency (for a module ``A`` with
- dependencies ``A'``, any module of ``A'`` must come before ``A`` in
- the array)
-
-Running through Python
-----------------------
-
-The web client includes the means to run these tests on the
-command-line (or in a CI system), but while actually running it is
-pretty simple the setup of the pre-requisite parts has some
-complexities.
-
-#. Install PhantomJS_. It is a headless
- browser which allows automating running and testing web
- pages. QUnitSuite_ uses it to actually run the qunit_ test suite.
-
- The PhantomJS_ website provides pre-built binaries for some
- platforms, and your OS's package management probably provides it as
- well.
-
- If you're building PhantomJS_ from source, I recommend preparing
- for some knitting time as it's not exactly fast (it needs to
- compile both `Qt <http://qt-project.org/>`_ and `Webkit
- <http://www.webkit.org/>`_, both being pretty big projects).
-
- .. note::
-
- Because PhantomJS_ is webkit-based, it will not be able to test
- if Firefox, Opera or Internet Explorer can correctly run the
- test suite (and it is only an approximation for Safari and
- Chrome). It is therefore recommended to *also* run the test
- suites in actual browsers once in a while.
-
- .. note::
-
- The version of PhantomJS_ this was build through is 1.7,
- previous versions *should* work but are not actually supported
- (and tend to just segfault when something goes wrong in
- PhantomJS_ itself so they're a pain to debug).
-
-#. Install a new database with all relevant modules (all modules with
- a web component at least), then restart the server
-
- .. note::
-
- For some tests, a source database needs to be duplicated. This
- operation requires that there be no connection to the database
- being duplicated, but Odoo doesn't currently break
- existing/outstanding connections, so restarting the server is
- the simplest way to ensure everything is in the right state.
-
-#. Launch ``oe run-tests -d $DATABASE -mweb`` with the correct
- addons-path specified (and replacing ``$DATABASE`` by the source
- database you created above)
-
- .. note::
-
- If you leave out ``-mweb``, the runner will attempt to run all
- the tests in all the modules, which may or may not work.
-
-If everything went correctly, you should now see a list of tests with
-(hopefully) ``ok`` next to their names, closing with a report of the
-number of tests run and the time it took:
-
-.. literalinclude:: test-report.txt
- :language: text
-
-Congratulation, you have just performed a successful "offline" run of
-the OpenERP Web test suite.
-
-.. note::
-
- Note that this runs all the Python tests for the ``web`` module,
- but all the web tests for all of Odoo. This can be surprising.
-
-.. _qunit: http://qunitjs.com/
-
-.. _qunit assertions: http://api.qunitjs.com/category/assert/
-
-.. _QUnitSuite: http://pypi.python.org/pypi/QUnitSuite/
-
-.. _PhantomJS: http://phantomjs.org/
-
-.. [#eventsdelegation] not all DOM events are compatible with events delegation
-
-.. [#terminal]
- with a small twist: :py:meth:`sqlalchemy.orm.query.Query.group_by` is not
- terminal, it returns a query which can still be altered.
-
+.. automodule:: *
+ :members:
diff --git a/doc/requirements.txt b/doc/requirements.txt
index e3059c049c9..6c2a5df88c2 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,3 +1,4 @@
-sphinx==1.2.3
+sphinx>=1.2
mercurial==3.2.2
sphinx-patchqueue==0.4.0
+PyJSDoc==0.9.1
diff --git a/odoo-bin b/odoo-bin
index 535ea47f60f..fae9bf233b6 100755
--- a/odoo-bin
+++ b/odoo-bin
@@ -2,7 +2,6 @@
# set server timezone in UTC before time module imported
__import__('os').environ['TZ'] = 'UTC'
-__import__('pkg_resources').declare_namespace('odoo.addons')
import odoo
if __name__ == "__main__":
diff --git a/odoo/__init__.py b/odoo/__init__.py
index 3c3b14fdadc..2814706b57a 100644
--- a/odoo/__init__.py
+++ b/odoo/__init__.py
@@ -4,6 +4,12 @@
""" OpenERP core library."""
#----------------------------------------------------------
+# odoo must be a namespace package for odoo.addons to become one too
+# https://packaging.python.org/guides/packaging-namespace-packages/
+#----------------------------------------------------------
+__path__ = __import__('pkgutil').extend_path(__path__, __name__)
+
+#----------------------------------------------------------
# Running mode flags (gevent, prefork)
#----------------------------------------------------------
# Is the server running with gevent.
diff --git a/odoo/addons/__init__.py b/odoo/addons/__init__.py
index d3a3e7e9ba2..c0058937588 100644
--- a/odoo/addons/__init__.py
+++ b/odoo/addons/__init__.py
@@ -14,4 +14,7 @@ This module also conveniently reexports some symbols from odoo.modules.
Importing them from here is deprecated.
"""
-__import__('pkg_resources').declare_namespace(__name__)
+# make odoo.addons a namespace package, while keeping this __init__.py
+# present, for python 2 compatibility
+# https://packaging.python.org/guides/packaging-namespace-packages/
+__path__ = __import__('pkgutil').extend_path(__path__, __name__)
diff --git a/odoo/addons/base/res/res_country.py b/odoo/addons/base/res/res_country.py
index 36e539f8e23..a3984155b4e 100644
--- a/odoo/addons/base/res/res_country.py
+++ b/odoo/addons/base/res/res_country.py
@@ -64,6 +64,7 @@ class Country(models.Model):
('after', 'After Address'),
], string="Customer Name Position", default="before",
help="Determines where the customer/company name should be placed, i.e. after or before the address.")
+ vat_label = fields.Char(string='Vat Label', translate=True, help="Use this field if you want to change vat label.")
_sql_constraints = [
('name_uniq', 'unique (name)',
diff --git a/odoo/addons/base/res/res_country_data.xml b/odoo/addons/base/res/res_country_data.xml
index 95b83a5fa52..ff2a5c71242 100644
--- a/odoo/addons/base/res/res_country_data.xml
+++ b/odoo/addons/base/res/res_country_data.xml
@@ -84,6 +84,7 @@
<field file="base/static/img/country_flags/at.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="43" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="au" model="res.country">
<field name="name">Australia</field>
@@ -142,6 +143,7 @@
<field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="EUR" />
<field eval="32" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="bf" model="res.country">
<field name="name">Burkina Faso</field>
@@ -156,6 +158,7 @@
<field file="base/static/img/country_flags/bg.png" name="image" type="base64" />
<field name="currency_id" ref="BGN" />
<field eval="359" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="bh" model="res.country">
<field name="name">Bahrain</field>
@@ -391,6 +394,7 @@
<field file="base/static/img/country_flags/cy.png" name="image" type="base64" />
<field name="currency_id" ref="CYP" />
<field eval="357" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="cz" model="res.country">
<field name="name">Czech Republic</field>
@@ -398,6 +402,7 @@
<field file="base/static/img/country_flags/cz.png" name="image" type="base64" />
<field name="currency_id" ref="CZK" />
<field eval="420" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="de" model="res.country">
<field name="name">Germany</field>
@@ -406,6 +411,7 @@
<field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="EUR" />
<field eval="49" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="dj" model="res.country">
<field name="name">Djibouti</field>
@@ -420,6 +426,7 @@
<field file="base/static/img/country_flags/dk.png" name="image" type="base64" />
<field name="currency_id" ref="DKK" />
<field eval="45" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="dm" model="res.country">
<field name="name">Dominica</field>
@@ -455,6 +462,7 @@
<field file="base/static/img/country_flags/ee.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="372" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="eg" model="res.country">
<field name="name">Egypt</field>
@@ -483,6 +491,7 @@
<field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="EUR" />
<field eval="34" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="et" model="res.country">
<field name="name">Ethiopia</field>
@@ -498,6 +507,7 @@
<field name="address_format" eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" />
<field name="currency_id" ref="EUR" />
<field eval="358" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="fj" model="res.country">
<field name="name">Fiji</field>
@@ -534,6 +544,7 @@
<field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="EUR" />
<field eval="33" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="ga" model="res.country">
<field name="name">Gabon</field>
@@ -623,6 +634,7 @@
<field file="base/static/img/country_flags/gr.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="30" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="gs" model="res.country">
<field name="name">South Georgia and the South Sandwich Islands</field>
@@ -685,6 +697,7 @@
<field file="base/static/img/country_flags/hr.png" name="image" type="base64" />
<field name="currency_id" ref="HRK" />
<field eval="385" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="ht" model="res.country">
<field name="name">Haiti</field>
@@ -699,6 +712,7 @@
<field file="base/static/img/country_flags/hu.png" name="image" type="base64" />
<field name="currency_id" ref="HUF" />
<field eval="36" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="id" model="res.country">
<field name="name">Indonesia</field>
@@ -713,6 +727,7 @@
<field file="base/static/img/country_flags/ie.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="353" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="il" model="res.country">
<field name="name">Israel</field>
@@ -732,9 +747,10 @@
<field name="name">India</field>
<field name="code">in</field>
<field file="base/static/img/country_flags/in.png" name="image" type="base64" />
- <field eval="'%(street)s\n%(street2)s\n%(city)s %(zip)s\n%(state_name)s\n%(country_name)s'" name="address_format" />
+ <field eval="'%(street)s\n%(street2)s\n%(city)s %(zip)s\n%(state_name)s %(state_code)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="INR" />
<field eval="91" name="phone_code" />
+ <field name="vat_label">GSTIN</field>
</record>
<record id="io" model="res.country">
<field name="name">British Indian Ocean Territory</field>
@@ -770,6 +786,7 @@
<field file="base/static/img/country_flags/it.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="39" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="je" model="res.country">
<field name="name">Jersey</field>
@@ -934,6 +951,7 @@
<field file="base/static/img/country_flags/lt.png" name="image" type="base64" />
<field name="currency_id" ref="EUR"/>
<field eval="370" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="lu" model="res.country">
<field name="name">Luxembourg</field>
@@ -941,6 +959,7 @@
<field file="base/static/img/country_flags/lu.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="352" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="lv" model="res.country">
<field name="name">Latvia</field>
@@ -948,6 +967,7 @@
<field file="base/static/img/country_flags/lv.png" name="image" type="base64" />
<field name="currency_id" ref="EUR"/>
<field eval="371" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="ly" model="res.country">
<field name="name">Libya</field>
@@ -1073,6 +1093,7 @@
<field file="base/static/img/country_flags/mt.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="356" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="mu" model="res.country">
<field name="name">Mauritius</field>
@@ -1165,6 +1186,7 @@
<field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="EUR" />
<field eval="31" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="no" model="res.country">
<field name="name">Norway</field>
@@ -1257,6 +1279,7 @@
<field name="address_format" eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" />
<field name="currency_id" ref="PLN" />
<field eval="48" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="pm" model="res.country">
<field name="name">Saint Pierre and Miquelon</field>
@@ -1291,6 +1314,7 @@
<field file="base/static/img/country_flags/pt.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="351" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="pw" model="res.country">
<field name="name">Palau</field>
@@ -1325,6 +1349,7 @@
<field file="base/static/img/country_flags/ro.png" name="image" type="base64" />
<field name="currency_id" ref="RON" />
<field eval="40" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="rs" model="res.country">
<field name="name">Serbia</field>
@@ -1381,6 +1406,7 @@
<field file="base/static/img/country_flags/se.png" name="image" type="base64" />
<field name="currency_id" ref="SEK" />
<field eval="46" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="sg" model="res.country">
<field name="name">Singapore</field>
@@ -1402,6 +1428,7 @@
<field file="base/static/img/country_flags/si.png" name="image" type="base64" />
<field name="currency_id" ref="EUR" />
<field eval="386" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="sj" model="res.country">
<field name="name">Svalbard and Jan Mayen</field>
@@ -1415,6 +1442,7 @@
<field file="base/static/img/country_flags/sk.png" name="image" type="base64" />
<field name="currency_id" ref="SKK" />
<field eval="421" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="sl" model="res.country">
<field name="name">Sierra Leone</field>
@@ -1626,6 +1654,7 @@
<field file="base/static/img/country_flags/gb.png" name="image" type="base64" />
<field name="currency_id" ref="GBP" />
<field eval="0" name="phone_code" />
+ <field name="vat_label">VAT</field>
</record>
<record id="um" model="res.country">
<field name="name">USA Minor Outlying Islands</field>
diff --git a/odoo/addons/base/res/res_country_view.xml b/odoo/addons/base/res/res_country_view.xml
index 5b883b993e3..aede3731c9d 100644
--- a/odoo/addons/base/res/res_country_view.xml
+++ b/odoo/addons/base/res/res_country_view.xml
@@ -31,6 +31,7 @@
</group>
<group>
<field name="phone_code"/>
+ <field name="vat_label"/>
</group>
<group string="Advanced Address Formatting" groups="base.group_no_one" col="4" colspan="4">
<field name="address_view_id"/>
diff --git a/odoo/api.py b/odoo/api.py
index 506c344da30..143f51bc31c 100644
--- a/odoo/api.py
+++ b/odoo/api.py
@@ -692,9 +692,9 @@ def call_kw(model, name, args, kwargs):
class Environment(Mapping):
""" An environment wraps data for ORM records:
- - :attr:`cr`, the current database cursor;
- - :attr:`uid`, the current user id;
- - :attr:`context`, the current context dictionary.
+ - :attr:`cr`, the current database cursor;
+ - :attr:`uid`, the current user id;
+ - :attr:`context`, the current context dictionary.
It provides access to the registry by implementing a mapping from model
names to new api models. It also holds a cache for records, and a data
diff --git a/odoo/release.py b/odoo/release.py
index c563d9ff4cd..81059d81bf1 100644
--- a/odoo/release.py
+++ b/odoo/release.py
@@ -12,7 +12,7 @@ RELEASE_LEVELS_DISPLAY = {ALPHA: ALPHA,
# properly comparable using normal operarors, for example:
# (6,1,0,'beta',0) < (6,1,0,'candidate',1) < (6,1,0,'candidate',2)
# (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0)
-version_info = (11, 0, 0, ALPHA, 1, '')
+version_info = (10, 'saas~18', 0, FINAL, 0, '')
version = '.'.join(str(s) for s in version_info[:2]) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5]
series = serie = major_version = '.'.join(str(s) for s in version_info[:2])
diff --git a/setup/odoo b/setup/odoo
index 535ea47f60f..fae9bf233b6 100644
--- a/setup/odoo
+++ b/setup/odoo
@@ -2,7 +2,6 @@
# set server timezone in UTC before time module imported
__import__('os').environ['TZ'] = 'UTC'
-__import__('pkg_resources').declare_namespace('odoo.addons')
import odoo
if __name__ == "__main__":