Commit 276e54c9 by aa.gusti

init data

0 parents
Showing 51 changed files with 2946 additions and 0 deletions
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
env2/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
inventori/
*.egg-info/
.installed.cfg
*.egg
bak/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# bat file
*.bat
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
*.ini
.idea/
.project/
.DS_Store
# Ignore PDF signature
opensipkd/aset/pemda/pdf_signature/*.pdf
\ No newline at end of file
Odoo Accounting
---------------
The Odoo <a href="https://www.odoo.com/page/accounting">Open Source Accounting</a> app allows a better way to
collaborate with your accountants, your customers and control your suppliers.
Activate features on demand, from integrated analytic accounting to budget,
assets and multiple companies consolidation.
A Smart User Interface
----------------------
Record transactions in a few clicks and easily manage all financial activities
in one place. Odoo's user interface is designed with productivity in mind.
A Better Way To Work – Together
-------------------------------
Share access to your latest business numbers with your team and your accountant
– so everyone is up to speed. From work, home or on the go.
Connect Your Bank Accounts
--------------------------
Import your bank statements and reconcile them in just a few clicks. Prepare
payment orders based on your supplier invoices and payment terms.
Electronic invoicing and automated follow-ups
---------------------------------------------
Create and send professional invoices & get paid online. Get rid of the stress
of having to constantly remind your debtors. Simply set-up and automate
follow-ups to get paid quickly.
Sales Integration
-----------------
Automatically create invoices from sales orders, delivery orders or base them
on time and material. Re-invoice expenses on projects to your customer in just
a few clicks.
Purchase Integration
--------------------
Control supplier invocies based on purchase orders. Get real-time inventory
valuation reports automatically posted in your accounts.
Multi-Level Analytic Accounting
-------------------------------
Integrate your analytic accounting operations with timesheets, projects,
invoices, expenses, etc. No need to record transactions, all analytic entries
are posted automatically following your business rules.
Everything you need to grow
---------------------------
Manage your assets, track expenses, control budgets, multi-level analytic
accounting; Odoo has all the features you need to sustain all your business
activities.
Scale With Your Organization
----------------------------
Odoo supports multiple currencies, multiple users with different access rights,
multiple companies with real time consolidation and unlimited analytic plans.
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import wizard
from . import report
from odoo import api, SUPERUSER_ID
SYSCOHADA_LIST = ['BJ', 'BF', 'CM', 'CF', 'KM', 'CG', 'CI', 'GA', 'GN', 'GW',
'GQ', 'ML', 'NE', 'CD', 'SN', 'TD', 'TG']
def _set_fiscal_country(env):
""" Sets the fiscal country on existing companies when installing the module.
That field is an editable computed field. It doesn't automatically get computed
on existing records by the ORM when installing the module, so doing that by hand
ensures existing records will get a value for it if needed.
"""
env['res.company'].search([]).compute_pad_tax_fiscal_country()
def _auto_install_l10n(env):
pass
# check the country of the main company (only) and eventually load some module needed in that country
# country_code = env.company.country_id.code
# if country_code:
# # auto install localization module(s) if available
# to_install_l10n = env['ir.module.module'].search_count([
# ('category_id', '=', env.ref('base.module_category_pading_localizations_pad_charts').id),
# ('state', '=', 'to install'),
# ])
# module_list = []
# if to_install_l10n:
# # We don't install a CoA if one was passed in the command line
# # or has been selected to install
# pass
# elif country_code in SYSCOHADA_LIST:
# # countries using OHADA Chart of pads
# module_list.append('l10n_syscohada')
# elif country_code == 'GB':
# module_list.append('l10n_uk')
# elif country_code == 'DE':
# module_list.append('l10n_de_skr03')
# module_list.append('l10n_de_skr04')
# else:
# if env['ir.module.module'].search([('name', '=', 'l10n_' + country_code.lower())]):
# module_list.append('l10n_' + country_code.lower())
# else:
# module_list.append('l10n_generic_coa')
# if country_code == 'US':
# module_list.append('pad_plaid')
# if country_code in ['US', 'CA']:
# module_list.append('pad_check_printing')
# if country_code in ['US', 'AU', 'NZ', 'CA', 'CO', 'EC', 'ES', 'FR', 'IN', 'MX', 'GB']:
# module_list.append('pad_yodlee')
# if country_code in SYSCOHADA_LIST + [
# 'AT', 'BE', 'CA', 'CO', 'DE', 'EC', 'ES', 'ET', 'FR', 'GR', 'IT', 'LU', 'MX', 'NL', 'NO',
# 'PL', 'PT', 'RO', 'SI', 'TR', 'GB', 'VE', 'VN'
# ]:
# module_list.append('base_vat')
# if country_code == 'MX':
# module_list.append('l10n_mx_edi')
# if country_code == 'IT':
# module_list.append('l10n_it_edi_sdicoop')
#
# module_ids = env['ir.module.module'].search([('name', 'in', module_list), ('state', '=', 'uninstalled')])
# module_ids.sudo().button_install()
def _pad_post_init(cr, registry):
pass
# env = api.Environment(cr, SUPERUSER_ID, {})
# _auto_install_l10n(env)
# _set_fiscal_country(env)
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'PDL Kab/Kota',
'version': '0.1',
'summary': 'Pajak & Retribusi Kab/Kota',
'sequence': 10,
'description': """
Pajak & Retribusi
====================
Memudahkan dalam mengelola tagihan kepada wajib pajak atau wajib retribusi
menganalisa pading, even when you are not an padant.
Menydiakan module untuk followup Wajib Pajak/Retribusi.
""",
'category': 'pad/pdl',
'website': 'https://www.odoo.com/page/billing',
'images': ['images/pad_pemda.jpeg', 'images/bank_statement.jpeg',
'images/cash_register.jpeg', 'images/chart_of_account.jpeg',
'images/customer_invoice.jpeg',
'images/journal_entries.jpeg'],
'depends': ['base_setup', 'account'],
'data': [
'views/view_config.xml',
'views/objek_pajak.xml',
'views/pdl_kab_menus.xml',
],
'demo': [],
'qweb': [],
'installable': True,
'application': True,
'auto_install': False,
'post_init_hook': '_pad_post_init',
'license': 'LGPL-3',
'module': 'pajak'
}
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import onboarding
from . import portal
from odoo import http
from odoo.http import request
class OnboardingController(http.Controller):
@http.route('/pad/pad_invoice_onboarding', auth='user', type='json')
def pad_invoice_onboarding(self):
""" Returns the `banner` for the pad invoice onboarding panel.
It can be empty if the user has closed it or if he doesn't have
the permission to see it. """
company = request.env.company
if not request.env.is_admin() or \
company.pad_invoice_onboarding_state == 'closed':
return {}
return {
'html': request.env.ref('pad.pad_invoice_onboarding_panel')._render({
'company': company,
'state': company.get_and_update_pad_invoice_onboarding_state()
})
}
@http.route('/pad/pad_dashboard_onboarding', auth='user', type='json')
def pad_dashboard_onboarding(self):
""" Returns the `banner` for the pad dashboard onboarding panel.
It can be empty if the user has closed it or if he doesn't have
the permission to see it. """
company = request.env.company
if not request.env.is_admin() or \
company.pad_dashboard_onboarding_state == 'closed':
return {}
return {
'html': request.env.ref('pad.pad_dashboard_onboarding_panel')._render({
'company': company,
'state': company.get_and_update_pad_dashboard_onboarding_state()
})
}
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http, _
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
from odoo.exceptions import AccessError, MissingError
from collections import OrderedDict
from odoo.http import request
class PortalAccount(CustomerPortal):
def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters)
if 'invoice_count' in counters:
invoice_count = request.env['account.move'].search_count(self._get_pad_domain()) \
if request.env['account.move'].check_access_rights('read', raise_exception=False) else 0
values['invoice_count'] = invoice_count
return values
# ------------------------------------------------------------
# My pad
# ------------------------------------------------------------
def _invoice_get_page_view_values(self, invoice, access_token, **kwargs):
values = {
'page_name': 'invoice',
'invoice': invoice,
}
return self._get_page_view_values(invoice, access_token, values, 'my_pad_history', False, **kwargs)
def _get_pad_domain(self):
return [('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]
@http.route(['/my/pad', '/my/pad/page/<int:page>'], type='http', auth="user", website=True)
def portal_my_pad(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw):
values = self._prepare_portal_layout_values()
AccountInvoice = request.env['account.move']
domain = self._get_pad_domain()
searchbar_sortings = {
'date': {'label': _('Date'), 'order': 'invoice_date desc'},
'duedate': {'label': _('Due Date'), 'order': 'invoice_date_due desc'},
'name': {'label': _('Reference'), 'order': 'name desc'},
'state': {'label': _('Status'), 'order': 'state'},
}
# default sort by order
if not sortby:
sortby = 'date'
order = searchbar_sortings[sortby]['order']
searchbar_filters = {
'all': {'label': _('All'), 'domain': []},
'pad': {'label': _('pad'), 'domain': [('move_type', '=', ('out_invoice', 'out_refund'))]},
'bills': {'label': _('Bills'), 'domain': [('move_type', '=', ('in_invoice', 'in_refund'))]},
}
# default filter by value
if not filterby:
filterby = 'all'
domain += searchbar_filters[filterby]['domain']
if date_begin and date_end:
domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
# count for pager
invoice_count = AccountInvoice.search_count(domain)
# pager
pager = portal_pager(
url="/my/pad",
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby},
total=invoice_count,
page=page,
step=self._items_per_page
)
# content according to pager and archive selected
pad = AccountInvoice.search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
request.session['my_pad_history'] = pad.ids[:100]
values.update({
'date': date_begin,
'pad': pad,
'page_name': 'invoice',
'pager': pager,
'default_url': '/my/pad',
'searchbar_sortings': searchbar_sortings,
'sortby': sortby,
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'filterby':filterby,
})
return request.render("account.portal_my_pad", values)
@http.route(['/my/pad/<int:invoice_id>'], type='http', auth="public", website=True)
def portal_my_invoice_detail(self, invoice_id, access_token=None, report_type=None, download=False, **kw):
try:
invoice_sudo = self._document_check_access('account.move', invoice_id, access_token)
except (AccessError, MissingError):
return request.redirect('/my')
if report_type in ('html', 'pdf', 'text'):
return self._show_report(model=invoice_sudo, report_type=report_type, report_ref='account.account_pad', download=download)
values = self._invoice_get_page_view_values(invoice_sudo, access_token, **kw)
acquirers = values.get('acquirers')
if acquirers:
country_id = values.get('partner_id') and values.get('partner_id')[0].country_id.id
values['acq_extra_fees'] = acquirers.get_acquirer_extra_fees(invoice_sudo.amount_residual, invoice_sudo.currency_id, country_id)
return request.render("account.portal_invoice_page", values)
# ------------------------------------------------------------
# My Home
# ------------------------------------------------------------
def details_form_validate(self, data):
error, error_message = super(PortalAccount, self).details_form_validate(data)
# prevent VAT/name change if pad exist
partner = request.env['res.users'].browse(request.uid).partner_id
if not partner.can_edit_vat():
if 'vat' in data and (data['vat'] or False) != (partner.vat or False):
error['vat'] = 'error'
error_message.append(_('Changing VAT number is not allowed once pad have been issued for your account. Please contact us directly for this operation.'))
if 'name' in data and (data['name'] or False) != (partner.name or False):
error['name'] = 'error'
error_message.append(_('Changing your name is not allowed once pad have been issued for your account. Please contact us directly for this operation.'))
if 'company_name' in data and (data['company_name'] or False) != (partner.company_name or False):
error['company_name'] = 'error'
error_message.append(_('Changing your company name is not allowed once pad have been issued for your account. Please contact us directly for this operation.'))
return error, error_message
File mode changed
from . import partner
import time
import logging
from psycopg2 import sql, DatabaseError
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.exceptions import ValidationError
from odoo.addons.base.models.res_partner import WARNING_MESSAGE, WARNING_HELP
class ResPartner(models.Model):
_name = 'res.partner'
_inherit = 'res.partner'
# is_hotel = fields.Boolean(string='Is a Hotel', default=False,
# help="Check if the contact is a hotel, otherwise it is non hotel")
.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 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;
}
}
// 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>
<data>
<record id="op_hotel_template_tree" model="ir.ui.view">
<field name="name">op.hotel.template.tree</field>
<field name="model">product.template</field>
<field name="arch" type="xml">
<tree string="Objek Pajak Hotel" sample="1">
<field name="default_code" string="NOPD"/>
<field name="name" string="Nama Objek"/>
<field name="lst_price" string="Min Omset"/>
<field name="taxes_id" widget="many2many_tags" string="Pajak"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
</tree>
</field>
</record>
<!-- <record id="product_product_action_sellable" model="ir.actions.act_window">-->
<!-- <field name="name">Products</field>-->
<!-- <field name="type">ir.actions.act_window</field>-->
<!-- <field name="res_model">product.template</field>-->
<!-- <field name="view_mode">kanban,tree,form,activity</field>-->
<!-- <field name="context">{'search_default_filter_to_sell': 1}</field>-->
<!-- <field name="view_id" ref="product_template_view_tree"/>-->
<!-- <field name="help" type="html">-->
<!-- <p class="o_view_nocontent_smiling_face">-->
<!-- Create a new sellable product-->
<!-- </p>-->
<!-- </field>-->
<!-- </record>-->
<record id="action_pendataan_hotel_pdl_kab" model="ir.actions.act_window">
<field name="name">Objek Pajak Hotel</field>
<field name="res_model">product.template</field>
<field name="view_mode">kanban,tree,form</field>
<field name="view_id" ref="op_hotel_template_tree"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Pendataan Objek Pajak Hotel
</p>
</field>
</record>
<record id="op_hotel_template_form" model="ir.ui.view">
<field name="name">op.hotel.template.form.inherit</field>
<field name="model">product.template</field>
<field name="priority">4</field>
<field name="inherit_id" ref="account.product_template_form_view"/>
<field name="arch" type="xml">
<!-- <xpath expr="//page[@name='general_information']" position="replace"/>-->
<xpath expr="//page[@name='inventory']" position="replace"/>
<xpath expr="//page[@name='sales']" position="replace"/>
<xpath expr="//page[@name='purchase']" position="replace"/>
<!-- <xpath expr="//div[@name='options']" position="after">-->
<!-- Menambah field setelah pricing -->
<!-- <field name="active" invisible=""/>-->
<!-- <field name="categ_id" invisible="0"/>-->
<!-- </xpath>-->
<xpath expr="//div[@name='default_code']" position="attribute">
<field name="string" string="NOPD"/>
</xpath>
</field>
</record>
</data>
</odoo>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Top menu item -->
<menuitem id="pdl_kab_menu_root"
name="PDL Kab/Kota"
web_icon="pdl_kab_icon,static/description/icon.png"
active="True"
sequence="400"/>
<!--Pendataan-->
<menuitem id="pendataan_pdl_kab_menu"
name="Pendataan"
parent="pdl_kab_menu_root"
sequence="2"/>
<menuitem id="pendataan_hotel_menu"
name="Pendataan Hotel"
parent="pendataan_pdl_kab_menu"
action="action_pendataan_hotel_pdl_kab"
sequence="2"/>
<!--Penetapan-->
<menuitem id="penetapan_pdl_kab_menu"
name="Penetapan"
parent="pdl_kab_menu_root"
sequence="3"/>
<menuitem id="penagihan_pdl_kab_menu"
name="Penagihan"
parent="pdl_kab_menu_root"
sequence="4"/>
<menuitem id="pembayaran_pdl_kab_menu"
name="Pembayaran"
parent="pdl_kab_menu_root"
sequence="5"/>
<menuitem id="pelayanan_pdl_kab_menu"
name="Pelayanan"
parent="pdl_kab_menu_root"
sequence="6"/>
<!-- Konfigurasi-->
<menuitem id="config_pdl_kab_menu"
name="Konfigurasi"
parent="pdl_kab_menu_root"
sequence="7"/>
<menuitem id="category_config_pdl_kab_menu"
name="Kategori Perusahaan"
parent="config_pdl_kab_menu"
action="action_category_config_pdl_kab"
sequence="2"/>
<menuitem id="industry_config_pdl_kab_menu"
name="Jenis Perusahaan"
parent="config_pdl_kab_menu"
action="action_industry_config_pdl_kab"
sequence="2"/>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Categories -->
<record id="view_partner_category_form" model="ir.ui.view">
<field name="name">Kategori Perusahaan</field>
<field name="model">res.partner.category</field>
<field name="arch" type="xml">
<form string="Contact Tag">
<sheet>
<group col="4">
<field name="name"/>
<field name="active" widget="boolean_toggle"/>
<field name="parent_id"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_partner_category_list" model="ir.ui.view">
<field name="name">Kategori Perusahaan</field>
<field name="model">res.partner.category</field>
<field eval="6" name="priority"/>
<field name="arch" type="xml">
<tree string="Contact Tags">
<field name="display_name"/>
</tree>
</field>
</record>
<record id="res_partner_category_search" model="ir.ui.view">
<field name="name">res.partner.category.search</field>
<field name="model">res.partner.category</field>
<field name="arch" type="xml">
<search string="Search Partner Category">
<field name="name"/>
<field name="display_name"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
</search>
</field>
</record>
<record id="action_category_config_pdl_kab" model="ir.actions.act_window">
<field name="name">Kategori Perusahaan</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner.category</field>
<field name="search_view_id" ref="res_partner_category_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new contact tag
</p>
<p>
Manage contact tags to better classify them for tracking and analysis purposes.
</p>
</field>
</record>
<!-- Industry -->
<record id="res_partner_industry_form" model="ir.ui.view">
<field name="name">Jenis Perusahaan</field>
<field name="model">res.partner.industry</field>
<field name="arch" type="xml">
<form string="Industry">
<sheet>
<group col="4">
<field name="name"/>
<field name="full_name"/>
<field name="active" widget="boolean_toggle"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="res_partner_industry_list" model="ir.ui.view">
<field name="name">Jenis Perusahaan</field>
<field name="model">res.partner.industry</field>
<field eval="6" name="priority"/>
<field name="arch" type="xml">
<tree string="Industry" editable="bottom">
<field name="name"/>
<field name="full_name"/>
<field name="active" invisible="1"/>
</tree>
</field>
</record>
<record id="res_partner_industry_search" model="ir.ui.view">
<field name="name">res.partner.industry.view.search</field>
<field name="model">res.partner.industry</field>
<field name="arch" type="xml">
<search string="Search Partner Industry">
<field name="name"/>
<field name="full_name"/>
<separator/>
<filter string="Archived" name="inactive"
domain="[('active', '=', False)]"/>
</search>
</field>
</record>
<!--Industry Action-->
<record id="action_industry_config_pdl_kab" model="ir.actions.act_window">
<field name="name">Jenis Perusahaan</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner.industry</field>
<field name="view_mode">kanban,tree,form</field>
<field name="search_view_id" ref="res_partner_industry_search"/>
<!-- <field name="view_id" ref="res_partner_industry_tree"/>-->
</record>
</data>
</odoo>
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!