summaryrefslogtreecommitdiff
path: root/addons/account/static
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/account/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/account/static')
-rw-r--r--addons/account/static/description/icon.pngbin0 -> 9117 bytes
-rw-r--r--addons/account/static/src/css/account.css38
-rw-r--r--addons/account/static/src/css/account_bank_and_cash.css31
-rw-r--r--addons/account/static/src/css/tax_group.css25
-rw-r--r--addons/account/static/src/img/account_dashboard_onboarding_bg.jpgbin0 -> 35204 bytes
-rw-r--r--addons/account/static/src/img/account_invoice_onboarding_bg.jpgbin0 -> 23728 bytes
-rw-r--r--addons/account/static/src/img/btn_paynowcc_lg.gifbin0 -> 2916 bytes
-rw-r--r--addons/account/static/src/img/graph.pngbin0 -> 1722 bytes
-rw-r--r--addons/account/static/src/img/invoice-stamps.pngbin0 -> 6140 bytes
-rw-r--r--addons/account/static/src/js/account_dashboard_setup_bar.js0
-rw-r--r--addons/account/static/src/js/account_payment_field.js156
-rw-r--r--addons/account/static/src/js/account_portal_sidebar.js73
-rw-r--r--addons/account/static/src/js/account_resequence_field.js32
-rw-r--r--addons/account/static/src/js/account_selection.js80
-rw-r--r--addons/account/static/src/js/bank_statement.js21
-rw-r--r--addons/account/static/src/js/bills_tree_upload.js127
-rw-r--r--addons/account/static/src/js/grouped_view_widget.js40
-rw-r--r--addons/account/static/src/js/mail_activity.js69
-rw-r--r--addons/account/static/src/js/section_and_note_fields_backend.js106
-rw-r--r--addons/account/static/src/js/tax_group.js171
-rw-r--r--addons/account/static/src/js/tours/account.js93
-rw-r--r--addons/account/static/src/scss/account_activity.scss8
-rw-r--r--addons/account/static/src/scss/account_dashboard.scss61
-rw-r--r--addons/account/static/src/scss/account_journal_dashboard.scss68
-rw-r--r--addons/account/static/src/scss/account_reconciliation.scss367
-rw-r--r--addons/account/static/src/scss/account_searchpanel.scss26
-rw-r--r--addons/account/static/src/scss/section_and_note_backend.scss29
-rw-r--r--addons/account/static/src/scss/variables.scss19
-rw-r--r--addons/account/static/src/xml/account_journal_activity.xml21
-rw-r--r--addons/account/static/src/xml/account_payment.xml85
-rw-r--r--addons/account/static/src/xml/account_resequence.xml25
-rw-r--r--addons/account/static/src/xml/bills_tree_upload_views.xml23
-rw-r--r--addons/account/static/src/xml/grouped_view_widget.xml36
-rw-r--r--addons/account/static/src/xml/tax_group.xml35
-rw-r--r--addons/account/static/tests/account_payment_field_tests.js84
-rw-r--r--addons/account/static/tests/section_and_note_tests.js98
-rw-r--r--addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js42
-rw-r--r--addons/account/static/tests/tours/tax_group_tests.js124
38 files changed, 2213 insertions, 0 deletions
diff --git a/addons/account/static/description/icon.png b/addons/account/static/description/icon.png
new file mode 100644
index 00000000..e3a38a76
--- /dev/null
+++ b/addons/account/static/description/icon.png
Binary files differ
diff --git a/addons/account/static/src/css/account.css b/addons/account/static/src/css/account.css
new file mode 100644
index 00000000..c3817370
--- /dev/null
+++ b/addons/account/static/src/css/account.css
@@ -0,0 +1,38 @@
+.openerp div.oe_account_help {
+ background : #D6EBFF;
+ width: 100%;
+ padding: 10px;
+ border: 3px solid #C1D4E6;
+}
+
+.openerp p.oe_account_font_help{
+ text-align: left;
+ font-weight: bold;
+ margin: 0px;
+ font-size: 14px;
+}
+
+.openerp p.oe_account_font_content{
+ margin-left: 30px;
+ font-size: 14px;
+}
+
+.openerp p.oe_account_font_title{
+ margin-top: 7px;
+ font-size: 15px;
+ font-style: italic;
+ color: grey;
+}
+
+.oe_invoice_outstanding_credits_debits {
+ clear: both;
+ float: right;
+ min-width: 350px;
+}
+
+@media (max-width: 767.98px) {
+ .oe_invoice_outstanding_credits_debits {
+ min-width: initial;
+ width: 100%;
+ }
+}
diff --git a/addons/account/static/src/css/account_bank_and_cash.css b/addons/account/static/src/css/account_bank_and_cash.css
new file mode 100644
index 00000000..1d2a4fba
--- /dev/null
+++ b/addons/account/static/src/css/account_bank_and_cash.css
@@ -0,0 +1,31 @@
+.openerp .oe_force_bold {
+ font-weight: bold !important;
+}
+.openerp label.oe_open_balance{
+ margin-right: -18px;
+}
+.openerp label.oe_subtotal_footer_separator{
+ float:right;
+ width: 184px !important;
+}
+.openerp label.oe_mini_subtotal_footer_separator{
+ margin-right: -14px;
+}
+.openerp .oe_account_total, .openerp .oe_pos_total {
+ margin-left: -2px;
+}
+.openerp label.oe_real_closing_balance{
+ min-width: 184px !important;
+}
+.openerp label.oe_difference, .openerp label.oe_pos_difference {
+ margin-right: -10px;
+ padding-left: 10px !important;
+ min-width: 195px !important;
+}
+.openerp .oe_opening_total{
+ margin-right: 4px;
+}
+
+.o_payment_label{
+ padding-right: 20px;
+} \ No newline at end of file
diff --git a/addons/account/static/src/css/tax_group.css b/addons/account/static/src/css/tax_group.css
new file mode 100644
index 00000000..0b617056
--- /dev/null
+++ b/addons/account/static/src/css/tax_group.css
@@ -0,0 +1,25 @@
+.oe_tax_group_editable {
+ width: 100%;
+}
+
+.tax_group_edit {
+ white-space: nowrap;
+}
+
+.tax_group_edit:hover {
+ color: #00A09D;
+ cursor: pointer;
+}
+
+.oe_tax_group_name {
+ font-weight: bold;
+ min-width: 150px;
+ text-align: right;
+ padding-right: 20px;
+}
+
+.oe_tax_group_editable .oe_tax_group_amount_value input {
+ width: 65%;
+ float: right;
+ text-align: right;
+}
diff --git a/addons/account/static/src/img/account_dashboard_onboarding_bg.jpg b/addons/account/static/src/img/account_dashboard_onboarding_bg.jpg
new file mode 100644
index 00000000..163ffdd9
--- /dev/null
+++ b/addons/account/static/src/img/account_dashboard_onboarding_bg.jpg
Binary files differ
diff --git a/addons/account/static/src/img/account_invoice_onboarding_bg.jpg b/addons/account/static/src/img/account_invoice_onboarding_bg.jpg
new file mode 100644
index 00000000..7b7e1a52
--- /dev/null
+++ b/addons/account/static/src/img/account_invoice_onboarding_bg.jpg
Binary files differ
diff --git a/addons/account/static/src/img/btn_paynowcc_lg.gif b/addons/account/static/src/img/btn_paynowcc_lg.gif
new file mode 100644
index 00000000..092c5542
--- /dev/null
+++ b/addons/account/static/src/img/btn_paynowcc_lg.gif
Binary files differ
diff --git a/addons/account/static/src/img/graph.png b/addons/account/static/src/img/graph.png
new file mode 100644
index 00000000..eb0efc7b
--- /dev/null
+++ b/addons/account/static/src/img/graph.png
Binary files differ
diff --git a/addons/account/static/src/img/invoice-stamps.png b/addons/account/static/src/img/invoice-stamps.png
new file mode 100644
index 00000000..83a67022
--- /dev/null
+++ b/addons/account/static/src/img/invoice-stamps.png
Binary files differ
diff --git a/addons/account/static/src/js/account_dashboard_setup_bar.js b/addons/account/static/src/js/account_dashboard_setup_bar.js
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/addons/account/static/src/js/account_dashboard_setup_bar.js
diff --git a/addons/account/static/src/js/account_payment_field.js b/addons/account/static/src/js/account_payment_field.js
new file mode 100644
index 00000000..fa56d1a1
--- /dev/null
+++ b/addons/account/static/src/js/account_payment_field.js
@@ -0,0 +1,156 @@
+odoo.define('account.payment', function (require) {
+"use strict";
+
+var AbstractField = require('web.AbstractField');
+var core = require('web.core');
+var field_registry = require('web.field_registry');
+var field_utils = require('web.field_utils');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+var ShowPaymentLineWidget = AbstractField.extend({
+ events: _.extend({
+ 'click .outstanding_credit_assign': '_onOutstandingCreditAssign',
+ }, AbstractField.prototype.events),
+ supportedFieldTypes: ['char'],
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @returns {boolean}
+ */
+ isSet: function() {
+ return true;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @override
+ */
+ _render: function() {
+ var self = this;
+ var info = JSON.parse(this.value);
+ if (!info) {
+ this.$el.html('');
+ return;
+ }
+ _.each(info.content, function (k, v){
+ k.index = v;
+ k.amount = field_utils.format.float(k.amount, {digits: k.digits});
+ if (k.date){
+ k.date = field_utils.format.date(field_utils.parse.date(k.date, {}, {isUTC: true}));
+ }
+ });
+ this.$el.html(QWeb.render('ShowPaymentInfo', {
+ lines: info.content,
+ outstanding: info.outstanding,
+ title: info.title
+ }));
+ _.each(this.$('.js_payment_info'), function (k, v){
+ var isRTL = _t.database.parameters.direction === "rtl";
+ var content = info.content[v];
+ var options = {
+ content: function () {
+ var $content = $(QWeb.render('PaymentPopOver', content));
+ var unreconcile_button = $content.filter('.js_unreconcile_payment').on('click', self._onRemoveMoveReconcile.bind(self));
+
+ $content.filter('.js_open_payment').on('click', self._onOpenPayment.bind(self));
+ return $content;
+ },
+ html: true,
+ placement: isRTL ? 'bottom' : 'left',
+ title: 'Payment Information',
+ trigger: 'focus',
+ delay: { "show": 0, "hide": 100 },
+ container: $(k).parent(), // FIXME Ugly, should use the default body container but system & tests to adapt to properly destroy the popover
+ };
+ $(k).popover(options);
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @override
+ * @param {MouseEvent} event
+ */
+ _onOpenPayment: function (event) {
+ var paymentId = parseInt($(event.target).attr('payment-id'));
+ var moveId = parseInt($(event.target).attr('move-id'));
+ var res_model;
+ var id;
+ if (paymentId !== undefined && !isNaN(paymentId)){
+ res_model = "account.payment";
+ id = paymentId;
+ } else if (moveId !== undefined && !isNaN(moveId)){
+ res_model = "account.move";
+ id = moveId;
+ }
+ //Open form view of account.move with id = move_id
+ if (res_model && id) {
+ this.do_action({
+ type: 'ir.actions.act_window',
+ res_model: res_model,
+ res_id: id,
+ views: [[false, 'form']],
+ target: 'current'
+ });
+ }
+ },
+ /**
+ * @private
+ * @override
+ * @param {MouseEvent} event
+ */
+ _onOutstandingCreditAssign: function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ var self = this;
+ var id = $(event.target).data('id') || false;
+ this._rpc({
+ model: 'account.move',
+ method: 'js_assign_outstanding_line',
+ args: [JSON.parse(this.value).move_id, id],
+ }).then(function () {
+ self.trigger_up('reload');
+ });
+ },
+ /**
+ * @private
+ * @override
+ * @param {MouseEvent} event
+ */
+ _onRemoveMoveReconcile: function (event) {
+ var self = this;
+ var moveId = parseInt($(event.target).attr('move-id'));
+ var partialId = parseInt($(event.target).attr('partial-id'));
+ if (partialId !== undefined && !isNaN(partialId)){
+ this._rpc({
+ model: 'account.move',
+ method: 'js_remove_outstanding_partial',
+ args: [moveId, partialId],
+ }).then(function () {
+ self.trigger_up('reload');
+ });
+ }
+ },
+});
+
+field_registry.add('payment', ShowPaymentLineWidget);
+
+return {
+ ShowPaymentLineWidget: ShowPaymentLineWidget
+};
+
+});
diff --git a/addons/account/static/src/js/account_portal_sidebar.js b/addons/account/static/src/js/account_portal_sidebar.js
new file mode 100644
index 00000000..58114e00
--- /dev/null
+++ b/addons/account/static/src/js/account_portal_sidebar.js
@@ -0,0 +1,73 @@
+odoo.define('account.AccountPortalSidebar', function (require) {
+'use strict';
+
+const dom = require('web.dom');
+var publicWidget = require('web.public.widget');
+var PortalSidebar = require('portal.PortalSidebar');
+var utils = require('web.utils');
+
+publicWidget.registry.AccountPortalSidebar = PortalSidebar.extend({
+ selector: '.o_portal_invoice_sidebar',
+ events: {
+ 'click .o_portal_invoice_print': '_onPrintInvoice',
+ },
+
+ /**
+ * @override
+ */
+ start: function () {
+ var def = this._super.apply(this, arguments);
+
+ var $invoiceHtml = this.$el.find('iframe#invoice_html');
+ var updateIframeSize = this._updateIframeSize.bind(this, $invoiceHtml);
+
+ $(window).on('resize', updateIframeSize);
+
+ var iframeDoc = $invoiceHtml[0].contentDocument || $invoiceHtml[0].contentWindow.document;
+ if (iframeDoc.readyState === 'complete') {
+ updateIframeSize();
+ } else {
+ $invoiceHtml.on('load', updateIframeSize);
+ }
+
+ return def;
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when the iframe is loaded or the window is resized on customer portal.
+ * The goal is to expand the iframe height to display the full report without scrollbar.
+ *
+ * @private
+ * @param {object} $el: the iframe
+ */
+ _updateIframeSize: function ($el) {
+ var $wrapwrap = $el.contents().find('div#wrapwrap');
+ // Set it to 0 first to handle the case where scrollHeight is too big for its content.
+ $el.height(0);
+ $el.height($wrapwrap[0].scrollHeight);
+
+ // scroll to the right place after iframe resize
+ if (!utils.isValidAnchor(window.location.hash)) {
+ return;
+ }
+ var $target = $(window.location.hash);
+ if (!$target.length) {
+ return;
+ }
+ dom.scrollTo($target[0], {duration: 0});
+ },
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onPrintInvoice: function (ev) {
+ ev.preventDefault();
+ var href = $(ev.currentTarget).attr('href');
+ this._printIframeContent(href);
+ },
+});
+});
diff --git a/addons/account/static/src/js/account_resequence_field.js b/addons/account/static/src/js/account_resequence_field.js
new file mode 100644
index 00000000..57b57bfe
--- /dev/null
+++ b/addons/account/static/src/js/account_resequence_field.js
@@ -0,0 +1,32 @@
+odoo.define('account.ShowResequenceRenderer', function (require) {
+"use strict";
+
+const { Component } = owl;
+const { useState } = owl.hooks;
+const AbstractFieldOwl = require('web.AbstractFieldOwl');
+const field_registry = require('web.field_registry_owl');
+
+class ChangeLine extends Component { }
+ChangeLine.template = 'account.ResequenceChangeLine';
+ChangeLine.props = ["changeLine", 'ordering'];
+
+
+class ShowResequenceRenderer extends AbstractFieldOwl {
+ constructor(...args) {
+ super(...args);
+ this.data = this.value ? JSON.parse(this.value) : {
+ changeLines: [],
+ ordering: 'date',
+ };
+ }
+ async willUpdateProps(nextProps) {
+ await super.willUpdateProps(nextProps);
+ Object.assign(this.data, JSON.parse(this.value));
+ }
+}
+ShowResequenceRenderer.template = 'account.ResequenceRenderer';
+ShowResequenceRenderer.components = { ChangeLine }
+
+field_registry.add('account_resequence_widget', ShowResequenceRenderer);
+return ShowResequenceRenderer;
+});
diff --git a/addons/account/static/src/js/account_selection.js b/addons/account/static/src/js/account_selection.js
new file mode 100644
index 00000000..feee1c17
--- /dev/null
+++ b/addons/account/static/src/js/account_selection.js
@@ -0,0 +1,80 @@
+odoo.define('account.hierarchy.selection', function (require) {
+"use strict";
+
+ var core = require('web.core');
+ var relational_fields = require('web.relational_fields');
+ var _t = core._t;
+ var registry = require('web.field_registry');
+
+
+ var FieldSelection = relational_fields.FieldSelection;
+
+ var qweb = core.qweb;
+
+ var HierarchySelection = FieldSelection.extend({
+ _renderEdit: function () {
+ var self = this;
+ var prom = Promise.resolve()
+ if (!self.hierarchy_groups) {
+ prom = this._rpc({
+ model: 'account.account.type',
+ method: 'search_read',
+ kwargs: {
+ domain: [],
+ fields: ['id', 'internal_group', 'display_name'],
+ },
+ }).then(function(arg) {
+ self.values = _.map(arg, v => [v['id'], v['display_name']])
+ self.hierarchy_groups = [
+ {
+ 'name': _t('Balance Sheet'),
+ 'children': [
+ {'name': _t('Assets'), 'ids': _.map(_.filter(arg, v => v['internal_group'] == 'asset'), v => v['id'])},
+ {'name': _t('Liabilities'), 'ids': _.map(_.filter(arg, v => v['internal_group'] == 'liability'), v => v['id'])},
+ {'name': _t('Equity'), 'ids': _.map(_.filter(arg, v => v['internal_group'] == 'equity'), v => v['id'])},
+ ],
+ },
+ {
+ 'name': _t('Profit & Loss'),
+ 'children': [
+ {'name': _t('Income'), 'ids': _.map(_.filter(arg, v => v['internal_group'] == 'income'), v => v['id'])},
+ {'name': _t('Expense'), 'ids': _.map(_.filter(arg, v => v['internal_group'] == 'expense'), v => v['id'])},
+ ],
+ },
+ {'name': _t('Other'), 'ids': _.map(_.filter(arg, v => !['asset', 'liability', 'equity', 'income', 'expense'].includes(v['internal_group'])), v => v['id'])},
+ ]
+ });
+ }
+
+ Promise.resolve(prom).then(function() {
+ self.$el.empty();
+ self._addHierarchy(self.$el, self.hierarchy_groups, 0);
+ var value = self.value;
+ if (self.field.type === 'many2one' && value) {
+ value = value.data.id;
+ }
+ self.$el.val(JSON.stringify(value));
+ });
+ },
+ _addHierarchy: function(el, group, level) {
+ var self = this;
+ _.each(group, function(item) {
+ var optgroup = $('<optgroup/>').attr(({
+ 'label': $('<div/>').html('&nbsp;'.repeat(6 * level) + item['name']).text(),
+ }))
+ _.each(item['ids'], function(id) {
+ var value = _.find(self.values, v => v[0] == id)
+ optgroup.append($('<option/>', {
+ value: JSON.stringify(value[0]),
+ text: value[1],
+ }));
+ })
+ el.append(optgroup)
+ if (item['children']) {
+ self._addHierarchy(el, item['children'], level + 1);
+ }
+ })
+ }
+ });
+ registry.add("account_hierarchy_selection", HierarchySelection);
+});
diff --git a/addons/account/static/src/js/bank_statement.js b/addons/account/static/src/js/bank_statement.js
new file mode 100644
index 00000000..3ab8d935
--- /dev/null
+++ b/addons/account/static/src/js/bank_statement.js
@@ -0,0 +1,21 @@
+odoo.define('account.bank_statement', function(require) {
+ "use strict";
+
+ var KanbanController = require("web.KanbanController");
+ var ListController = require("web.ListController");
+
+ var includeDict = {
+ renderButtons: function () {
+ this._super.apply(this, arguments);
+ if (this.modelName === "account.bank.statement") {
+ var data = this.model.get(this.handle);
+ if (data.context.journal_type !== 'cash') {
+ this.$buttons.find('button.o_button_import').hide();
+ }
+ }
+ }
+ };
+
+ KanbanController.include(includeDict);
+ ListController.include(includeDict);
+}); \ No newline at end of file
diff --git a/addons/account/static/src/js/bills_tree_upload.js b/addons/account/static/src/js/bills_tree_upload.js
new file mode 100644
index 00000000..2894b868
--- /dev/null
+++ b/addons/account/static/src/js/bills_tree_upload.js
@@ -0,0 +1,127 @@
+odoo.define('account.upload.bill.mixin', function (require) {
+"use strict";
+
+ var core = require('web.core');
+ var _t = core._t;
+
+ var qweb = core.qweb;
+
+ var UploadBillMixin = {
+
+ start: function () {
+ // define a unique uploadId and a callback method
+ this.fileUploadID = _.uniqueId('account_bill_file_upload');
+ $(window).on(this.fileUploadID, this._onFileUploaded.bind(this));
+ return this._super.apply(this, arguments);
+ },
+
+ _onAddAttachment: function (ev) {
+ // Auto submit form once we've selected an attachment
+ var $input = $(ev.currentTarget).find('input.o_input_file');
+ if ($input.val() !== '') {
+ var $binaryForm = this.$('.o_vendor_bill_upload form.o_form_binary_form');
+ $binaryForm.submit();
+ }
+ },
+
+ _onFileUploaded: function () {
+ // Callback once attachment have been created, create a bill with attachment ids
+ var self = this;
+ var attachments = Array.prototype.slice.call(arguments, 1);
+ // Get id from result
+ var attachent_ids = attachments.reduce(function(filtered, record) {
+ if (record.id) {
+ filtered.push(record.id);
+ }
+ return filtered;
+ }, []);
+ return this._rpc({
+ model: 'account.journal',
+ method: 'create_invoice_from_attachment',
+ args: ["", attachent_ids],
+ context: this.initialState.context,
+ }).then(function(result) {
+ self.do_action(result);
+ });
+ },
+
+ _onUpload: function (event) {
+ var self = this;
+ // If hidden upload form don't exists, create it
+ var $formContainer = this.$('.o_content').find('.o_vendor_bill_upload');
+ if (!$formContainer.length) {
+ $formContainer = $(qweb.render('account.BillsHiddenUploadForm', {widget: this}));
+ $formContainer.appendTo(this.$('.o_content'));
+ }
+ // Trigger the input to select a file
+ this.$('.o_vendor_bill_upload .o_input_file').click();
+ },
+ }
+ return UploadBillMixin;
+});
+
+
+odoo.define('account.bills.tree', function (require) {
+"use strict";
+ var core = require('web.core');
+ var ListController = require('web.ListController');
+ var ListView = require('web.ListView');
+ var UploadBillMixin = require('account.upload.bill.mixin');
+ var viewRegistry = require('web.view_registry');
+
+ var BillsListController = ListController.extend(UploadBillMixin, {
+ buttons_template: 'BillsListView.buttons',
+ events: _.extend({}, ListController.prototype.events, {
+ 'click .o_button_upload_bill': '_onUpload',
+ 'change .o_vendor_bill_upload .o_form_binary_form': '_onAddAttachment',
+ }),
+ });
+
+ var BillsListView = ListView.extend({
+ config: _.extend({}, ListView.prototype.config, {
+ Controller: BillsListController,
+ }),
+ });
+
+ viewRegistry.add('account_tree', BillsListView);
+});
+
+odoo.define('account.dashboard.kanban', function (require) {
+"use strict";
+ var core = require('web.core');
+ var KanbanController = require('web.KanbanController');
+ var KanbanView = require('web.KanbanView');
+ var UploadBillMixin = require('account.upload.bill.mixin');
+ var viewRegistry = require('web.view_registry');
+
+ var DashboardKanbanController = KanbanController.extend(UploadBillMixin, {
+ events: _.extend({}, KanbanController.prototype.events, {
+ 'click .o_button_upload_bill': '_onUpload',
+ 'change .o_vendor_bill_upload .o_form_binary_form': '_onAddAttachment',
+ }),
+ /**
+ * We override _onUpload (from the upload bill mixin) to pass default_journal_id
+ * and default_move_type in context.
+ *
+ * @override
+ */
+ _onUpload: function (event) {
+ var kanbanRecord = $(event.currentTarget).closest('.o_kanban_record').data('record');
+ this.initialState.context['default_journal_id'] = kanbanRecord.id;
+ if ($(event.currentTarget).attr('journal_type') == 'sale') {
+ this.initialState.context['default_move_type'] = 'out_invoice'
+ } else if ($(event.currentTarget).attr('journal_type') == 'purchase') {
+ this.initialState.context['default_move_type'] = 'in_invoice'
+ }
+ UploadBillMixin._onUpload.apply(this, arguments);
+ }
+ });
+
+ var DashboardKanbanView = KanbanView.extend({
+ config: _.extend({}, KanbanView.prototype.config, {
+ Controller: DashboardKanbanController,
+ }),
+ });
+
+ viewRegistry.add('account_dashboard_kanban', DashboardKanbanView);
+});
diff --git a/addons/account/static/src/js/grouped_view_widget.js b/addons/account/static/src/js/grouped_view_widget.js
new file mode 100644
index 00000000..e1df30cf
--- /dev/null
+++ b/addons/account/static/src/js/grouped_view_widget.js
@@ -0,0 +1,40 @@
+odoo.define('account.ShowGroupedList', function (require) {
+"use strict";
+
+const { Component } = owl;
+const { useState } = owl.hooks;
+const AbstractFieldOwl = require('web.AbstractFieldOwl');
+const field_registry = require('web.field_registry_owl');
+
+class ListItem extends Component { }
+ListItem.template = 'account.GroupedItemTemplate';
+ListItem.props = ["item_vals", "options"];
+
+class ListGroup extends Component { }
+ListGroup.template = 'account.GroupedItemsTemplate';
+ListGroup.components = { ListItem }
+ListGroup.props = ["group_vals", "options"];
+
+
+class ShowGroupedList extends AbstractFieldOwl {
+ constructor(...args) {
+ super(...args);
+ this.data = this.value ? JSON.parse(this.value) : {
+ groups_vals: [],
+ options: {
+ discarded_number: '',
+ columns: [],
+ },
+ };
+ }
+ async willUpdateProps(nextProps) {
+ await super.willUpdateProps(nextProps);
+ Object.assign(this.data, JSON.parse(this.value));
+ }
+}
+ShowGroupedList.template = 'account.GroupedListTemplate';
+ShowGroupedList.components = { ListGroup }
+
+field_registry.add('grouped_view_widget', ShowGroupedList);
+return ShowGroupedList;
+});
diff --git a/addons/account/static/src/js/mail_activity.js b/addons/account/static/src/js/mail_activity.js
new file mode 100644
index 00000000..8b84afda
--- /dev/null
+++ b/addons/account/static/src/js/mail_activity.js
@@ -0,0 +1,69 @@
+odoo.define('account.activity', function (require) {
+"use strict";
+
+var AbstractField = require('web.AbstractField');
+var core = require('web.core');
+var field_registry = require('web.field_registry');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+var VatActivity = AbstractField.extend({
+ className: 'o_journal_activity_kanban',
+ events: {
+ 'click .see_all_activities': '_onOpenAll',
+ 'click .see_activity': '_onOpenActivity',
+ },
+ init: function () {
+ this.MAX_ACTIVITY_DISPLAY = 5;
+ this._super.apply(this, arguments);
+ },
+ //------------------------------------------------------------
+ // Private
+ //------------------------------------------------------------
+ _render: function () {
+ var self = this;
+ var info = JSON.parse(this.value);
+ if (!info) {
+ this.$el.html('');
+ return;
+ }
+ info.more_activities = false;
+ if (info.activities.length > this.MAX_ACTIVITY_DISPLAY) {
+ info.more_activities = true;
+ info.activities = info.activities.slice(0, this.MAX_ACTIVITY_DISPLAY);
+ }
+ this.$el.html(QWeb.render('accountJournalDashboardActivity', info));
+ },
+
+ _onOpenActivity: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.do_action({
+ type: 'ir.actions.act_window',
+ name: _t('Journal Entry'),
+ target: 'current',
+ res_id: $(e.target).data('resId'),
+ res_model: 'account.move',
+ views: [[false, 'form']],
+ });
+ },
+
+ _onOpenAll: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.do_action({
+ type: 'ir.actions.act_window',
+ name: _t('Journal Entries'),
+ res_model: 'account.move',
+ views: [[false, 'kanban'], [false, 'form']],
+ search_view_id: [false],
+ domain: [['journal_id', '=', self.res_id], ['activity_ids', '!=', false]],
+ });
+ }
+})
+
+field_registry.add('kanban_vat_activity', VatActivity);
+
+return VatActivity;
+});
diff --git a/addons/account/static/src/js/section_and_note_fields_backend.js b/addons/account/static/src/js/section_and_note_fields_backend.js
new file mode 100644
index 00000000..749d29fc
--- /dev/null
+++ b/addons/account/static/src/js/section_and_note_fields_backend.js
@@ -0,0 +1,106 @@
+
+odoo.define('account.section_and_note_backend', function (require) {
+// The goal of this file is to contain JS hacks related to allowing
+// section and note on sale order and invoice.
+
+// [UPDATED] now also allows configuring products on sale order.
+
+"use strict";
+var FieldChar = require('web.basic_fields').FieldChar;
+var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
+var fieldRegistry = require('web.field_registry');
+var ListFieldText = require('web.basic_fields').ListFieldText;
+var ListRenderer = require('web.ListRenderer');
+
+var SectionAndNoteListRenderer = ListRenderer.extend({
+ /**
+ * We want section and note to take the whole line (except handle and trash)
+ * to look better and to hide the unnecessary fields.
+ *
+ * @override
+ */
+ _renderBodyCell: function (record, node, index, options) {
+ var $cell = this._super.apply(this, arguments);
+
+ var isSection = record.data.display_type === 'line_section';
+ var isNote = record.data.display_type === 'line_note';
+
+ if (isSection || isNote) {
+ if (node.attrs.widget === "handle") {
+ return $cell;
+ } else if (node.attrs.name === "name") {
+ var nbrColumns = this._getNumberOfCols();
+ if (this.handleField) {
+ nbrColumns--;
+ }
+ if (this.addTrashIcon) {
+ nbrColumns--;
+ }
+ $cell.attr('colspan', nbrColumns);
+ } else {
+ $cell.removeClass('o_invisible_modifier');
+ return $cell.addClass('o_hidden');
+ }
+ }
+
+ return $cell;
+ },
+ /**
+ * We add the o_is_{display_type} class to allow custom behaviour both in JS and CSS.
+ *
+ * @override
+ */
+ _renderRow: function (record, index) {
+ var $row = this._super.apply(this, arguments);
+
+ if (record.data.display_type) {
+ $row.addClass('o_is_' + record.data.display_type);
+ }
+
+ return $row;
+ },
+ /**
+ * We want to add .o_section_and_note_list_view on the table to have stronger CSS.
+ *
+ * @override
+ * @private
+ */
+ _renderView: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ self.$('.o_list_table').addClass('o_section_and_note_list_view');
+ });
+ }
+});
+
+// We create a custom widget because this is the cleanest way to do it:
+// to be sure this custom code will only impact selected fields having the widget
+// and not applied to any other existing ListRenderer.
+var SectionAndNoteFieldOne2Many = FieldOne2Many.extend({
+ /**
+ * We want to use our custom renderer for the list.
+ *
+ * @override
+ */
+ _getRenderer: function () {
+ if (this.view.arch.tag === 'tree') {
+ return SectionAndNoteListRenderer;
+ }
+ return this._super.apply(this, arguments);
+ },
+});
+
+// This is a merge between a FieldText and a FieldChar.
+// We want a FieldChar for section,
+// and a FieldText for the rest (product and note).
+var SectionAndNoteFieldText = function (parent, name, record, options) {
+ var isSection = record.data.display_type === 'line_section';
+ var Constructor = isSection ? FieldChar : ListFieldText;
+ return new Constructor(parent, name, record, options);
+};
+
+fieldRegistry.add('section_and_note_one2many', SectionAndNoteFieldOne2Many);
+fieldRegistry.add('section_and_note_text', SectionAndNoteFieldText);
+
+return SectionAndNoteListRenderer;
+});
diff --git a/addons/account/static/src/js/tax_group.js b/addons/account/static/src/js/tax_group.js
new file mode 100644
index 00000000..a9de7f9f
--- /dev/null
+++ b/addons/account/static/src/js/tax_group.js
@@ -0,0 +1,171 @@
+odoo.define('account.tax_group', function (require) {
+ "use strict";
+
+ var core = require('web.core');
+ var session = require('web.session');
+ var fieldRegistry = require('web.field_registry');
+ var AbstractField = require('web.AbstractField');
+ var fieldUtils = require('web.field_utils');
+ var QWeb = core.qweb;
+
+ var TaxGroupCustomField = AbstractField.extend({
+ events: {
+ 'click .tax_group_edit': '_onClick',
+ 'keydown .oe_tax_group_editable .tax_group_edit_input input': '_onKeydown',
+ 'blur .oe_tax_group_editable .tax_group_edit_input input': '_onBlur',
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * This method is called by "_setTaxGroups". It is
+ * responsible for calculating taxes based on
+ * tax groups and triggering an event to
+ * notify the ORM of a change.
+ *
+ * @param {Id} taxGroupId
+ * @param {Float} deltaAmount
+ */
+ _changeTaxValueByTaxGroup: function (taxGroupId, deltaAmount) {
+ var self = this;
+ // Search for the first tax line with the same tax group and modify its value
+ var line_id = self.record.data.line_ids.data.find(elem => elem.data.tax_group_id && elem.data.tax_group_id.data.id === taxGroupId);
+
+ var debitAmount = 0;
+ var creditAmount = 0;
+ var amount_currency = 0;
+ if (line_id.data.currency_id) { // If multi currency enable
+ if (this.record.data.move_type === "in_invoice") {
+ amount_currency = line_id.data.amount_currency - deltaAmount;
+ } else {
+ amount_currency = line_id.data.amount_currency + deltaAmount;
+ }
+ } else {
+ var balance = line_id.data.price_subtotal;
+ balance -= deltaAmount;
+ if (this.record.data.move_type === "in_invoice") { // For vendor bill
+ if (balance > 0) {
+ debitAmount = balance;
+ } else if (balance < 0) {
+ creditAmount = -balance;
+ }
+ } else { // For refund
+ if (balance > 0) {
+ creditAmount = balance;
+ } else if (balance < 0) {
+ debitAmount = -balance;
+ }
+ }
+ }
+ // Trigger ORM
+ self.trigger_up('field_changed', {
+ dataPointID: self.record.id,
+ changes: { line_ids: { operation: "UPDATE", id: line_id.id, data: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount } } }, // account.move change
+ initialEvent: { dataPointID: line_id.id, changes: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount }, }, // account.move.line change
+ });
+ },
+
+ /**
+ * This method checks that the document where the widget
+ * is located is of the "in_invoice" or "in_refund" type.
+ * This makes it possible to know if it is a purchase
+ * document.
+ *
+ * @returns boolean (true if the invoice is a purchase document)
+ */
+ _isPurchaseDocument: function () {
+ return this.record.data.move_type === "in_invoice" || this.record.data.move_type === 'in_refund';
+ },
+
+ /**
+ * This method is part of the widget life cycle and allows you to render
+ * the widget.
+ *
+ * @private
+ * @override
+ */
+ _render: function () {
+ var self = this;
+ // Display the pencil and allow the event to click and edit only on purchase that are not posted and in edit mode.
+ // since the field is readonly its mode will always be readonly. Therefore we have to use a trick by checking the
+ // formRenderer (the parent) and check if it is in edit in order to know the correct mode.
+ var displayEditWidget = self._isPurchaseDocument() && this.record.data.state === 'draft' && this.getParent().mode === 'edit';
+ this.$el.html($(QWeb.render('AccountTaxGroupTemplate', {
+ lines: self.value,
+ displayEditWidget: displayEditWidget,
+ })));
+ },
+
+ //--------------------------------------------------------------------------
+ // Handler
+ //--------------------------------------------------------------------------
+
+ /**
+ * This method is called when the user is in edit mode and
+ * leaves the <input> field. Then, we execute the code that
+ * modifies the information.
+ *
+ * @param {event} ev
+ */
+ _onBlur: function (ev) {
+ ev.preventDefault();
+ var $input = $(ev.target);
+ var newValue = $input.val();
+ var currency = session.get_currency(this.record.data.currency_id.data.id);
+ try {
+ newValue = fieldUtils.parse.float(newValue); // Need a float for format the value.
+ newValue = fieldUtils.format.float(newValue, null, {digits: currency.digits}); // return a string rounded to currency precision
+ newValue = fieldUtils.parse.float(newValue); // convert back to Float to compare with oldValue to know if value has changed
+ } catch (err) {
+ $input.addClass('o_field_invalid');
+ return;
+ }
+ var oldValue = $input.data('originalValue');
+ if (newValue === oldValue || newValue === 0) {
+ return this._render();
+ }
+ var taxGroupId = $input.parents('.oe_tax_group_editable').data('taxGroupId');
+ this._changeTaxValueByTaxGroup(taxGroupId, oldValue-newValue);
+ },
+
+ /**
+ * This method is called when the user clicks on a specific <td>.
+ * it will hide the edit button and display the field to be edited.
+ *
+ * @param {event} ev
+ */
+ _onClick: function (ev) {
+ ev.preventDefault();
+ var $taxGroupElement = $(ev.target).parents('.oe_tax_group_editable');
+ // Show input and hide previous element
+ $taxGroupElement.find('.tax_group_edit').addClass('d-none');
+ $taxGroupElement.find('.tax_group_edit_input').removeClass('d-none');
+ var $input = $taxGroupElement.find('.tax_group_edit_input input');
+ // Get original value and display it in user locale in the input
+ var formatedOriginalValue = fieldUtils.format.float($input.data('originalValue'), {}, {});
+ $input.focus(); // Focus the input
+ $input.val(formatedOriginalValue); //add value in user locale to the input
+ },
+
+ /**
+ * This method is called when the user is in edit mode and pressing
+ * a key on his keyboard. If this key corresponds to ENTER or TAB,
+ * the code that modifies the information is executed.
+ *
+ * @param {event} ev
+ */
+ _onKeydown: function (ev) {
+ switch (ev.which) {
+ // Trigger only if the user clicks on ENTER or on TAB.
+ case $.ui.keyCode.ENTER:
+ case $.ui.keyCode.TAB:
+ // trigger blur to prevent the code being executed twice
+ $(ev.target).blur();
+ }
+ },
+
+ });
+ fieldRegistry.add('tax-group-custom-field', TaxGroupCustomField)
+});
diff --git a/addons/account/static/src/js/tours/account.js b/addons/account/static/src/js/tours/account.js
new file mode 100644
index 00000000..e854f1c3
--- /dev/null
+++ b/addons/account/static/src/js/tours/account.js
@@ -0,0 +1,93 @@
+odoo.define('account.tour', function(require) {
+"use strict";
+
+var core = require('web.core');
+var tour = require('web_tour.tour');
+
+var _t = core._t;
+
+tour.register('account_tour', {
+ url: "/web",
+ sequence: 60,
+}, [
+ ...tour.stepUtils.goToAppSteps('account.menu_finance', _t('Send invoices to your customers in no time with the <b>Invoicing app</b>.')),
+ {
+ trigger: "a.o_onboarding_step_action[data-method=action_open_base_onboarding_company]",
+ content: _t("Start by checking your company's data."),
+ position: "bottom",
+ }, {
+ trigger: "button[name=action_save_onboarding_company_step]",
+ extra_trigger: "a.o_onboarding_step_action[data-method=action_open_base_onboarding_company]",
+ content: _t("Looks good. Let's continue."),
+ position: "left",
+ }, {
+ trigger: "a.o_onboarding_step_action[data-method=action_open_base_document_layout]",
+ content: _t("Customize your layout."),
+ position: "bottom",
+ }, {
+ trigger: "button[name=document_layout_save]",
+ extra_trigger: "a.o_onboarding_step_action[data-method=action_open_base_document_layout]",
+ content: _t("Once everything is as you want it, validate."),
+ position: "left",
+ }, {
+ trigger: "a.o_onboarding_step_action[data-method=action_open_account_onboarding_create_invoice]",
+ content: _t("Now, we'll create your first invoice."),
+ position: "bottom",
+ }, {
+ trigger: "div[name=partner_id] input",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Write a company name to <b>create one</b> or <b>see suggestions</b>."),
+ position: "bottom",
+ }, {
+ trigger: ".o_m2o_dropdown_option a:contains('Create')",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Select first partner"),
+ auto: true,
+ }, {
+ trigger: ".modal-content button.btn-primary",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Once everything is set, you are good to continue. You will be able to edit this later in the <b>Customers</b> menu."),
+ auto: true,
+ }, {
+ trigger: "div[name=invoice_line_ids] .o_field_x2many_list_row_add a:not([data-context])",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Add a line to your invoice"),
+ }, {
+ trigger: "div[name=invoice_line_ids] textarea[name=name]",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Fill in the details of the line."),
+ position: "bottom",
+ }, {
+ trigger: "div[name=invoice_line_ids] input[name=price_unit]",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Set a price"),
+ position: "bottom",
+ run: 'text 100',
+ }, {
+ trigger: "button[name=action_post]",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Once your invoice is ready, press CONFIRM."),
+ }, {
+ trigger: "button[name=action_invoice_sent]",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Send the invoice and check what the customer will receive."),
+ }, {
+ trigger: "input[name=email]",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Write here <b>your own email address</b> to test the flow."),
+ run: 'text customer@example.com',
+ auto: true,
+ }, {
+ trigger: ".modal-content button.btn-primary",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Validate."),
+ auto: true,
+ }, {
+ trigger: "button[name=send_and_print_action]",
+ extra_trigger: "[name=move_type][raw-value=out_invoice]",
+ content: _t("Let's send the invoice."),
+ position: "left"
+ }
+]);
+
+});
diff --git a/addons/account/static/src/scss/account_activity.scss b/addons/account/static/src/scss/account_activity.scss
new file mode 100644
index 00000000..b4514086
--- /dev/null
+++ b/addons/account/static/src/scss/account_activity.scss
@@ -0,0 +1,8 @@
+.o_journal_activity_kanban {
+ display: block;
+ .align_activity_center {
+ width: 100%;
+ align-items: center;
+ margin-bottom: 5px;
+ }
+} \ No newline at end of file
diff --git a/addons/account/static/src/scss/account_dashboard.scss b/addons/account/static/src/scss/account_dashboard.scss
new file mode 100644
index 00000000..b93e2d35
--- /dev/null
+++ b/addons/account/static/src/scss/account_dashboard.scss
@@ -0,0 +1,61 @@
+.o_kanban_view.o_kanban_dashboard.o_account_kanban {
+
+ &.o_kanban_ungrouped .o_account_dashboard_header {
+ margin: (0 - $o-kanban-record-margin) ($o-kanban-record-margin - $o-horizontal-padding) $o-kanban-record-margin;
+ }
+
+ .o_account_dashboard_header {
+ flex: 1 0 100%;
+ flex-flow: column nowrap;
+ align-self: flex-start;
+ width: 100%;
+ height: auto; // cancel o_form_view height 100%, which hides the help tip message at the bottom of the screen
+ min-height: 0%; // cancel o_form_view min-height 100%, which hides the help tip message at the bottom of the screen
+ background-color: $o-view-background-color;
+
+ .o_form_statusbar {
+ padding-right: $o-horizontal-padding;
+ }
+
+ h4 {
+ font-size: $font-size-base;
+ font-weight: 500;
+ }
+
+ .fa-gift {
+ color: #eeeeee;
+ &:hover {
+ color: #555555;
+ }
+ }
+
+ .o_arrow_button.btn-secondary {
+ color: $text-muted;
+ text-transform: none;
+ font-weight: 500;
+
+ .o_account_dashboard_index {
+ color: gray('900');
+ }
+
+ &.o_action_done {
+ color: gray('900');
+ background-color: gray('200');
+
+ &:after {
+ border-left-color: gray('200');
+ }
+
+ .fa-check {
+ color: theme-color('success');
+ }
+ }
+
+ &:last-of-type {
+ margin-left: $o-horizontal-padding;
+ padding-left: $o-horizontal-padding*.5;
+ border-left: 1px solid gray('300');
+ }
+ }
+ }
+}
diff --git a/addons/account/static/src/scss/account_journal_dashboard.scss b/addons/account/static/src/scss/account_journal_dashboard.scss
new file mode 100644
index 00000000..6c36e61a
--- /dev/null
+++ b/addons/account/static/src/scss/account_journal_dashboard.scss
@@ -0,0 +1,68 @@
+.o_kanban_view.o_kanban_dashboard.o_account_kanban {
+ .o_kanban_record {
+
+ @include media-breakpoint-up(sm) {
+ .oe_kanban_action_button {
+ display: block;
+ margin-bottom: 5px;
+ }
+ }
+
+ .o_kanban_card_settings {
+ padding-top: $o-horizontal-padding/2;
+ padding-bottom: $o-horizontal-padding/2;
+
+ border-top: 1px solid;
+ border-color: $o-brand-lightsecondary;
+ }
+ .o_dashboard_star {
+ font-size: 12px;
+
+ &.fa-star-o {
+ color: $o-main-color-muted;
+ &:hover {
+ color: gold;
+ }
+ }
+ &.fa-star {
+ color: gold;
+ }
+ }
+
+ .o_dashboard_graph {
+ margin-bottom: -$o-horizontal-padding/2;
+ }
+ }
+
+ &.o_kanban_ungrouped {
+ .o_kanban_record {
+ width: 450px;
+ }
+ }
+
+ .o_kanban_group {
+ &:not(.o_column_folded) {
+ width: 450px + 2*$o-kanban-group-padding;
+
+ @include media-breakpoint-down(sm) {
+ width: 100%;
+ }
+ }
+ }
+}
+
+// Style for the widget "dashboard_graph"
+.o_dashboard_graph {
+ position: relative;
+ margin: 16px -16px;
+
+ canvas {
+ height: 75px;
+ }
+
+}
+
+.o_sample_data .o_dashboard_graph.o_graph_linechart > svg g.nv-linesWrap g.nv-group.nv-series-0 {
+ fill: gray !important;
+ opacity: 0.1;
+}
diff --git a/addons/account/static/src/scss/account_reconciliation.scss b/addons/account/static/src/scss/account_reconciliation.scss
new file mode 100644
index 00000000..d745f825
--- /dev/null
+++ b/addons/account/static/src/scss/account_reconciliation.scss
@@ -0,0 +1,367 @@
+.progress-reconciliation {
+ .progress-bar {
+ font-size: 1.08333333rem;
+ height: 14px;
+ background-color: $o-enterprise-color;
+ span {
+ display: contents;
+ }
+ }
+}
+
+.o_reconciliation {
+
+ .o_filter_input_wrapper {
+ position: relative;
+ width: 150px;
+ margin: 0.5rem !important;
+ .searchIcon {
+ position: absolute;
+ right: 10px;
+ }
+ .o_filter_input {
+ border: none;
+ border-bottom: 1px black solid;
+ }
+ }
+
+ .import_to_suspense {
+ margin: 0.5rem !important;
+ }
+
+ .notification_area {
+ clear: both;
+ }
+
+ .o_view_noreconciliation {
+ max-width: none;
+ padding: 0 10%;
+ color: $o-main-color-muted;
+ font-size: 125%;
+ }
+
+ .accounting_view {
+ width: 100%;
+
+ .cell_left {
+ border-right: 1px solid #333;
+ padding-right: 5px;
+ }
+ .edit_amount {
+ margin-left: 20px;
+ color: #bbb;
+ }
+ .cell:hover .edit_amount {
+ color: #00A09D;
+ }
+ .strike_amount {
+ text-decoration: line-through;
+ }
+ tbody tr:hover .cell_account_code::before {
+ content: "\f068";
+ font-family: FontAwesome;
+ position: relative;
+ margin-left: -17px;
+ left: -4px;
+ line-height: 0;
+ padding: 3px 2px 5px 5px;
+ }
+
+ }
+
+ .o_multi_currency {
+ margin-right: 5px;
+ &.o_multi_currency_color_0 {
+ color: #dd6666;
+ }
+ &.o_multi_currency_color_1 {
+ color: #aaaaaa;
+ }
+ &.o_multi_currency_color_2 {
+ color: #66dd66;
+ }
+ &.o_multi_currency_color_3 {
+ color: #6666dd;
+ }
+ &.o_multi_currency_color_4 {
+ color: #dddd66;
+ }
+ &.o_multi_currency_color_5 {
+ color: #dd66dd;
+ }
+ &.o_multi_currency_color_6 {
+ color: #66dddd;
+ }
+ &.o_multi_currency_color_7 {
+ color: #aaa333;
+ }
+ }
+
+ .o_reconciliation_line {
+ margin-bottom: 30px;
+ table {
+ width: 100%;
+ vertical-align: top;
+ }
+ tbody tr {
+ cursor: pointer;
+ }
+ tr.already_reconciled {
+ color: $o-account-info-color;
+ }
+ tr.invalid {
+ text-decoration: line-through;
+ }
+ td {
+ padding: 1px 2px;
+ }
+ thead td {
+ border-top: $o-account-light-border;
+ padding-top: 4px;
+ padding-bottom: 5px;
+ background-color: $o-account-initial-line-background;
+ }
+ tfoot td {
+ color: #bbb;
+ }
+
+ /* columns */
+
+ .cell_action {
+ width: 15px;
+ color: gray('700');
+ background: #fff;
+ border: 0;
+ text-align: center;
+ .fa-add-remove:before {
+ content: "";
+ }
+ }
+ tr:hover .cell_action .fa-add-remove:before {
+ content: "\f068";
+ }
+ .is_tax .cell_action .fa-add-remove:before {
+ position: relative;
+ top: -18px;
+ }
+ .cell_account_code {
+ width: 80px;
+ padding-left: 5px;
+ }
+ .cell_due_date {
+ width: 100px;
+ }
+ .cell_label {
+ width: auto;
+ }
+ .cell_left {
+ padding-right: 5px;
+ }
+ .cell_right, .cell_left {
+ text-align: right;
+ width: 120px;
+ }
+ .cell_info_popover {
+ text-align: right;
+ width: 15px;
+ color: #ccc;
+
+ &:empty {
+ padding: 0;
+ width: 0;
+ }
+ }
+
+ table.accounting_view {
+ .cell_right, .cell_left, .cell_label, .cell_due_date, .cell_account_code,.cell_info_popover {
+ box-shadow: 0 1px 0 #EAEAEA;
+ }
+ }
+ /* info popover */
+ .popover {
+ max-width: none;
+ }
+
+ table.details {
+ vertical-align: top;
+ td:first-child {
+ vertical-align: top;
+ padding-right: 10px;
+ font-weight: bold;
+ }
+ }
+
+ tr.one_line_info {
+ td {
+ padding-top: 10px;
+ text-align: center;
+ color: $o-account-info-color;
+ }
+ }
+
+ /* Icons */
+
+ .toggle_match, .toggle_create {
+ transform: rotate(0deg);
+ transition: transform 300ms ease 0s;
+ }
+ .visible_toggle, &[data-mode="match"] .toggle_match, &[data-mode="create"] .toggle_create {
+ visibility: visible !important;
+ transform: rotate(90deg);
+ }
+ .toggle_create {
+ font-size: 10px;
+ }
+
+ /* Match view & Create view */
+ > .o_notebook {
+ display: none;
+
+ > .o_notebook_headers {
+ margin-right: 0;
+ margin-left: 0;
+ }
+ }
+
+ > .o_notebook > .tab-content > div {
+ border: 1px solid #ddd;
+ border-top: 0;
+ }
+
+ > .o_notebook .match table tr:hover {
+ background-color: #eee;
+ }
+
+ &:not([data-mode="inactive"]) > .o_notebook {
+ display: block;
+ }
+
+ &:not(:focus-within) .o_web_accesskey_overlay {
+ display: none;
+ }
+ &:focus caption .o_buttons button {
+ outline: none;
+ box-shadow: 4px 4px 4px 0px $o-enterprise-color;
+ }
+ &:focus{
+ outline: none;
+ box-shadow: 0 0 0 0;
+ }
+ }
+
+ .o_reconcile_models .btn-primary {
+ margin: 0 2px 3px 0;
+ }
+
+ /* Match view */
+
+ .match {
+ .cell_action .fa-add-remove:before {
+ content: "";
+ }
+ tr:hover .cell_action .fa-add-remove:before {
+ content: "\f067";
+ }
+ .match_controls {
+ padding: 5px 0 5px ($o-account-action-col-width+$o-account-main-table-borders-padding);
+
+ .filter {
+ width: 240px;
+ display: inline-block;
+ }
+
+ .fa-chevron-left, .fa-chevron-right {
+ display: inline-block;
+ cursor: pointer;
+ }
+
+ .fa-chevron-left {
+ margin-right: 10px;
+ }
+
+ .fa-chevron-left.disabled, .fa-chevron-right.disabled {
+ color: #ddd;
+ cursor: default;
+ }
+ }
+ .show_more {
+ display: inline-block;
+ margin-left: ($o-account-action-col-width+$o-account-main-table-borders-padding);
+ margin-top: 5px;
+ }
+ }
+
+ /* Create view */
+ .create {
+ > div > div.quick_add > .o_reconcile_models {
+ max-width: 100%;
+ max-height: 70px;
+ flex-wrap: wrap;
+ overflow: auto;
+
+ & > * {
+ flex-grow: 0;
+ }
+ }
+ .quick_add {
+ margin-bottom: 7px;
+ padding: 0 8px;
+ }
+ .o_group table.o_group_col_6 {
+ width: 49%;
+ margin: 0;
+ vertical-align: top;
+ }
+ .o_group table.o_group_col_6:first-child {
+ margin-left: 8px;
+ }
+ .btn {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+ .add_line_container {
+ text-align: center;
+ clear: both;
+ color: $o-enterprise-primary-color;
+ cursor: pointer;
+ }
+ }
+
+ .o_notebook .tab-content > .tab-pane {
+ padding: 5px 0;
+ }
+}
+
+/*Manual Reconciliation*/
+.o_manual_statement {
+ .accounting_view {
+ td[colspan="3"] span:first-child {
+ width: 100%;
+ display: inline-block;
+ }
+ td[colspan="2"] {
+ border-bottom: 1px solid #333;
+ text-align: center;
+ width: 240px;
+ }
+ .do_partial_reconcile_true {
+ display: none;
+ }
+ }
+}
+
+// This is rtl language specific fix
+// It will flip the fa-fa play icon in left direction
+.o_rtl {
+ .o_reconciliation {
+ .o_reconciliation_line {
+ .toggle_match, .toggle_create {
+ transform: rotate(180deg);
+ transition: transform 300ms;
+ }
+ .visible_toggle, &[data-mode="match"] .toggle_match, &[data-mode="create"] .toggle_create {
+ transform: rotate(270deg);
+ }
+ }
+ }
+}
diff --git a/addons/account/static/src/scss/account_searchpanel.scss b/addons/account/static/src/scss/account_searchpanel.scss
new file mode 100644
index 00000000..621a283a
--- /dev/null
+++ b/addons/account/static/src/scss/account_searchpanel.scss
@@ -0,0 +1,26 @@
+.o_search_panel.account_root {
+ flex: 0 0 50px;
+ padding: 6px;
+ scrollbar-width: thin;
+ .o_search_panel_section_header {
+ display: none;
+ }
+ .list-group-item span.o_search_panel_label_title {
+ display: contents;
+ }
+ .o_search_panel_category_value {
+ header {
+ margin-left: 0;
+ padding-left: 0;
+ }
+ .o_search_panel_category_value .o_toggle_fold {
+ width: 0.3rem;
+ }
+ }
+ &::-webkit-scrollbar {
+ width: 4px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: lightgray;
+ }
+}
diff --git a/addons/account/static/src/scss/section_and_note_backend.scss b/addons/account/static/src/scss/section_and_note_backend.scss
new file mode 100644
index 00000000..93ce4dfd
--- /dev/null
+++ b/addons/account/static/src/scss/section_and_note_backend.scss
@@ -0,0 +1,29 @@
+
+// The goal of this file is to contain CSS hacks related to allowing
+// section and note on sale order and invoice.
+
+table.o_section_and_note_list_view tr.o_data_row.o_is_line_note,
+table.o_section_and_note_list_view tr.o_data_row.o_is_line_note textarea[name="name"],
+div.oe_kanban_card.o_is_line_note {
+ font-style: italic;
+}
+table.o_section_and_note_list_view tr.o_data_row.o_is_line_section,
+div.oe_kanban_card.o_is_line_section {
+ font-weight: bold;
+ background-color: #DDDDDD;
+}
+table.o_section_and_note_list_view tr.o_data_row.o_is_line_section {
+ border-top: 1px solid #BBB;
+ border-bottom: 1px solid #BBB;
+}
+
+table.o_section_and_note_list_view tr.o_data_row {
+ &.o_is_line_note,
+ &.o_is_line_section {
+ td {
+ // There is an undeterministic CSS behaviour in Chrome related to
+ // the combination of the row's and its children's borders.
+ border: none !important;
+ }
+ }
+}
diff --git a/addons/account/static/src/scss/variables.scss b/addons/account/static/src/scss/variables.scss
new file mode 100644
index 00000000..3e79231c
--- /dev/null
+++ b/addons/account/static/src/scss/variables.scss
@@ -0,0 +1,19 @@
+$o-account-action-col-width: 15px;
+$o-account-main-table-borders-padding: 3px;
+$o-account-light-border: 1px solid #bbb;
+$o-account-initial-line-background: #f0f0f0;
+$o-account-info-color: #44c;
+
+
+@keyframes animate-red {
+ 0% {
+ color: red;
+ }
+ 100% {
+ color: inherit;
+ }
+}
+
+.animate {
+ animation: animate-red 1s ease;
+}
diff --git a/addons/account/static/src/xml/account_journal_activity.xml b/addons/account/static/src/xml/account_journal_activity.xml
new file mode 100644
index 00000000..e91b9d4c
--- /dev/null
+++ b/addons/account/static/src/xml/account_journal_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<templates>
+
+ <t t-name="accountJournalDashboardActivity">
+ <t t-foreach="activities" t-as="activity">
+ <div class="row">
+ <div class="col-8 o_mail_activity">
+ <a href="#" t-att-class="(activity.status == 'late' ? 'o_activity_color_overdue ' : ' ') + (activity.activity_category == 'tax_report' ? 'o_open_vat_report' : 'see_activity')" t-att-data-res-id="activity.res_id" t-att-data-id="activity.id" t-att-data-model="activity.res_model">
+ <t t-esc="activity.name"/>
+ </a>
+ </div>
+ <div class="col-4 text-right">
+ <span><t t-esc="activity.date"/></span>
+ </div>
+ </div>
+ </t>
+ <a t-if="more_activities" class="pull-right see_all_activities" href="#">See all activities</a>
+ </t>
+
+</templates>
diff --git a/addons/account/static/src/xml/account_payment.xml b/addons/account/static/src/xml/account_payment.xml
new file mode 100644
index 00000000..c752f838
--- /dev/null
+++ b/addons/account/static/src/xml/account_payment.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<templates xml:space="preserve">
+
+ <t t-name="ShowPaymentInfo">
+ <div>
+ <t t-if="outstanding">
+ <div>
+ <strong class="float-left" id="outstanding"><t t-esc="title"></t></strong>
+ </div>
+ </t>
+ <table style="width:100%;">
+ <t t-foreach="lines" t-as="line">
+ <tr>
+ <t t-if="outstanding">
+ <td>
+ <a title="assign to invoice" role="button" class="oe_form_field btn btn-link outstanding_credit_assign" t-att-data-id="line.id" style="margin-right: 10px;" href="#" data-toggle="tooltip">Add</a>
+ </td>
+ <td style="max-width: 30em;">
+ <div class="oe_form_field" style="margin-right: 30px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;" t-att-title="line.date" data-toggle="tooltip"><t t-esc="line.journal_name"></t></div>
+ </td>
+ </t>
+ <t t-if="!outstanding">
+ <td>
+ <a role="button" tabindex="0" class="js_payment_info fa fa-info-circle" t-att-index="line.index" style="margin-right:5px;" aria-label="Info" title="Payment Info" data-toggle="tooltip"></a>
+ </td>
+ <td>
+ <i class="o_field_widget text-right o_payment_label">Paid on <t t-esc="line.date"></t></i>
+ </td>
+ </t>
+ <td style="text-align:right;">
+ <span class="oe_form_field oe_form_field_float oe_form_field_monetary" style="margin-left: -10px;">
+ <t t-if="line.position === 'before'">
+ <t t-esc="line.currency"/>
+ </t>
+ <t t-esc="line.amount"></t>
+ <t t-if="line.position === 'after'">
+ <t t-esc="line.currency"/>
+ </t>
+ </span>
+ </td>
+ </tr>
+ </t>
+ </table>
+ </div>
+ </t>
+
+ <t t-name="PaymentPopOver">
+ <div>
+ <table>
+ <tr>
+ <td><strong>Amount: </strong></td>
+ <td>
+ <t t-if="position === 'before'">
+ <t t-esc="currency"/>
+ </t>
+ <t t-esc="amount"></t>
+ <t t-if="position === 'after'">
+ <t t-esc="currency"/>
+ </t>
+ </td>
+ </tr>
+ <tr>
+ <td><strong>Memo: </strong></td>
+ <td>
+ <div style="width: 200px; word-wrap: break-word">
+ <t t-esc="ref"/>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td><strong>Date: </strong></td>
+ <td><t t-esc="date"/></td>
+ </tr>
+ <tr>
+ <td><strong>Payment Journal: </strong></td>
+ <td><t t-esc="journal_name"/><span t-if="payment_method_name"> (<t t-esc="payment_method_name"/>)</span></td>
+ </tr>
+ </table>
+ </div>
+ <button class="btn btn-sm btn-primary js_unreconcile_payment float-left" t-att-partial-id="partial_id" t-att-payment-id="payment_id" t-att-move-id="move_id" style="margin-top:5px; margin-bottom:5px;" groups="account.group_account_invoice">Unreconcile</button>
+ <button class="btn btn-sm btn-secondary js_open_payment float-right" t-att-payment-id="account_payment_id" t-att-move-id="move_id" style="margin-top:5px; margin-bottom:5px;">View</button>
+ </t>
+
+</templates>
diff --git a/addons/account/static/src/xml/account_resequence.xml b/addons/account/static/src/xml/account_resequence.xml
new file mode 100644
index 00000000..172a0e94
--- /dev/null
+++ b/addons/account/static/src/xml/account_resequence.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<templates>
+
+ <div t-name="account.ResequenceRenderer" owl="1" class="d-block">
+ <table t-if="data.changeLines.length" class="table table-sm">
+ <thead><tr>
+ <th>Date</th>
+ <th>Before</th>
+ <th>After</th>
+ </tr></thead>
+ <tbody t-foreach="data.changeLines" t-as="changeLine" t-key="changeLine.id">
+ <ChangeLine changeLine="changeLine" ordering="data.ordering"/>
+ </tbody>
+ </table>
+ </div>
+
+ <t t-name="account.ResequenceChangeLine" owl="1">
+ <tr>
+ <td t-esc="props.changeLine.date"/>
+ <td t-esc="props.changeLine.current_name"/>
+ <td t-if="props.ordering == 'keep'" t-esc="props.changeLine.new_by_name" t-attf-class="{{ props.changeLine.new_by_name != props.changeLine.new_by_date ? 'animate' : ''}}"/>
+ <td t-else="" t-esc="props.changeLine.new_by_date" t-attf-class="{{ props.changeLine.new_by_name != props.changeLine.new_by_date ? 'animate' : ''}}"/>
+ </tr>
+ </t>
+</templates>
diff --git a/addons/account/static/src/xml/bills_tree_upload_views.xml b/addons/account/static/src/xml/bills_tree_upload_views.xml
new file mode 100644
index 00000000..32505e12
--- /dev/null
+++ b/addons/account/static/src/xml/bills_tree_upload_views.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<templates>
+ <t t-name="account.BillsHiddenUploadForm">
+ <div class="d-none o_vendor_bill_upload">
+ <t t-call="HiddenInputFile">
+ <t t-set="multi_upload" t-value="true"/>
+ <t t-set="fileupload_id" t-value="widget.fileUploadID"/>
+ <t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
+ <input type="hidden" name="model" value=""/>
+ <input type="hidden" name="id" value="0"/>
+ </t>
+ </div>
+ </t>
+
+ <t t-extend="ListView.buttons" t-name="BillsListView.buttons">
+ <t t-jquery="button.o_list_button_add" t-operation="after">
+ <button type="button" class="btn btn-secondary o_button_upload_bill">
+ Upload
+ </button>
+ </t>
+ </t>
+</templates>
diff --git a/addons/account/static/src/xml/grouped_view_widget.xml b/addons/account/static/src/xml/grouped_view_widget.xml
new file mode 100644
index 00000000..9863fc82
--- /dev/null
+++ b/addons/account/static/src/xml/grouped_view_widget.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<templates>
+ <div t-name="account.GroupedListTemplate" owl="1" class="d-block">
+ <table t-if="data.groups_vals.length" class="table table-sm o_list_table table table-sm table-hover table-striped o_list_table_grouped">
+ <thead><tr>
+ <t t-foreach="data.options.columns" t-as="col">
+ <th t-esc="col['label']" t-attf-class="{{col['class']}}"/>
+ </t>
+ </tr></thead>
+ <t t-foreach="data.groups_vals" t-as="group_vals">
+ <ListGroup group_vals="group_vals" options="data.options"/>
+ </t>
+ </table>
+ <t t-if="data.options.discarded_number">
+ <span><t t-esc="data.options.discarded_number"/> are not shown in the preview</span>
+ </t>
+ </div>
+
+ <tbody t-name="account.GroupedItemsTemplate" owl="1">
+ <tr style="background-color: #dee2e6;">
+ <td t-attf-colspan="{{props.options.columns.length}}">
+ <t t-esc="props.group_vals.group_name"/>
+ </td>
+ </tr>
+ <t t-foreach="props.group_vals.items_vals" t-as="item_vals">
+ <ListItem item_vals="item_vals[2]" options="props.options"/>
+ </t>
+ </tbody>
+
+ <tr t-name="account.GroupedItemTemplate" owl="1">
+ <t t-foreach="props.options.columns" t-as="col">
+ <td t-esc="props.item_vals[col['field']]" t-attf-class="{{col['class']}}"/>
+ </t>
+ </tr>
+
+</templates>
diff --git a/addons/account/static/src/xml/tax_group.xml b/addons/account/static/src/xml/tax_group.xml
new file mode 100644
index 00000000..807fcfc7
--- /dev/null
+++ b/addons/account/static/src/xml/tax_group.xml
@@ -0,0 +1,35 @@
+<?xml version='1.0' encoding='utf-8'?>
+
+<templates>
+ <t t-name="AccountTaxGroupTemplate">
+ <table class="o_group o_inner_group oe_subtotal_footer border-0 my-0" style="min-width: 100%;">
+ <tbody>
+ <t t-foreach="lines" t-as="line">
+ <tr>
+ <td class="o_td_label oe_tax_group_name">
+ <label class="o_form_label" t-esc="line[0]"/>
+ </td>
+ <td class="oe_tax_group_editable" t-att-data-tax-group-id="line[6]">
+ <t t-if="displayEditWidget and line[1] !== 0">
+ <span class="tax_group_edit">
+ <i class="fa fa-pencil"></i>
+ <span class="oe_tax_group_amount_value">
+ <t t-esc="line[3]"/>
+ </span>
+ </span>
+ <span class="tax_group_edit_input d-none">
+ <input type="text" class="o_field_float o_field_number o_input" t-att-data-original-value="line[1]"/>
+ </span>
+ </t>
+ <t t-if="!displayEditWidget or line[1] === 0">
+ <span class="oe_tax_group_amount_value">
+ <t t-esc="line[3]"/>
+ </span>
+ </t>
+ </td>
+ </tr>
+ </t>
+ </tbody>
+ </table>
+ </t>
+</templates>
diff --git a/addons/account/static/tests/account_payment_field_tests.js b/addons/account/static/tests/account_payment_field_tests.js
new file mode 100644
index 00000000..48609728
--- /dev/null
+++ b/addons/account/static/tests/account_payment_field_tests.js
@@ -0,0 +1,84 @@
+odoo.define('account.reconciliation_field_tests', function (require) {
+"use strict";
+
+var FormView = require('web.FormView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('account', {
+ beforeEach: function () {
+ this.data = {
+ 'account.move': {
+ fields: {
+ payments_widget: {string: "payments_widget data", type: "char"},
+ outstanding_credits_debits_widget: {string: "outstanding_credits_debits_widget data", type: "char"},
+ },
+ records: [{
+ id: 1,
+ payments_widget: '{"content": [{"digits": [69, 2], "currency": "$", "amount": 555.0, "name": "Customer Payment: INV/2017/0004", "date": "2017-04-25", "position": "before", "ref": "BNK1/2017/0003 (INV/2017/0004)", "payment_id": 22, "move_id": 10, "partial_id": 38, "journal_name": "Bank"}], "outstanding": false, "title": "Less Payment"}',
+ outstanding_credits_debits_widget: '{"content": [{"digits": [69, 2], "currency": "$", "amount": 100.0, "journal_name": "INV/2017/0004", "position": "before", "id": 20}], "move_id": 4, "outstanding": true, "title": "Outstanding credits"}',
+ }]
+ },
+ };
+ }
+}, function () {
+ QUnit.module('Reconciliation');
+
+ QUnit.test('Reconciliation form field', async function (assert) {
+ assert.expect(5);
+
+ var form = await createView({
+ View: FormView,
+ model: 'account.move',
+ data: this.data,
+ arch: '<form>'+
+ '<field name="outstanding_credits_debits_widget" widget="payment"/>'+
+ '<field name="payments_widget" widget="payment"/>'+
+ '</form>',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'js_remove_outstanding_partial') {
+ assert.deepEqual(args.args, [10, 38], "should call js_remove_outstanding_partial {warning: required focus}");
+ return Promise.resolve();
+ }
+ if (args.method === 'js_assign_outstanding_line') {
+ assert.deepEqual(args.args, [4, 20], "should call js_assign_outstanding_line {warning: required focus}");
+ return Promise.resolve();
+ }
+ return this._super.apply(this, arguments);
+ },
+ intercepts: {
+ do_action: function (event) {
+ assert.deepEqual(event.data.action, {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'account.move',
+ 'res_id': 10,
+ 'views': [[false, 'form']],
+ 'target': 'current'
+ },
+ "should open the form view");
+ },
+ },
+ });
+
+ assert.strictEqual(form.$('.o_field_widget[name="payments_widget"]').text().replace(/[\s\n\r]+/g, ' '),
+ " Paid on 04/25/2017 $ 555.00 ",
+ "should display payment information");
+
+ form.$('.o_field_widget[name="outstanding_credits_debits_widget"] .outstanding_credit_assign').trigger('click');
+
+ assert.strictEqual(form.$('.o_field_widget[name="outstanding_credits_debits_widget"]').text().replace(/[\s\n\r]+/g, ' '),
+ " Outstanding credits Add INV/2017/0004 $ 100.00 ",
+ "should display outstanding information");
+
+ form.$('.o_field_widget[name="payments_widget"] .js_payment_info').trigger('focus');
+ form.$('.popover .js_open_payment').trigger('click');
+
+ form.$('.o_field_widget[name="payments_widget"] .js_payment_info').trigger('focus');
+ form.$('.popover .js_unreconcile_payment').trigger('click');
+
+ form.destroy();
+ });
+});
+});
diff --git a/addons/account/static/tests/section_and_note_tests.js b/addons/account/static/tests/section_and_note_tests.js
new file mode 100644
index 00000000..7dcef00a
--- /dev/null
+++ b/addons/account/static/tests/section_and_note_tests.js
@@ -0,0 +1,98 @@
+odoo.define('account.section_and_note_tests', function (require) {
+"use strict";
+
+var FormView = require('web.FormView');
+var testUtils = require('web.test_utils');
+var createView = testUtils.createView;
+
+QUnit.module('section_and_note', {
+ beforeEach: function () {
+ this.data = {
+ invoice: {
+ fields: {
+ invoice_line_ids: {
+ string: "Lines",
+ type: 'one2many',
+ relation: 'invoice_line',
+ relation_field: 'invoice_id'
+ },
+ },
+ records: [
+ {id: 1, invoice_line_ids: [1, 2]},
+ ],
+ },
+ invoice_line: {
+ fields: {
+ display_type: {
+ string: 'Type',
+ type: 'selection',
+ selection: [['line_section', "Section"], ['line_note', "Note"]]
+ },
+ invoice_id: {
+ string: "Invoice",
+ type: 'many2one',
+ relation: 'invoice'
+ },
+ name: {
+ string: "Name",
+ type: 'text'
+ },
+ },
+ records: [
+ {id: 1, display_type: false, invoice_id: 1, name: 'product\n2 lines'},
+ {id: 2, display_type: 'line_section', invoice_id: 1, name: 'section'},
+ ]
+ },
+ };
+ },
+}, function () {
+ QUnit.test('correct display of section and note fields', async function (assert) {
+ assert.expect(5);
+ var form = await createView({
+ View: FormView,
+ model: 'invoice',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="invoice_line_ids" widget="section_and_note_one2many"/>' +
+ '</form>',
+ archs: {
+ 'invoice_line,false,list': '<tree editable="bottom">' +
+ '<field name="display_type" invisible="1"/>' +
+ '<field name="name" widget="section_and_note_text"/>' +
+ '</tree>',
+ },
+ res_id: 1,
+ });
+
+ assert.hasClass(form.$('[name="invoice_line_ids"] table'), 'o_section_and_note_list_view');
+
+ // section should be displayed correctly
+ var $tr0 = form.$('tr.o_data_row:eq(0)');
+
+ assert.doesNotHaveClass($tr0, 'o_is_line_section',
+ "should not have a section class");
+
+ var $tr1 = form.$('tr.o_data_row:eq(1)');
+
+ assert.hasClass($tr1, 'o_is_line_section',
+ "should have a section class");
+
+ // enter edit mode
+ await testUtils.form.clickEdit(form);
+
+ // editing line should be textarea
+ $tr0 = form.$('tr.o_data_row:eq(0)');
+ await testUtils.dom.click($tr0.find('td.o_data_cell'));
+ assert.containsOnce($tr0, 'td.o_data_cell textarea[name="name"]',
+ "editing line should be textarea");
+
+ // editing section should be input
+ $tr1 = form.$('tr.o_data_row:eq(1)');
+ await testUtils.dom.click($tr1.find('td.o_data_cell'));
+ assert.containsOnce($tr1, 'td.o_data_cell input[name="name"]',
+ "editing section should be input");
+
+ form.destroy();
+ });
+});
+});
diff --git a/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js
new file mode 100644
index 00000000..0d11b027
--- /dev/null
+++ b/addons/account/static/tests/tours/account_dashboard_setup_bar_tests.js
@@ -0,0 +1,42 @@
+odoo.define('account.dashboard.setup.tour', function (require) {
+ "use strict";
+
+ var core = require('web.core');
+ var tour = require('web_tour.tour');
+
+ var _t = core._t;
+
+ tour.register('account_render_report', {
+ test: true,
+ url: '/web',
+ }, [tour.stepUtils.showAppsMenuItem(),
+ {
+ id: 'account_menu_click',
+ trigger: '.o_app[data-menu-xmlid="account.menu_finance"]',
+ position: 'bottom',
+ }, {
+ trigger: '.o_data_row:first',
+ extra_trigger: '.breadcrumb',
+ }, {
+ trigger: '.o_control_panel button:contains("' + _t('Print') + '")',
+ }, {
+ trigger: '.o_control_panel .o_dropdown_menu a:contains("' + _t('Invoices without Payment') + '")',
+ }, {
+ trigger: 'iframe .o_report_layout_standard h2',
+ content: 'Primary color is correct',
+ run: function () {
+ if (this.$anchor.css('color') !== "rgb(18, 52, 86)") {
+ console.error('The primary color should be the one set on the company.');
+ }
+ },
+ }, {
+ trigger: 'iframe .o_report_layout_standard #informations div strong',
+ content: 'Secondary color is correct',
+ run: function () {
+ if (this.$anchor.css('color') !== "rgb(120, 145, 1)") {
+ console.error('The secondary color should be the one set on the company.');
+ }
+ },
+ }
+ ]);
+});
diff --git a/addons/account/static/tests/tours/tax_group_tests.js b/addons/account/static/tests/tours/tax_group_tests.js
new file mode 100644
index 00000000..08cb218f
--- /dev/null
+++ b/addons/account/static/tests/tours/tax_group_tests.js
@@ -0,0 +1,124 @@
+odoo.define('account.tax.group.tour.tests', function (require) {
+ "use strict";
+
+ var core = require('web.core');
+ var tour = require('web_tour.tour');
+ var _t = core._t;
+
+ tour.register('account_tax_group', {
+ test: true,
+ url: "/web",
+ }, [tour.stepUtils.showAppsMenuItem(),
+ {
+ content: "Go to Invoicing",
+ trigger: '.o_app[data-menu-xmlid="account.menu_finance"]',
+ edition: 'community',
+ },
+ {
+ content: "Go to Accounting",
+ trigger: '.o_app[data-menu-xmlid="account_accountant.menu_accounting"]',
+ edition: 'enterprise',
+ },
+ {
+ content: "Go to Vendors",
+ trigger: 'a:contains("Vendors")',
+ },
+ {
+ content: "Go to Bills",
+ trigger: 'span:contains("Bills")',
+ },
+ {
+ extra_trigger: '.breadcrumb:contains("Bills")',
+ content: "Create new bill",
+ trigger: '.o_list_button_add',
+ },
+ // Set a vendor
+ {
+ content: "Add vendor",
+ trigger: 'div.o_field_widget.o_field_many2one[name="partner_id"] div input',
+ run: 'text Azure Interior',
+ },
+ {
+ content: "Valid vendor",
+ trigger: '.ui-menu-item a:contains("Azure Interior")',
+ },
+ // Add First product
+ {
+ content: "Add items",
+ trigger: 'div[name="invoice_line_ids"] .o_field_x2many_list_row_add a:contains("Add a line")',
+ },
+ {
+ content: "Select input",
+ trigger: 'div[name="invoice_line_ids"] .o_list_view .o_selected_row .o_list_many2one:first input',
+ },
+ {
+ content: "Type item",
+ trigger: 'div[name="invoice_line_ids"] .o_list_view .o_selected_row .o_list_many2one:first input',
+ run: "text Large Desk",
+ },
+ {
+ content: "Valid item",
+ trigger: '.ui-menu-item-wrapper:contains("Large Desk")',
+ },
+ // Save account.move
+ {
+ content: "Save the account move",
+ trigger: '.o_form_button_save',
+ },
+ // Edit account.move
+ {
+ content: "Edit the account move",
+ trigger: '.o_form_button_edit',
+ },
+ // Edit tax group amount
+ {
+ content: "Edit tax group amount",
+ trigger: '.oe_tax_group_amount_value',
+ },
+ {
+ content: "Modify the input value",
+ trigger: '.tax_group_edit_input input',
+ run: function (actions) {
+ $('.tax_group_edit_input input').val(200);
+ $('.tax_group_edit_input input').select();
+ var keydownEvent = jQuery.Event('keydown');
+ keydownEvent.which = 13;
+ this.$anchor.trigger(keydownEvent);
+ },
+ },
+ // Check new value for total (with modified tax_group_amount).
+ {
+ content: "Valid total amount",
+ trigger: 'span[name="amount_total"]:contains("1,499.00")',
+ },
+ // Modify the quantity of the object
+ {
+ content: "Select item quantity",
+ trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.000"]',
+ },
+ {
+ content: "Change item quantity",
+ trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.000"] input',
+ run: 'text 2',
+ },
+ {
+ content: "Valid the new value",
+ trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.000"] input',
+ run: function (actions) {
+ var keydownEvent = jQuery.Event('keydown');
+ keydownEvent.which = 13;
+ this.$anchor.trigger(keydownEvent);
+ },
+ },
+ // Save form
+ {
+ content: "Save the account move",
+ trigger: '.o_form_button_save',
+ },
+ // Check new tax group value
+ {
+ content: "Check new value of tax group",
+ trigger: '.oe_tax_group_amount_value:contains("389.70")',
+ },
+ ]);
+});