[MERGE] forward port of branch 8.0 up to 2e092ac
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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> -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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."))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user