mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-10-25 23:40:05 -05:00
[PATCH] Upstream patch - 05112021
This commit is contained in:
1 parent
0f3ee980ed
commit
66e7d898f0
16 files changed
+229
-28
No files matched your search
@@ -203,14 +203,6 @@ class AccountEdiFormat(models.Model):
|
||||
vat=self._find_value('//ram:' + partner_type + '/ram:SpecifiedTaxRegistration/ram:ID', tree, namespaces=tree.nsmap),
|
||||
)
|
||||
|
||||
# Delivery partner
|
||||
if 'partner_shipping_id' in invoice._fields:
|
||||
invoice_form.partner_shipping_id = self_ctx._retrieve_partner(
|
||||
name=self._find_value('//ram:ShipToTradeParty/ram:Name', tree, namespaces=tree.nsmap),
|
||||
mail=self._find_value('//ram:ShipToTradeParty//ram:URIID[@schemeID=\'SMTP\']', tree, namespaces=tree.nsmap),
|
||||
vat=self._find_value('//ram:ShipToTradeParty/ram:SpecifiedTaxRegistration/ram:ID', tree, namespaces=tree.nsmap),
|
||||
)
|
||||
|
||||
# Reference.
|
||||
elements = tree.xpath('//rsm:ExchangedDocument/ram:ID', namespaces=tree.nsmap)
|
||||
if elements:
|
||||
|
||||
@@ -14,9 +14,12 @@ class pos_config(models.Model):
|
||||
_inherit = 'pos.config'
|
||||
|
||||
def open_ui(self):
|
||||
for config in self.filtered(lambda c: c.company_id._is_accounting_unalterable()):
|
||||
if config.current_session_id:
|
||||
config.current_session_id._check_session_timing()
|
||||
for config in self:
|
||||
if not config.company_id.country_id:
|
||||
raise UserError(_("You have to set a country in your company setting."))
|
||||
if config.company_id._is_accounting_unalterable():
|
||||
if config.current_session_id:
|
||||
config.current_session_id._check_session_timing()
|
||||
return super(pos_config, self).open_ui()
|
||||
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ class MailMail(models.Model):
|
||||
# To remove when automatic context propagation is removed in web client
|
||||
if self._context.get('default_type') not in type(self).message_type.base_field.selection:
|
||||
self = self.with_context(dict(self._context, default_type=None))
|
||||
if self._context.get('default_state') not in type(self).state.base_field.selection:
|
||||
self = self.with_context(dict(self._context, default_state='outgoing'))
|
||||
return super(MailMail, self).default_get(fields)
|
||||
|
||||
def mark_outgoing(self):
|
||||
|
||||
@@ -251,6 +251,8 @@ class MrpWorkcenter(models.Model):
|
||||
else:
|
||||
# Decrease a part of the remaining duration
|
||||
remaining -= interval_minutes
|
||||
# Go to the next available interval because the possible current interval duration has been used
|
||||
break
|
||||
return False, 'Not available slot 700 days after the planned start'
|
||||
|
||||
def action_archive(self):
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<field name="display_type"/>
|
||||
<field name="html_color" attrs="{'invisible': [('display_type', '!=', 'color')]}" widget="color"/>
|
||||
<field name="ptav_active" optional="hide"/>
|
||||
<field name="price_extra" widget="monetary"/>
|
||||
<field name="price_extra" widget="monetary" options="{'field_digits': True}"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -111,7 +111,7 @@
|
||||
<field name="name"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<field name="html_color" attrs="{'invisible': [('display_type', '!=', 'color')]}"/>
|
||||
<field name="price_extra" widget="monetary"/>
|
||||
<field name="price_extra" widget="monetary" options="{'field_digits': True}"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="exclude_for" widget="one2many" mode="tree">
|
||||
<tree editable="bottom">
|
||||
|
||||
@@ -224,6 +224,7 @@ class PurchaseRequisitionLine(models.Model):
|
||||
|
||||
@api.depends('requisition_id.purchase_ids.state')
|
||||
def _compute_ordered_qty(self):
|
||||
line_found = set()
|
||||
for line in self:
|
||||
total = 0.0
|
||||
for po in line.requisition_id.purchase_ids.filtered(lambda purchase_order: purchase_order.state in ['purchase', 'done']):
|
||||
@@ -232,7 +233,11 @@ class PurchaseRequisitionLine(models.Model):
|
||||
total += po_line.product_uom._compute_quantity(po_line.product_qty, line.product_uom_id)
|
||||
else:
|
||||
total += po_line.product_qty
|
||||
line.qty_ordered = total
|
||||
if line.product_id not in line_found :
|
||||
line.qty_ordered = total
|
||||
line_found.add(line.product_id)
|
||||
else:
|
||||
line.qty_ordered = 0
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
|
||||
@@ -453,7 +453,7 @@ class PurchaseOrderLine(models.Model):
|
||||
def _check_orderpoint_picking_type(self):
|
||||
warehouse_loc = self.order_id.picking_type_id.warehouse_id.view_location_id
|
||||
dest_loc = self.move_dest_ids.location_id or self.orderpoint_id.location_id
|
||||
if warehouse_loc and dest_loc and not warehouse_loc.parent_path in dest_loc[0].parent_path:
|
||||
if warehouse_loc and dest_loc and dest_loc.get_warehouse() and not warehouse_loc.parent_path in dest_loc[0].parent_path:
|
||||
raise UserError(_('For the product %s, the warehouse of the operation type (%s) is inconsistent with the location (%s) of the reordering rule (%s). Change the operation type or cancel the request for quotation.',
|
||||
self.product_id.display_name, self.order_id.picking_type_id.display_name, self.orderpoint_id.location_id.display_name, self.orderpoint_id.display_name))
|
||||
|
||||
|
||||
@@ -511,11 +511,13 @@ class SaleOrder(models.Model):
|
||||
return result
|
||||
|
||||
def _compute_field_value(self, field):
|
||||
if field.name == 'invoice_status':
|
||||
filtered_self = self.filtered(lambda so: so.user_id and so._origin.invoice_status != 'upselling')
|
||||
super()._compute_field_value(field)
|
||||
if field.name != 'invoice_status' or self.env.context.get('mail_activity_automation_skip'):
|
||||
return
|
||||
|
||||
filtered_self = self.filtered(lambda so: so.user_id and so.invoice_status == 'upselling')
|
||||
filtered_self = filtered_self.filtered(lambda so: so.invoice_status == 'upselling')
|
||||
if not filtered_self:
|
||||
return
|
||||
|
||||
|
||||
@@ -11,14 +11,15 @@ class SaleOrderLine(models.Model):
|
||||
def _compute_purchase_price(self):
|
||||
lines_without_moves = self.browse()
|
||||
for line in self:
|
||||
product = line.product_id.with_company(line.company_id)
|
||||
if not line.move_ids:
|
||||
lines_without_moves |= line
|
||||
elif line.product_id.categ_id.property_cost_method != 'standard':
|
||||
purch_price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids)
|
||||
if line.product_uom and line.product_uom != line.product_id.uom_id:
|
||||
purch_price = line.product_id.uom_id._compute_price(purch_price, line.product_uom)
|
||||
elif product.categ_id.property_cost_method != 'standard':
|
||||
purch_price = product._compute_average_price(0, line.product_uom_qty, line.move_ids)
|
||||
if line.product_uom and line.product_uom != product.uom_id:
|
||||
purch_price = product.uom_id._compute_price(purch_price, line.product_uom)
|
||||
to_cur = line.currency_id or line.order_id.currency_id
|
||||
line.purchase_price = line.product_id.cost_currency_id._convert(
|
||||
line.purchase_price = product.cost_currency_id._convert(
|
||||
from_amount=purch_price,
|
||||
to_currency=to_cur,
|
||||
company=line.company_id or self.env.company,
|
||||
|
||||
@@ -200,3 +200,68 @@ class TestSaleStockMargin(TestStockValuationCommon):
|
||||
so.action_confirm()
|
||||
self.assertEqual(so_line.purchase_price, 200)
|
||||
self.assertEqual(so_line.price_unit, 400)
|
||||
|
||||
def test_so_and_multicompany(self):
|
||||
""" In a multicompany environnement, when the user is on company C01 and confirms a SO that
|
||||
belongs to a second company C02, this test ensures that the computations will be based on
|
||||
C02's data"""
|
||||
main_company = self.env['res.company']._get_main_company()
|
||||
main_company_currency = main_company.currency_id
|
||||
new_company_currency = self.env.ref('base.EUR') if main_company_currency == self.env.ref('base.USD') else self.env.ref('base.USD')
|
||||
|
||||
date = fields.Date.today()
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create([
|
||||
{'currency_id': main_company_currency.id, 'rate': 1, 'name': date, 'company_id': False},
|
||||
{'currency_id': new_company_currency.id, 'rate': 3, 'name': date, 'company_id': False},
|
||||
])
|
||||
|
||||
new_company = self.env['res.company'].create({
|
||||
'name': 'Super Company',
|
||||
'currency_id': new_company_currency.id,
|
||||
})
|
||||
self.env.user.company_id = new_company.id
|
||||
|
||||
self.pricelist.currency_id = new_company_currency.id
|
||||
|
||||
product = self._create_product()
|
||||
|
||||
incoming_picking_type = self.env['stock.picking.type'].search([('company_id', '=', new_company.id), ('code', '=', 'incoming')])
|
||||
production_location = self.env['stock.location'].search([('company_id', '=', new_company.id), ('usage', '=', 'production')])
|
||||
|
||||
picking = self.env['stock.picking'].create({
|
||||
'picking_type_id': incoming_picking_type.id,
|
||||
'location_id': production_location.id,
|
||||
'location_dest_id': incoming_picking_type.default_location_dest_id.id,
|
||||
})
|
||||
self.env['stock.move'].create({
|
||||
'name': 'Incoming Product',
|
||||
'product_id': product.id,
|
||||
'location_id': production_location.id,
|
||||
'location_dest_id': incoming_picking_type.default_location_dest_id.id,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 100,
|
||||
'picking_type_id': incoming_picking_type.id,
|
||||
'picking_id': picking.id,
|
||||
})
|
||||
picking.action_confirm()
|
||||
res_dict = picking.button_validate()
|
||||
wizard = Form(self.env[(res_dict.get('res_model'))].with_context(res_dict['context'])).save()
|
||||
wizard.process()
|
||||
|
||||
self.pricelist.currency_id = new_company_currency.id
|
||||
partner = self.env['res.partner'].create({'name': 'Super Partner'})
|
||||
so = self.env['sale.order'].create({
|
||||
'name': 'Sale order',
|
||||
'partner_id': partner.id,
|
||||
'partner_invoice_id': partner.id,
|
||||
'pricelist_id': self.pricelist.id,
|
||||
})
|
||||
sol = self._create_sale_order_line(so, product, 1, price_unit=200)
|
||||
|
||||
self.env.user.company_id = main_company.id
|
||||
so.action_confirm()
|
||||
|
||||
self.assertEqual(sol.purchase_price, 100)
|
||||
self.assertEqual(sol.margin, 100)
|
||||
@@ -671,3 +671,70 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
||||
self.assertEqual(self.partner_b, task_so1_timesheet2.partner_id, "The Task's second Timesheet entry should have its partner changed, as it was not invoiced and the Task's partner/customer changed.")
|
||||
self.assertEqual(so1_product_global_project_so_line, task_so1_timesheet1.so_line, "The Task's first Timesheet entry should not have changed as it was already invoiced (its so_line should still be equal to the first Sales Order line).")
|
||||
self.assertEqual(so2_product_global_project_so_line, task_so1_timesheet2.so_line, "The Task's second Timesheet entry should have it's so_line changed, as the Sales Order Item of the Task changed, and this entry was not invoiced.")
|
||||
|
||||
def test_timesheet_upsell(self):
|
||||
""" Test timesheet upselling and email """
|
||||
|
||||
sale_order = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'user_id': self.user_employee_company_B.id,
|
||||
})
|
||||
# create SO and confirm it
|
||||
uom_days = self.env.ref('uom.product_uom_day')
|
||||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'order_id': sale_order.id,
|
||||
'name': self.product_order_timesheet3.name,
|
||||
'product_id': self.product_order_timesheet3.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': uom_days.id,
|
||||
'price_unit': self.product_order_timesheet3.list_price,
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
task = sale_order_line.task_id
|
||||
|
||||
# let's log some timesheets
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Test Line',
|
||||
'project_id': task.project_id.id,
|
||||
'task_id': task.id,
|
||||
'unit_amount': 8,
|
||||
'employee_id': self.employee_manager.id,
|
||||
})
|
||||
|
||||
sale_order._create_invoices()
|
||||
last_message_id = self.env['mail.message'].search([('model', '=', 'sale.order'), ('res_id', '=', sale_order.id)], limit=1).id or 0
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Test Line',
|
||||
'project_id': task.project_id.id,
|
||||
'task_id': task.id,
|
||||
'unit_amount': 5,
|
||||
'employee_id': self.employee_user.id,
|
||||
})
|
||||
|
||||
self.assertEqual(sale_order.invoice_status, 'upselling', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
|
||||
self.assertEqual(len(message_sent), 1, 'Sale Timesheet: An email should always be sent to the saleperson when the state of the sale order change to upselling')
|
||||
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Test Line',
|
||||
'project_id': task.project_id.id,
|
||||
'task_id': task.id,
|
||||
'unit_amount': 5,
|
||||
'employee_id': self.employee_user.id,
|
||||
})
|
||||
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
self.assertEqual(len(message_sent), 1, 'Sale Timesheet: An email should only be sent to the saleperson when the state of the sale order change to upselling')
|
||||
@@ -59,9 +59,9 @@
|
||||
<field name="product_uom_name" string="UoM" groups="uom.group_uom"/>
|
||||
<field name="company_id" optional="hide" readonly="1" groups="base.group_multi_company"/>
|
||||
<button name="action_replenish" string="Order Once" type="object" class="o_replenish_buttons" icon="fa-truck"
|
||||
attrs="{'invisible': [('qty_to_order', '<', 1.0)]}"/>
|
||||
attrs="{'invisible': [('qty_to_order', '<=', 0.0)]}"/>
|
||||
<button name="action_replenish_auto" string="Automate Orders" type="object" class="o_replenish_buttons" icon="fa-refresh"
|
||||
attrs="{'invisible': ['|', ('qty_to_order', '<', 1.0), ('trigger', '=', 'auto')]}"/>
|
||||
attrs="{'invisible': ['|', ('qty_to_order', '<=', 0.0), ('trigger', '=', 'auto')]}"/>
|
||||
<button name="%(action_orderpoint_snooze)d" string="Snooze" type="action" class="text-warning" icon="fa-bell-slash"
|
||||
attrs="{'invisible': [('trigger', '!=', 'manual')]}" context="{'default_orderpoint_ids': [id]}"/>
|
||||
</tree>
|
||||
|
||||
@@ -3702,8 +3702,9 @@ See https://github.com/flectra/owl/blob/master/doc/reference/config.md#mode for
|
||||
component.__patch(target, fiber.vnode);
|
||||
}
|
||||
else {
|
||||
if (fiber.shouldPatch) {
|
||||
component.__patch(component.__owl__.vnode, fiber.vnode);
|
||||
const vnode = component.__owl__.vnode;
|
||||
if (fiber.shouldPatch && vnode) {
|
||||
component.__patch(vnode, fiber.vnode);
|
||||
// When updating a Component's props (in directive),
|
||||
// the component has a pvnode AND should be patched.
|
||||
// However, its pvnode.elm may have changed if it is a High Order Component
|
||||
@@ -5545,9 +5546,9 @@ See https://github.com/flectra/owl/blob/master/doc/reference/config.md#mode for
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
|
||||
__info__.version = '1.4.7';
|
||||
__info__.date = '2021-10-19T14:49:16.603Z';
|
||||
__info__.hash = '4e3b7c7';
|
||||
__info__.version = '1.4.8';
|
||||
__info__.date = '2021-11-03T12:45:36.455Z';
|
||||
__info__.hash = '307b936';
|
||||
__info__.url = 'https://github.com/flectra/owl';
|
||||
|
||||
|
||||
|
||||
@@ -1377,6 +1377,60 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('readonly attrs on lines are re-evaluated on field change 2', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data.partner.records[0].product_ids = [37];
|
||||
this.data.partner.records[0].trululu = false;
|
||||
this.data.partner.onchanges = {
|
||||
trululu(record) {
|
||||
// when trululu changes, push another record in product_ids.
|
||||
// only push a second record once.
|
||||
if (record.product_ids.map(command => command[1]).includes(41)) {
|
||||
return;
|
||||
}
|
||||
// copy the list to force it as different from the original
|
||||
record.product_ids = record.product_ids.slice();
|
||||
record.product_ids.push([4,41,false]);
|
||||
}
|
||||
};
|
||||
|
||||
this.data.product.records[0].name = 'test';
|
||||
// This one is necessary to have a valid, rendered widget
|
||||
this.data.product.fields.int_field = { type:"integer", string: "intField" };
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="trululu"/>
|
||||
<field name="product_ids" attrs="{'readonly': [['trululu', '=', False]]}">
|
||||
<tree editable="top"><field name="int_field" widget="handle" /><field name="name"/></tree>
|
||||
</field>
|
||||
</form>
|
||||
`,
|
||||
res_id: 1,
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
});
|
||||
|
||||
for (let value of [true, false, true, false]) {
|
||||
if (value) {
|
||||
await testUtils.fields.many2one.clickOpenDropdown('trululu')
|
||||
await testUtils.fields.many2one.clickHighlightedItem('trululu')
|
||||
assert.notOk($('.o_field_one2many[name="product_ids"]').hasClass("o_readonly_modifier"), 'lines should not be readonly')
|
||||
} else {
|
||||
await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="trululu"] input'), '', ['keyup'])
|
||||
assert.ok($('.o_field_one2many[name="product_ids"]').hasClass("o_readonly_modifier"), 'lines should be readonly')
|
||||
}
|
||||
}
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('empty fields have o_form_empty class in readonly mode', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
|
||||
@@ -23,3 +23,7 @@ class Website(models.Model):
|
||||
self.env['ir.default'].get('sale.order', 'warehouse_id') or
|
||||
self.env['stock.warehouse'].sudo().search([('company_id', '=', self.company_id.id)], limit=1).id
|
||||
)
|
||||
|
||||
def sale_get_order(self, force_create=False, code=None, update_pricelist=False, force_pricelist=False):
|
||||
so = super().sale_get_order(force_create=force_create, code=code, update_pricelist=update_pricelist, force_pricelist=force_pricelist)
|
||||
return so.with_context(warehouse=so.warehouse_id.id) if so else so
|
||||
@@ -97,6 +97,9 @@ class TestWebsiteSaleStockProductWarehouse(TestSaleProductAttributeValueCommon):
|
||||
})
|
||||
|
||||
with MockRequest(self.env, website=self.website, sale_order_id=so.id):
|
||||
website_so = self.website.sale_get_order()
|
||||
self.assertEqual(website_so.order_line.product_id.virtual_available, 10, "This quantity should be based on SO's warehouse")
|
||||
|
||||
values = so._cart_update(product_id=self.product_A.id, line_id=so.order_line.id, set_qty=20)
|
||||
self.assertTrue(values.get('warning', False))
|
||||
self.assertEqual(values.get('quantity'), 10)
|
||||
Reference in new issue
Block a user