Merge branch 'master' of gitlab.com:flectra-hq/flectra

This commit is contained in:
Parthiv Patel committed 2021-07-09 06:06:25 +00:00
commit 166f242fbf
26 files changed
+248 -36

No files matched your search

+1 -1
View File
@@ -151,7 +151,7 @@ class AccountMove(models.Model):
('in_refund', 'Vendor Credit Note'),
('out_receipt', 'Sales Receipt'),
('in_receipt', 'Purchase Receipt'),
], string='Type', required=True, store=True, index=True, readonly=True, tracking=True,
], string='Move Type', required=True, store=True, index=True, readonly=True, tracking=True,
default="entry", change_default=True)
type_name = fields.Char('Type Name', compute='_compute_type_name')
to_check = fields.Boolean(string='To Check', default=False,
@@ -90,11 +90,11 @@ class AccountInvoiceLine(models.Model):
elif self.move_id.move_type == 'in_invoice' and self.asset_category_id:
self.account_id = self.asset_category_id.account_asset_id.id
@api.onchange('uom_id')
def _onchange_uom_id(self):
result = super(AccountInvoiceLine, self)._onchange_uom_id()
self.onchange_asset_category_id()
return result
# @api.onchange('uom_id')
# def _onchange_uom_id(self):
# result = super(AccountInvoiceLine, self)._onchange_uom_id()
# self.onchange_asset_category_id()
# return result
@api.onchange('product_id')
def _onchange_product_id(self):
@@ -14,7 +14,7 @@ class AccountBankStatementLine(models.Model):
help="This technical field can be used at the statement line creation/import time in order to avoid the reconciliation"
" process on it later on. The statement line will simply create a counterpart on this account")
note = fields.Text(string='Notes')
journal_entry_ids = fields.One2many('account.move.line', 'statement_line_id', 'Journal Items', copy=False,
journal_entry_ids = fields.One2many('account.move.line', 'statement_line_id', 'Journals', copy=False,
readonly=True)
move_name = fields.Char(string='Journal Entry Name', readonly=True,
default=False, copy=False,
@@ -26,7 +26,7 @@ class AccountMove(models.Model):
('not_paid', 'Not Paid'),
('in_payment', 'In Payment'),
('paid', 'Paid')],
string='Payment', store=True, readonly=True, copy=False, tracking=True,
string='Status of Payment', store=True, readonly=True, copy=False, tracking=True,
compute='_compute_amount')
def action_open_reconcile(self):
@@ -8,7 +8,7 @@ from flectra.exceptions import UserError, ValidationError
class AccountPayment(models.Model):
_inherit = "account.payment"
payment_date = fields.Date(string='Date', default=fields.Date.context_today, required=True, readonly=True,
payment_date = fields.Date(string='Payment Date', default=fields.Date.context_today, required=True, readonly=True,
states={'draft': [('readonly', False)]}, copy=False, tracking=True)
communication = fields.Char(string='Memo', readonly=True, states={'draft': [('readonly', False)]})
move_line_ids = fields.One2many('account.move.line', 'payment_id', readonly=True, copy=False)
@@ -20,8 +20,8 @@ class AccountingReport(models.TransientModel):
account_report_id = fields.Many2one('account.financial.report', string='Account Reports', required=True, default=_get_account_report)
label_filter = fields.Char(string='Column Label', help="This label will be displayed on report to show the balance computed for the given comparison filter.")
filter_cmp = fields.Selection([('filter_no', 'No Filters'), ('filter_date', 'Date')], string='Filter by', required=True, default='filter_no')
date_from_cmp = fields.Date(string='Start Date')
date_to_cmp = fields.Date(string='End Date')
date_from_cmp = fields.Date(string='From Date')
date_to_cmp = fields.Date(string='To Date')
debit_credit = fields.Boolean(string='Display Debit/Credit Columns', help="This option allows you to get more details about the way your balances are computed. Because it is space consuming, we do not allow to use it while doing a comparison.")
def _build_comparison_context(self, data):
+1 -1
View File
@@ -13,6 +13,6 @@ class ResUsers(models.Model):
default_digest_emails = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_emails')
default_digest_id = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_id')
if default_digest_emails and default_digest_id:
digest = self.env['digest.digest'].sudo().browse(int(default_digest_id))
digest = self.env['digest.digest'].sudo().browse(int(default_digest_id)).exists()
digest.user_ids |= users.filtered_domain([('share', '=', False)])
return users
+1 -1
View File
@@ -6,7 +6,7 @@ import flectra.tests
@flectra.tests.tagged("post_install", "-at_install")
class TestDmsPortal(flectra.tests.HttpCase):
def test_tour(self):
self.phantom_js(
self.start_tour(
"/",
"flectra.__DEBUG__.services['web_tour.tour'].run('dms_portal_tour')",
"flectra.__DEBUG__.services['web_tour.tour'].tours.dms_portal_tour.ready",
+11
View File
@@ -47,6 +47,10 @@ class SaleOrder(models.Model):
for sale_order in self:
sale_order.attendee_count = attendee_count_data.get(sale_order.id, 0)
def unlink(self):
self.order_line._unlink_associated_registrations()
return super(SaleOrder, self).unlink()
class SaleOrderLine(models.Model):
@@ -120,6 +124,13 @@ class SaleOrderLine(models.Model):
# we call this to force update the default name
self.product_id_change()
def unlink(self):
self._unlink_associated_registrations()
return super(SaleOrderLine, self).unlink()
def _unlink_associated_registrations(self):
self.env['event.registration'].search([('sale_order_line_id', 'in', self.ids)]).unlink()
def get_sale_order_line_multiline_description_sale(self, product):
""" We override this method because we decided that:
The default description of a sales order line containing a ticket must be different than the default description when no ticket is present.
+66 -5
View File
@@ -2,6 +2,8 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra.addons.event_sale.tests.common import TestEventSaleCommon
from flectra.addons.mail.tests.common import mail_new_test_user
from flectra.tests import tagged
from flectra.tests.common import users
@@ -13,20 +15,59 @@ class TestEventSale(TestEventSaleCommon):
def setUpClass(cls):
super(TestEventSale, cls).setUpClass()
product = cls.env['product.product'].create({
'name': 'Event',
'type': 'service',
'event_ok': True,
})
cls.user_salesperson = mail_new_test_user(cls.env, login='user_salesman', groups='sales_team.group_sale_salesman')
cls.ticket = cls.env['event.event.ticket'].create({
'name': 'First Ticket',
'product_id': cls.event_product.id,
'seats_max': 30,
'event_id': cls.event_0.id,
})
cls.event_0.write({
'event_ticket_ids': [
(5, 0),
(6, 0, cls.ticket.ids),
(0, 0, {
'name': 'First Ticket',
'product_id': cls.event_product.id,
'seats_max': 30,
}), (0, 0, {
'name': 'Second Ticket',
'product_id': cls.event_product.id,
})
],
})
cls.sale_order = cls.env['sale.order'].create({
'partner_id': cls.env.ref('base.res_partner_2').id,
'note': 'Invoice after delivery',
'payment_term_id': cls.env.ref('account.account_payment_term_end_following_month').id
})
# In the sales order I add some sales order lines. i choose event product
cls.env['sale.order.line'].create({
'product_id': product.id,
'price_unit': 190.50,
'product_uom': cls.env.ref('uom.product_uom_unit').id,
'product_uom_qty': 1.0,
'order_id': cls.sale_order.id,
'name': 'sales order line',
'event_id': cls.event_0.id,
'event_ticket_id': cls.ticket.id,
})
cls.register_person = cls.env['registration.editor'].create({
'sale_order_id': cls.sale_order.id,
'event_registration_ids': [(0, 0, {
'event_id': cls.event_0.id,
'name': 'Administrator',
'email': 'abc@example.com',
'sale_order_line_id': cls.sale_order.order_line.id,
})],
})
# make a SO for a customer, selling some tickets
cls.customer_so = cls.env['sale.order'].with_user(cls.user_sales_salesman).create({
'partner_id': cls.event_customer.id,
@@ -209,3 +250,23 @@ class TestEventSale(TestEventSaleCommon):
})
sol.product_id_change()
self.assertEqual(so.amount_total, 660.0, "Ticket is $1000 but the event product is on a pricelist 10 -> 6. So, $600 + a 10% tax.")
@users('user_salesman')
def test_unlink_so(self):
""" This test ensures that when deleting a sale order, if the latter is linked to an event registration,
the number of expected seats will be correctly updated """
event = self.env['event.event'].browse(self.event_0.ids)
self.register_person.action_make_registration()
self.assertEqual(event.seats_expected, 1)
self.sale_order.unlink()
self.assertEqual(event.seats_expected, 0)
@users('user_salesman')
def test_unlink_soline(self):
""" This test ensures that when deleting a sale order line, if the latter is linked to an event registration,
the number of expected seats will be correctly updated """
event = self.env['event.event'].browse(self.event_0.ids)
self.register_person.action_make_registration()
self.assertEqual(event.seats_expected, 1)
self.sale_order.order_line.unlink()
self.assertEqual(event.seats_expected, 0)
@@ -7,7 +7,7 @@ class RegistrationEditor(models.TransientModel):
_name = "registration.editor"
_description = 'Edit Attendee Details on Sales Confirmation'
sale_order_id = fields.Many2one('sale.order', 'Sales Order', required=True)
sale_order_id = fields.Many2one('sale.order', 'Sales Order', required=True, ondelete='cascade')
event_registration_ids = fields.One2many('registration.editor.line', 'editor_id', string='Registrations to Edit')
@api.model
+7 -1
View File
@@ -2,7 +2,7 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models, _
from flectra.exceptions import Warning
from flectra.exceptions import UserError, Warning
class l10n_eu_service(models.TransientModel):
@@ -70,6 +70,12 @@ class l10n_eu_service(models.TransientModel):
'res.country', 'l10n_eu_service_country_rel_todo', default=_default_todo_country_ids,
string='EU Customers From', required=True)
@api.model
def load_views(self, views, options=None):
# This wizard is outdated; it shouldn't be used anymore. Users might still be able to open it using the
# link in the settings if they didn't update the module. If they try, we tell them.
raise UserError(_("Starting July 1st 2021, OSS regulation has replaced MOSS. Please first upgrade 'l10n_eu_service' module in the Apps menu, then go back to this setting and click on 'Refresh tax mapping'."))
def _get_repartition_line_copy_values(self, original_rep_lines):
return [(0, 0, {
'factor_percent': line.factor_percent,
@@ -85,7 +85,7 @@
</div>
<p>Published on <t t-esc="rating.publisher_datetime"/></p>
</div>
<t t-raw="rating.publisher_comment"/>
<t t-esc="rating.publisher_comment"/>
</div>
</div>
</t>
@@ -507,6 +507,7 @@ class PricelistItem(models.Model):
self.percent_price = 0.0
if self.compute_price != 'formula':
self.update({
'base': 'list_price',
'price_discount': 0.0,
'price_surcharge': 0.0,
'price_round': 0.0,
@@ -142,11 +142,12 @@ class Project(models.Model):
'time': data_timesheet_invoice_type}
for key, value in res['repartition_employee'].items():
res['repartition_employee'][key].update({
emp_data[key]['type']: emp_data[key]['time']})
if emp_data:
res['repartition_employee'][key].update({
emp_data[key]['type']: emp_data[key]['time']})
res['repartition_employee'][key]['total'] = sum(
[value[inv_type] for inv_type in [*billable_types, 'canceled']])
res['repartition_employee'][key]['total'] = sum(
[value[inv_type] for inv_type in [*billable_types, 'canceled']])
return res
+1 -1
View File
@@ -696,7 +696,7 @@ Reason(s) of this behavior could be:
invoiceable_lines = order._get_invoiceable_lines(final)
if not any(not line.display_type for line in invoiceable_lines):
raise self._nothing_to_invoice_error()
continue
invoice_line_vals = []
down_payment_section_added = False
+3 -3
View File
@@ -372,12 +372,12 @@
</div>
<div class="row" id="so_date">
<div class="mb-3 col-6">
<t t-if="sale_order.state == 'draft'">
<strong>Quotation Date:</strong>
</t>
<t t-if="sale_order.state in ['sale', 'done', 'cancel']">
<strong>Order Date:</strong>
</t>
<t t-else="">
<strong>Quotation Date:</strong>
</t>
<span t-field="sale_order.date_order" t-options='{"widget": "date"}'/>
</div>
<div class="mb-3 col-6" t-if="sale_order.validity_date">
+4 -2
View File
@@ -619,16 +619,17 @@ class StockMoveLine(models.Model):
# As the move's state is not computed over the move lines, we'll have to manually
# recompute the moves which we adapted their lines.
move_to_recompute_state = self.env['stock.move']
to_unlink_candidates = set()
rounding = self.product_uom_id.rounding
for candidate in outdated_candidates:
if float_compare(candidate.product_qty, quantity, precision_rounding=rounding) <= 0:
quantity -= candidate.product_qty
move_to_recompute_state |= candidate.move_id
if candidate.qty_done:
move_to_recompute_state |= candidate.move_id
candidate.product_uom_qty = 0.0
else:
candidate.unlink()
to_unlink_candidates.add(candidate.id)
if float_is_zero(quantity, precision_rounding=rounding):
break
else:
@@ -640,6 +641,7 @@ class StockMoveLine(models.Model):
candidate.product_uom_qty = self.product_id.uom_id._compute_quantity(quantity_split, candidate.product_uom_id, rounding_method='HALF-UP')
move_to_recompute_state |= candidate.move_id
break
self.env['stock.move.line'].browse(to_unlink_candidates).unlink()
move_to_recompute_state._recompute_state()
def _should_bypass_reservation(self, location):
@@ -26,7 +26,7 @@ const ReplenishReport = clientAction.extend({
if (this.context.warehouse) {
this.active_warehouse = {id: this.context.warehouse};
}
this.report_url += `?context=${JSON.stringify(this.context)}`;
this.report_url += `?context=${JSON.stringify(this.context)}&force_context_lang=1`;
this._title = action.name;
},
+1 -1
View File
@@ -34,7 +34,7 @@ class AccountMove(models.Model):
for copy_vals in res:
if 'line_ids' in copy_vals:
copy_vals['line_ids'] = [line_vals for line_vals in copy_vals['line_ids']
if line_vals[0] != 0 or not line_vals[2]['is_anglo_saxon_line']]
if line_vals[0] != 0 or not line_vals[2].get('is_anglo_saxon_line')]
return res
+1 -1
View File
@@ -2046,7 +2046,7 @@ class ReportController(http.Controller):
# Ignore 'lang' here, because the context in data is the one from the webclient *but* if
# the user explicitely wants to change the lang, this mechanism overwrites it.
data['context'] = json.loads(data['context'])
if data['context'].get('lang'):
if data['context'].get('lang') and not data.get('force_context_lang'):
del data['context']['lang']
context.update(data['context'])
if converter == 'html':
@@ -647,6 +647,31 @@ var FieldDateRange = InputField.extend({
}
this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Field widget is valid if value entered can convered to date/dateime value
* while parsing input value to date/datetime throws error then widget considered
* invalid
*
* @override
*/
isValid: function () {
const value = this.mode === "readonly" ? this.value : this.$input.val();
try {
return field_utils.parse[this.formatType](value, this.field, { timezone: true }) || true;
} catch (error) {
return false;
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Return the date written in the input, in UTC.
*
@@ -654,7 +679,14 @@ var FieldDateRange = InputField.extend({
* @returns {Moment|false}
*/
_getValue: function () {
return field_utils.parse[this.formatType](this.$input.val(), this.field, { timezone: true });
try {
// user may enter manual value in input and it may not be parsed as date/datetime value
this.removeInvalidClass();
return field_utils.parse[this.formatType](this.$input.val(), this.field, { timezone: true });
} catch (error) {
this.setInvalidClass();
return false;
}
},
//--------------------------------------------------------------------------
@@ -1837,6 +1837,12 @@ ListRenderer.include({
return;
}
// ignore click if search facet is removed as it will re-render whole
// listview again
if ($(event.target).hasClass('o_facet_remove')) {
return;
}
this.unselectRow();
},
});
+3 -1
View File
@@ -261,7 +261,9 @@
.o_data_row.o_selected_row > .o_data_cell:not(.o_readonly_modifier):not(.o_invisible_modifier) {
position: relative; // for o_field_translate
background-color: white;
&:not(.o_handle_cell) {
background-color: white;
}
.o_input {
border: none;
padding: 0;
@@ -3531,6 +3531,49 @@ QUnit.module('basic_fields', {
form.destroy();
});
QUnit.test('Daterange field manually input wrong value should show toaster', async function (assert) {
assert.expect(5);
this.data.partner.fields.date_end = { string: 'Date End', type: 'date' };
this.data.partner.records[0].date_end = '2017-02-08';
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<field name="date" widget="daterange" options="{'related_end_date': 'date_end'}"/>
<field name="date_end" widget="daterange" options="{'related_start_date': 'date'}"/>
</form>`,
interceptsPropagate: {
call_service: function (ev) {
if (ev.data.service === 'notification') {
assert.strictEqual(ev.data.method, 'notify');
assert.strictEqual(ev.data.args[0].title, 'Invalid fields:');
assert.strictEqual(ev.data.args[0].message, '<ul><li>A date</li></ul>');
}
}
},
});
await testUtils.fields.editInput(form.$('.o_field_date_range:first'), 'blabla');
// click outside daterange field
await testUtils.dom.click(form.$el);
assert.hasClass(form.$('input[name=date]'), 'o_field_invalid',
"date field should be displayed as invalid");
// update input date with right value
await testUtils.fields.editInput(form.$('.o_field_date_range:first'), '02/08/2017');
assert.doesNotHaveClass(form.$('input[name=date]'), 'o_field_invalid',
"date field should not be displayed as invalid now");
// again enter wrong value and try to save should raise invalid fields value
await testUtils.fields.editInput(form.$('.o_field_date_range:first'), 'blabla');
await testUtils.form.clickSave(form);
form.destroy();
});
QUnit.module('FieldDate');
QUnit.test('date field: toggle datepicker [REQUIRE FOCUS]', async function (assert) {
@@ -9931,6 +9931,53 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('removing a groupby while adding a line from list', async function (assert) {
assert.expect(1);
let checkUnselectRow = false;
testUtils.mock.patch(ListRenderer, {
unselectRow(options = {}) {
if (checkUnselectRow) {
assert.step('unselectRow');
}
return this._super(...arguments);
},
});
const list = await createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree multi_edit="1" editable="bottom">
<field name="display_name"/>
<field name="foo"/>
</tree>
`,
archs: {
'foo,false,search': `
<search>
<field name="foo"/>
<group expand="1" string="Group By">
<filter name="groupby_foo" context="{'group_by': 'foo'}"/>
</group>
</search>
`,
},
});
await cpHelpers.toggleGroupByMenu(list);
await cpHelpers.toggleMenuItem(list, 0);
// expand group
await testUtils.dom.click(list.el.querySelector('th.o_group_name'));
await testUtils.dom.click(list.el.querySelector('td.o_group_field_row_add a'));
checkUnselectRow = true;
await testUtils.dom.click($('.o_searchview_facet .o_facet_remove'));
assert.verifySteps([]);
testUtils.mock.unpatch(ListRenderer);
list.destroy();
});
QUnit.test('cell-level keyboard navigation in editable grouped list', async function (assert) {
assert.expect(56);