diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-09-19 13:05:04 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-09-19 13:05:04 +0200 |
| commit | 08db385d02dd67f82937e4325179dda1a82519b7 (patch) | |
| tree | 9d1be20b01650218fefa38ec3ecae9a708fa0cf3 | |
| parent | 071b46c22786d9b0b7d1ef46387907cbfeb8980b (diff) | |
| parent | d15a31ae6d944cf04f9da8752b069b221cac0d9a (diff) | |
[MERGE] forward port branch saas-18 up to d15a31ae6d9
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>&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;">&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 < 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 @@ -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__": |
