mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 07012023
This commit is contained in:
@@ -874,6 +874,12 @@ class AccountBankStatementLine(models.Model):
|
||||
if 'date' not in vals:
|
||||
vals['date'] = statement.date
|
||||
|
||||
# Avoid having the same foreign_currency_id as currency_id.
|
||||
journal_currency = journal.currency_id or journal.company_id.currency_id
|
||||
if vals.get('foreign_currency_id') == journal_currency.id:
|
||||
vals['foreign_currency_id'] = None
|
||||
vals['amount_currency'] = 0.0
|
||||
|
||||
# Hack to force different account instead of the suspense account.
|
||||
counterpart_account_ids.append(vals.pop('counterpart_account_id', None))
|
||||
|
||||
|
||||
@@ -641,6 +641,7 @@ class AccountJournal(models.Model):
|
||||
if not invoice:
|
||||
invoice = self.env['account.move'].create({})
|
||||
invoice.with_context(no_new_invoice=True).message_post(attachment_ids=[attachment.id])
|
||||
attachment.write({'res_model': 'account.move', 'res_id': invoice.id})
|
||||
invoices += invoice
|
||||
return invoices
|
||||
|
||||
|
||||
@@ -128,14 +128,15 @@ def update_taxes_from_templates(cr, chart_template_xmlid):
|
||||
for account_tax in taxes_to_check:
|
||||
message_body += f"<li>{html_escape(account_tax.name)}</li>"
|
||||
message_body += "</ul>"
|
||||
partner_managers_ids.message_post(
|
||||
subject=_('Your taxes have been updated !'),
|
||||
author_id=flectrabot.id,
|
||||
body=message_body,
|
||||
message_type='notification',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
partner_ids=[partner.id for partner in partner_managers_ids],
|
||||
)
|
||||
for partner_manager in partner_managers_ids:
|
||||
partner_manager.message_post(
|
||||
subject=_('Your taxes have been updated !'),
|
||||
author_id=flectrabot.id,
|
||||
body=message_body,
|
||||
message_type='notification',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
partner_ids=partner_manager.ids,
|
||||
)
|
||||
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
chart_template_id = env['ir.model.data'].xmlid_to_res_id(chart_template_xmlid)
|
||||
|
||||
@@ -741,12 +741,6 @@ class TestAccountBankStatementLine(TestAccountBankStatementCommon):
|
||||
|
||||
# ==== Test constraints at creation ====
|
||||
|
||||
# Foreign currency must not be the same as the journal one.
|
||||
assertStatementLineConstraint(statement_vals, {
|
||||
**statement_line_vals,
|
||||
'foreign_currency_id': self.currency_1.id,
|
||||
})
|
||||
|
||||
# Can't have a stand alone amount in foreign currency without foreign currency set.
|
||||
assertStatementLineConstraint(statement_vals, {
|
||||
**statement_line_vals,
|
||||
@@ -1784,3 +1778,26 @@ class TestAccountBankStatementLine(TestAccountBankStatementCommon):
|
||||
self.assertRecordValues(statement.line_ids, [{
|
||||
'partner_id': False,
|
||||
}])
|
||||
|
||||
def test_create_statement_line_with_inconsistent_currencies(self):
|
||||
statement = self.env['account.bank.statement'].create({
|
||||
'name': 'test_statement',
|
||||
'date': '2019-01-01',
|
||||
'journal_id': self.bank_journal_1.id,
|
||||
'line_ids': [
|
||||
(0, 0, {
|
||||
'date': '2019-01-01',
|
||||
'payment_ref': "Happy new year",
|
||||
'amount': 200.0,
|
||||
'amount_currency': 200.0,
|
||||
'foreign_currency_id': self.env.company.currency_id.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
self.assertRecordValues(statement.line_ids, [{
|
||||
'currency_id': self.env.company.currency_id.id,
|
||||
'foreign_currency_id': False,
|
||||
'amount': 200.0,
|
||||
'amount_currency': 0.0,
|
||||
}])
|
||||
|
||||
@@ -108,7 +108,7 @@ class Employee(models.AbstractModel):
|
||||
"views": [[self.env.ref('hr_presence.hr_employee_view_kanban').id, "kanban"], [False, "tree"], [False, "form"]],
|
||||
'view_mode': 'kanban,tree,form',
|
||||
"domain": [],
|
||||
"name": "Employee's Presence to Define",
|
||||
"name": _("Employee's Presence to Define"),
|
||||
"search_view_id": [self.env.ref('hr_presence.hr_employee_view_presence_search').id, 'search'],
|
||||
"context": {'search_default_group_hr_presence_state': 1,
|
||||
'searchpanel_default_hr_presence_state_display': 'to_define'},
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
"a1311","Other reserves not available","1311","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
"a132","Untaxed reserves","132","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
"a133","Available reserves","133","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
"a140","Deferred profit","140","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
"a141","Loss carried forward","141","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
"a15","Investment grants","15","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
"a151","Investment grants received in cash","151","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
"a152","Investment grants received in kind","152","account.data_account_type_equity","l10n_be.l10nbe_chart_template","","False"
|
||||
@@ -362,7 +364,7 @@
|
||||
"a673","Foreign income taxes on the result of prior periods","673","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
"a680","Transfer to deferred taxes","680","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
"a689","Transfer to untaxed reserves","689","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
"a690","Loss brought forward","690","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
"a690","Loss brought forward from previous year","690","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
"a691","Appropriations to capital and share premium account","691","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
"a6920","Appropriations to legal reserve","6920","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
"a6921","Appropriations to other reserves","6921","account.data_account_type_expenses","l10n_be.l10nbe_chart_template","","False"
|
||||
@@ -421,7 +423,7 @@
|
||||
"a773","Adjustment of foreign income taxes","773","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","","False"
|
||||
"a780","Transfer from deferred taxes","780","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","account.account_tag_operating","False"
|
||||
"a789","Transfer from untaxed reserves","789","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","account.account_tag_operating","False"
|
||||
"a790","Profit brought forward","790","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","","False"
|
||||
"a790","Profit brought forward from previous year","790","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","","False"
|
||||
"a791","Withdrawal from the association or foundation funds","791","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","","False"
|
||||
"a792","Withdrawal from allocated funds","792","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","","False"
|
||||
"a793","Losses to be carried forward","793","account.data_account_type_revenue","l10n_be.l10nbe_chart_template","","False"
|
||||
|
||||
|
@@ -1173,6 +1173,16 @@ msgstr "Steuerfreie Rücklagen"
|
||||
msgid "Available reserves"
|
||||
msgstr "Verfügbare Rücklagen"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a140
|
||||
msgid "Profit brought forward"
|
||||
msgstr "Gewinnvortrag"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a141
|
||||
msgid "Loss brought forward"
|
||||
msgstr "Verlustvortrag"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a15
|
||||
msgid "Investment grants"
|
||||
@@ -2690,7 +2700,7 @@ msgstr "Einstellung in die steuerfreien Rücklagen"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a690
|
||||
msgid "Loss brought forward"
|
||||
msgid "Loss brought forward from previous year"
|
||||
msgstr "Verlusvortrag aus dem Vorjahr"
|
||||
|
||||
#. module: l10n_be
|
||||
@@ -2985,7 +2995,7 @@ msgstr "Entnahmen aus den steuerfreien Rücklagen"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a790
|
||||
msgid "Profit brought forward"
|
||||
msgid "Profit brought forward from previous year"
|
||||
msgstr "Gewinnvortrag aus dem Vorjahr"
|
||||
|
||||
#. module: l10n_be
|
||||
|
||||
@@ -1179,6 +1179,16 @@ msgstr "Réserves immunisées"
|
||||
msgid "Available reserves"
|
||||
msgstr "Réserves disponibles"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a140
|
||||
msgid "Profit brought forward"
|
||||
msgstr "Bénéfice reporté"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a141
|
||||
msgid "Loss brought forward"
|
||||
msgstr "Perte reportée"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a15
|
||||
msgid "Investment grants"
|
||||
@@ -2696,7 +2706,7 @@ msgstr "Transfert aux réserves immunisées"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a690
|
||||
msgid "Loss brought forward"
|
||||
msgid "Loss brought forward from previous year"
|
||||
msgstr "Perte reportée de l'exercice précédent"
|
||||
|
||||
#. module: l10n_be
|
||||
@@ -2991,7 +3001,7 @@ msgstr "Prélèvements sur les réserves immunisées"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a790
|
||||
msgid "Profit brought forward"
|
||||
msgid "Profit brought forward from previous year"
|
||||
msgstr "Bénéfice reporté de l'exercice précédent"
|
||||
|
||||
#. module: l10n_be
|
||||
|
||||
@@ -1178,6 +1178,16 @@ msgstr ""
|
||||
msgid "Available reserves"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a140
|
||||
msgid "Profit brought forward"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a141
|
||||
msgid "Loss brought forward"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a15
|
||||
msgid "Investment grants"
|
||||
@@ -2696,7 +2706,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a690
|
||||
msgid "Loss brought forward"
|
||||
msgid "Loss brought forward from previous year"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be
|
||||
@@ -2991,7 +3001,7 @@ msgstr ""
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a790
|
||||
msgid "Profit brought forward"
|
||||
msgid "Profit brought forward from previous year"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be
|
||||
|
||||
@@ -1179,6 +1179,16 @@ msgstr "Belastingvrije reserves"
|
||||
msgid "Available reserves"
|
||||
msgstr "Beschikbare reserves"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a140
|
||||
msgid "Profit brought forward"
|
||||
msgstr "Winst naar voren gehaald"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a141
|
||||
msgid "Loss brought forward"
|
||||
msgstr "Verlies naar voren gehaald"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a15
|
||||
msgid "Investment grants"
|
||||
@@ -2697,7 +2707,7 @@ msgstr "Overboeking naar de belastingvrije reserves"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a690
|
||||
msgid "Loss brought forward"
|
||||
msgid "Loss brought forward from previous year"
|
||||
msgstr "Overgedragen verlies van het vorige boekjaar"
|
||||
|
||||
#. module: l10n_be
|
||||
@@ -2992,7 +3002,7 @@ msgstr "Onttrekking aan de belastingvrije reserves"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.account.template,name:l10n_be.a790
|
||||
msgid "Profit brought forward"
|
||||
msgid "Profit brought forward from previous year"
|
||||
msgstr "Overgedragen winst van het vorige boekjaar"
|
||||
|
||||
#. module: l10n_be
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
<strong t-att-style="'color: %s;' % o.company_id.primary_color">
|
||||
<br/>
|
||||
<span style="line-height: 180%;">RUT:</span>
|
||||
<span t-esc="o.company_id.partner_id._format_dotted_vat_cl(o.company_id.partner_id.vat)"/>
|
||||
<t t-if="o.company_id.partner_id.vat">
|
||||
<span t-esc="o.company_id.partner_id._format_dotted_vat_cl(o.company_id.partner_id.vat)"/>
|
||||
</t>
|
||||
<br/>
|
||||
<span class="text-uppercase" t-esc="report_name"/>
|
||||
<br/>
|
||||
|
||||
@@ -37,7 +37,7 @@ class AccountMove(models.Model):
|
||||
statement_ids = self.line_ids.mapped('statement_id')
|
||||
payment_ids = self.line_ids.mapped('payment_id')
|
||||
if statement_ids:
|
||||
domains.append([('res_model', '=', 'account.bank.statement'), ('res_id', 'in', statement_ids)])
|
||||
domains.append([('res_model', '=', 'account.bank.statement'), ('res_id', 'in', statement_ids.ids)])
|
||||
if payment_ids:
|
||||
domains.append([('res_model', '=', 'account.payment'), ('res_id', 'in', payment_ids)])
|
||||
domains.append([('res_model', '=', 'account.payment'), ('res_id', 'in', payment_ids.ids)])
|
||||
return self.env['ir.attachment'].search_count(expression.OR(domains))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'France - Accounting',
|
||||
'version': '2.0',
|
||||
'version': '2.1',
|
||||
'category': 'Accounting/Localizations/Account Charts',
|
||||
'description': """
|
||||
This is the module to manage the accounting chart for France in Flectra.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra.addons.account.models.chart_template import update_taxes_from_templates
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
update_taxes_from_templates(cr, 'l10n_fr.l10n_fr_pcg_chart_template')
|
||||
@@ -10,7 +10,8 @@ flectra.define('l10n_gcc_pos.OrderReceipt', function (require) {
|
||||
get receiptEnv() {
|
||||
let receipt_render_env = super.receiptEnv;
|
||||
let receipt = receipt_render_env.receipt;
|
||||
receipt.is_gcc_country = ['SA', 'AE', 'BH', 'OM', 'QA', 'KW'].includes(receipt_render_env.order.pos.company.country.code);
|
||||
let company = this.env.pos.company;
|
||||
receipt.is_gcc_country = company.country ? ['SA', 'AE', 'BH', 'OM', 'QA', 'KW'].includes(company.country.code) : false;
|
||||
return receipt_render_env;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import pos_order
|
||||
from . import pos_config
|
||||
|
||||
15
addons/l10n_sa_pos/models/pos_config.py
Normal file
15
addons/l10n_sa_pos/models/pos_config.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra import models
|
||||
from flectra.exceptions import UserError
|
||||
from flectra.tools.translate import _
|
||||
|
||||
|
||||
class pos_config(models.Model):
|
||||
_inherit = 'pos.config'
|
||||
|
||||
def open_ui(self):
|
||||
for config in self:
|
||||
if not config.company_id.country_id:
|
||||
raise UserError(_("You have to set a country in your company setting."))
|
||||
return super(pos_config, self).open_ui()
|
||||
@@ -16,7 +16,7 @@ var _super_order = models.Order.prototype;
|
||||
models.Order = models.Order.extend({
|
||||
export_for_printing: function() {
|
||||
var result = _super_order.export_for_printing.apply(this,arguments);
|
||||
if (this.pos.company.country.code === 'SA') {
|
||||
if (this.pos.company.country && this.pos.company.country.code === 'SA') {
|
||||
const codeWriter = new window.ZXing.BrowserQRCodeSvgWriter()
|
||||
let qr_values = this.compute_sa_qr_code(result.company.name, result.company.vat, result.date.isostring, result.total_with_tax, result.total_tax);
|
||||
let qr_code_svg = new XMLSerializer().serializeToString(codeWriter.write(qr_values, 150, 150));
|
||||
|
||||
@@ -43,6 +43,25 @@ function _parseAndTransform(nodes, transformFunction) {
|
||||
}).join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape < > & as html entities (copy from _.escape with less escaped characters)
|
||||
*
|
||||
* @param {string}
|
||||
* @return {string}
|
||||
*/
|
||||
var _escapeEntities = (function () {
|
||||
var map = {"&":"&","<":"<",">":">"};
|
||||
var escaper = function(match) {
|
||||
return map[match];
|
||||
};
|
||||
var testRegexp = RegExp('(?:&|<|>)');
|
||||
var replaceRegexp = RegExp('(?:&|<|>)', 'g');
|
||||
return function(string) {
|
||||
string = string == null ? '' : '' + string;
|
||||
return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
|
||||
};
|
||||
})();
|
||||
|
||||
// Suggested URL Javascript regex of http://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url
|
||||
// Adapted to make http(s):// not required if (and only if) www. is given. So `should.notmatch` does not match.
|
||||
// And further extended to include Latin-1 Supplement, Latin Extended-A, Latin Extended-B and Latin Extended Additional.
|
||||
@@ -63,10 +82,17 @@ function linkify(text, attrs) {
|
||||
attrs = _.map(attrs, function (value, key) {
|
||||
return key + '="' + _.escape(value) + '"';
|
||||
}).join(' ');
|
||||
return text.replace(urlRegexp, function (url) {
|
||||
var href = (!/^https?:\/\//i.test(url)) ? "http://" + url : url;
|
||||
return '<a ' + attrs + ' href="' + href + '">' + url + '</a>';
|
||||
});
|
||||
var curIndex = 0;
|
||||
var result = "";
|
||||
var match;
|
||||
while ((match = urlRegexp.exec(text)) !== null) {
|
||||
result += _escapeEntities(text.slice(curIndex, match.index));
|
||||
var url = match[0];
|
||||
var href = (!/^https?:\/\//i.test(url)) ? "http://" + _.escape(url) : _.escape(url);
|
||||
result += '<a ' + attrs + ' href="' + href + '">' + _escapeEntities(url) + '</a>';
|
||||
curIndex = match.index + match[0].length;
|
||||
}
|
||||
return result + _escapeEntities(text.slice(curIndex));
|
||||
}
|
||||
|
||||
function addLink(node, transformChildren) {
|
||||
|
||||
@@ -35,6 +35,37 @@ QUnit.test('add_link utility function', function (assert) {
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('addLink: utility function and special entities', function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
var testInputs = {
|
||||
// textContent not unescaped
|
||||
'<p>https://example.com/?&currency_id</p>':
|
||||
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/?&currency_id">https://example.com/?&currency_id</a></p>',
|
||||
// entities not unescaped
|
||||
'& &amp; > <': '& &amp; > <',
|
||||
// > and " not linkified since they are not in URL regex
|
||||
'<p>https://example.com/></p>':
|
||||
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/">https://example.com/</a>></p>',
|
||||
'<p>https://example.com/"hello"></p>':
|
||||
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/">https://example.com/</a>"hello"></p>',
|
||||
// & and ' linkified since they are in URL regex
|
||||
'<p>https://example.com/&hello</p>':
|
||||
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/&hello">https://example.com/&hello</a></p>',
|
||||
'<p>https://example.com/\'yeah\'</p>':
|
||||
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/\'yeah\'">https://example.com/\'yeah\'</a></p>',
|
||||
// normal character should not be escaped
|
||||
':\'(': ':\'(',
|
||||
// special character in smileys should be escaped
|
||||
'<3': '<3',
|
||||
};
|
||||
|
||||
_.each(testInputs, function (result, content) {
|
||||
var output = utils.parseAndTransform(content, utils.addLink);
|
||||
assert.strictEqual(output, result);
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('addLink: linkify inside text node (1 occurrence)', function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<flectra>
|
||||
<flectra noupdate="1">
|
||||
<record id="account_payment_method_electronic_in" model="account.payment.method">
|
||||
<field name="name">Electronic</field>
|
||||
<field name="code">electronic</field>
|
||||
|
||||
@@ -332,6 +332,11 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
TODO adapt in master: remove the use of the "title" variable in this
|
||||
template (or rename the variable). Using it at controller level actually
|
||||
also controls the browser tab title, which makes no sense.
|
||||
-->
|
||||
<template id="portal_contact" name="Contact">
|
||||
<div class="o_portal_contact_details mb-5">
|
||||
<h4><t t-if="title" t-esc="title"/><t t-else="">Your contact</t></h4>
|
||||
|
||||
@@ -509,3 +509,50 @@ class TestSaleMrpFlow(TransactionCase):
|
||||
self.assertEqual(self.po.picking_ids.move_ids_without_package[1].product_uom_qty,2, "The amount of the kit components must be updated when changing the quantity of the kit.")
|
||||
self.assertEqual(self.po.picking_ids.move_ids_without_package[2].product_uom_qty,6, "The amount of the kit components must be updated when changing the quantity of the kit.")
|
||||
|
||||
def test_procurement_with_preferred_route(self):
|
||||
"""
|
||||
3-steps receipts. Suppose a product that has both buy and manufacture
|
||||
routes. The user runs an orderpoint with the preferred route defined to
|
||||
"Buy". A purchase order should be generated.
|
||||
"""
|
||||
self.warehouse.reception_steps = 'three_steps'
|
||||
|
||||
manu_route = self.warehouse.manufacture_pull_id.route_id
|
||||
buy_route = self.warehouse.buy_pull_id.route_id
|
||||
|
||||
# un-prioritize the buy rules
|
||||
self.env['stock.rule'].search([]).sequence = 1
|
||||
buy_route.rule_ids.sequence = 2
|
||||
|
||||
vendor = self.env['res.partner'].create({'name': 'super vendor'})
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'super product',
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'name': vendor.id})],
|
||||
'route_ids': [(4, manu_route.id), (4, buy_route.id)],
|
||||
})
|
||||
|
||||
rr = self.env['stock.warehouse.orderpoint'].create({
|
||||
'name': product.name,
|
||||
'location_id': self.warehouse.lot_stock_id.id,
|
||||
'product_id': product.id,
|
||||
'product_min_qty': 1,
|
||||
'product_max_qty': 1,
|
||||
'route_id': buy_route.id,
|
||||
})
|
||||
rr.action_replenish()
|
||||
|
||||
move_stock, move_check = self.env['stock.move'].search([('product_id', '=', product.id)])
|
||||
|
||||
self.assertRecordValues(move_check | move_stock, [
|
||||
{'location_id': self.warehouse.wh_input_stock_loc_id.id, 'location_dest_id': self.warehouse.wh_qc_stock_loc_id.id, 'state': 'waiting', 'move_dest_ids': move_stock.ids},
|
||||
{'location_id': self.warehouse.wh_qc_stock_loc_id.id, 'location_dest_id': self.warehouse.lot_stock_id.id, 'state': 'waiting', 'move_dest_ids': []},
|
||||
])
|
||||
|
||||
po = self.env['purchase.order'].search([('partner_id', '=', vendor.id)])
|
||||
self.assertTrue(po)
|
||||
|
||||
po.button_confirm()
|
||||
move_in = po.picking_ids.move_lines
|
||||
self.assertEqual(move_in.move_dest_ids.ids, move_check.ids)
|
||||
|
||||
@@ -326,3 +326,20 @@ class ProductionLot(models.Model):
|
||||
action['domain'] = [('id', 'in', self.mapped('purchase_order_ids.id'))]
|
||||
action['context'] = dict(self._context, create=False)
|
||||
return action
|
||||
|
||||
|
||||
class ProcurementGroup(models.Model):
|
||||
_inherit = 'procurement.group'
|
||||
|
||||
@api.model
|
||||
def run(self, procurements, raise_user_error=True):
|
||||
wh_by_comp = dict()
|
||||
for procurement in procurements:
|
||||
routes = procurement.values.get('route_ids')
|
||||
if routes and any(r.action == 'buy' for r in routes.rule_ids):
|
||||
company = procurement.company_id
|
||||
if company not in wh_by_comp:
|
||||
wh_by_comp[company] = self.env['stock.warehouse'].search([('company_id', '=', company.id)])
|
||||
wh = wh_by_comp[company]
|
||||
procurement.values['route_ids'] |= wh.reception_route_id
|
||||
return super().run(procurements, raise_user_error=raise_user_error)
|
||||
|
||||
@@ -21,7 +21,10 @@ class StockRule(models.Model):
|
||||
message_dict = super(StockRule, self)._get_message_dict()
|
||||
dummy, destination, dummy = self._get_message_values()
|
||||
message_dict.update({
|
||||
'buy': _('When products are needed in <b>%s</b>, <br/> a request for quotation is created to fulfill the need.') % (destination)
|
||||
'buy': _('When products are needed in <b>%s</b>, <br/> '
|
||||
'a request for quotation is created to fulfill the need.<br/>'
|
||||
'Note: This rule will be used in combination with the rules<br/>'
|
||||
'of the reception route(s)') % (destination)
|
||||
})
|
||||
return message_dict
|
||||
|
||||
|
||||
@@ -29,14 +29,12 @@ SELECT m.id AS id,
|
||||
m.product_id AS product_id,
|
||||
Min(pc.id) AS category_id,
|
||||
Min(po.partner_id) AS partner_id,
|
||||
Sum(pol.product_uom_qty) AS qty_total,
|
||||
Min(m.product_qty) AS qty_total,
|
||||
Sum(CASE
|
||||
WHEN (pol.date_planned::date >= m.date::date) THEN ml.qty_done
|
||||
WHEN (m.state = 'done' and pol.date_planned::date >= m.date::date) THEN (ml.qty_done / ml_uom.factor * pt_uom.factor)
|
||||
ELSE 0
|
||||
END) AS qty_on_time
|
||||
FROM stock_move m
|
||||
JOIN stock_move_line ml
|
||||
ON m.id = ml.move_id
|
||||
JOIN purchase_order_line pol
|
||||
ON pol.id = m.purchase_line_id
|
||||
JOIN purchase_order po
|
||||
@@ -45,9 +43,14 @@ FROM stock_move m
|
||||
ON p.id = m.product_id
|
||||
JOIN product_template pt
|
||||
ON pt.id = p.product_tmpl_id
|
||||
JOIN uom_uom pt_uom
|
||||
ON pt_uom.id = pt.uom_id
|
||||
JOIN product_category pc
|
||||
ON pc.id = pt.categ_id
|
||||
WHERE m.state = 'done'
|
||||
LEFT JOIN stock_move_line ml
|
||||
ON ml.move_id = m.id
|
||||
LEFT JOIN uom_uom ml_uom
|
||||
ON ml_uom.id = ml.product_uom_id
|
||||
GROUP BY m.id
|
||||
)""")
|
||||
|
||||
|
||||
@@ -178,3 +178,161 @@ class TestPurchaseStockReports(TestReportsCommon):
|
||||
|
||||
docs = self.get_report_forecast(product_template_ids=self.product_template.ids)[1]
|
||||
self.assertEqual(docs['draft_purchase_qty'], 150)
|
||||
|
||||
def test_vendor_delay_report_with_uom(self):
|
||||
"""
|
||||
PO 12 units x P
|
||||
Receive 1 dozen x P
|
||||
-> 100% received
|
||||
"""
|
||||
uom_12 = self.env.ref('uom.product_uom_dozen')
|
||||
|
||||
po_form = Form(self.env['purchase.order'])
|
||||
po_form.partner_id = self.partner
|
||||
with po_form.order_line.new() as line:
|
||||
line.product_id = self.product
|
||||
line.product_qty = 12
|
||||
po = po_form.save()
|
||||
po.button_confirm()
|
||||
|
||||
receipt = po.picking_ids
|
||||
receipt_move = receipt.move_lines
|
||||
receipt_move.move_line_ids.unlink()
|
||||
receipt_move.move_line_ids = [(0, 0, {
|
||||
'location_id': receipt_move.location_id.id,
|
||||
'location_dest_id': receipt_move.location_dest_id.id,
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': uom_12.id,
|
||||
'qty_done': 1,
|
||||
'picking_id': receipt.id,
|
||||
})]
|
||||
receipt.button_validate()
|
||||
|
||||
data = self.env['vendor.delay.report'].read_group(
|
||||
[('partner_id', '=', self.partner.id)],
|
||||
['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'],
|
||||
['product_id'],
|
||||
)[0]
|
||||
self.assertEqual(data['qty_on_time'], 12)
|
||||
self.assertEqual(data['qty_total'], 12)
|
||||
self.assertEqual(data['on_time_rate'], 100)
|
||||
|
||||
def test_vendor_delay_report_with_multi_location(self):
|
||||
"""
|
||||
PO 10 units x P
|
||||
Receive
|
||||
- 6 x P in Child Location 01
|
||||
- 4 x P in Child Location 02
|
||||
-> 100% received
|
||||
"""
|
||||
child_loc_01, child_loc_02 = self.stock_location.child_ids
|
||||
|
||||
po_form = Form(self.env['purchase.order'])
|
||||
po_form.partner_id = self.partner
|
||||
with po_form.order_line.new() as line:
|
||||
line.product_id = self.product
|
||||
line.product_qty = 10
|
||||
po = po_form.save()
|
||||
po.button_confirm()
|
||||
|
||||
receipt = po.picking_ids
|
||||
receipt_move = receipt.move_lines
|
||||
receipt_move.move_line_ids.unlink()
|
||||
receipt_move.move_line_ids = [(0, 0, {
|
||||
'location_id': receipt_move.location_id.id,
|
||||
'location_dest_id': child_loc_01.id,
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'qty_done': 6,
|
||||
'picking_id': receipt.id,
|
||||
}), (0, 0, {
|
||||
'location_id': receipt_move.location_id.id,
|
||||
'location_dest_id': child_loc_02.id,
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'qty_done': 4,
|
||||
'picking_id': receipt.id,
|
||||
})]
|
||||
receipt.button_validate()
|
||||
|
||||
data = self.env['vendor.delay.report'].read_group(
|
||||
[('partner_id', '=', self.partner.id)],
|
||||
['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'],
|
||||
['product_id'],
|
||||
)[0]
|
||||
self.assertEqual(data['qty_on_time'], 10)
|
||||
self.assertEqual(data['qty_total'], 10)
|
||||
self.assertEqual(data['on_time_rate'], 100)
|
||||
|
||||
def test_vendor_delay_report_with_backorder(self):
|
||||
"""
|
||||
PO 10 units x P
|
||||
Receive 6 x P with backorder
|
||||
-> 60% received
|
||||
Process the backorder
|
||||
-> 100% received
|
||||
"""
|
||||
po_form = Form(self.env['purchase.order'])
|
||||
po_form.partner_id = self.partner
|
||||
with po_form.order_line.new() as line:
|
||||
line.product_id = self.product
|
||||
line.product_qty = 10
|
||||
po = po_form.save()
|
||||
po.button_confirm()
|
||||
|
||||
receipt01 = po.picking_ids
|
||||
receipt01_move = receipt01.move_lines
|
||||
receipt01_move.quantity_done = 6
|
||||
action = receipt01.button_validate()
|
||||
Form(self.env[action['res_model']].with_context(action['context'])).save().process()
|
||||
|
||||
data = self.env['vendor.delay.report'].read_group(
|
||||
[('partner_id', '=', self.partner.id)],
|
||||
['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'],
|
||||
['product_id'],
|
||||
)[0]
|
||||
self.assertEqual(data['qty_on_time'], 6)
|
||||
self.assertEqual(data['qty_total'], 10)
|
||||
self.assertEqual(data['on_time_rate'], 60)
|
||||
|
||||
receipt02 = receipt01.backorder_ids
|
||||
receipt02.move_lines.quantity_done = 4
|
||||
receipt02.button_validate()
|
||||
|
||||
data = self.env['vendor.delay.report'].read_group(
|
||||
[('partner_id', '=', self.partner.id)],
|
||||
['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'],
|
||||
['product_id'],
|
||||
)[0]
|
||||
self.assertEqual(data['qty_on_time'], 10)
|
||||
self.assertEqual(data['qty_total'], 10)
|
||||
self.assertEqual(data['on_time_rate'], 100)
|
||||
|
||||
def test_vendor_delay_report_without_backorder(self):
|
||||
"""
|
||||
PO 10 units x P
|
||||
Receive 6 x P without backorder
|
||||
-> 60% received
|
||||
"""
|
||||
po_form = Form(self.env['purchase.order'])
|
||||
po_form.partner_id = self.partner
|
||||
with po_form.order_line.new() as line:
|
||||
line.product_id = self.product
|
||||
line.product_qty = 10
|
||||
po = po_form.save()
|
||||
po.button_confirm()
|
||||
|
||||
receipt01 = po.picking_ids
|
||||
receipt01_move = receipt01.move_lines
|
||||
receipt01_move.quantity_done = 6
|
||||
action = receipt01.button_validate()
|
||||
Form(self.env[action['res_model']].with_context(action['context'])).save().process_cancel_backorder()
|
||||
|
||||
data = self.env['vendor.delay.report'].read_group(
|
||||
[('partner_id', '=', self.partner.id)],
|
||||
['product_id', 'on_time_rate', 'qty_on_time', 'qty_total'],
|
||||
['product_id'],
|
||||
)[0]
|
||||
self.assertEqual(data['qty_on_time'], 6)
|
||||
self.assertEqual(data['qty_total'], 10)
|
||||
self.assertEqual(data['on_time_rate'], 60)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<field name="name"/>
|
||||
<field name="product_id" readonly="1" optional="show"/>
|
||||
<field name="product_qty" optional="hide" string="Quantity"/>
|
||||
<field name="product_uom" string="Unit of Measure" readonly="1" optional="hide"/>
|
||||
<field name="product_uom" string="Unit of Measure" readonly="1" groups="uom.group_uom" optional="hide"/>
|
||||
<field name="user_id" optional="hide" widget='many2one_avatar_user'/>
|
||||
<field name="partner_id" readonly="1" optional="show"/>
|
||||
<field name="address_id" optional="show"/>
|
||||
|
||||
@@ -115,7 +115,6 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, {
|
||||
if (!self.preventOpening) {
|
||||
self.$modal.find(".modal-body").replaceWith(self.$el);
|
||||
self.$modal.attr('open', true);
|
||||
self.$modal.removeAttr("aria-hidden");
|
||||
self.$modal.modal().appendTo(self.container);
|
||||
self.$modal.focus();
|
||||
self._openedResolver();
|
||||
|
||||
@@ -52,7 +52,7 @@ class Project(models.Model):
|
||||
warning_employee_rate = fields.Boolean(compute='_compute_warning_employee_rate')
|
||||
|
||||
_sql_constraints = [
|
||||
('timesheet_product_required_if_billable_and_timesheets', """
|
||||
('timesheet_product_required_if_billable_and_time', """
|
||||
CHECK(
|
||||
(allow_billable = 't' AND allow_timesheets = 't' AND timesheet_product_id IS NOT NULL)
|
||||
OR (allow_billable IS NOT TRUE)
|
||||
|
||||
@@ -97,17 +97,11 @@ function _genericJsonRpc (fct_name, params, settings, fct) {
|
||||
});
|
||||
|
||||
// FIXME: jsonp?
|
||||
/**
|
||||
* @param {Boolean} rejectError Returns an error if true. Allows you to cancel
|
||||
* ignored rpc's in order to unblock the ui and not display an error.
|
||||
*/
|
||||
promise.abort = function (rejectError = true) {
|
||||
if (rejectError) {
|
||||
rejection({
|
||||
message: "XmlHttpRequestError abort",
|
||||
event: $.Event('abort')
|
||||
});
|
||||
}
|
||||
promise.abort = function () {
|
||||
rejection({
|
||||
message: "XmlHttpRequestError abort",
|
||||
event: $.Event('abort')
|
||||
});
|
||||
|
||||
if (!shadow) {
|
||||
core.bus.trigger('rpc_response');
|
||||
|
||||
@@ -200,7 +200,6 @@ var Dialog = Widget.extend({
|
||||
}
|
||||
self.$modal.find(".modal-body").replaceWith(self.$el);
|
||||
self.$modal.attr('open', true);
|
||||
self.$modal.removeAttr("aria-hidden");
|
||||
if (self.$parentNode) {
|
||||
self.$modal.appendTo(self.$parentNode);
|
||||
}
|
||||
|
||||
@@ -390,9 +390,11 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
|
||||
* @private
|
||||
*/
|
||||
_configureLocale: function () {
|
||||
const dow = (_t.database.parameters.week_start || 0) % 7;
|
||||
moment.updateLocale(moment.locale(), {
|
||||
week: {
|
||||
dow: (_t.database.parameters.week_start || 0) % 7,
|
||||
dow: dow,
|
||||
doy: 7 + dow - 4 // Note: ISO 8601 week date: https://momentjscom.readthedocs.io/en/latest/moment/07-customization/16-dow-doy/
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -634,14 +634,14 @@ function parseMonetary(value, field, options) {
|
||||
if (!value.includes(currency.symbol)) {
|
||||
throw new Error(_.str.sprintf(core._t("'%s' is not a correct monetary field"), value));
|
||||
}
|
||||
if (currency.position === 'before') {
|
||||
return parseFloat(value
|
||||
.replace(`${ currency.symbol }${ NBSP }`, '')
|
||||
.replace(`${ currency.symbol } `, ''));
|
||||
} else {
|
||||
if (currency.position === 'after') {
|
||||
return parseFloat(value
|
||||
.replace(`${ NBSP }${ currency.symbol }`, '')
|
||||
.replace(` ${ currency.symbol }`, ''));
|
||||
} else {
|
||||
return parseFloat(value
|
||||
.replace(`${ currency.symbol }${ NBSP }`, '')
|
||||
.replace(`${ currency.symbol } `, ''));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -577,6 +577,10 @@ var FieldMany2One = AbstractField.extend({
|
||||
}
|
||||
|
||||
if (this.lastNameSearch) {
|
||||
this.lastNameSearch.catch((reason) => {
|
||||
// the last rpc name_search will be aborted, so we want to ignore its rejection
|
||||
reason.event.preventDefault();
|
||||
})
|
||||
this.lastNameSearch.abort(false)
|
||||
}
|
||||
this.lastNameSearch = this._rpc({
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
[aria-hidden="true"], [aria-hidden="1"] {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
white-space: nowrap;
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ QUnit.test('parse integer', function(assert) {
|
||||
});
|
||||
|
||||
QUnit.test('parse monetary', function(assert) {
|
||||
assert.expect(15);
|
||||
assert.expect(16);
|
||||
var originalCurrencies = session.currencies;
|
||||
const originalParameters = _.clone(core._t.database.parameters);
|
||||
session.currencies = {
|
||||
@@ -270,6 +270,10 @@ QUnit.test('parse monetary', function(assert) {
|
||||
position: "after",
|
||||
symbol: "€"
|
||||
},
|
||||
2: {
|
||||
digits: [69, 2],
|
||||
symbol: "¥"
|
||||
},
|
||||
3: {
|
||||
digits: [69, 2],
|
||||
position: "before",
|
||||
@@ -285,6 +289,8 @@ QUnit.test('parse monetary', function(assert) {
|
||||
assert.strictEqual(fieldUtils.parse.monetary("1,000,000.00"), 1000000);
|
||||
assert.strictEqual(fieldUtils.parse.monetary("$ 125.00", {}, {currency_id: 3}), 125);
|
||||
assert.strictEqual(fieldUtils.parse.monetary("1,000.00 €", {}, {currency_id: 1}), 1000);
|
||||
const formated_value = fieldUtils.format.monetary(100, {}, {currency_id: 2});
|
||||
assert.strictEqual(fieldUtils.parse.monetary(formated_value, {}, {currency_id: 2}), 100);
|
||||
assert.throws(function() {fieldUtils.parse.monetary("$ 12.00", {}, {currency_id: 3})}, /is not a correct/);
|
||||
assert.throws(function() {fieldUtils.parse.monetary("$ 12.00", {}, {currency_id: 1})}, /is not a correct/);
|
||||
assert.throws(function() {fieldUtils.parse.monetary("$ 12.00 34", {}, {currency_id: 3})}, /is not a correct/);
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
<div class="modal-body">
|
||||
{{ create_form() }}
|
||||
<small class="text-muted">
|
||||
To enhance your experience, some data may be sent to Flectra online services. See our <a href="https://www.flectrahq.com/privacy">Privacy Policy</a>.
|
||||
To enhance your experience, some data may be sent to Flectra online services. See our <a href="https://www.flectrahq.com/privacy" target="_blank">Privacy Policy</a>.
|
||||
</small>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -466,10 +466,6 @@ a.o_underline {
|
||||
}
|
||||
}
|
||||
|
||||
.ace_editor > .ace_gutter {
|
||||
display: block !important; // display even with aria-hidden
|
||||
}
|
||||
|
||||
.o_ace_select2_dropdown {
|
||||
width: auto !important;
|
||||
padding-top: 4px;
|
||||
|
||||
Reference in New Issue
Block a user