[PATCH] Upstream patch - 07012023

This commit is contained in:
Parthiv Patel
2023-01-07 08:35:41 +00:00
parent 364f7c997b
commit a3ac8082b2
39 changed files with 451 additions and 73 deletions

View File

@@ -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))

View File

@@ -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

View File

@@ -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)

View File

@@ -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,
}])

View File

@@ -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'},

View File

@@ -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"
1 id name code user_type_id/id chart_template_id/id tag_ids/id reconcile
59 a1311 Other reserves not available 1311 account.data_account_type_equity l10n_be.l10nbe_chart_template False
60 a132 Untaxed reserves 132 account.data_account_type_equity l10n_be.l10nbe_chart_template False
61 a133 Available reserves 133 account.data_account_type_equity l10n_be.l10nbe_chart_template False
62 a140 Deferred profit 140 account.data_account_type_equity l10n_be.l10nbe_chart_template False
63 a141 Loss carried forward 141 account.data_account_type_equity l10n_be.l10nbe_chart_template False
64 a15 Investment grants 15 account.data_account_type_equity l10n_be.l10nbe_chart_template False
65 a151 Investment grants received in cash 151 account.data_account_type_equity l10n_be.l10nbe_chart_template False
66 a152 Investment grants received in kind 152 account.data_account_type_equity l10n_be.l10nbe_chart_template False
364 a673 Foreign income taxes on the result of prior periods 673 account.data_account_type_expenses l10n_be.l10nbe_chart_template False
365 a680 Transfer to deferred taxes 680 account.data_account_type_expenses l10n_be.l10nbe_chart_template False
366 a689 Transfer to untaxed reserves 689 account.data_account_type_expenses l10n_be.l10nbe_chart_template False
367 a690 Loss brought forward Loss brought forward from previous year 690 account.data_account_type_expenses l10n_be.l10nbe_chart_template False
368 a691 Appropriations to capital and share premium account 691 account.data_account_type_expenses l10n_be.l10nbe_chart_template False
369 a6920 Appropriations to legal reserve 6920 account.data_account_type_expenses l10n_be.l10nbe_chart_template False
370 a6921 Appropriations to other reserves 6921 account.data_account_type_expenses l10n_be.l10nbe_chart_template False
423 a773 Adjustment of foreign income taxes 773 account.data_account_type_revenue l10n_be.l10nbe_chart_template False
424 a780 Transfer from deferred taxes 780 account.data_account_type_revenue l10n_be.l10nbe_chart_template account.account_tag_operating False
425 a789 Transfer from untaxed reserves 789 account.data_account_type_revenue l10n_be.l10nbe_chart_template account.account_tag_operating False
426 a790 Profit brought forward Profit brought forward from previous year 790 account.data_account_type_revenue l10n_be.l10nbe_chart_template False
427 a791 Withdrawal from the association or foundation funds 791 account.data_account_type_revenue l10n_be.l10nbe_chart_template False
428 a792 Withdrawal from allocated funds 792 account.data_account_type_revenue l10n_be.l10nbe_chart_template False
429 a793 Losses to be carried forward 793 account.data_account_type_revenue l10n_be.l10nbe_chart_template False

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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/>

View File

@@ -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))

View File

@@ -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.

View File

@@ -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')

View File

@@ -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;
}
}

View File

@@ -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

View 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()

View File

@@ -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));

View File

@@ -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 = {"&":"&amp;","<":"&lt;",">":"&gt;"};
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) {

View File

@@ -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/?&amp;currency_id</p>':
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/?&amp;currency_id">https://example.com/?&amp;currency_id</a></p>',
// entities not unescaped
'&amp; &amp;amp; &gt; &lt;': '&amp; &amp;amp; &gt; &lt;',
// > and " not linkified since they are not in URL regex
'<p>https://example.com/&gt;</p>':
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/">https://example.com/</a>&gt;</p>',
'<p>https://example.com/"hello"&gt;</p>':
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/">https://example.com/</a>"hello"&gt;</p>',
// & and ' linkified since they are in URL regex
'<p>https://example.com/&amp;hello</p>':
'<p><a target="_blank" rel="noreferrer noopener" href="https://example.com/&amp;hello">https://example.com/&amp;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
'&lt;3': '&lt;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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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
)""")

View File

@@ -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)

View File

@@ -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"/>

View File

@@ -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();

View File

@@ -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)

View File

@@ -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');

View File

@@ -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);
}

View File

@@ -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/
},
});
},

View File

@@ -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 }&nbsp;`, ''));
} else {
if (currency.position === 'after') {
return parseFloat(value
.replace(`${ NBSP }${ currency.symbol }`, '')
.replace(`&nbsp;${ currency.symbol }`, ''));
} else {
return parseFloat(value
.replace(`${ currency.symbol }${ NBSP }`, '')
.replace(`${ currency.symbol }&nbsp;`, ''));
}
}

View File

@@ -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({

View File

@@ -14,10 +14,6 @@
}
}
[aria-hidden="true"], [aria-hidden="1"] {
display: none!important;
}
.dropdown-toggle {
white-space: nowrap;

View File

@@ -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("$&nbsp;125.00", {}, {currency_id: 3}), 125);
assert.strictEqual(fieldUtils.parse.monetary("1,000.00&nbsp;€", {}, {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("$&nbsp;12.00", {}, {currency_id: 1})}, /is not a correct/);
assert.throws(function() {fieldUtils.parse.monetary("$&nbsp;12.00&nbsp;34", {}, {currency_id: 3})}, /is not a correct/);

View File

@@ -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">

View File

@@ -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;