[MERGE] forward port branch saas-16 up to 7bfde6e05d
This commit is contained in:
@@ -1016,7 +1016,7 @@ class AccountTax(models.Model):
|
||||
if tax.include_base_amount:
|
||||
base = recompute_base(base, incl_fixed_amount, incl_percent_amount)
|
||||
incl_fixed_amount = incl_percent_amount = 0
|
||||
if tax.price_include:
|
||||
if self._context.get('force_price_include', tax.price_include):
|
||||
if tax.amount_type == 'fixed':
|
||||
incl_fixed_amount += tax.amount
|
||||
elif tax.amount_type == 'percent':
|
||||
@@ -1038,7 +1038,10 @@ class AccountTax(models.Model):
|
||||
# took into account on the base amount except for 'division' tax:
|
||||
# (tax.amount_type == 'percent' && not tax.price_include)
|
||||
# == (tax.amount_type == 'division' && tax.price_include)
|
||||
tax_amount = tax.with_context(force_price_include=False)._compute_amount(base, price_unit, quantity, product, partner)
|
||||
# N.B: don't use the with_context if force_price_include already False in context
|
||||
if 'force_price_include' not in self._context or self._context['force_price_include']:
|
||||
tax = tax.with_context(force_price_include=False)
|
||||
tax_amount = tax._compute_amount(base, price_unit, quantity, product, partner)
|
||||
if not round_tax:
|
||||
tax_amount = round(tax_amount, prec)
|
||||
else:
|
||||
|
||||
@@ -812,6 +812,7 @@ class AccountBankStatementLine(models.Model):
|
||||
whose value is the same as described in process_reconciliation except that ids are used instead of recordsets.
|
||||
"""
|
||||
AccountMoveLine = self.env['account.move.line']
|
||||
ctx = dict(self._context, force_price_include=False)
|
||||
for st_line, datum in pycompat.izip(self, data):
|
||||
payment_aml_rec = AccountMoveLine.browse(datum.get('payment_aml_ids', []))
|
||||
for aml_dict in datum.get('counterpart_aml_dicts', []):
|
||||
@@ -819,7 +820,7 @@ class AccountBankStatementLine(models.Model):
|
||||
del aml_dict['counterpart_aml_id']
|
||||
if datum.get('partner_id') is not None:
|
||||
st_line.write({'partner_id': datum['partner_id']})
|
||||
st_line.process_reconciliation(datum.get('counterpart_aml_dicts', []), payment_aml_rec, datum.get('new_aml_dicts', []))
|
||||
st_line.with_context(ctx).process_reconciliation(datum.get('counterpart_aml_dicts', []), payment_aml_rec, datum.get('new_aml_dicts', []))
|
||||
|
||||
def fast_counterpart_creation(self):
|
||||
for st_line in self:
|
||||
|
||||
@@ -50,8 +50,9 @@ class AccountInvoice(models.Model):
|
||||
@api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'tax_line_ids.amount_rounding',
|
||||
'currency_id', 'company_id', 'date_invoice', 'type')
|
||||
def _compute_amount(self):
|
||||
round_curr = self.currency_id.round
|
||||
self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line_ids)
|
||||
self.amount_tax = sum(line.amount_total for line in self.tax_line_ids)
|
||||
self.amount_tax = sum(round_curr(line.amount_total) for line in self.tax_line_ids)
|
||||
self.amount_total = self.amount_untaxed + self.amount_tax
|
||||
amount_total_company_signed = self.amount_total
|
||||
amount_untaxed_signed = self.amount_untaxed
|
||||
|
||||
@@ -1238,7 +1238,7 @@ class AccountMoveLine(models.Model):
|
||||
taxes = self.env['account.tax'].browse(tax_ids)
|
||||
currency = self.env['res.currency'].browse(vals.get('currency_id'))
|
||||
partner = self.env['res.partner'].browse(vals.get('partner_id'))
|
||||
res = taxes.with_context(round=True).compute_all(amount,
|
||||
res = taxes.with_context(dict(self._context, round=True)).compute_all(amount,
|
||||
currency, 1, vals.get('product_id'), partner)
|
||||
# Adjust line amount if any tax is price_include
|
||||
if abs(res['total_excluded']) < abs(amount):
|
||||
|
||||
@@ -184,7 +184,8 @@ class AccountVoucher(models.Model):
|
||||
'amount_currency': (sign * abs(self.amount) # amount < 0 for refunds
|
||||
if company_currency != current_currency else 0.0),
|
||||
'date': self.account_date,
|
||||
'date_maturity': self.date_due
|
||||
'date_maturity': self.date_due,
|
||||
'payment_id': self._context.get('payment_id'),
|
||||
}
|
||||
return move_line
|
||||
|
||||
@@ -223,6 +224,24 @@ class AccountVoucher(models.Model):
|
||||
for voucher in self:
|
||||
return voucher.currency_id.compute(amount, voucher.company_id.currency_id)
|
||||
|
||||
@api.multi
|
||||
def voucher_pay_now_payment_create(self):
|
||||
payment_methods = self.journal_id.outbound_payment_method_ids
|
||||
return {
|
||||
'payment_type': 'outbound',
|
||||
'payment_method_id': payment_methods and payment_methods[0].id or False,
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner_id.id,
|
||||
'amount': self.amount,
|
||||
'currency_id': self.currency_id.id,
|
||||
'payment_date': self.date,
|
||||
'journal_id': self.journal_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
'communication': self.name,
|
||||
'name': self.name,
|
||||
'state': 'reconciled',
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def voucher_move_line_create(self, line_total, move_id, company_currency, current_currency):
|
||||
'''
|
||||
@@ -260,8 +279,8 @@ class AccountVoucher(models.Model):
|
||||
'tax_ids': [(4,t.id) for t in line.tax_ids],
|
||||
'amount_currency': line.price_subtotal if current_currency != company_currency else 0.0,
|
||||
'currency_id': company_currency != current_currency and current_currency or False,
|
||||
'payment_id': self._context.get('payment_id'),
|
||||
}
|
||||
|
||||
self.env['account.move.line'].with_context(apply_taxes=True).create(move_line)
|
||||
return line_total
|
||||
|
||||
@@ -281,6 +300,9 @@ class AccountVoucher(models.Model):
|
||||
ctx = local_context.copy()
|
||||
ctx['date'] = voucher.account_date
|
||||
ctx['check_move_validity'] = False
|
||||
# Create a payment to allow the reconciliation when pay_now = 'pay_now'.
|
||||
if self.pay_now == 'pay_now' and self.amount > 0:
|
||||
ctx['payment_id'] = self.env['account.payment'].create(self.voucher_pay_now_payment_create()).id
|
||||
# Create the account move record.
|
||||
move = self.env['account.move'].create(voucher.account_move_get())
|
||||
# Get the name of the account_move just created
|
||||
|
||||
@@ -282,7 +282,15 @@ class BaseAutomation(models.Model):
|
||||
|
||||
# retrieve all actions, and patch their corresponding model
|
||||
for action_rule in self.with_context({}).search([]):
|
||||
Model = self.env[action_rule.model_name]
|
||||
Model = self.env.get(action_rule.model_name)
|
||||
|
||||
# Do not crash if the model of the base_action_rule was uninstalled
|
||||
if Model is None:
|
||||
_logger.warning("Action rule with ID %d depends on model %s" %
|
||||
(action_rule.id,
|
||||
action_rule.model_name))
|
||||
continue
|
||||
|
||||
if action_rule.trigger == 'on_create':
|
||||
patch(Model, 'create', make_create())
|
||||
|
||||
|
||||
@@ -85,7 +85,6 @@ class TestWorkOrderProcess(common.TransactionCase):
|
||||
# --------------------------------------------------------------
|
||||
# Process assembly line
|
||||
# ---------------------------------------------------------
|
||||
|
||||
finished_lot =self.env['stock.production.lot'].create({'product_id': production_table.product_id.id})
|
||||
workorder.write({'final_lot_id': finished_lot.id})
|
||||
workorder.button_start()
|
||||
@@ -112,6 +111,119 @@ class TestWorkOrderProcess(common.TransactionCase):
|
||||
self.assertEqual(product_table_leg.qty_available, 19)
|
||||
self.assertEqual(product_table_sheet.qty_available, 19)
|
||||
|
||||
def test_00b_workorder_process(self):
|
||||
""" Testing consume quants and produced quants with workorder """
|
||||
dining_table = self.env.ref("mrp.product_product_computer_desk")
|
||||
product_table_sheet = self.env.ref('mrp.product_product_computer_desk_head')
|
||||
product_table_leg = self.env.ref('mrp.product_product_computer_desk_leg')
|
||||
product_bolt = self.env.ref('mrp.product_product_computer_desk_bolt')
|
||||
|
||||
production_table = self.env['mrp.production'].create({
|
||||
'product_id': dining_table.id,
|
||||
'product_qty': 2.0,
|
||||
'product_uom_id': dining_table.uom_id.id,
|
||||
'bom_id': self.ref("mrp.mrp_bom_desk")
|
||||
})
|
||||
|
||||
# Set tracking lot on finish and consume products.
|
||||
dining_table.tracking = 'lot'
|
||||
product_table_sheet.tracking = 'lot'
|
||||
product_table_leg.tracking = 'lot'
|
||||
product_bolt.tracking = "lot"
|
||||
|
||||
# Initial inventory of product sheet, lags and bolt
|
||||
lot_sheet = self.env['stock.production.lot'].create({'product_id': product_table_sheet.id})
|
||||
lot_leg = self.env['stock.production.lot'].create({'product_id': product_table_leg.id})
|
||||
lot_bolt = self.env['stock.production.lot'].create({'product_id': product_bolt.id})
|
||||
|
||||
# Initialize inventory
|
||||
# --------------------
|
||||
inventory = self.env['stock.inventory'].create({
|
||||
'name': 'Inventory Product Table',
|
||||
'filter': 'partial',
|
||||
'line_ids': [(0, 0, {
|
||||
'product_id': product_table_sheet.id,
|
||||
'product_uom_id': product_table_sheet.uom_id.id,
|
||||
'product_qty': 20,
|
||||
'prod_lot_id': lot_sheet.id,
|
||||
'location_id': self.source_location_id
|
||||
}), (0, 0, {
|
||||
'product_id': product_table_leg.id,
|
||||
'product_uom_id': product_table_leg.uom_id.id,
|
||||
'product_qty': 20,
|
||||
'prod_lot_id': lot_leg.id,
|
||||
'location_id': self.source_location_id
|
||||
}), (0, 0, {
|
||||
'product_id': product_bolt.id,
|
||||
'product_uom_id': product_bolt.uom_id.id,
|
||||
'product_qty': 20,
|
||||
'prod_lot_id': lot_bolt.id,
|
||||
'location_id': self.source_location_id
|
||||
})]
|
||||
})
|
||||
inventory.action_done()
|
||||
|
||||
# Create work order
|
||||
production_table.button_plan()
|
||||
# Check Work order created or not
|
||||
self.assertEqual(len(production_table.workorder_ids), 3)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Process all workorder and check it state.
|
||||
# ----------------------------------------------------------
|
||||
|
||||
workorders = production_table.workorder_ids
|
||||
self.assertEqual(workorders[0].state, 'ready', "First workorder state should be ready.")
|
||||
self.assertEqual(workorders[1].state, 'pending')
|
||||
self.assertEqual(workorders[2].state, 'pending')
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# Process cutting operation...
|
||||
# ---------------------------------------------------------
|
||||
finished_lot = self.env['stock.production.lot'].create({'product_id': production_table.product_id.id})
|
||||
workorders[0].write({'final_lot_id': finished_lot.id, 'qty_producing': 1.0})
|
||||
workorders[0].button_start()
|
||||
workorders[0].active_move_lot_ids[0].write({'lot_id': lot_sheet.id, 'quantity_done': 1})
|
||||
self.assertEqual(workorders[0].state, 'progress')
|
||||
workorders[0].record_production()
|
||||
|
||||
move_table_sheet = production_table.move_raw_ids.filtered(lambda x : x.product_id == product_table_sheet)
|
||||
self.assertEqual(move_table_sheet.quantity_done, 1)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# Process drilling operation ...
|
||||
# ---------------------------------------------------------
|
||||
workorders[1].button_start()
|
||||
workorders[1].qty_producing = 1.0
|
||||
workorders[1].active_move_lot_ids[0].write({'lot_id': lot_leg.id, 'quantity_done': 4})
|
||||
workorders[1].record_production()
|
||||
move_leg = production_table.move_raw_ids.filtered(lambda x : x.product_id == product_table_leg)
|
||||
#self.assertEqual(workorders[1].state, 'done')
|
||||
self.assertEqual(move_leg.quantity_done, 4)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# Process fitting operation ...
|
||||
# ---------------------------------------------------------
|
||||
workorders[2].button_start()
|
||||
workorders[2].qty_producing = 1.0
|
||||
move_lot = workorders[2].active_move_lot_ids[0]
|
||||
move_lot.write({'lot_id': lot_bolt.id, 'quantity_done': 4})
|
||||
move_table_bolt = production_table.move_raw_ids.filtered(lambda x : x.product_id.id == product_bolt.id)
|
||||
workorders[2].record_production()
|
||||
self.assertEqual(move_table_bolt.quantity_done, 4)
|
||||
|
||||
# Change the quantity of the production order to 1
|
||||
wiz = self.env['change.production.qty'].create({'mo_id': production_table.id ,
|
||||
'product_qty': 1.0})
|
||||
wiz.change_prod_qty()
|
||||
# ---------------------------------------------------------------
|
||||
# Check consume quants and produce quants after posting inventory
|
||||
# ---------------------------------------------------------------
|
||||
production_table.post_inventory()
|
||||
self.assertEqual(sum(move_table_sheet.quant_ids.mapped('qty')), 1, "Wrong quantity of consumed product %s" % move_table_sheet.product_id.name)
|
||||
self.assertEqual(sum(move_leg.quant_ids.mapped('qty')), 4, "Wrong quantity of consumed product %s" % move_leg.product_id.name)
|
||||
self.assertEqual(sum(move_table_bolt.quant_ids.mapped('qty')), 4, "Wrong quantity of consumed product %s" % move_table_bolt.product_id.name)
|
||||
|
||||
def test_01_without_workorder(self):
|
||||
""" Testing consume quants and produced quants without workorder """
|
||||
unit = self.ref("product.product_uom_unit")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import float_is_zero
|
||||
import math
|
||||
|
||||
class ChangeProductionQty(models.TransientModel):
|
||||
@@ -37,6 +38,7 @@ class ChangeProductionQty(models.TransientModel):
|
||||
|
||||
@api.multi
|
||||
def change_prod_qty(self):
|
||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
for wizard in self:
|
||||
production = wizard.mo_id
|
||||
produced = sum(production.move_finished_ids.filtered(lambda m: m.product_id == production.product_id).mapped('quantity_done'))
|
||||
@@ -63,11 +65,14 @@ class ChangeProductionQty(models.TransientModel):
|
||||
wo.duration_expected = (operation.workcenter_id.time_start +
|
||||
operation.workcenter_id.time_stop +
|
||||
cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency)
|
||||
quantity = wo.qty_production - wo.qty_produced
|
||||
if production.product_id.tracking == 'serial':
|
||||
quantity = 1.0
|
||||
quantity = 1.0 if float_is_zero(quantity, precision_digits=precision) else 0.0
|
||||
else:
|
||||
quantity = wo.qty_production - wo.qty_produced
|
||||
quantity = quantity if (quantity > 0) else 0
|
||||
if float_is_zero(quantity, precision_digits=precision):
|
||||
wo.final_lot_id = False
|
||||
wo.active_move_lot_ids.unlink()
|
||||
wo.qty_producing = quantity
|
||||
if wo.qty_produced < wo.qty_production and wo.state == 'done':
|
||||
wo.state = 'progress'
|
||||
@@ -80,6 +85,6 @@ class ChangeProductionQty(models.TransientModel):
|
||||
moves_finished = production.move_finished_ids.filtered(lambda move: move.operation_id == operation) #TODO: code does nothing, unless maybe by_products?
|
||||
moves_raw.mapped('move_line_ids').write({'workorder_id': wo.id})
|
||||
(moves_finished + moves_raw).write({'workorder_id': wo.id})
|
||||
if wo.move_raw_ids.filtered(lambda x: x.product_id.tracking != 'none') and not wo.active_move_line_ids:
|
||||
if quantity > 0 and wo.move_raw_ids.filtered(lambda x: x.product_id.tracking != 'none') and not wo.active_move_line_ids:
|
||||
wo._generate_lot_ids()
|
||||
return {}
|
||||
|
||||
@@ -1730,7 +1730,7 @@ exports.Orderline = Backbone.Model.extend({
|
||||
var sign = 1;
|
||||
if(base < 0){
|
||||
base = -base;
|
||||
var sign = -1;
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
var incl_fixed_amount = 0.0;
|
||||
@@ -1777,8 +1777,8 @@ exports.Orderline = Backbone.Model.extend({
|
||||
});
|
||||
return {
|
||||
taxes: taxes_vals,
|
||||
total_excluded: round_pr(total_excluded, currency_rounding_bak),
|
||||
total_included: round_pr(total_included, currency_rounding_bak)
|
||||
total_excluded: sign * round_pr(total_excluded, currency_rounding_bak),
|
||||
total_included: sign * round_pr(total_included, currency_rounding_bak)
|
||||
};
|
||||
},
|
||||
get_all_prices: function(){
|
||||
@@ -1796,6 +1796,7 @@ exports.Orderline = Backbone.Model.extend({
|
||||
return t.id === el;
|
||||
}));
|
||||
});
|
||||
product_taxes = _.map(product_taxes, this._map_tax_fiscal_position.bind(this));
|
||||
|
||||
var all_taxes = this.compute_all(product_taxes, price_unit, this.get_quantity(), this.pos.currency.rounding);
|
||||
_(all_taxes.taxes).each(function(tax) {
|
||||
|
||||
@@ -445,7 +445,7 @@ class PricelistItem(models.Model):
|
||||
elif self.compute_price == 'percentage':
|
||||
self.price = _("%s %% discount") % (self.percent_price)
|
||||
else:
|
||||
self.price = _("%s %% discount and %s surcharge") % (abs(self.price_discount), self.price_surcharge)
|
||||
self.price = _("%s %% discount and %s surcharge") % (self.price_discount, self.price_surcharge)
|
||||
|
||||
@api.onchange('applied_on')
|
||||
def _onchange_applied_on(self):
|
||||
|
||||
@@ -966,15 +966,16 @@ class SaleOrderLine(models.Model):
|
||||
# TO DO: move me in master/saas-16 on sale.order
|
||||
if self.order_id.pricelist_id.discount_policy == 'with_discount':
|
||||
return product.with_context(pricelist=self.order_id.pricelist_id.id).price
|
||||
price, rule_id = self.order_id.pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
|
||||
final_price, rule_id = self.order_id.pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
|
||||
pricelist_item = self.env['product.pricelist.item'].browse(rule_id)
|
||||
if (pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id.discount_policy == 'with_discount'):
|
||||
price, rule_id = pricelist_item.base_pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
|
||||
return price
|
||||
if pricelist_item.base == 'pricelist':
|
||||
base_price, rule_id = pricelist_item.base_pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id)
|
||||
base_price = pricelist_item.base_pricelist_id.currency_id.compute(base_price, self.order_id.pricelist_id.currency_id)
|
||||
else:
|
||||
from_currency = self.order_id.company_id.currency_id
|
||||
product_price = product[pricelist_item.base] if (pricelist_item and pricelist_item.base != 'pricelist') else product.lst_price
|
||||
return from_currency.compute(product_price, self.order_id.pricelist_id.currency_id)
|
||||
base_price = product[pricelist_item.base] if pricelist_item else product.lst_price
|
||||
base_price = product.currency_id.compute(base_price, self.order_id.pricelist_id.currency_id)
|
||||
# negative discounts (= surcharge) are included in the display price (= unit price)
|
||||
return max(base_price, final_price)
|
||||
|
||||
@api.multi
|
||||
@api.onchange('product_id')
|
||||
|
||||
@@ -301,7 +301,7 @@ class InventoryLine(models.Model):
|
||||
domain=[('type', '=', 'product')],
|
||||
index=True, required=True)
|
||||
product_name = fields.Char(
|
||||
'Product Name', related='product_id.name', store=True)
|
||||
'Product Name', related='product_id.name', store=True, readonly=True)
|
||||
product_code = fields.Char(
|
||||
'Product Code', related='product_id.default_code', store=True)
|
||||
product_uom_id = fields.Many2one(
|
||||
@@ -364,8 +364,14 @@ class InventoryLine(models.Model):
|
||||
self._compute_theoretical_qty()
|
||||
self.product_qty = self.theoretical_qty
|
||||
|
||||
@api.model
|
||||
def write(self, values):
|
||||
values.pop('product_name', False)
|
||||
res = super(InventoryLine, self).write(values)
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
values.pop('product_name', False)
|
||||
if 'product_id' in values and 'product_uom_id' not in values:
|
||||
values['product_uom_id'] = self.env['product.product'].browse(values['product_id']).uom_id.id
|
||||
existings = self.search([
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
<field name="lot_id" groups="stock.group_production_lot"/>
|
||||
<field name="owner_id" groups="stock.group_tracking_owner"/>
|
||||
<group expand='0' string='Filters'>
|
||||
<filter name="stockable" string="Stockable" domain="[('product_id.type', '=', 'product')]"/>
|
||||
<separator/>
|
||||
<filter name='internal_loc' string="Internal Locations" domain="[('location_id.usage','=', 'internal')]"/>
|
||||
<filter name='transit_loc' string="Transit Locations" domain="[('location_id.usage' ,'=', 'transit')]"/>
|
||||
<separator/>
|
||||
@@ -117,7 +119,7 @@
|
||||
<field name="res_model">stock.quant</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,pivot,graph</field>
|
||||
<field name="context">{'search_default_internal_loc': 1,}</field>
|
||||
<field name="context">{'search_default_internal_loc': 1, 'search_default_stockable': 1, 'group_by': ['product_id', 'location_id']}</field>
|
||||
<field name="help">This analysis gives you a fast overview on the current stock level of your products and their current inventory value.</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="product_template_open_quants"> <!-- Not used -->
|
||||
|
||||
@@ -391,6 +391,8 @@ var AbstractField = Widget.extend({
|
||||
* Please do not use this flag unless you really need it. Our only use
|
||||
* case is currently the pad widget, which does a _setValue in the
|
||||
* renderEdit method.
|
||||
* @param {boolean} [options.notifyChange=true] if false, the basic model
|
||||
* will not notify and not trigger the onchange, even though it was changed.
|
||||
* @param {boolean} [options.forceChange=false] if true, the change event will be
|
||||
* triggered even if the new value is the same as the old one
|
||||
* @returns {Deferred}
|
||||
@@ -421,6 +423,7 @@ var AbstractField = Widget.extend({
|
||||
changes: changes,
|
||||
viewType: this.viewType,
|
||||
doNotSetDirty: options && options.doNotSetDirty,
|
||||
notifyChange: !options || options.notifyChange !== false,
|
||||
onSuccess: def.resolve.bind(def),
|
||||
onFailure: def.reject.bind(def),
|
||||
});
|
||||
|
||||
@@ -205,7 +205,7 @@ var InputField = DebouncedField.extend({
|
||||
if (!event || event === this.lastChangeEvent) {
|
||||
this.isDirty = false;
|
||||
}
|
||||
if (this.isDirty || (event && event.target === this)) {
|
||||
if (this.isDirty || (event && event.target === this && event.data.changes[this.name] === this.value)) {
|
||||
return $.when();
|
||||
} else {
|
||||
return this._render();
|
||||
|
||||
@@ -1033,13 +1033,25 @@ var FieldX2Many = AbstractField.extend({
|
||||
*/
|
||||
_onResequence: function (event) {
|
||||
var self = this;
|
||||
_.each(event.data.rowIDs, function (rowID, index) {
|
||||
var rowIDs = event.data.rowIDs.slice();
|
||||
var rowID = rowIDs.pop();
|
||||
var defs = _.map(rowIDs, function (rowID, index) {
|
||||
var data = {};
|
||||
data[event.data.handleField] = event.data.offset + index;
|
||||
self._setValue({
|
||||
return self._setValue({
|
||||
operation: 'UPDATE',
|
||||
id: rowID,
|
||||
data: data,
|
||||
}, {
|
||||
notifyChange: false,
|
||||
});
|
||||
});
|
||||
$.when.apply($, defs).then(function () {
|
||||
// trigger only once the onchange for parent record
|
||||
self._setValue({
|
||||
operation: 'UPDATE',
|
||||
id: rowID,
|
||||
data: _.object([event.data.handleField], [event.data.offset + rowIDs.length]),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -447,7 +447,11 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
*/
|
||||
_setMode: function (mode, recordID) {
|
||||
if ((recordID || this.handle) === this.handle) {
|
||||
return this.update({mode: mode}, {reload: false});
|
||||
return this.update({mode: mode}, {reload: false}).then(function () {
|
||||
// necessary to allow all sub widgets to use their dimensions in
|
||||
// layout related activities, such as autoresize on fieldtexts
|
||||
core.bus.trigger('DOM_updated');
|
||||
});
|
||||
}
|
||||
return $.when();
|
||||
},
|
||||
|
||||
@@ -1092,6 +1092,9 @@ var BasicModel = AbstractModel.extend({
|
||||
* @param {boolean} [options.doNotSetDirty=false] if this flag is set to
|
||||
* true, then we will not tag the record as dirty. This should be avoided
|
||||
* for most situations.
|
||||
* @param {boolean} [options.notifyChange=true] if this flag is set to
|
||||
* false, then we will not notify and not trigger the onchange, even though
|
||||
* it was changed.
|
||||
* @param {string} [options.viewType] current viewType. If not set, we will assume
|
||||
* main viewType from the record
|
||||
* @returns {Deferred}
|
||||
@@ -1119,6 +1122,10 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
}
|
||||
|
||||
if (options.notifyChange === false) {
|
||||
return $.when();
|
||||
}
|
||||
|
||||
return $.when.apply($, defs).then(function () {
|
||||
var onChangeFields = []; // the fields that have changed and that have an on_change
|
||||
for (var fieldName in changes) {
|
||||
@@ -2305,6 +2312,7 @@ var BasicModel = AbstractModel.extend({
|
||||
var ids = record.data[fieldName] || [];
|
||||
var list = self._makeDataPoint({
|
||||
count: ids.length,
|
||||
context: record.context,
|
||||
fieldsInfo: fieldsInfo,
|
||||
fields: view ? view.fields : fieldInfo.relatedFields,
|
||||
limit: fieldInfo.limit,
|
||||
|
||||
@@ -56,7 +56,6 @@ var CalendarController = AbstractController.extend({
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Render the buttons according to the CalendarView.buttons template and
|
||||
* add listeners on it. Set this.$buttons with the produced jQuery element
|
||||
@@ -160,10 +159,10 @@ var CalendarController = AbstractController.extend({
|
||||
}
|
||||
this.quickCreating = true;
|
||||
this.model.createRecord(event)
|
||||
.then(function (id) {
|
||||
.then(function () {
|
||||
self.quick.destroy();
|
||||
self.quick = null;
|
||||
self.reload(id);
|
||||
self.reload();
|
||||
})
|
||||
.fail(function (error, errorEvent) {
|
||||
// This will occurs if there are some more fields required
|
||||
@@ -215,7 +214,6 @@ var CalendarController = AbstractController.extend({
|
||||
|
||||
if(!options.disableQuickCreate && !event.data.disableQuickCreate && this.quickAddPop) {
|
||||
this.quick = new QuickCreate(this, true, options, data, event.data);
|
||||
this.quick.on('added', this, this.reload.bind(this));
|
||||
this.quick.open();
|
||||
this.quick.focus();
|
||||
return;
|
||||
|
||||
@@ -322,6 +322,7 @@ return AbstractModel.extend({
|
||||
// List authorized values for every field
|
||||
// fields with an active 'all' filter are skipped
|
||||
var authorizedValues = {};
|
||||
var avoidValues = {};
|
||||
|
||||
_.each(this.data.filters, function (filter) {
|
||||
// Skip 'all' filters because they do not affect the domain
|
||||
@@ -329,11 +330,20 @@ return AbstractModel.extend({
|
||||
|
||||
// Loop over subfilters to complete authorizedValues
|
||||
_.each(filter.filters, function (f) {
|
||||
if (!authorizedValues[filter.fieldName])
|
||||
authorizedValues[filter.fieldName] = [];
|
||||
if (filter.write_model) {
|
||||
if (!authorizedValues[filter.fieldName])
|
||||
authorizedValues[filter.fieldName] = [];
|
||||
|
||||
if (f.active) {
|
||||
authorizedValues[filter.fieldName].push(f.value);
|
||||
if (f.active) {
|
||||
authorizedValues[filter.fieldName].push(f.value);
|
||||
}
|
||||
} else {
|
||||
if (!avoidValues[filter.fieldName])
|
||||
avoidValues[filter.fieldName] = [];
|
||||
|
||||
if (!f.active) {
|
||||
avoidValues[filter.fieldName].push(f.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -343,6 +353,9 @@ return AbstractModel.extend({
|
||||
for (var field in authorizedValues) {
|
||||
domain.push([field, 'in', authorizedValues[field]]);
|
||||
}
|
||||
for (var field in avoidValues) {
|
||||
domain.push([field, 'not in', avoidValues[field]]);
|
||||
}
|
||||
|
||||
return domain;
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ var FieldManagerMixin = {
|
||||
*/
|
||||
_applyChanges: function (dataPointID, changes, event) {
|
||||
var self = this;
|
||||
var options = _.pick(event.data, 'viewType', 'doNotSetDirty');
|
||||
var options = _.pick(event.data, 'viewType', 'doNotSetDirty', 'notifyChange');
|
||||
return this.model.notifyChanges(dataPointID, changes, options)
|
||||
.then(function (result) {
|
||||
if (event.data.force_save) {
|
||||
|
||||
@@ -800,10 +800,6 @@ var FormRenderer = BasicRenderer.extend({
|
||||
this.$el.toggleClass('o_form_editable', this.mode === 'edit');
|
||||
this.$el.toggleClass('o_form_readonly', this.mode === 'readonly');
|
||||
|
||||
// Necessary to allow all sub widgets to use their dimensions in
|
||||
// layout related activities, such as autoresize on fieldtexts
|
||||
core.bus.trigger('DOM_updated');
|
||||
|
||||
// Attach the tooltips on the fields' label
|
||||
_.each(this.allFieldWidgets[this.state.id], function (widget) {
|
||||
var idForLabel = self.idsForLabels[widget.name];
|
||||
|
||||
@@ -101,6 +101,7 @@ ListRenderer.include({
|
||||
});
|
||||
var $row = self.$('.o_data_row:nth(' + rowIndex + ')');
|
||||
self._setDecorationClasses(state.data[rowIndex], $row);
|
||||
self._updateFooter();
|
||||
}
|
||||
return widgets;
|
||||
});
|
||||
@@ -542,12 +543,21 @@ ListRenderer.include({
|
||||
_resequence: function (event, ui) {
|
||||
var self = this;
|
||||
var movedRecordID = ui.item.data('id');
|
||||
var rowIDs = _.pluck(this.state.data, 'id');
|
||||
rowIDs = _.without(rowIDs, movedRecordID);
|
||||
rowIDs.splice(ui.item.index(), 0, movedRecordID);
|
||||
var sequences = _.map(this.state.data, function(record) {
|
||||
return record.data[self.handleField];
|
||||
});
|
||||
var rows = this.state.data;
|
||||
var row = _.findWhere(rows, {id: movedRecordID});
|
||||
var index0 = rows.indexOf(row);
|
||||
var index1 = ui.item.index();
|
||||
rows = rows.slice(Math.min(index0, index1), Math.max(index0, index1) + 1);
|
||||
rows = _.without(rows, row);
|
||||
if (index0 > index1) {
|
||||
rows.unshift(row);
|
||||
} else {
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
var sequences = _.pluck(_.pluck(rows, 'data'), self.handleField);
|
||||
var rowIDs = _.pluck(rows, 'id');
|
||||
|
||||
this.trigger_up('resequence', {
|
||||
rowIDs: rowIDs,
|
||||
offset: _.min(sequences),
|
||||
|
||||
@@ -681,6 +681,17 @@ var ListRenderer = BasicRenderer.extend({
|
||||
$tr.toggleClass(cssClass, py.PY_isTrue(py.evaluate(expr, record.evalContext)));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Update the footer aggregate values. This method should be called each
|
||||
* time the state of some field is changed, to make sure their sum are kept
|
||||
* in sync.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_updateFooter: function () {
|
||||
this._computeAggregates();
|
||||
this.$('tfoot').replaceWith(this._renderFooter(!!this.state.groupedBy.length));
|
||||
},
|
||||
/**
|
||||
* Whenever we change the state of the selected rows, we need to call this
|
||||
* method to keep the this.selection variable in sync, and also to recompute
|
||||
@@ -695,8 +706,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
return $(row).data('id');
|
||||
});
|
||||
this.trigger_up('selection_changed', { selection: this.selection });
|
||||
this._computeAggregates();
|
||||
this.$('tfoot').replaceWith(this._renderFooter(!!this.state.groupedBy.length));
|
||||
this._updateFooter();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -203,6 +203,7 @@
|
||||
&.disabled {
|
||||
opacity: 1.0;
|
||||
color: @text-muted;
|
||||
pointer-events: none;
|
||||
border-left: 1px solid @gray-lighter-dark;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,14 @@
|
||||
// consider this later if needed.
|
||||
z-index: 1;
|
||||
|
||||
// The in-edition elements will overflow other elements (the rows below,
|
||||
// the content below the list view, ...). As those might be using
|
||||
// position: relative, the in-edition elements have to define a z-index.
|
||||
// Technically, creating a stacking context for a meaningful parent
|
||||
// (maybe the .o_content) might be required for safety but we should
|
||||
// consider this later if needed.
|
||||
z-index: 1;
|
||||
|
||||
// Field widgets override
|
||||
> .o_field_widget {
|
||||
.o-position-absolute(0, 0, auto, 0);
|
||||
|
||||
@@ -2003,9 +2003,10 @@ QUnit.module('relational_fields', {
|
||||
});
|
||||
|
||||
QUnit.test('embedded one2many with handle widget', function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(7);
|
||||
|
||||
this.data.partner.records[0].turtles = [1, 2, 3];
|
||||
|
||||
this.data.partner.records[0].p = [1, 2, 4];
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
@@ -2014,10 +2015,10 @@ QUnit.module('relational_fields', {
|
||||
'<sheet>' +
|
||||
'<notebook>' +
|
||||
'<page string="P page">' +
|
||||
'<field name="p">' +
|
||||
'<tree default_order="int_field">' +
|
||||
'<field name="int_field" widget="handle"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="turtles">' +
|
||||
'<tree default_order="turtle_int">' +
|
||||
'<field name="turtle_int" widget="handle"/>' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</page>' +
|
||||
@@ -2028,14 +2029,14 @@ QUnit.module('relational_fields', {
|
||||
});
|
||||
|
||||
testUtils.intercept(form, "field_changed", function (event) {
|
||||
assert.step(event.data.changes.p.data.int_field.toString());
|
||||
assert.step(event.data.changes.turtles.data.turtle_int.toString());
|
||||
}, true);
|
||||
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop",
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa",
|
||||
"should have the 3 rows in the correct order");
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop",
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa",
|
||||
"should still have the 3 rows in the correct order");
|
||||
|
||||
// Drag and drop the fourth line in second position
|
||||
@@ -2045,21 +2046,169 @@ QUnit.module('relational_fields', {
|
||||
{position: 'top'}
|
||||
);
|
||||
|
||||
assert.verifySteps(["0", "1", "2"],
|
||||
assert.verifySteps(["0", "1"],
|
||||
"sequences values should be incremental starting from the previous minimum one");
|
||||
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop",
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipyopkawa",
|
||||
"should have the 3 rows in the new order");
|
||||
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop",
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipyopkawa",
|
||||
"should still have the 3 rows in the new order");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('onchange for embedded one2many with handle widget', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data.partner.records[0].turtles = [1, 2, 3];
|
||||
var partnerOnchange = 0;
|
||||
this.data.partner.onchanges = {
|
||||
turtles: function (obj) {
|
||||
partnerOnchange++;
|
||||
},
|
||||
};
|
||||
var turtleOnchange = 0;
|
||||
this.data.turtle.onchanges = {
|
||||
turtle_int: function (obj) {
|
||||
turtleOnchange++;
|
||||
},
|
||||
};
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<notebook>' +
|
||||
'<page string="P page">' +
|
||||
'<field name="turtles">' +
|
||||
'<tree default_order="turtle_int">' +
|
||||
'<field name="turtle_int" widget="handle"/>' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</page>' +
|
||||
'</notebook>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
|
||||
// Drag and drop the fourth line in second position
|
||||
testUtils.dragAndDrop(
|
||||
form.$('.ui-sortable-handle').eq(1),
|
||||
form.$('tbody tr').first(),
|
||||
{position: 'top'}
|
||||
);
|
||||
|
||||
assert.strictEqual(turtleOnchange, 2, "should trigger one onchange per line updated");
|
||||
assert.strictEqual(partnerOnchange, 1, "should trigger only one onchange on the parent");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('embedded one2many with handle widget with minimum setValue calls', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(20);
|
||||
|
||||
|
||||
this.data.turtle.records[0].turtle_int = 6;
|
||||
this.data.turtle.records.push({
|
||||
id: 4,
|
||||
turtle_int: 20,
|
||||
turtle_foo: "a1",
|
||||
}, {
|
||||
id: 5,
|
||||
turtle_int: 9,
|
||||
turtle_foo: "a2",
|
||||
}, {
|
||||
id: 6,
|
||||
turtle_int: 2,
|
||||
turtle_foo: "a3",
|
||||
}, {
|
||||
id: 7,
|
||||
turtle_int: 11,
|
||||
turtle_foo: "a4",
|
||||
});
|
||||
this.data.partner.records[0].turtles = [1, 2, 3, 4, 5, 6, 7];
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<notebook>' +
|
||||
'<page string="P page">' +
|
||||
'<field name="turtles">' +
|
||||
'<tree default_order="turtle_int">' +
|
||||
'<field name="turtle_int" widget="handle"/>' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</page>' +
|
||||
'</notebook>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
testUtils.intercept(form, "field_changed", function (event) {
|
||||
assert.step(form.model.get(event.data.changes.turtles.id).res_id);
|
||||
}, true);
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
|
||||
var steps = [];
|
||||
var positions = [
|
||||
[6, 0, 'top', [3, 6, 1, 2, 5, 7, 4]], // move the last to the first line
|
||||
[5, 1, 'top', [7, 6, 1, 2, 5]], // move the penultimate to the second line
|
||||
[2, 5, 'center', [1, 2, 5, 6]], // move the third to the penultimate line
|
||||
];
|
||||
function dragAndDrop() {
|
||||
var pos = positions.shift();
|
||||
|
||||
testUtils.dragAndDrop(
|
||||
form.$('.ui-sortable-handle').eq(pos[0]),
|
||||
form.$('tbody tr').eq(pos[1]),
|
||||
{position: pos[2]}
|
||||
);
|
||||
|
||||
steps = steps.concat(pos[3]);
|
||||
assert.verifySteps(steps,
|
||||
"sequences values should be apply from the begin index to the drop index");
|
||||
|
||||
if (positions.length) {
|
||||
|
||||
setTimeout(dragAndDrop, 10);
|
||||
|
||||
} else {
|
||||
|
||||
assert.deepEqual(_.pluck(form.model.get(form.handle).data.turtles.data, 'data'), [
|
||||
{ id: 3, turtle_foo: "kawa", turtle_int: 2 },
|
||||
{ id: 7, turtle_foo: "a4", turtle_int: 3 },
|
||||
{ id: 1, turtle_foo: "yop", turtle_int: 4 },
|
||||
{ id: 2, turtle_foo: "blip", turtle_int: 5 },
|
||||
{ id: 5, turtle_foo: "a2", turtle_int: 6 },
|
||||
{ id: 6, turtle_foo: "a3", turtle_int: 7 },
|
||||
{ id: 4, turtle_foo: "a1", turtle_int: 8 }
|
||||
], "sequences must be apply correctly");
|
||||
|
||||
form.destroy();
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
dragAndDrop();
|
||||
});
|
||||
|
||||
QUnit.test('embedded one2many (editable list) with handle widget', function (assert) {
|
||||
assert.expect(9);
|
||||
assert.expect(8);
|
||||
|
||||
this.data.partner.records[0].p = [1, 2, 4];
|
||||
var form = createView({
|
||||
@@ -2101,7 +2250,7 @@ QUnit.module('relational_fields', {
|
||||
{position: 'top'}
|
||||
);
|
||||
|
||||
assert.verifySteps(["0", "1", "2"],
|
||||
assert.verifySteps(["0", "1"],
|
||||
"sequences values should be incremental starting from the previous minimum one");
|
||||
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop",
|
||||
@@ -5904,6 +6053,7 @@ QUnit.module('relational_fields', {
|
||||
if (args.method === 'read' && args.model === 'partner_type') {
|
||||
assert.deepEqual(args.kwargs.context, {
|
||||
key: 'yop',
|
||||
active_field: 2,
|
||||
someKey: 'some value',
|
||||
}, "sent context should be correct");
|
||||
}
|
||||
@@ -6383,6 +6533,36 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('propagate context to sub views', function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="turtles">' +
|
||||
'<tree editable="bottom">' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
mockRPC: function (route, args) {
|
||||
assert.strictEqual(args.kwargs.context.flutter, 'shy',
|
||||
'view context key should be used for every rpcs');
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
viewOptions: {context: {flutter: 'shy'}},
|
||||
});
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
form.$('input[name="turtle_foo"]').val('pinky pie').trigger('input');
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
|
||||
QUnit.module('FieldMany2Many');
|
||||
|
||||
|
||||
@@ -31,15 +31,15 @@ QUnit.module('Views', {
|
||||
event: {
|
||||
fields: {
|
||||
id: {string: "ID", type: "integer"},
|
||||
user_id: {string: "user", type: "many2one", relation: 'user'},
|
||||
partner_id: {string: "user", type: "many2one", relation: 'partner', related: 'user_id.partner_id'},
|
||||
user_id: {string: "user", type: "many2one", relation: 'user', default: session.uid},
|
||||
partner_id: {string: "user", type: "many2one", relation: 'partner', related: 'user_id.partner_id', default: 1},
|
||||
name: {string: "name", type: "char"},
|
||||
start_date: {string: "start date", type: "date"},
|
||||
stop_date: {string: "stop date", type: "date"},
|
||||
start: {string: "start datetime", type: "datetime"},
|
||||
stop: {string: "stop datetime", type: "datetime"},
|
||||
allday: {string: "allday", type: "boolean"},
|
||||
partner_ids: {string: "attendees", type: "one2many", relation: 'partner'},
|
||||
partner_ids: {string: "attendees", type: "one2many", relation: 'partner', default: [[6, 0, [1]]]},
|
||||
type: {string: "type", type: "integer"},
|
||||
},
|
||||
records: [
|
||||
@@ -1085,7 +1085,6 @@ QUnit.module('Views', {
|
||||
assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body");
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('readonly date_start field', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
@@ -1207,6 +1206,305 @@ QUnit.module('Views', {
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('create event with filters', function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data.event.fields.user_id.default = 5;
|
||||
this.data.event.fields.partner_id.default = 3;
|
||||
this.data.user.records.push({id: 5, display_name: "user 5", partner_id: 3});
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar class="o_calendar_test" '+
|
||||
'event_open_popup="true" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
var $view = $('#qunit-fixture').contents();
|
||||
$view.prependTo('body'); // => select with click position
|
||||
|
||||
calendar.$('.o_calendar_filter_item[data-value=4] input').click();
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 5, "should display 5 filter items");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 3, "should display 3 events");
|
||||
|
||||
// quick create a record
|
||||
var left = calendar.$('.fc-bg td:eq(4)').offset().left+15;
|
||||
var top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15;
|
||||
try {
|
||||
testUtils.triggerPositionalMouseEvent(left, top, "mousedown");
|
||||
} catch (e) {
|
||||
calendar.destroy();
|
||||
$view.remove();
|
||||
throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.');
|
||||
}
|
||||
testUtils.triggerPositionalMouseEvent(left, top + 200, "mousemove");
|
||||
testUtils.triggerPositionalMouseEvent(left, top + 200, "mouseup");
|
||||
|
||||
$('.modal-body input:first').val('coucou').trigger('input');
|
||||
$('.modal button.btn:contains(Create)').trigger('click');
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should add the missing filter (active)");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 4, "should display the created item");
|
||||
|
||||
// change default value for quick create an hide record
|
||||
this.data.event.fields.user_id.default = 4;
|
||||
this.data.event.fields.partner_id.default = 4;
|
||||
|
||||
// quick create and other record
|
||||
left = calendar.$('.fc-bg td:eq(3)').offset().left+15;
|
||||
top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15;
|
||||
testUtils.triggerPositionalMouseEvent(left, top, "mousedown");
|
||||
testUtils.triggerPositionalMouseEvent(left, top + 200, "mousemove");
|
||||
testUtils.triggerPositionalMouseEvent(left, top + 200, "mouseup");
|
||||
|
||||
$('.modal-body input:first').val('coucou 2').trigger('input');
|
||||
$('.modal button.btn:contains(Create)').trigger('click');
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should have the same filters");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 4, "should not display the created item");
|
||||
|
||||
calendar.$('.o_calendar_filter_item[data-value=4] input').click();
|
||||
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 11, "should display all records");
|
||||
|
||||
calendar.destroy();
|
||||
$view.remove();
|
||||
});
|
||||
|
||||
QUnit.test('create event with filters (no quickCreate)', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data.event.fields.user_id.default = 5;
|
||||
this.data.event.fields.partner_id.default = 3;
|
||||
this.data.user.records.push({
|
||||
id: 5,
|
||||
display_name: "user 5",
|
||||
partner_id: 3
|
||||
});
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar class="o_calendar_test" '+
|
||||
'event_open_popup="true" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
archs: {
|
||||
"event,false,form":
|
||||
'<form>'+
|
||||
'<group>'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="start"/>'+
|
||||
'<field name="stop"/>'+
|
||||
'<field name="user_id"/>'+
|
||||
'<field name="partner_id" invisible="1"/>'+
|
||||
'</group>'+
|
||||
'</form>',
|
||||
},
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
var $view = $('#qunit-fixture').contents();
|
||||
$view.prependTo('body'); // => select with click position
|
||||
|
||||
calendar.$('.o_calendar_filter_item[data-value=4] input').click();
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 5, "should display 5 filter items");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 3, "should display 3 events");
|
||||
|
||||
// quick create a record
|
||||
var left = calendar.$('.fc-bg td:eq(4)').offset().left+15;
|
||||
var top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15;
|
||||
try {
|
||||
testUtils.triggerPositionalMouseEvent(left, top, "mousedown");
|
||||
} catch (e) {
|
||||
calendar.destroy();
|
||||
$view.remove();
|
||||
throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.');
|
||||
}
|
||||
testUtils.triggerPositionalMouseEvent(left, top + 200, "mousemove");
|
||||
testUtils.triggerPositionalMouseEvent(left, top + 200, "mouseup");
|
||||
|
||||
$('.modal-body input:first').val('coucou').trigger('input');
|
||||
|
||||
$('.modal button.btn:contains(Edit)').trigger('click');
|
||||
$('.modal button.btn:contains(Save)').trigger('click');
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should add the missing filter (active)");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 4, "should display the created item");
|
||||
|
||||
calendar.destroy();
|
||||
$view.remove();
|
||||
});
|
||||
|
||||
QUnit.test('Update event with filters', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var records = this.data.user.records;
|
||||
records.push({
|
||||
id: 5,
|
||||
display_name: "user 5",
|
||||
partner_id: 3
|
||||
});
|
||||
|
||||
this.data.event.onchanges = {
|
||||
user_id: function (obj) {
|
||||
obj.partner_id = _.findWhere(records, {id:obj.user_id}).partner_id;
|
||||
}
|
||||
};
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar class="o_calendar_test" '+
|
||||
'event_open_popup="true" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
archs: {
|
||||
"event,false,form":
|
||||
'<form>'+
|
||||
'<group>'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="start"/>'+
|
||||
'<field name="stop"/>'+
|
||||
'<field name="user_id"/>'+
|
||||
'<field name="partner_ids" widget="many2many_tags"/>'+
|
||||
'</group>'+
|
||||
'</form>',
|
||||
},
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
calendar.$('.o_calendar_filter_item[data-value=4] input').click();
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 5, "should display 5 filter items");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 3, "should display 3 events");
|
||||
|
||||
calendar.$('.fc-event:contains(event 2) .fc-content').trigger('click');
|
||||
$('.modal button.btn:contains(Edit)').trigger('click');
|
||||
$('.modal .o_field_widget[name="user_id"] input').trigger('click');
|
||||
$('.ui-menu-item a:contains(user 5)').trigger('mouseenter').trigger('click');
|
||||
$('.modal button.btn:contains(Save)').trigger('click');
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should add the missing filter (active)");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 3, "should display the updated item");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('change pager with filters', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data.user.records.push({
|
||||
id: 5,
|
||||
display_name: "user 5",
|
||||
partner_id: 3
|
||||
});
|
||||
this.data.event.records.push({
|
||||
id: 7,
|
||||
user_id: 5,
|
||||
partner_id: 3,
|
||||
name: "event 7",
|
||||
start: "2016-12-06 04:00:00",
|
||||
stop: "2016-12-06 08:00:00",
|
||||
allday: false,
|
||||
partner_ids: [1,2,3],
|
||||
type: 1
|
||||
}, {
|
||||
id: 8,
|
||||
user_id: session.uid,
|
||||
partner_id: 1,
|
||||
name: "event 8",
|
||||
start: "2016-12-07 04:00:00",
|
||||
stop: "2016-12-07 08:00:00",
|
||||
allday: false,
|
||||
partner_ids: [1,2,3],
|
||||
type: 1
|
||||
},{
|
||||
id: 9,
|
||||
user_id: 4,
|
||||
partner_id: 4,
|
||||
name: "event 9",
|
||||
start: "2016-12-08 04:00:00",
|
||||
stop: "2016-12-08 08:00:00",
|
||||
allday: false,
|
||||
partner_ids: [1,2,3],
|
||||
type: 1
|
||||
});
|
||||
|
||||
var calendar = createView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar class="o_calendar_test" '+
|
||||
'event_open_popup="true" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
calendar.$('.o_calendar_filter_item[data-value=4] input').click();
|
||||
$('.o_calendar_button_prev').click();
|
||||
|
||||
assert.strictEqual(calendar.$('.o_calendar_filter_item').length, 6, "should display 6 filter items");
|
||||
assert.strictEqual(calendar.$('.fc-event').length, 2, "should display 2 events");
|
||||
assert.strictEqual(calendar.$('.fc-event .o_field_name').text().replace(/\s/g, ''), "event7event8",
|
||||
"should display 2 events");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('ensure events are still shown if filters give an empty domain', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
|
||||
@@ -1116,6 +1116,42 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('properly apply onchange when changed field is active field', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
int_field: function (obj) {
|
||||
obj.int_field = 14;
|
||||
},
|
||||
};
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<group><field name="int_field"/></group>' +
|
||||
'</form>',
|
||||
res_id: 2,
|
||||
viewOptions: {mode: 'edit'},
|
||||
});
|
||||
|
||||
|
||||
assert.strictEqual(form.$('input').val(), "9",
|
||||
"should contain input with initial value");
|
||||
|
||||
form.$('input').val("666").trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('input').val(), "14",
|
||||
"value should have been set to 14 by onchange");
|
||||
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name=int_field]').text(), "14",
|
||||
"value should still be 14");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('onchange send only the present fields to the server', function (assert) {
|
||||
assert.expect(1);
|
||||
this.data.partner.records[0].product_id = false;
|
||||
@@ -6045,6 +6081,49 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('autoresize of text fields is done when switching to edit mode', function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data.partner.fields.text_field = { string: 'Text field', type: 'text' };
|
||||
this.data.partner.fields.text_field.default = "some\n\nmulti\n\nline\n\ntext\n";
|
||||
this.data.partner.records[0].text_field = "a\nb\nc\nd\ne\nf";
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="text_field"/>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
// switch to edit mode to ensure that autoresize is correctly done
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
var height = form.$('.o_field_widget[name=text_field]').height();
|
||||
// focus the field to manually trigger autoresize
|
||||
form.$('.o_field_widget[name=text_field]').trigger('focus');
|
||||
assert.strictEqual(form.$('.o_field_widget[name=text_field]').height(), height,
|
||||
"autoresize should have been done automatically at rendering");
|
||||
// next assert simply tries to ensure that the textarea isn't stucked to
|
||||
// its minimal size, even after being focused
|
||||
assert.ok(height > 80, "textarea should have an height of at least 80px");
|
||||
|
||||
// save and create a new record to ensure that autoresize is correctly done
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
form.$buttons.find('.o_form_button_create').click();
|
||||
height = form.$('.o_field_widget[name=text_field]').height();
|
||||
// focus the field to manually trigger autoresize
|
||||
form.$('.o_field_widget[name=text_field]').trigger('focus');
|
||||
assert.strictEqual(form.$('.o_field_widget[name=text_field]').height(), height,
|
||||
"autoresize should have been done automatically at rendering");
|
||||
assert.ok(height > 80, "textarea should have an height of at least 80px");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -595,23 +595,43 @@ QUnit.module('Views', {
|
||||
arch: '<tree editable="bottom"><field name="int_field" sum="Sum"/></tree>',
|
||||
});
|
||||
|
||||
var $group_1_header = list.$('.o_group_header').filter(function (index, el) {
|
||||
var $groupHeader1 = list.$('.o_group_header').filter(function (index, el) {
|
||||
return $(el).data('group').res_id === 1;
|
||||
});
|
||||
var $group_2_header = list.$('.o_group_header').filter(function (index, el) {
|
||||
var $groupHeader2 = list.$('.o_group_header').filter(function (index, el) {
|
||||
return $(el).data('group').res_id === 2;
|
||||
});
|
||||
assert.strictEqual($group_1_header.find('td:nth(1)').text(), "23", "first group total should be 23");
|
||||
assert.strictEqual($group_2_header.find('td:nth(1)').text(), "9", "second group total should be 9");
|
||||
assert.strictEqual($groupHeader1.find('td:nth(1)').text(), "23", "first group total should be 23");
|
||||
assert.strictEqual($groupHeader2.find('td:nth(1)').text(), "9", "second group total should be 9");
|
||||
assert.strictEqual(list.$('tfoot td:nth(2)').text(), "32", "total should be 32");
|
||||
|
||||
$group_1_header.click();
|
||||
$groupHeader1.click();
|
||||
list.$('tbody .o_list_record_selector input').first().click();
|
||||
assert.strictEqual(list.$('tfoot td:nth(2)').text(), "10",
|
||||
"total should be 10 as first record of first group is selected");
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('aggregates are updated when a line is edited', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="bottom"><field name="int_field" sum="Sum"/></tree>',
|
||||
});
|
||||
|
||||
assert.strictEqual(list.$('td[title="Sum"]').text(), "32", "current total should be 32");
|
||||
|
||||
list.$('tr.o_data_row td.o_data_cell').first().click();
|
||||
list.$('td.o_data_cell input').val("15").trigger("input");
|
||||
|
||||
assert.strictEqual(list.$('td[title="Sum"]').text(), "37",
|
||||
"current total should now be 37");
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('properly apply onchange in simple case', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
@@ -2707,7 +2727,7 @@ QUnit.module('Views', {
|
||||
"should write the sequence starting from the lowest current one");
|
||||
assert.strictEqual(args.field, 'int_field',
|
||||
"should write the right field as sequence");
|
||||
assert.deepEqual(args.ids, [1, 4, 2 , 3],
|
||||
assert.deepEqual(args.ids, [4, 2 , 3],
|
||||
"should write the sequence in correct order");
|
||||
return $.when();
|
||||
}
|
||||
@@ -2760,7 +2780,7 @@ QUnit.module('Views', {
|
||||
"should write the sequence starting from the lowest current one");
|
||||
assert.strictEqual(args.field, 'int_field',
|
||||
"should write the right field as sequence");
|
||||
assert.deepEqual(args.ids, [1, 4, 2, 3],
|
||||
assert.deepEqual(args.ids, [4, 2, 3],
|
||||
"should write the sequence in correct order");
|
||||
return $.when();
|
||||
}
|
||||
@@ -2820,7 +2840,7 @@ QUnit.module('Views', {
|
||||
"should write the sequence starting from the lowest current one");
|
||||
assert.strictEqual(args.field, 'int_field',
|
||||
"should write the right field as sequence");
|
||||
assert.deepEqual(args.ids, [1, 4, 2, 3],
|
||||
assert.deepEqual(args.ids, [4, 2, 3],
|
||||
"should write the sequence in correct order");
|
||||
return $.when(def);
|
||||
}
|
||||
|
||||
@@ -539,6 +539,13 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
|
||||
so = 0;
|
||||
eo = 1;
|
||||
}
|
||||
|
||||
var parentNode = sc && sc.parentNode;
|
||||
if (parentNode && sc.tagName === 'BR') {
|
||||
sc = parentNode;
|
||||
ec = parentNode;
|
||||
}
|
||||
|
||||
return {
|
||||
sc: sc,
|
||||
so: so,
|
||||
@@ -1130,9 +1137,9 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
|
||||
if (!r) return;
|
||||
|
||||
if (!r.isCollapsed()) {
|
||||
if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
|
||||
if ((dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) && dom.node(r.sc) !== dom.node(r.ec)) {
|
||||
remove_table_content(r);
|
||||
r = range.create(r.ec, 0).select();
|
||||
r = range.create(r.ec, 0);
|
||||
}
|
||||
r.select();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ odoo.define('website_sale_delivery.checkout', function (require) {
|
||||
|
||||
/* Handle interactive carrier choice + cart update */
|
||||
var $pay_button = $('#o_payment_form_pay');
|
||||
$pay_button.prop('disabled', false);
|
||||
|
||||
var _onCarrierUpdateAnswer = function(result) {
|
||||
var $amount_delivery = $('#order_delivery span.oe_currency_value');
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Algeria, 10-19-2017
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Ayman Nedjmeddine theycallmethedr@gmail.com https://github.com/IOAyman
|
||||
@@ -397,7 +397,8 @@
|
||||
<search string="Search Partner">
|
||||
<field name="name"
|
||||
filter_domain="['|','|',('display_name','ilike',self),('ref','=',self),('email','ilike',self)]"/>
|
||||
<filter help="My Partners" domain="[('user_id','=',uid)]"/>
|
||||
<filter name="filter_my_partners" help="My Partners"
|
||||
domain="[('user_id','=',uid)]"/>
|
||||
<separator/>
|
||||
<filter string="Persons" name="type_person" domain="[('is_company','=',0)]"/>
|
||||
<filter string="Companies" name="type_company" domain="[('is_company','=',1)]"/>
|
||||
@@ -411,8 +412,8 @@
|
||||
<field name="parent_id" domain="[('is_company','=',1)]" operator="child_of"/>
|
||||
<group expand="0" name="group_by" string="Group By">
|
||||
<filter name="salesperson" string="Salesperson" domain="[]" context="{'group_by' : 'user_id'}" />
|
||||
<filter string="Company" context="{'group_by': 'parent_id'}"/>
|
||||
<filter string="Country" context="{'group_by': 'country_id'}"/>
|
||||
<filter name="group_company" string="Company" context="{'group_by': 'parent_id'}"/>
|
||||
<filter name="group_country" string="Country" context="{'group_by': 'country_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
+1
-1
@@ -605,7 +605,7 @@ class ConnectionPool(object):
|
||||
cnx.close()
|
||||
break
|
||||
else:
|
||||
raise PoolError('This connection does not below to the pool')
|
||||
raise PoolError('This connection does not belong to the pool')
|
||||
|
||||
@locked
|
||||
def close_all(self, dsn=None):
|
||||
|
||||
Reference in New Issue
Block a user