Commit 8b5e3e49 by Kunto Bregananta Adi
2 parents 5c749c42 1cedf4c5
Showing 41 changed files with 2301 additions and 2 deletions
......@@ -20,6 +20,7 @@ Indonesia Goverment Accounting
'data/data_account_type.xml',
'data/account_group.xml',
'data/account.account.csv',
'views/account.xml',
'views/account_account_views.xml',
'views/menus.xml',
],
......
......@@ -252,8 +252,13 @@ class AccountAccount(models.Model):
# This field should have been a char, but the aim is to use it in a side panel view with hierarchy,
# and it's only supported by many2one fields so far.
# So instead, we make it a many2one to a psql view with what we need as records.
# for record in self:
# record.root_id = (ord(record.code[0]) * 1000 + ord(record.code[1:2] or '\x00')) if record.code else False
for record in self:
record.root_id = (ord(record.code[0]) * 1000 + ord(record.code[1:2] or '\x00')) if record.code else False
# _logger.info(F"root : {record.code[1:4]}")
record.root_id = (ord(record.code[0]) * 1000000 + int(record.code[1:6] or '\x00')) if record.code else False
# ASCII(code) * 10000 + SUBSTRING(code, 2, 3)::int
@api.depends('code')
def _compute_account_group(self):
......@@ -751,7 +756,12 @@ class AccountRoot(models.Model):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute('''
CREATE OR REPLACE VIEW %s AS (
SELECT DISTINCT ASCII(code) * 10000 + SUBSTRING(code,2,3)::int AS id,
SELECT DISTINCT ASCII(code) * 1000000 + SUBSTRING(code,2,5)::int AS id,
LEFT(code,6) AS name,
ASCII(code) * 100000 + SUBSTRING(code,2,3) ::int AS parent_id
FROM account_account WHERE code IS NOT NULL
UNION ALL
SELECT DISTINCT ASCII(code) * 100000 + SUBSTRING(code,2,3)::int AS id,
LEFT(code,4) AS name,
ASCII(code) * 1000 + ASCII(SUBSTRING(code,2,1)) AS parent_id
FROM account_account WHERE code IS NOT NULL
......
.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%;
}
}
.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
.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;
}
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
};
});
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);
},
});
});
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;
});
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);
});
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
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);
});
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;
});
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;
});
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;
});
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) {
const self = this;
// Search for the first tax line with the same tax group and modify its value
function applyChange(line_id){
let debitAmount = 0;
let creditAmount = 0;
let amount_currency = 0;
if (line_id.data.currency_id) { // If multi currency enable
if (self.record.data.move_type === "in_invoice") {
amount_currency = line_id.data.amount_currency - deltaAmount;
} else {
amount_currency = line_id.data.amount_currency + deltaAmount;
}
} else {
let balance = line_id.data.price_subtotal;
balance -= deltaAmount;
if (self.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
});
}
let line_id = self.record.data.line_ids.data.find(elem => elem.data.tax_group_id && elem.data.tax_group_id.data.id === taxGroupId);
if (line_id){
applyChange(line_id);
} else {
const {limit, id, count} = self.record.data.line_ids;
let offset = count - limit;
self.trigger_up('load', {
id,
limit,
offset,
on_success: value => {
line_id = value.data.find(elem => elem.data.tax_group_id && elem.data.tax_group_id.data.id === taxGroupId);
applyChange(line_id);
},
});
}
},
/**
* 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)
});
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: "bottom",
}, {
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: "top",
}, {
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: "top"
}
]);
});
.o_journal_activity_kanban {
display: block;
.align_activity_center {
width: 100%;
align-items: center;
margin-bottom: 5px;
}
}
\ No newline at end of file
.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');
}
}
}
}
.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;
}
.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);
}
}
}
}
.o_search_panel.account_root {
flex: 0 0 150px;
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;
}
}
// 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;
}
}
}
$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;
}
<?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>
<?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>
<?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>
<?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>
<?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>
<?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>
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();
});
});
});
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();
});
});
});
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.');
}
},
}
]);
});
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")',
},
]);
});
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="_assets_primary_variables" inherit_id="web._assets_primary_variables">
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/variables.scss"/>
</xpath>
</template>
<template id="assets_backend" name="account assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/account/static/src/css/account_bank_and_cash.css"/>
<link rel="stylesheet" href="/account/static/src/css/account.css"/>
<link rel="stylesheet" href="/account/static/src/css/tax_group.css"/>
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/account_reconciliation.scss"/>
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/account_journal_dashboard.scss"/>
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/account_dashboard.scss"/>
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/account_searchpanel.scss"/>
<link rel="stylesheet" href="/account/static/src/scss/section_and_note_backend.scss"/>
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/account_activity.scss"/>
<script type="text/javascript" src="/account/static/src/js/account_payment_field.js"></script>
<script type="text/javascript" src="/account/static/src/js/account_resequence_field.js"></script>
<script type="text/javascript" src="/account/static/src/js/grouped_view_widget.js"></script>
<script type="text/javascript" src="/account/static/src/js/mail_activity.js"></script>
<script type="text/javascript" src="/account/static/src/js/tax_group.js"></script>
<script type="text/javascript" src="/account/static/src/js/bank_statement.js"></script>
<script type="text/javascript" src="/account/static/src/js/section_and_note_fields_backend.js"></script>
<script type="text/javascript" src="/account/static/src/js/tours/account.js"></script>
<script type="text/javascript" src="/account/static/src/js/bills_tree_upload.js"/>
<script type="text/javascript" src="/account/static/src/js/account_selection.js"/>
</xpath>
</template>
<template id="assets_frontend" name="account assets" inherit_id="web.assets_frontend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/account/static/src/js/account_portal_sidebar.js"></script>
</xpath>
</template>
<template id="assets_tests" name="Account Assets Tests" inherit_id="web.assets_tests">
<xpath expr="." position="inside">
<script type="text/javascript" src="/account/static/tests/tours/account_dashboard_setup_bar_tests.js"></script>
<script type="text/javascript" src="/account/static/tests/tours/tax_group_tests.js"></script>
</xpath>
</template>
<template id="qunit_suite" name="account tests" inherit_id="web.qunit_suite_tests">
<xpath expr="//script[contains(@src, '/web/static/tests/views/kanban_tests.js')]" position="after">
<script type="text/javascript" src="/account/static/tests/account_payment_field_tests.js"></script>
<script type="text/javascript" src="/account/static/tests/section_and_note_tests.js"></script>
</xpath>
</template>
</odoo>
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!