[MERGE] forward port branch saas-16 up to 88395bdb8e

This commit is contained in:
Christophe Simonis
2017-09-21 17:18:49 +02:00
29 changed files with 280 additions and 50 deletions
+8
View File
@@ -904,6 +904,14 @@ class AccountTax(models.Model):
return incl_tax.compute_all(price)['total_excluded']
return price
@api.model
def _fix_tax_included_price_company(self, price, prod_taxes, line_taxes, company_id):
if company_id:
#To keep the same behavior as in _compute_tax_id
prod_taxes = prod_taxes.filtered(lambda tax: tax.company_id == company_id)
line_taxes = line_taxes.filtered(lambda tax: tax.company_id == company_id)
return self._fix_tax_included_price(price, prod_taxes, line_taxes)
class AccountReconcileModel(models.Model):
_name = "account.reconcile.model"
_description = "Preset to create journal entries during a invoices and payments matching"
+7 -2
View File
@@ -600,6 +600,7 @@ class AccountInvoice(models.Model):
self.account_id = account_id
self.payment_term_id = payment_term_id
self.date_due = False
self.fiscal_position_id = fiscal_position
if type in ('in_invoice', 'out_refund'):
@@ -1228,7 +1229,7 @@ class AccountInvoice(models.Model):
if self.origin:
communication = '%s (%s)' % (communication, self.origin)
payment = self.env['account.payment'].create({
payment_vals = {
'invoice_ids': [(6, 0, self.ids)],
'amount': pay_amount or self.residual,
'payment_date': date or fields.Date.context_today(self),
@@ -1240,7 +1241,11 @@ class AccountInvoice(models.Model):
'payment_method_id': payment_method.id,
'payment_difference_handling': writeoff_acc and 'reconcile' or 'open',
'writeoff_account_id': writeoff_acc and writeoff_acc.id or False,
})
}
if self.env.context.get('tx_currency_id'):
payment_vals['currency_id'] = self.env.context.get('tx_currency_id')
payment = self.env['account.payment'].create(payment_vals)
payment.post()
return True
@@ -235,6 +235,7 @@ var StatementModel = BasicModel.extend({
return $.when(partner && this._changePartner(handle, partner.id))
.then(function() {
line.reconciliation_proposition = [];
self._computeLine(line);
return self.changeMode(handle, 'match');
})
.then(function () {
@@ -607,6 +607,10 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
* @param {keyup event} event
*/
_onInputKeyup: function (event) {
var target_partner_id = $(event.target).parents('[name="partner_id"]');
if (target_partner_id.length === 1) {
return;
}
if(event.keyCode === 13) {
if (_.findWhere(this.model.lines, {mode: 'create'}).balance.amount) {
this._onCreateProposition();
@@ -741,7 +741,7 @@ QUnit.module('account', {
});
QUnit.test('Reconciliation change partner', function (assert) {
assert.expect(13);
assert.expect(17);
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
@@ -759,6 +759,20 @@ QUnit.module('account', {
var widget = clientAction.widgets[0];
assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait");
assert.strictEqual(widget.$('.match table tr').length, 2, "agrolait should have 2 propositions for reconciliation");
// Adding the two propositions
// This is in order to try that after changing partner the propositions are emptied
widget.$('.match .cell_account_code:first').trigger('click');
widget.$('.match .cell_account_code:first').trigger('click');
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "Both proposition should be selected");
// Similate changing partner to one that does not have propositions to see if create mode is open after
widget.$('.o_input_dropdown input').trigger('click');
$('.ui-autocomplete .ui-menu-item a:contains(partner 1)').trigger('mouseenter').trigger('click');
clientAction._onAction({target: widget, name: 'change_partner', data: {data: {display_name: 'partner 1', id: 1}}, stopped: false});
assert.strictEqual(widget.$('.o_input_dropdown input').val(), "partner 1", "the partner many2one should display partner 1");
assert.strictEqual(widget.$('.match table tr.mv_line').length, 0, "partner 1 should have 0 propositions for reconciliation");
assert.strictEqual(widget.$el.data('mode'), 'create', "widget should be in create mode");
// Simulate changing partner
widget.$('.o_input_dropdown input').trigger('click');
@@ -664,7 +664,7 @@
<field name="view_mode">tree,kanban,form,calendar,pivot,graph</field>
<field eval="False" name="view_id"/>
<field name="domain">[('type','=','out_refund')]</field>
<field name="context">{'default_type': 'out_refund', 'journal_type': 'sale'}</field>
<field name="context">{'default_type': 'out_refund', 'type': 'out_refund', 'journal_type': 'sale'}</field>
<field name="search_view_id" ref="view_account_invoice_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@@ -738,7 +738,7 @@
<field name="view_mode">tree,kanban,form,calendar,pivot,graph</field>
<field eval="False" name="view_id"/>
<field name="domain">[('type','=','in_refund')]</field>
<field name="context">{'default_type': 'in_refund', 'journal_type': 'purchase'}</field>
<field name="context">{'default_type': 'in_refund', 'type': 'in_refund', 'journal_type': 'purchase'}</field>
<field name="search_view_id" ref="view_account_invoice_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
+1 -1
View File
@@ -49,7 +49,7 @@
<td><span t-esc="aml.move_id.name &lt;&gt; '/' and aml.move_id.name or ('*'+str(aml.move_id.id))"/></td>
<td><span t-field="aml.date"/></td>
<td><span t-field="aml.account_id.code"/></td>
<td><span t-esc="aml.sudo().partner_id and aml.sudo().partner_id.name[:23] or ''"/></td>
<td><span t-esc="aml.sudo().partner_id and aml.sudo().partner_id.name and aml.sudo().partner_id.name[:23] or ''"/></td>
<td><span t-esc="aml.name[:35]"/></td>
<td><span t-esc="aml.debit" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td>
<td><span t-esc="aml.credit" t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"/></td>
+15 -4
View File
@@ -108,6 +108,7 @@ class ImBus(models.Model):
class ImDispatch(object):
def __init__(self):
self.channels = {}
self.started = False
def poll(self, dbname, channels, last, options=None, timeout=TIMEOUT):
if options is None:
@@ -127,8 +128,17 @@ class ImDispatch(object):
with registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
notifications = env['bus.bus'].poll(channels, last, options)
# immediatly returns in peek mode
if options.get('peek'):
return dict(notifications=notifications, channels=channels)
# or wait for future ones
if not notifications:
if not self.started:
# Lazy start of events listener
self.start()
event = self.Event()
for channel in channels:
self.channels.setdefault(hashable(channel), []).append(event)
@@ -178,15 +188,16 @@ class ImDispatch(object):
import gevent
self.Event = gevent.event.Event
gevent.spawn(self.run)
elif odoo.multi_process:
# disabled in prefork mode
return
else:
# threaded mode
self.Event = threading.Event
t = threading.Thread(name="%s.Bus" % __name__, target=self.run)
t.daemon = True
t.start()
self.started = True
return self
dispatch = ImDispatch().start()
dispatch = None
if not odoo.multi_process or odoo.evented:
# We only use the event dispatcher in threaded and gevent mode
dispatch = ImDispatch()
+1 -1
View File
@@ -58,7 +58,7 @@ class StockWarehouse(models.Model):
wh_stock_loc = warehouse.lot_stock_id
seq = seq_obj.search([('code', '=', 'mrp.production')], limit=1)
other_pick_type = picking_type_obj.search([('warehouse_id', '=', warehouse.id)], order = 'sequence desc', limit=1)
color = other_pick_type and other_pick_type.color or 1
color = other_pick_type.color if other_pick_type else 1
max_sequence = other_pick_type and other_pick_type.sequence or 0
manu_type = picking_type_obj.create({
'name': _('Manufacturing'),
+1 -1
View File
@@ -916,7 +916,7 @@ class PosOrderLine(models.Model):
self.tax_ids = self.product_id.taxes_id.filtered(lambda r: not self.company_id or r.company_id == self.company_id)
fpos = self.order_id.fiscal_position_id
tax_ids_after_fiscal_position = fpos.map_tax(self.tax_ids, self.product_id, self.order_id.partner_id) if fpos else self.tax_ids
self.price_unit = self.env['account.tax']._fix_tax_included_price(price, self.product_id.taxes_id, tax_ids_after_fiscal_position)
self.price_unit = self.env['account.tax']._fix_tax_included_price_company(price, self.product_id.taxes_id, tax_ids_after_fiscal_position, self.company_id)
@api.onchange('qty', 'discount', 'price_unit', 'tax_ids')
def _onchange_qty(self):
+4 -4
View File
@@ -856,7 +856,7 @@ class PurchaseOrderLine(models.Model):
if not seller:
return
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, self.taxes_id) if seller else 0.0
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, self.product_id.supplier_taxes_id, self.taxes_id, self.company_id) if seller else 0.0
if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id:
price_unit = seller.currency_id.compute(price_unit, self.order_id.currency_id)
@@ -920,7 +920,7 @@ class ProcurementOrder(models.Model):
date=procurement.purchase_line_id.order_id.date_order and procurement.purchase_line_id.order_id.date_order[:10],
uom_id=procurement.purchase_line_id.product_uom)
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, procurement.purchase_line_id.product_id.supplier_taxes_id, procurement.purchase_line_id.taxes_id) if seller else 0.0
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, procurement.purchase_line_id.product_id.supplier_taxes_id, procurement.purchase_line_id.taxes_id, self.company_id) if seller else 0.0
if price_unit and seller and procurement.purchase_line_id.order_id.currency_id and seller.currency_id != procurement.purchase_line_id.order_id.currency_id:
price_unit = seller.currency_id.compute(price_unit, procurement.purchase_line_id.order_id.currency_id)
@@ -985,7 +985,7 @@ class ProcurementOrder(models.Model):
if taxes_id:
taxes_id = taxes_id.filtered(lambda x: x.company_id.id == self.company_id.id)
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, self.product_id.supplier_taxes_id, taxes_id) if seller else 0.0
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, self.product_id.supplier_taxes_id, taxes_id, self.company_id) if seller else 0.0
if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
price_unit = seller.currency_id.compute(price_unit, po.currency_id)
@@ -1110,7 +1110,7 @@ class ProcurementOrder(models.Model):
date=po.date_order and po.date_order[:10],
uom_id=procurement.product_id.uom_po_id)
price_unit = self.env['account.tax']._fix_tax_included_price(seller.price, line.product_id.supplier_taxes_id, line.taxes_id) if seller else 0.0
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, self.company_id) if seller else 0.0
if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
price_unit = seller.currency_id.compute(price_unit, po.currency_id)
+3 -3
View File
@@ -950,7 +950,7 @@ class SaleOrderLine(models.Model):
self._compute_tax_id()
if self.order_id.pricelist_id and self.order_id.partner_id:
vals['price_unit'] = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id)
vals['price_unit'] = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id)
self.update(vals)
return result
@@ -970,7 +970,7 @@ class SaleOrderLine(models.Model):
uom=self.product_uom.id,
fiscal_position=self.env.context.get('fiscal_position')
)
self.price_unit = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id)
self.price_unit = self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_id, self.company_id)
@api.multi
def unlink(self):
@@ -1046,7 +1046,7 @@ class SaleOrderLine(models.Model):
price, rule_id = self.order_id.pricelist_id.with_context(pricelist_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id)
new_list_price = self.env['account.tax']._fix_tax_included_price(new_list_price, self.product_id.taxes_id, self.tax_id)
new_list_price = self.env['account.tax']._fix_tax_included_price_company(new_list_price, self.product_id.taxes_id, self.tax_id, self.company_id)
if new_list_price != 0:
if self.product_id.company_id and self.order_id.pricelist_id.currency_id != self.product_id.company_id.currency_id:
+1
View File
@@ -212,6 +212,7 @@
<field name="product_id"
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
attrs="{'readonly': ['|', ('qty_invoiced', '&gt;', 0), ('procurement_ids', '!=', [])]}"
force_save="1"
/>
<field name="layout_category_id" groups="sale.group_sale_layout"/>
<field name="invoice_status" invisible="1"/>
+1 -1
View File
@@ -84,7 +84,7 @@ class PaymentTransaction(models.Model):
_logger.warning('<%s> transaction completed, could not auto-generate payment for %s (ID %s) (no journal set on acquirer)',
self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
self.acquirer_id.journal_id = default_journal
created_invoice.pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=created_invoice.amount_total)
created_invoice.with_context(tx_currency_id=self.currency_id.id).pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=created_invoice.amount_total)
if created_invoice.payment_ids:
created_invoice.payment_ids[0].payment_transaction_id = self
else:
+7
View File
@@ -289,6 +289,13 @@ class ProcurementOrder(models.Model):
1000 orderpoints.
This is appropriate for batch jobs only.
"""
if company_id and self.env.user.company_id.id != company_id:
# To ensure that the company_id is taken into account for
# all the processes triggered by this method
# i.e. If a PO is generated by the run of the procurements the
# sequence to use is the one for the specified company not the
# one of the user's company
self = self.with_context(company_id=company_id, force_company=company_id)
OrderPoint = self.env['stock.warehouse.orderpoint']
domain = self._get_orderpoint_domain(company_id=company_id)
+2 -2
View File
@@ -188,8 +188,8 @@ class Warehouse(models.Model):
# choose the next available color for the operation types of this warehouse
all_used_colors = [res['color'] for res in PickingType.search_read([('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')]
available_colors = [zef for zef in [0, 3, 4, 5, 6, 7, 8, 1, 2] if zef not in all_used_colors]
color = available_colors and available_colors[0] or 1
available_colors = [zef for zef in [1, 3, 4, 5, 6, 7, 8, 0, 2] if zef not in all_used_colors]
color = available_colors[0] if available_colors else 1
# suit for each warehouse: reception, internal, pick, pack, ship
max_sequence = PickingType.search_read([('sequence', '!=', False)], ['sequence'], limit=1, order='sequence desc')
@@ -292,12 +292,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
}).fail(view.loaded.reject.bind(view.loaded));
} else {
view.loaded = view.loaded.then(function() {
// By default, the view will be loaded in readonly mode
// Returns to this default mode if you load action from breadcrumb
view_options = _.extend({}, view_options, self.env);
if (view_type === 'form') {
view_options.mode = view_options.mode || 'readonly';
}
return view.controller.reload(view_options);
});
}
@@ -382,8 +377,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
});
},
select_view: function (index) {
var view_type = this.view_stack[index].type;
return this.switch_mode(view_type);
var viewType = this.view_stack[index].type;
var viewOptions = {};
if (viewType === 'form') {
// reload form views in readonly, except for inline actions (i.e.
// settings views) that stay in edit
viewOptions.mode = this.action.target === 'inline' ? 'edit' : 'readonly';
}
return this.switch_mode(viewType, viewOptions);
},
/**
* Renders the switch buttons for multi- and mono-record views and adds
@@ -768,7 +768,7 @@ var BasicModel = AbstractModel.extend({
removeLine: function (elementID) {
var record = this.localData[elementID];
var parent = this.localData[record.parentID];
if (parent.parentID) {
if (parent.static) {
// x2Many case: the new record has been stored in _changes, as a
// command so we remove the command(s) related to that record
parent._changes = _.filter(parent._changes, function (change) {
@@ -777,6 +777,7 @@ var BasicModel = AbstractModel.extend({
} else {
// main list view case: the new record is in data
parent.data = _.without(parent.data, elementID);
parent.count--;
}
},
/**
@@ -273,18 +273,18 @@ var BasicRenderer = AbstractRenderer.extend({
* from internal referencing.
*
* @private
* @param {Object} record
* @param {string} recordID id of the local resource
* @param {AbstractField} widget
* @returns {integer} the index of the removed widget
*/
_destroyFieldWidget: function (record, widget) {
var recordWidgets = this.allFieldWidgets[record.id];
_destroyFieldWidget: function (recordID, widget) {
var recordWidgets = this.allFieldWidgets[recordID];
var index = recordWidgets.indexOf(widget);
if (index >= 0) {
recordWidgets.splice(index, 1);
}
this._unregisterModifiersElement(widget.__node, record, widget);
widget.$el.destroy();
this._unregisterModifiersElement(widget.__node, recordID, widget);
widget.destroy();
return index;
},
/**
@@ -541,7 +541,7 @@ var BasicRenderer = AbstractRenderer.extend({
widget.$el.replaceWith(newWidget.$el);
// Destroy the old widget and position the new one at the old one's
var oldIndex = this._destroyFieldWidget(record, widget);
var oldIndex = this._destroyFieldWidget(record.id, widget);
var recordWidgets = this.allFieldWidgets[record.id];
recordWidgets.splice(oldIndex, 0, newWidget);
recordWidgets.pop();
@@ -553,13 +553,13 @@ var BasicRenderer = AbstractRenderer.extend({
* node and record.
*
* @param {Object} node
* @param {Object} record
* @param {string} recordID id of the local resource
* @param {jQuery|AbstractField} element
*/
_unregisterModifiersElement: function (node, record, element) {
_unregisterModifiersElement: function (node, recordID, element) {
var modifiersData = this._getModifiersData(node);
if (modifiersData) {
var elements = modifiersData.elementsByRecord[record.id];
var elements = modifiersData.elementsByRecord[recordID];
var index = _.findIndex(elements, function (oldElement) {
return oldElement.widget === element
|| oldElement.$el[0] === element[0];
@@ -12,6 +12,7 @@ var FormRenderer = BasicRenderer.extend({
className: "o_form_view",
events: _.extend({}, BasicRenderer.prototype.events, {
'click .o_notification_box .oe_field_translate': '_onTranslate',
'click .oe_title, .o_group': '_onClick',
}),
/**
* @override
@@ -835,6 +836,16 @@ var FormRenderer = BasicRenderer.extend({
// Handlers
//--------------------------------------------------------------------------
/**
* Makes the Edit button bounce in readonly
*
* @private
*/
_onClick: function () {
if (this.mode === 'readonly') {
this.trigger_up('bounce_edit');
}
},
/**
* @override
* @private
@@ -182,6 +182,7 @@ var ListController = BasicController.extend({
if ((recordID || this.handle) !== this.handle) {
var state = this.model.get(this.handle);
this.renderer.removeLine(state, recordID);
this._updatePager();
}
},
/**
@@ -202,6 +203,7 @@ var ListController = BasicController.extend({
var state = self.model.get(self.handle);
self.renderer.updateState(state, {});
self.renderer.editRecord(recordID);
self._updatePager();
}).always(this._enableButtons.bind(this));
},
/**
@@ -133,6 +133,7 @@ ListRenderer.include({
* @param {string} recordID
*/
removeLine: function (state, recordID) {
var self = this;
var rowIndex = _.findIndex(this.state.data, {id: recordID});
this.state = state;
if (rowIndex === -1) {
@@ -141,8 +142,20 @@ ListRenderer.include({
if (rowIndex === this.currentRow) {
this.currentRow = null;
}
// remove the row
var $row = this.$('.o_data_row:nth(' + rowIndex + ')');
$row.remove();
if (this.state.count >= 4) {
$row.remove();
} else {
$row.replaceWith(this._renderEmptyRow());
}
// destroy the removed row's widgets
var widgetsToDestroy = this.allFieldWidgets[recordID].slice();
_.each(widgetsToDestroy, function (widget) {
self._destroyFieldWidget(recordID, widget);
});
},
/**
* Updates the already rendered row associated to the given recordID so that
@@ -221,7 +234,7 @@ ListRenderer.include({
// destroyed. This is not the case for simple buttons so we have to
// do it here.
if ($td.hasClass('o_list_button')) {
self._unregisterModifiersElement(node, record, $td.children());
self._unregisterModifiersElement(node, recordID, $td.children());
}
// For edit mode we only replace the content of the cell with its
@@ -231,14 +244,14 @@ ListRenderer.include({
if (editMode) {
$td.empty().append($newTd.contents());
} else {
self._unregisterModifiersElement(node, record, $td);
self._unregisterModifiersElement(node, recordID, $td);
$td.replaceWith($newTd);
}
});
delete this.defs;
// Destroy old field widgets
_.each(oldWidgets, this._destroyFieldWidget.bind(this, record));
_.each(oldWidgets, this._destroyFieldWidget.bind(this, recordID));
// Toggle selected class here so that style is applied at the end
$row.toggleClass('o_selected_row', editMode);
@@ -666,6 +679,17 @@ ListRenderer.include({
this._super.apply(this, arguments);
}
},
/**
* Overrides to prevent from sorting if we are currently editing a record.
*
* @override
* @private
*/
_onSortColumn: function () {
if (this.currentRow === null) {
this._super.apply(this, arguments);
}
},
/**
* Triggers a delete event. I don't know why we stop the propagation of the
* event.
@@ -74,9 +74,16 @@ var DateWidget = Widget.extend({
* set datetime value
*/
changeDatetime: function () {
if(this.isValid()) {
if (this.isValid()) {
var oldValue = this.getValue();
this._setValueFromUi();
this.trigger("datetime_changed");
var newValue = this.getValue();
if (!oldValue !== !newValue || oldValue && newValue && !oldValue.isSame(newValue)) {
// The condition is strangely written; this is because the
// values can be false/undefined
this.trigger("datetime_changed");
}
}
},
/**
@@ -5913,5 +5913,36 @@ QUnit.module('Views', {
form.destroy();
});
QUnit.test('bounce edit button in readonly mode', function (assert) {
assert.expect(3);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form>' +
'<div class="oe_title">' +
'<field name="display_name"/>' +
'</div>' +
'</form>',
res_id: 1,
intercepts: {
bounce_edit: function() {
assert.step('bounce');
},
},
});
// in readonly
form.$('[name="display_name"]').click();
assert.verifySteps(['bounce']);
// in edit
form.$buttons.find('.o_form_button_edit').click();
form.$('[name="display_name"]').click();
assert.verifySteps(['bounce']);
form.destroy();
});
});
});
@@ -2,6 +2,7 @@ odoo.define('web.list_tests', function (require) {
"use strict";
var config = require('web.config');
var basicFields = require('web.basic_fields');
var FormView = require('web.FormView');
var ListView = require('web.ListView');
var testUtils = require('web.test_utils');
@@ -388,6 +389,54 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('editable list: add a line and discard', function (assert) {
assert.expect(11);
var oldDestroy = basicFields.FieldChar.prototype.destroy;
basicFields.FieldChar.prototype.destroy = function () {
assert.step('destroy');
oldDestroy.apply(this, arguments);
};
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree editable="bottom"><field name="foo"/><field name="bar"/></tree>',
domain: [['foo', '=', 'yop']],
});
assert.strictEqual(list.$('tbody tr').length, 4,
"list should contain 4 rows");
assert.strictEqual(list.$('.o_data_row').length, 1,
"list should contain one record (and thus 3 empty rows)");
assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1',
"pager should be correct");
list.$buttons.find('.o_list_button_add').click();
assert.strictEqual(list.$('tbody tr').length, 4,
"list should still contain 4 rows");
assert.strictEqual(list.$('.o_data_row').length, 2,
"list should contain two record (and thus 2 empty rows)");
assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-2',
"pager should be correct");
list.$buttons.find('.o_list_button_discard').click();
assert.strictEqual(list.$('tbody tr').length, 4,
"list should still contain 4 rows");
assert.strictEqual(list.$('.o_data_row').length, 1,
"list should contain one record (and thus 3 empty rows)");
assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1',
"pager should be correct");
assert.verifySteps(['destroy'],
"should have destroyed the widget of the removed line");
basicFields.FieldChar.prototype.destroy = oldDestroy;
list.destroy();
});
QUnit.test('field changes are triggered correctly', function (assert) {
assert.expect(2);
@@ -436,6 +485,39 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('editable list view: save data when list sorting in edit mode', function (assert) {
assert.expect(3);
this.data.foo.fields.foo.sortable = true;
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree editable="bottom"><field name="foo"/></tree>',
mockRPC: function (route, args) {
if (args.method === 'write') {
assert.deepEqual(args.args, [[1], {foo: 'xyz'}],
"should correctly save the edited record");
}
return this._super.apply(this, arguments);
}
});
list.$('.o_data_cell:first').click();
list.$('input[name="foo"]').val('xyz').trigger('input');
list.$('.o_column_sortable').click();
assert.ok(list.$('.o_data_row:first').hasClass('o_selected_row'),
"first row should still be in edition");
list.$buttons.find('.o_list_button_save').click();
assert.ok(!list.$buttons.hasClass('o-editing'),
"list buttons should be back to their readonly mode");
list.destroy();
});
QUnit.test('selection changes are triggered correctly', function (assert) {
assert.expect(8);
@@ -861,6 +943,7 @@ QUnit.module('Views', {
assert.ok(form.$('tbody tr:eq(2) td input').val(),
"Value 2 should be third (shouldn't be sorted)");
form.$('.o_form_sheet_bg').click(); // validate the row before sorting
$o2m.find('.o_column_sortable').click();
assert.ok(form.$('tbody tr:first td:contains(Value 3)').length,
"Value 3 should be first");
@@ -151,7 +151,7 @@ QUnit.module('DomainSelector', {
});
QUnit.test("building a domain with a datetime", function (assert) {
assert.expect(1);
assert.expect(2);
var $target = $("#qunit-fixture");
@@ -163,8 +163,16 @@ QUnit.module('DomainSelector', {
domainSelector.appendTo($target);
// Check that there is a datepicker to choose the date
assert.strictEqual(domainSelector.$(".o_datepicker:visible").length, 1,
var $datepicker = domainSelector.$(".o_datepicker:visible");
assert.strictEqual($datepicker.length, 1,
"there should be a datepicker");
var val = $datepicker.find('input').focus().click().val();
$('.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]').click();
assert.notEqual(domainSelector.$(".o_datepicker:visible input").val(), val,
"datepicker value should have changed");
domainSelector.destroy();
});
});
});
+4 -3
View File
@@ -68,7 +68,7 @@ class SaleOrder(models.Model):
if order.pricelist_id and order.partner_id:
order_line = order._cart_find_product_line(product.id)
if order_line:
pu = self.env['account.tax']._fix_tax_included_price(pu, product.taxes_id, order_line[0].tax_id)
pu = self.env['account.tax']._fix_tax_included_price_company(pu, product.taxes_id, order_line[0].tax_id, self.company_id)
return {
'product_id': product_id,
@@ -156,10 +156,11 @@ class SaleOrder(models.Model):
'pricelist': order.pricelist_id.id,
})
product = self.env['product.product'].with_context(product_context).browse(product_id)
values['price_unit'] = self.env['account.tax']._fix_tax_included_price(
values['price_unit'] = self.env['account.tax']._fix_tax_included_price_company(
order_line._get_display_price(product),
order_line.product_id.taxes_id,
order_line.tax_id
order_line.tax_id,
self.company_id
)
order_line.write(values)
+5
View File
@@ -48,6 +48,11 @@ related to testing Odoo content (modules, mainly):
.. autoclass:: odoo.tests.common.SingleTransactionCase
:members: browse_ref, ref
.. autoclass:: odoo.tests.common.SavepointCase
.. autoclass:: odoo.tests.common.HttpCase
:members: browse_ref, ref, url_open, phantom_js
By default, tests are run once right after the corresponding module has been
installed. Test cases can also be configured to run after all modules have
been installed, and not run right after the module installation:
@@ -283,6 +283,11 @@
<field name="state_id"/>
<field name="image_small"/>
<field name="lang"/>
<!-- fields in form x2many view to diminish requests -->
<field name="comment"/>
<field name="customer"/>
<field name="display_name"/>
<field name="supplier"/>
<templates>
<t t-name="kanban-box">
<t t-set="color" t-value="kanban_color(record.color.raw_value)"/>