[MERGE] forward port of branch 8.0 up to 2e092ac

This commit is contained in:
Christophe Simonis
2014-12-03 15:18:36 +01:00
28 changed files with 235 additions and 135 deletions
+2 -2
View File
@@ -884,11 +884,11 @@ class account_voucher(osv.osv):
else:
currency_id = journal.company_id.currency_id.id
period_id = self.pool['account.period'].find(cr, uid, context=dict(context, company_id=company_id))
period_ids = self.pool['account.period'].find(cr, uid, context=dict(context, company_id=company_id))
vals['value'].update({
'currency_id': currency_id,
'payment_rate_currency_id': currency_id,
'period_id' : period_id
'period_id': period_ids and period_ids[0] or False
})
#in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal
#without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid
@@ -73,6 +73,9 @@ class mail_compose_message(osv.TransientModel):
result['res_id'] = result.get('res_id', context.get('active_id'))
result['parent_id'] = result.get('parent_id', context.get('message_id'))
if not result['model'] or not self.pool.get(result['model']) or not hasattr(self.pool[result['model']], 'message_post'):
result['no_auto_thread'] = True
# default values according to composition mode - NOTE: reply is deprecated, fall back on comment
if result['composition_mode'] == 'reply':
result['composition_mode'] = 'comment'
-1
View File
@@ -111,7 +111,6 @@ class procurement_order(osv.osv):
res[procurement.id] = produce_id
self.write(cr, uid, [procurement.id], {'production_id': produce_id})
procurement.refresh()
self.production_order_create_note(cr, uid, procurement, context=context)
production_obj.action_compute(cr, uid, [produce_id], properties=[x.id for x in procurement.property_ids])
production_obj.signal_workflow(cr, uid, [produce_id], 'button_confirm')
+4
View File
@@ -243,6 +243,10 @@ class stock_warehouse(osv.osv):
'manufacture_pull_id': fields.many2one('procurement.rule', 'Manufacture Rule'),
}
_defaults = {
'manufacture_to_resupply': True,
}
def _get_manufacture_pull_rule(self, cr, uid, warehouse, context=None):
route_obj = self.pool.get('stock.location.route')
data_obj = self.pool.get('ir.model.data')
-1
View File
@@ -203,7 +203,6 @@ class procurement_order(osv.osv):
if procurement.state not in ("running", "done"):
try:
if self._assign(cr, uid, procurement, context=context):
procurement.refresh()
res = self._run(cr, uid, procurement, context=context or {})
if res:
self.write(cr, uid, [procurement.id], {'state': 'running'}, context=context)
+6 -5
View File
@@ -142,7 +142,7 @@ class product_uom(osv.osv):
string='Bigger Ratio',
help='How many times this Unit of Measure is bigger than the reference Unit of Measure in this category:\n'\
'1 * (this unit) = ratio * (reference unit)', required=True),
'rounding': fields.float('Rounding Precision', digits_compute=dp.get_precision('Product Unit of Measure'), required=True,
'rounding': fields.float('Rounding Precision', digits=0, required=True,
help="The computed quantity will be a multiple of this value. "\
"Use 1.0 for a Unit of Measure that cannot be further split, such as a piece."),
'active': fields.boolean('Active', help="By unchecking the active field you can disable a unit of measure without deleting it."),
@@ -156,13 +156,14 @@ class product_uom(osv.osv):
'rounding': 0.01,
'factor': 1,
'uom_type': 'reference',
'factor': 1.0,
}
_sql_constraints = [
('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!')
]
def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False, round=True):
def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False, round=True, rounding_method='UP'):
if not from_uom_id or not qty or not to_uom_id:
return qty
uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
@@ -170,9 +171,9 @@ class product_uom(osv.osv):
from_unit, to_unit = uoms[0], uoms[-1]
else:
from_unit, to_unit = uoms[-1], uoms[0]
return self._compute_qty_obj(cr, uid, from_unit, qty, to_unit, round=round)
return self._compute_qty_obj(cr, uid, from_unit, qty, to_unit, round=round, rounding_method=rounding_method)
def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, round=True, context=None):
def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, round=True, rounding_method='UP', context=None):
if context is None:
context = {}
if from_unit.category_id.id != to_unit.category_id.id:
@@ -184,7 +185,7 @@ class product_uom(osv.osv):
if to_unit:
amount = amount * to_unit.factor
if round:
amount = ceiling(amount, to_unit.rounding)
amount = float_round(amount, precision_rounding=to_unit.rounding, rounding_method=rounding_method)
return amount
def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
+2 -1
View File
@@ -217,6 +217,7 @@
<field name="view_type">form</field>
<field name="context">{'search_default_product_tmpl_id': [active_id], 'default_product_tmpl_id': active_id}</field>
<field name="search_view_id" ref="product_search_form_view"/>
<field name="view_id" eval="False"/> <!-- Force empty -->
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define a new product.
@@ -624,7 +625,7 @@
</group>
<group>
<field name="active"/>
<field name="rounding"/>
<field name="rounding" digits="[42, 5]"/>
</group>
</group>
</form>
+1 -1
View File
@@ -100,7 +100,7 @@ class stock_move(osv.osv):
# If partner given, search price in its purchase pricelist
if partner and partner.property_product_pricelist_purchase:
pricelist_obj = self.pool.get("product.pricelist")
pricelist = partner.property_product_pricelist.id
pricelist = partner.property_product_pricelist_purchase.id
price = pricelist_obj.price_get(cr, uid, [pricelist],
move.product_id.id, move.product_uom_qty, partner, {
'uom': move.product_uom.id,
+11 -6
View File
@@ -23,6 +23,7 @@ from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.tools.safe_eval import safe_eval as eval
import openerp.addons.decimal_precision as dp
from openerp.tools.float_utils import float_round
class product_product(osv.osv):
_inherit = "product.product"
@@ -136,14 +137,18 @@ class product_product(osv.osv):
moves_in = dict(map(lambda x: (x['product_id'][0], x['product_qty']), moves_in))
moves_out = dict(map(lambda x: (x['product_id'][0], x['product_qty']), moves_out))
res = {}
for id in ids:
for product in self.browse(cr, uid, ids, context=context):
id = product.id
qty_available = float_round(quants.get(id, 0.0), precision_rounding=product.uom_id.rounding)
incoming_qty = float_round(moves_in.get(id, 0.0), precision_rounding=product.uom_id.rounding)
outgoing_qty = float_round(moves_out.get(id, 0.0), precision_rounding=product.uom_id.rounding)
virtual_available = float_round(quants.get(id, 0.0) + moves_in.get(id, 0.0) - moves_out.get(id, 0.0), precision_rounding=product.uom_id.rounding)
res[id] = {
'qty_available': quants.get(id, 0.0),
'incoming_qty': moves_in.get(id, 0.0),
'outgoing_qty': moves_out.get(id, 0.0),
'virtual_available': quants.get(id, 0.0) + moves_in.get(id, 0.0) - moves_out.get(id, 0.0),
'qty_available': qty_available,
'incoming_qty': incoming_qty,
'outgoing_qty': outgoing_qty,
'virtual_available': virtual_available,
}
return res
def _search_product_quantity(self, cr, uid, obj, name, domain, context):
+94 -61
View File
@@ -25,6 +25,7 @@ import json
import time
from openerp.osv import fields, osv
from openerp.tools.float_utils import float_compare, float_round
from openerp.tools.translate import _
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
from openerp import SUPERUSER_ID, api
@@ -377,9 +378,10 @@ class stock_quant(osv.osv):
if move.picking_id:
self.pool.get('stock.picking').write(cr, uid, [move.picking_id.id], {'recompute_pack_op': True}, context=context)
#check if move'state needs to be set as 'assigned'
if reserved_availability == move.product_qty and move.state in ('confirmed', 'waiting'):
rounding = move.product_id.uom_id.rounding
if float_compare(reserved_availability, move.product_qty, precision_rounding=rounding) == 0 and move.state in ('confirmed', 'waiting') :
self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context)
elif reserved_availability > 0 and not move.partially_available:
elif float_compare(reserved_availability, 0, precision_rounding=rounding) > 0 and not move.partially_available:
self.pool.get('stock.move').write(cr, uid, [move.id], {'partially_available': True}, context=context)
def quants_move(self, cr, uid, quants, move, location_to, location_from=False, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
@@ -402,7 +404,6 @@ class stock_quant(osv.osv):
quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=location_from, force_location_to=location_to, context=context)
else:
self._quant_split(cr, uid, quant, qty, context=context)
quant.refresh()
to_move_quants.append(quant)
quants_reconcile.append(quant)
if to_move_quants:
@@ -412,7 +413,6 @@ class stock_quant(osv.osv):
if location_to.usage == 'internal':
if self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context):
for quant in quants_reconcile:
quant.refresh()
self._quant_reconcile_negative(cr, uid, quant, move, context=context)
def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None):
@@ -438,7 +438,8 @@ class stock_quant(osv.osv):
if not prefered_domain_list:
return self.quants_get(cr, uid, location, product, qty, domain=domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context)
for prefered_domain in prefered_domain_list:
if res_qty > 0:
res_qty_cmp = float_compare(res_qty, 0, precision_rounding=product.uom_id.rounding)
if res_qty_cmp > 0:
#try to replace the last tuple (None, res_qty) with something that wasn't chosen at first because of the prefered order
quants.pop()
tmp_quants = self.quants_get(cr, uid, location, product, res_qty, domain=domain + prefered_domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context)
@@ -485,10 +486,11 @@ class stock_quant(osv.osv):
context = {}
price_unit = self.pool.get('stock.move').get_price_unit(cr, uid, move, context=context)
location = force_location_to or move.location_dest_id
rounding = move.product_id.uom_id.rounding
vals = {
'product_id': move.product_id.id,
'location_id': location.id,
'qty': qty,
'qty': float_round(qty, precision_rounding=rounding),
'cost': price_unit,
'history_ids': [(4, move.id)],
'in_date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
@@ -503,7 +505,7 @@ class stock_quant(osv.osv):
#it means that a negative quant has to be created as well.
negative_vals = vals.copy()
negative_vals['location_id'] = force_location_from and force_location_from.id or move.location_id.id
negative_vals['qty'] = -qty
negative_vals['qty'] = float_round(-qty, precision_rounding=rounding)
negative_vals['cost'] = price_unit
negative_vals['negative_move_id'] = move.id
negative_vals['package_id'] = src_package_id
@@ -516,11 +518,13 @@ class stock_quant(osv.osv):
def _quant_split(self, cr, uid, quant, qty, context=None):
context = context or {}
if (quant.qty > 0 and quant.qty <= qty) or (quant.qty <= 0 and quant.qty >= qty):
rounding = quant.product_id.uom_id.rounding
if float_compare(abs(quant.qty), abs(qty), precision_rounding=rounding) <= 0: # if quant <= qty in abs, take it entirely
return False
new_quant = self.copy(cr, SUPERUSER_ID, quant.id, default={'qty': quant.qty - qty}, context=context)
self.write(cr, SUPERUSER_ID, quant.id, {'qty': qty}, context=context)
quant.refresh()
qty_round = float_round(qty, precision_rounding=rounding)
new_qty_round = float_round(quant.qty - qty, precision_rounding=rounding)
new_quant = self.copy(cr, SUPERUSER_ID, quant.id, default={'qty': new_qty_round, 'history_ids': [(4, x.id) for x in quant.history_ids]}, context=context)
self.write(cr, SUPERUSER_ID, quant.id, {'qty': qty_round}, context=context)
return self.browse(cr, uid, new_quant, context=context)
def _get_latest_move(self, cr, uid, quant, context=None):
@@ -549,9 +553,11 @@ class stock_quant(osv.osv):
dom += [('lot_id', '=', quant.lot_id.id)]
dom += [('owner_id', '=', quant.owner_id.id)]
dom += [('package_id', '=', quant.package_id.id)]
dom += [('id', '!=', quant.propagated_from_id.id)]
quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context)
product_uom_rounding = quant.product_id.uom_id.rounding
for quant_neg, qty in quants:
if not quant_neg:
if not quant_neg or not solving_quant:
continue
to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context)
if not to_solve_quant_ids:
@@ -559,7 +565,7 @@ class stock_quant(osv.osv):
solving_qty = qty
solved_quant_ids = []
for to_solve_quant in self.browse(cr, uid, to_solve_quant_ids, context=context):
if solving_qty <= 0:
if float_compare(solving_qty, 0, precision_rounding=product_uom_rounding) <= 0:
continue
solved_quant_ids.append(to_solve_quant.id)
self._quant_split(cr, uid, to_solve_quant, min(solving_qty, to_solve_quant.qty), context=context)
@@ -571,12 +577,15 @@ class stock_quant(osv.osv):
remaining_to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', 'not in', solved_quant_ids)], context=context)
if remaining_to_solve_quant_ids:
self.write(cr, SUPERUSER_ID, remaining_to_solve_quant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context)
if solving_quant.propagated_from_id and solved_quant_ids:
self.write(cr, uid, solved_quant_ids, {'propagated_from_id': solving_quant.propagated_from_id.id}, context=context)
#delete the reconciled quants, as it is replaced by the solved quants
self.unlink(cr, SUPERUSER_ID, [quant_neg.id], context=context)
#price update + accounting entries adjustments
self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context)
#merge history (and cost?)
self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context)
if solved_quant_ids:
#price update + accounting entries adjustments
self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context)
#merge history (and cost?)
self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context)
self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context)
solving_quant = remaining_solving_quant
@@ -607,16 +616,17 @@ class stock_quant(osv.osv):
domain += [('company_id', '=', self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id)]
res = []
offset = 0
while quantity > 0:
while float_compare(quantity, 0, precision_rounding=product.uom_id.rounding) > 0:
quants = self.search(cr, uid, domain, order=orderby, limit=10, offset=offset, context=context)
if not quants:
res.append((None, quantity))
break
for quant in self.browse(cr, uid, quants, context=context):
if quantity >= abs(quant.qty):
rounding = product.uom_id.rounding
if float_compare(quantity, abs(quant.qty), precision_rounding=rounding) >= 0:
res += [(quant, abs(quant.qty))]
quantity -= abs(quant.qty)
elif quantity != 0:
elif float_compare(quantity, 0.0, precision_rounding=rounding) != 0:
res += [(quant, quantity)]
quantity = 0
break
@@ -864,7 +874,6 @@ class stock_picking(osv.osv):
for pick in self.browse(cr, uid, ids, context=context):
if pick.state == 'draft':
self.action_confirm(cr, uid, [pick.id], context=context)
pick.refresh()
#skip the moves that don't need to be checked
move_ids = [x.id for x in pick.move_lines if x.state not in ('draft', 'cancel', 'done')]
if not move_ids:
@@ -1015,6 +1024,14 @@ class stock_picking(osv.osv):
product_putaway_strats[product.id] = location
return location or picking.location_dest_id.id
# If we encounter an UoM that is smaller than the default UoM or the one already chosen, use the new one instead.
product_uom = {} # Determines UoM used in pack operations
for move in picking.move_lines:
if not product_uom.get(move.product_id.id):
product_uom[move.product_id.id] = move.product_id.uom_id.id
if move.product_uom.id != move.product_id.uom_id.id and move.product_uom.factor > product_uom[move.product_id.id]:
product_uom[move.product_id.id] = move.product_uom.id
pack_obj = self.pool.get("stock.quant.package")
quant_obj = self.pool.get("stock.quant")
vals = []
@@ -1065,17 +1082,24 @@ class stock_picking(osv.osv):
qtys_grouped[key] = qty
# Create the necessary operations for the grouped quants and remaining qtys
uom_obj = self.pool.get('product.uom')
for key, qty in qtys_grouped.items():
product = self.pool.get("product.product").browse(cr, uid, key[0], context=context)
uom_id = product.uom_id.id
qty_uom = qty
if product_uom.get(key[0]):
uom_id = product_uom[key[0]]
qty_uom = uom_obj._compute_qty(cr, uid, product.uom_id.id, qty, uom_id)
vals.append({
'picking_id': picking.id,
'product_qty': qty,
'product_qty': qty_uom,
'product_id': key[0],
'package_id': key[1],
'lot_id': key[2],
'owner_id': key[3],
'location_id': key[4],
'location_dest_id': key[5],
'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id,
'product_uom_id': uom_id,
})
return vals
@@ -1111,8 +1135,8 @@ class stock_picking(osv.osv):
move_quants = move.reserved_quant_ids
picking_quants += move_quants
forced_qty = (move.state == 'assigned') and move.product_qty - sum([x.qty for x in move_quants]) or 0
#if we used force_assign() on the move, or if the move is incomming, forced_qty > 0
if forced_qty:
#if we used force_assign() on the move, or if the move is incoming, forced_qty > 0
if float_compare(forced_qty, 0, precision_rounding=move.product_id.uom_id.rounding) > 0:
if forced_qties.get(move.product_id):
forced_qties[move.product_id] += forced_qty
else:
@@ -1165,15 +1189,21 @@ class stock_picking(osv.osv):
'''method that creates the link between a given operation and move(s) of given product, for the given quantity.
Returns True if it was possible to create links for the requested quantity (False if there was not enough quantity on stock moves)'''
qty_to_assign = qty
prod_obj = self.pool.get("product.product")
product = prod_obj.browse(cr, uid, product_id)
rounding = product.uom_id.rounding
qtyassign_cmp = float_compare(qty_to_assign, 0.0, precision_rounding=rounding)
if prod2move_ids.get(product_id):
while prod2move_ids[product_id] and qty_to_assign > 0:
while prod2move_ids[product_id] and qtyassign_cmp > 0:
qty_on_link = _create_link_for_index(operation_id, 0, product_id, qty_to_assign, quant_id=False)
qty_to_assign -= qty_on_link
return qty_to_assign == 0
qtyassign_cmp = float_compare(qty_to_assign, 0.0, precision_rounding=rounding)
return qtyassign_cmp == 0
uom_obj = self.pool.get('product.uom')
package_obj = self.pool.get('stock.quant.package')
quant_obj = self.pool.get('stock.quant')
link_obj = self.pool.get('stock.move.operation.link')
quants_in_package_done = set()
prod2move_ids = {}
still_to_do = []
@@ -1189,8 +1219,9 @@ class stock_picking(osv.osv):
operations = picking.pack_operation_ids
operations = sorted(operations, key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
#delete existing operations to start again from scratch
cr.execute("DELETE FROM stock_move_operation_link WHERE operation_id in %s", (tuple([x.id for x in operations]),))
links = link_obj.search(cr, uid, [('operation_id', 'in', [x.id for x in operations])], context=context)
if links:
link_obj.unlink(cr, uid, links, context=context)
#1) first, try to create links when quants can be identified without any doubt
for ops in operations:
#for each operation, create the links with the stock move by seeking on the matching reserved quants,
@@ -1230,7 +1261,8 @@ class stock_picking(osv.osv):
max_qty_on_link = min(quant.qty, qty_to_assign)
qty_on_link = _create_link_for_quant(ops.id, quant, max_qty_on_link)
qty_to_assign -= qty_on_link
if qty_to_assign > 0:
qty_assign_cmp = float_compare(qty_to_assign, 0, precision_rounding=ops.product_id.uom_id.rounding)
if qty_assign_cmp > 0:
#qty reserved is less than qty put in operations. We need to create a link but it's deferred after we processed
#all the quants (because they leave no choice on their related move and needs to be processed with higher priority)
still_to_do += [(ops, ops.product_id.id, qty_to_assign)]
@@ -1259,14 +1291,22 @@ class stock_picking(osv.osv):
"""
Creates an extra move when there is no corresponding original move to be copied
"""
uom_obj = self.pool.get("product.uom")
uom_id = product.uom_id.id
qty = remaining_qty
if op.product_id and op.product_uom_id and op.product_uom_id.id != product.uom_id.id:
if op.product_uom_id.factor > product.uom_id.factor: #If the pack operation's is a smaller unit
uom_id = op.product_uom_id.id
#HALF-UP rounding as only rounding errors will be because of propagation of error from default UoM
qty = uom_obj._compute_qty_obj(cr, uid, product.uom_id, remaining_qty, op.product_uom_id, rounding_method='HALF-UP')
picking = op.picking_id
res = {
'picking_id': picking.id,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
'product_id': product.id,
'product_uom': product.uom_id.id,
'product_uom_qty': remaining_qty,
'product_uom': uom_id,
'product_uom_qty': qty,
'name': _('Extra Move: ') + product.name,
'state': 'draft',
}
@@ -1281,8 +1321,8 @@ class stock_picking(osv.osv):
moves = []
for op in picking.pack_operation_ids:
for product_id, remaining_qty in operation_obj._get_remaining_prod_quantities(cr, uid, op, context=context).items():
if remaining_qty > 0:
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
if float_compare(remaining_qty, 0, precision_rounding=product.uom_id.rounding) > 0:
vals = self._prepare_values_extra_move(cr, uid, op, product, remaining_qty, context=context)
moves.append(move_obj.create(cr, uid, vals, context=context))
if moves:
@@ -1341,9 +1381,7 @@ class stock_picking(osv.osv):
if not all_op_processed:
todo_move_ids += self._create_extra_moves(cr, uid, picking, context=context)
picking.refresh()
#split move lines eventually
#split move lines if needed
toassign_move_ids = []
for move in picking.move_lines:
remaining_qty = move.remaining_qty
@@ -1352,10 +1390,11 @@ class stock_picking(osv.osv):
continue
elif move.state == 'draft':
toassign_move_ids.append(move.id)
if remaining_qty == 0:
if float_compare(remaining_qty, 0, precision_rounding = move.product_id.uom_id.rounding) == 0:
if move.state in ('draft', 'assigned', 'confirmed'):
todo_move_ids.append(move.id)
elif remaining_qty > 0 and remaining_qty < move.product_qty:
elif float_compare(remaining_qty,0, precision_rounding = move.product_id.uom_id.rounding) > 0 and \
float_compare(remaining_qty, move.product_qty, precision_rounding = move.product_id.uom_id.rounding) < 0:
new_move = stock_move_obj.split(cr, uid, move, remaining_qty, context=context)
todo_move_ids.append(move.id)
#Assign move as it was assigned before
@@ -1368,7 +1407,6 @@ class stock_picking(osv.osv):
self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context)
elif context.get('do_only_split'):
context = dict(context, split=todo_move_ids)
picking.refresh()
self._create_backorder(cr, uid, picking, context=context)
if toassign_move_ids:
stock_move_obj.action_assign(cr, uid, toassign_move_ids, context=context)
@@ -1549,7 +1587,7 @@ class stock_move(osv.osv):
uom_obj = self.pool.get('product.uom')
res = {}
for m in self.browse(cr, uid, ids, context=context):
res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, round=False, context=context)
res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, context=context)
return res
def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None):
@@ -1559,8 +1597,8 @@ class stock_move(osv.osv):
qty = move.product_qty
for record in move.linked_move_operation_ids:
qty -= record.qty
#converting the remaining quantity in the move UoM
res[move.id] = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom, round=False, context=context)
# Keeping in product default UoM
res[move.id] = float_round(qty, precision_rounding=move.product_id.uom_id.rounding)
return res
def _get_lot_ids(self, cr, uid, ids, field_name, args, context=None):
@@ -1654,11 +1692,10 @@ class stock_move(osv.osv):
'date': fields.datetime('Date', required=True, select=True, help="Move date: scheduled date until move is done, then date of actual move processing", states={'done': [('readonly', True)]}),
'date_expected': fields.datetime('Expected Date', states={'done': [('readonly', True)]}, required=True, select=True, help="Scheduled date for the processing of this move"),
'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type', '<>', 'service')], states={'done': [('readonly', True)]}),
'product_qty': fields.function(_quantity_normalize, fnct_inv=_set_product_qty, _type='float', store={
'product_qty': fields.function(_quantity_normalize, fnct_inv=_set_product_qty, type='float', digits=0, store={
'stock.move': (lambda self, cr, uid, ids, ctx: ids, ['product_id', 'product_uom_qty', 'product_uom'], 20),
'product.product': (_get_moves_from_prod, ['uom_id'], 20),
}, string='Quantity',
digits_compute=dp.get_precision('Product Unit of Measure'),
help='Quantity in the default UoM of the product'),
'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
required=True, states={'done': [('readonly', True)]},
@@ -1715,8 +1752,8 @@ class stock_move(osv.osv):
'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants'),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'move_id', string='Linked Operations', readonly=True, help='Operations that impact this move for the computation of the remaining quantities'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', digits=0,
states={'done': [('readonly', True)]}, help="Remaining Quantity in default UoM according to operations matched with this move"),
'procurement_id': fields.many2one('procurement.order', 'Procurement'),
'group_id': fields.many2one('procurement.group', 'Procurement Group'),
'rule_id': fields.many2one('procurement.rule', 'Procurement Rule', help='The pull rule that created this stock move'),
@@ -1808,9 +1845,9 @@ class stock_move(osv.osv):
'company_id': move.company_id and move.company_id.id or False,
'date_planned': move.date,
'product_id': move.product_id.id,
'product_qty': move.product_qty,
'product_qty': move.product_uom_qty,
'product_uom': move.product_uom.id,
'product_uos_qty': (move.product_uos and move.product_uos_qty) or move.product_qty,
'product_uos_qty': (move.product_uos and move.product_uos_qty) or move.product_uom_qty,
'product_uos': (move.product_uos and move.product_uos.id) or move.product_uom.id,
'location_id': move.location_id.id,
'move_dest_id': move.id,
@@ -2170,7 +2207,6 @@ class stock_move(osv.osv):
for move in todo_moves:
if move.linked_move_operation_ids:
continue
move.refresh()
#then if the move isn't totally assigned, try to find quants without any specific domain
if move.state != 'assigned':
qty_already_assigned = move.reserved_availability
@@ -2300,11 +2336,14 @@ class stock_move(osv.osv):
# Handle pack in pack
if not ops.product_id and ops.package_id and ops.result_package_id.id != ops.package_id.parent_id.id:
self.pool.get('stock.quant.package').write(cr, SUPERUSER_ID, [ops.package_id.id], {'parent_id': ops.result_package_id.id}, context=context)
if not move_qty.get(move.id):
raise osv.except_osv(_("Error"), _("The roundings of your Unit of Measures %s on the move vs. %s on the product don't allow to do these operations or you are not transferring the picking at once. ") % (move.product_uom.name, move.product_id.uom_id.name))
move_qty[move.id] -= record.qty
#Check for remaining qtys and unreserve/check move_dest_id in
move_dest_ids = set()
for move in self.browse(cr, uid, ids, context=context):
if move_qty[move.id] > 0: # (=In case no pack operations in picking)
move_qty_cmp = float_compare(move_qty[move.id], 0, precision_rounding=move.product_id.uom_id.rounding)
if move_qty_cmp > 0: # (=In case no pack operations in picking)
main_domain = [('qty', '>', 0)]
prefered_domain = [('reservation_id', '=', move.id)]
fallback_domain = [('reservation_id', '=', False)]
@@ -2418,7 +2457,8 @@ class stock_move(osv.osv):
uom_obj = self.pool.get('product.uom')
context = context or {}
uom_qty = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom)
#HALF-UP rounding as only rounding errors will be because of propagation of error from default UoM
uom_qty = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom, rounding_method='HALF-UP', context=context)
uos_qty = uom_qty * move.product_uos_qty / move.product_uom_qty
defaults = {
@@ -2571,7 +2611,6 @@ class stock_inventory(osv.osv):
if inventory_line.product_qty < 0 and inventory_line.product_qty != inventory_line.theoretical_qty:
raise osv.except_osv(_('Warning'), _('You cannot set a negative product quantity in an inventory line:\n\t%s - qty: %s' % (inventory_line.product_id.name, inventory_line.product_qty)))
self.action_check(cr, uid, [inv.id], context=context)
inv.refresh()
self.write(cr, uid, [inv.id], {'state': 'done'}, context=context)
self.post_inventory(cr, uid, inv, context=context)
return True
@@ -3325,7 +3364,6 @@ class stock_warehouse(osv.osv):
new_id = super(stock_warehouse, self).create(cr, uid, vals=vals, context=context)
warehouse = self.browse(cr, uid, new_id, context=context)
self.create_sequences_and_picking_types(cr, uid, warehouse, context=context)
warehouse.refresh()
#create routes and push/pull rules
new_objects_dict = self.create_routes(cr, uid, new_id, warehouse, context=context)
@@ -3445,7 +3483,6 @@ class stock_warehouse(osv.osv):
self.change_route(cr, uid, ids, warehouse, vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context_with_inactive)
# Check if we need to change something to resupply warehouses and associated MTO rules
self._check_resupply(cr, uid, warehouse, vals.get('reception_steps'), vals.get('delivery_steps'), context=context)
warehouse.refresh()
if vals.get('code') or vals.get('name'):
name = warehouse.name
#rename sequence
@@ -3587,7 +3624,6 @@ class stock_location_path(osv.osv):
'date_expected': newdate,
'location_dest_id': rule.location_dest_id.id
})
move.refresh()
#avoid looping if a push rule is not well configured
if rule.location_dest_id.id != old_dest_location:
#call again push_apply to see if a next step is defined
@@ -3803,10 +3839,7 @@ class stock_pack_operation(osv.osv):
qty = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context)
for record in ops.linked_move_operation_ids:
qty -= record.qty
#converting the remaining quantity in the pack operation UoM
if ops.product_uom_id:
qty = uom_obj._compute_qty_obj(cr, uid, ops.product_id.uom_id, qty, ops.product_uom_id, context=context)
res[ops.id] = qty
res[ops.id] = float_round(qty, precision_rounding=ops.product_id.uom_id.rounding)
return res
def product_id_change(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None):
@@ -3852,7 +3885,7 @@ class stock_pack_operation(osv.osv):
'cost': fields.float("Cost", help="Unit Cost for this product line"),
'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'),
'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'operation_id', string='Linked Moves', readonly=True, help='Moves impacted by this operation for the computation of the remaining quantities'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', digits = 0, string="Remaining Qty", help="Remaining quantity in default UoM according to moves matched with this operation. "),
'location_id': fields.many2one('stock.location', 'Source Location', required=True),
'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True),
'processed': fields.selection([('true','Yes'), ('false','No')],'Has been processed?', required=True),
+1 -1
View File
@@ -103,7 +103,7 @@ class stock_return_picking(osv.osv_memory):
# Cancel assignment of existing chained assigned moves
moves_to_unreserve = []
for move in pick.move_lines:
to_check_moves = [move.move_dest_id]
to_check_moves = [move.move_dest_id] if move.move_dest_id.id else []
while to_check_moves:
current_move = to_check_moves.pop()
if current_move.state not in ('done', 'cancel') and current_move.reserved_quant_ids:
-1
View File
@@ -267,7 +267,6 @@ class stock_move(osv.osv):
def _store_average_cost_price(self, cr, uid, move, context=None):
''' move is a browe record '''
product_obj = self.pool.get('product.product')
move.refresh()
if any([q.qty <= 0 for q in move.quant_ids]):
#if there is a negative quant, the standard price shouldn't be updated
return
+13
View File
@@ -479,6 +479,19 @@
&& this.day === other.day)
? py.True : py.False;
},
replace: function () {
var args = py.PY_parseArgs(arguments, [
['year', py.None], ['month', py.None], ['day', py.None]
]);
var params = {};
for(var key in args) {
if (!args.hasOwnProperty(key)) { continue; }
var arg = args[key];
params[key] = (arg === py.None ? this[key] : asJS(arg));
}
return py.PY_call(datetime.date, params);
},
__add__: function (other) {
if (!py.PY_isInstance(other, datetime.timedelta)) {
return py.NotImplemented;
+16 -16
View File
@@ -8,7 +8,7 @@ Start/Stop the Odoo server
==========================
Odoo uses a client/server architecture in which clients are web browsers
accessing the odoo server via RPC.
accessing the Odoo server via RPC.
Business logic and extension is generally performed on the server side,
although supporting client features (e.g. new data representation such as
@@ -1691,14 +1691,14 @@ server with the library ``xmlrpclib``::
uid = xmlrpclib.ServerProxy(root + 'common').login(DB, USER, PASS)
print "Logged in as %s (uid: %d)" % (USER, uid)
# Create a new idea
# Create a new note
sock = xmlrpclib.ServerProxy(root + 'object')
args = {
'name' : 'Another idea',
'description' : 'This is another idea of mine',
'inventor_id': uid,
'color' : 8,
'memo' : 'This is a note',
'create_uid': uid,
}
idea_id = sock.execute(DB, uid, PASS, 'idea.idea', 'create', args)
note_id = sock.execute(DB, uid, PASS, 'note.note', 'create', args)
.. exercise:: Add a new service to the client
@@ -1780,13 +1780,13 @@ with the standard Python libraries ``urllib2`` and ``json``::
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
uid = call(url, "common", "login", DB, USER, PASS)
# create a new idea
# create a new note
args = {
'name' : 'Another idea',
'description' : 'This is another idea of mine',
'inventor_id': uid,
'color' : 8,
'memo' : 'This is another note',
'create_uid': uid,
}
idea_id = call(url, "object", "execute", DB, uid, PASS, 'idea.idea', 'create', args)
note_id = call(url, "object", "execute", DB, uid, PASS, 'note.note', 'create', args)
Here is the same program, using the library
`jsonrpclib <https://pypi.python.org/pypi/jsonrpclib>`::
@@ -1805,13 +1805,13 @@ Here is the same program, using the library
args = [DB, uid, PASS, model, method] + list(args)
return server.call(service="object", method="execute", args=args)
# create a new idea
# create a new note
args = {
'name' : 'Another idea',
'description' : 'This is another idea of mine',
'inventor_id': uid,
'color' : 8,
'memo' : 'This is another note',
'create_uid': uid,
}
idea_id = invoke('idea.idea', 'create', args)
note_id = invoke('note.note', 'create', args)
Examples can be easily adapted from XML-RPC to JSON-RPC.
+1 -1
View File
@@ -36,7 +36,7 @@ Index: addons/openacademy/views/openacademy.xml
+ </field>
+ </record>
+
+ <!-- top level menu: no paret -->
+ <!-- top level menu: no parent -->
+ <menuitem id="main_openacademy_menu" name="Open Academy"/>
+ <!-- A first level in the left side menu is needed
+ before using action= attribute -->
+1 -1
View File
@@ -133,7 +133,7 @@ Index: addons/openacademy/templates.xml
+ <!-- <template id="listing"> -->
+ <!-- <ul> -->
+ <!-- <li t-foreach="objects" t-as="object"> -->
+ <!-- <a t-attf-href="#{ root }/objects/#{ object.id }"> -->
+ <!-- <a t-attf-href="{{ root }}/objects/{{ object.id }}"> -->
+ <!-- <t t-esc="object.display_name"/> -->
+ <!-- </a> -->
+ <!-- </li> -->
+4 -1
View File
@@ -26,11 +26,12 @@ Index: addons/openacademy/views/session_board.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ addons/openacademy/views/session_board.xml 2014-08-26 17:26:21.531783052 +0200
@@ -0,0 +1,63 @@
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<openerp>
+ <data>
+ <record model="ir.actions.act_window" id="act_session_graph">
+ <field name="name">Attendees by course</field>
+ <field name="res_model">openacademy.session</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">graph</field>
@@ -38,12 +39,14 @@ Index: addons/openacademy/views/session_board.xml
+ ref="openacademy.openacademy_session_graph_view"/>
+ </record>
+ <record model="ir.actions.act_window" id="act_session_calendar">
+ <field name="name">Sessions</field>
+ <field name="res_model">openacademy.session</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">calendar</field>
+ <field name="view_id" ref="openacademy.session_calendar_view"/>
+ </record>
+ <record model="ir.actions.act_window" id="act_course_list">
+ <field name="name">Courses</field>
+ <field name="res_model">openacademy.course</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+2 -2
View File
@@ -30,9 +30,9 @@ Index: addons/openacademy/views/openacademy.xml
+ <templates>
+ <t t-name="kanban-box">
+ <div
+ t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)}
+ t-attf-class="oe_kanban_color_{{kanban_getcolor(record.color.raw_value)}}
+ oe_kanban_global_click_edit oe_semantic_html_override
+ oe_kanban_card #{record.group_fancy==1 ? 'oe_kanban_card_fancy' : ''}">
+ oe_kanban_card {{record.group_fancy==1 ? 'oe_kanban_card_fancy' : ''}}">
+ <div class="oe_dropdown_kanban">
+ <!-- dropdown menu -->
+ <div class="oe_dropdown_toggle">
+1 -1
View File
@@ -8,7 +8,7 @@ Basic set up
============
Create a basic theme module with :command:`odoo.py scaffold` and the ``theme``
template: from the root odoo folder, use
template: from the root Odoo folder, use
.. code-block:: console
+23 -13
View File
@@ -45,7 +45,14 @@ If you browse the ``petstore`` folder, you should see the following content:
.. code-block:: text
oepetstore
|-- images
| |-- alligator.jpg
| |-- ball.jpg
| |-- crazy_circle.jpg
| |-- fish.jpg
| `-- mice.jpg
|-- __init__.py
|-- oepetstore.message_of_the_day.csv
|-- __openerp__.py
|-- petstore_data.xml
|-- petstore.py
@@ -78,9 +85,9 @@ sub-folders are conventional and not strictly necessary.
currently look like::
openerp.oepetstore = function(instance, local) {
var _t = openerp.web._t,
_lt = openerp.web._lt;
var QWeb = openerp.web.qweb;
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
local.HomePage = instance.Widget.extend({
start: function() {
@@ -88,8 +95,8 @@ sub-folders are conventional and not strictly necessary.
},
});
openerp.web.client_actions.add(
'petstore.homepage', 'local.HomePage');
instance.web.client_actions.add(
'petstore.homepage', 'instance.oepetstore.HomePage');
}
Which only prints a small message in the browser's console.
@@ -528,7 +535,7 @@ characteristics:
structural extensibility where an XML-based templating engine can be
generically altered using e.g. XPath or CSS and a tree-alteration DSL (or
even just XSLT). This flexibility and extensibility is a core
characteristic of Odoo, and losting it was considered unacceptable.
characteristic of Odoo, and losing it was considered unacceptable.
Using QWeb
----------
@@ -1121,6 +1128,7 @@ Exercise
},
start: function() {
this.input_changed();
return this._super();
},
input_changed: function() {
var color = [
@@ -1136,9 +1144,9 @@ Exercise
local.HomePage = instance.Widget.extend({
template: "HomePage",
start: function() {
this.colorInput = new local.ColorInputWidget(this)
.on("change:color", this, this.color_changed);
.appendTo(this.$el);
this.colorInput = new local.ColorInputWidget(this);
this.colorInput.on("change:color", this, this.color_changed);
return this.colorInput.appendTo(this.$el);
},
color_changed: function() {
this.$(".oe_color_div").css("background-color", this.colorInput.get("color"));
@@ -1560,8 +1568,10 @@ Exercises
.filter([['categ_id.name', '=', "Pet Toys"]])
.limit(5)
.all()
.then(function (result) {
self.$el.append(QWeb.render('PetToys', {item: item}));
.then(function (results) {
_(results).each(function (item) {
self.$el.append(QWeb.render('PetToy', {item: item}));
});
});
}
});
@@ -1578,7 +1588,7 @@ Exercises
<div class="oe_petstore_homepage_right"></div>
</div>
</t>
<t t-name="MessageofTheDay">
<t t-name="MessageOfTheDay">
<div class="oe_petstore_motd">
<p class="oe_mywidget_message_of_the_day"></p>
</div>
@@ -1732,7 +1742,7 @@ attributes are:
<t t-name="PetToy">
<div class="oe_petstore_pettoy" t-att-data-id="item.id">
<p><t t-esc="item.name"/></p>
<p><img t-attf-src="data:image/jpg;base64,#{item.image}"/></p>
<p><img t-attf-src="data:image/jpg;base64,{{item.image}}"/></p>
</div>
</t>
+6 -6
View File
@@ -142,7 +142,7 @@ First define an Odoo model file and import it:
.. patch::
Then setup :ref:`basic access control <reference/security/acl>` for the model
and and add them to the manifest:
and add them to the manifest:
.. patch::
@@ -211,7 +211,7 @@ integration and a few other services (e.g. default styling, theming) via the
#. then add the ``website=True`` flag on the controller, this sets up a few
new variables on :ref:`the request object <reference/http/request>` and
allows using the website layout in our template
#. use the wesite layout in the template
#. use the website layout in the template
.. patch::
@@ -307,7 +307,7 @@ interfaces. Change the *person* template to use ``t-field``:
Restart Odoo and upgrade the module, there is now a placeholder under the
teacher's name and a new zone for blocks in :guilabel:`Edit` mode. Content
dropped there is stored in the correspoding teacher's ``biography`` field, and
dropped there is stored in the corresponding teacher's ``biography`` field, and
thus specific to that teacher.
The teacher's name is also editable, and when saved the change is visible on
@@ -344,7 +344,7 @@ The conceptual structure of the Odoo backend is simple:
#. actions. Actions have various types: links, reports, code which Odoo should
execute or data display. Data display actions are called *window actions*,
and tell Odoo to display a given *model* according to a set of views…
#. a view has a type, a the broad category to which it corresponds (a list,
#. a view has a type, a broad category to which it corresponds (a list,
a graph, a calendar) and an *architecture* which customises the way the
model is displayed inside the view.
@@ -457,8 +457,8 @@ though they may have to be looked for.
* to extend a model in-place, it's :attr:`inherited
<openerp.models.Model._inherit>` without giving it a new
:attr:`~openerp.models.Model._name`
* ``product.template`` already uses the discussions system, so we
can remove it from our extension model
* ``product.template`` already uses the discussions system, so we can
remove it from our extension model
* we're creating our courses as *published* by default so they can be
seen without having to log in
+8
View File
@@ -132,6 +132,14 @@ root can have the following attributes:
<reference/views/form>`'s fields and buttons are thus accepted by list
views although they may not have any meaning if the list view is
non-editable
``default_order``
overrides the ordering of the view, replacing the model's default order.
The value is a comma-separated list of fields, postfixed by ``desc`` to
sort in reverse order:
.. code-block:: xml
<tree default_order="sequence,name desc">
``colors``
allows changing the color of a row's text based on the corresponding
record's attributes.
+1 -1
View File
@@ -272,7 +272,7 @@ class ir_actions_act_window(osv.osv):
_columns = {
'name': fields.char('Action Name', translate=True),
'type': fields.char('Action Type', required=True),
'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='set null'),
'domain': fields.char('Domain Value',
help="Optional domain filtering of the destination data, as a Python expression"),
'context': fields.char('Context Value', required=True,
+12
View File
@@ -111,6 +111,18 @@ class TestORM(common.TransactionCase):
found = self.partner.search_read(self.cr, UID, [['name', '=', 'Does not exists']], ['name'])
self.assertEqual(len(found), 0)
def test_exists(self):
partner = self.partner.browse(self.cr, UID, [])
# check that records obtained from search exist
recs = partner.search([])
self.assertTrue(recs)
self.assertEqual(recs.exists(), recs)
# check that there is no record with id 0
recs = partner.browse([0])
self.assertFalse(recs.exists())
def test_groupby_date(self):
partners = dict(
A='2012-11-19',
+6 -3
View File
@@ -516,16 +516,19 @@ class JsonRequest(WebRequest):
try:
return super(JsonRequest, self)._handle_exception(exception)
except Exception:
if not isinstance(exception, openerp.exceptions.Warning):
if not isinstance(exception, (openerp.exceptions.Warning, SessionExpiredException)):
_logger.exception("Exception during JSON request handling.")
error = {
'code': 200,
'message': "OpenERP Server Error",
'message': "Odoo Server Error",
'data': serialize_exception(exception)
}
if isinstance(exception, AuthenticationError):
error['code'] = 100
error['message'] = "OpenERP Session Invalid"
error['message'] = "Odoo Session Invalid"
if isinstance(exception, SessionExpiredException):
error['code'] = 100
error['message'] = "Odoo Session Expired"
return self._json_response(error=error)
def dispatch(self):
+11 -5
View File
@@ -241,6 +241,11 @@ class MetaModel(api.Meta):
if not self._custom:
self.module_to_models.setdefault(self._module, []).append(self)
# check for new-api conversion error: leave comma after field definition
for key, val in attrs.iteritems():
if type(val) is tuple and len(val) == 1 and isinstance(val[0], Field):
_logger.error("Trailing comma after field definition: %s.%s", self, key)
# transform columns into new-style fields (enables field inheritance)
for name, column in self._columns.iteritems():
if name in self.__dict__:
@@ -4782,14 +4787,15 @@ class BaseModel(object):
By convention, new records are returned as existing.
"""
ids = filter(None, self._ids) # ids to check in database
ids, new_ids = [], []
for i in self._ids:
(ids if isinstance(i, (int, long)) else new_ids).append(i)
if not ids:
return self
query = """SELECT id FROM "%s" WHERE id IN %%s""" % self._table
self._cr.execute(query, (ids,))
ids = ([r[0] for r in self._cr.fetchall()] + # ids in database
[id for id in self._ids if not id]) # new ids
existing = self.browse(ids)
self._cr.execute(query, [tuple(ids)])
ids = [r[0] for r in self._cr.fetchall()]
existing = self.browse(ids + new_ids)
if len(existing) < len(self):
# mark missing records in cache with a failed value
exc = MissingError(_("Record does not exist or has been deleted."))
+5 -4
View File
@@ -862,10 +862,11 @@ def preload_registries(dbnames):
# run test_file if provided
if test_file:
_logger.info('loading test file %s', test_file)
if test_file.endswith('yml'):
load_test_file_yml(registry, test_file)
elif test_file.endswith('py'):
load_test_file_py(registry, test_file)
with openerp.api.Environment.manage():
if test_file.endswith('yml'):
load_test_file_yml(registry, test_file)
elif test_file.endswith('py'):
load_test_file_py(registry, test_file)
if registry._assertion_report.failures:
rc += 1
+1 -1
View File
@@ -54,4 +54,4 @@ ExecStart=/usr/bin/odoo.py --config=/etc/odoo/openerp-server.conf
[Install]
WantedBy=multi-user.target
EOF
easy_install pyPdf vatnumber pydot
easy_install pyPdf vatnumber pydot psycogreen