[MERGE] forward port of branch 8.0 up to e27744d
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
wiz = wizards.browse(cr, uid, ref('account.account_configuration_installer_todo'))
|
||||
part = self.pool.get('res.partner').browse(cr, uid, ref('base.main_partner'))
|
||||
# if we know the country and the wizard has not yet been executed, we do it
|
||||
if (part.country_id.id) and (wiz.state=='open'):
|
||||
if (part.country_id.id) and (part.country_id.code) and (wiz.state=='open'):
|
||||
mod = 'l10n_'+part.country_id.code.lower()
|
||||
ids = modules.search(cr, uid, [ ('name','=',mod) ], context=context)
|
||||
if ids:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="col-xs-5 col-xs-offset-7">
|
||||
<address t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}' />
|
||||
<span t-field="o.partner_id.vat"/>
|
||||
<span t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -171,10 +171,6 @@ class account_asset_asset(osv.osv):
|
||||
for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
|
||||
i = x + 1
|
||||
amount = self._compute_board_amount(cr, uid, asset, i, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date, context=context)
|
||||
company_currency = asset.company_id.currency_id.id
|
||||
current_currency = asset.currency_id.id
|
||||
# compute amount into company currency
|
||||
amount = currency_obj.compute(cr, uid, current_currency, company_currency, amount, context=context)
|
||||
residual_amount -= amount
|
||||
vals = {
|
||||
'amount': amount,
|
||||
@@ -215,7 +211,10 @@ class account_asset_asset(osv.osv):
|
||||
l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
|
||||
res=dict(cr.fetchall())
|
||||
for asset in self.browse(cr, uid, ids, context):
|
||||
res[asset.id] = asset.purchase_value - res.get(asset.id, 0.0) - asset.salvage_value
|
||||
company_currency = asset.company_id.currency_id.id
|
||||
current_currency = asset.currency_id.id
|
||||
amount = self.pool['res.currency'].compute(cr, uid, company_currency, current_currency, res.get(asset.id, 0.0), context=context)
|
||||
res[asset.id] = asset.purchase_value - amount - asset.salvage_value
|
||||
for id in ids:
|
||||
res.setdefault(id, 0.0)
|
||||
return res
|
||||
|
||||
@@ -27,8 +27,10 @@ from operator import itemgetter
|
||||
class account_move_line(osv.osv):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
# delegate to parent, used for local fields.function redefinition
|
||||
def _amount_residual(self, cr, uid, ids, field_names, args, context=None):
|
||||
return self._amount_residual(self, cr, uid, ids, field_names, args, context=context)
|
||||
return super(account_move_line, self)._amount_residual(
|
||||
cr, uid, ids, field_names, args, context=context)
|
||||
|
||||
def _to_pay_search(self, cr, uid, obj, name, args, context=None):
|
||||
if not args:
|
||||
|
||||
@@ -16,6 +16,18 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Force update of groups' user view.
|
||||
When updating (not new install), as `share` module is not loaded yet, shared groups
|
||||
are taken into account when getting the group list, resulting to a different
|
||||
organisation of the `Sharing` category (booleans instead of a selection) and thus,
|
||||
different virtual field names.
|
||||
When applying then inheritance of the following view, the check of field names
|
||||
failed due to these virtual field name missmatch.
|
||||
This is only for databases updated between a6eac0f08ee9100d1a44a4068d4f580b1c7c4b2c
|
||||
and the commit introducing this line.
|
||||
-->
|
||||
<function model="res.groups" name="update_user_groups_view" />
|
||||
|
||||
<!-- Update user form !-->
|
||||
<record id="view_users_form_mail" model="ir.ui.view">
|
||||
<field name="name">res.users.form.mail</field>
|
||||
|
||||
+20
-5
@@ -1094,14 +1094,27 @@ class mrp_production(osv.osv):
|
||||
#is 1 element long, so we can take the first.
|
||||
return stock_move.action_confirm(cr, uid, [move_id], context=context)[0]
|
||||
|
||||
def _get_raw_material_procure_method(self, cr, uid, product, context=None):
|
||||
'''This method returns the procure_method to use when creating the stock move for the production raw materials'''
|
||||
def _get_raw_material_procure_method(self, cr, uid, product, location_id=False, location_dest_id=False, context=None):
|
||||
'''This method returns the procure_method to use when creating the stock move for the production raw materials
|
||||
Besides the standard configuration of looking if the product or product category has the MTO route,
|
||||
you can also define a rule e.g. from Stock to Production (which might be used in the future like the sale orders)
|
||||
'''
|
||||
warehouse_obj = self.pool['stock.warehouse']
|
||||
routes = product.route_ids + product.categ_id.total_route_ids
|
||||
|
||||
if location_id and location_dest_id:
|
||||
pull_obj = self.pool['procurement.rule']
|
||||
pulls = pull_obj.search(cr, uid, [('route_id', 'in', [x.id for x in routes]),
|
||||
('location_id', '=', location_dest_id),
|
||||
('location_src_id', '=', location_id)], limit=1, context=context)
|
||||
if pulls:
|
||||
return pull_obj.browse(cr, uid, pulls[0], context=context).procure_method
|
||||
|
||||
try:
|
||||
mto_route = warehouse_obj._get_mto_route(cr, uid, context=context)
|
||||
except:
|
||||
return "make_to_stock"
|
||||
routes = product.route_ids + product.categ_id.total_route_ids
|
||||
|
||||
if mto_route in [x.id for x in routes]:
|
||||
return "make_to_order"
|
||||
return "make_to_stock"
|
||||
@@ -1133,7 +1146,8 @@ class mrp_production(osv.osv):
|
||||
move = stock_move.copy(cr, uid, move_id, default = {
|
||||
'location_id': source_location_id,
|
||||
'location_dest_id': dest_location_id,
|
||||
'procure_method': self._get_raw_material_procure_method(cr, uid, product, context=context),
|
||||
'procure_method': self._get_raw_material_procure_method(cr, uid, product, location_id=source_location_id,
|
||||
location_dest_id=dest_location_id, context=context),
|
||||
'raw_material_production_id': False,
|
||||
'move_dest_id': move_id,
|
||||
'picking_type_id': types and types[0] or False,
|
||||
@@ -1166,7 +1180,8 @@ class mrp_production(osv.osv):
|
||||
'location_id': source_location_id,
|
||||
'location_dest_id': destination_location_id,
|
||||
'company_id': production.company_id.id,
|
||||
'procure_method': prev_move and 'make_to_stock' or self._get_raw_material_procure_method(cr, uid, product, context=context), #Make_to_stock avoids creating procurement
|
||||
'procure_method': prev_move and 'make_to_stock' or self._get_raw_material_procure_method(cr, uid, product, location_id=source_location_id,
|
||||
location_dest_id=destination_location_id, context=context), #Make_to_stock avoids creating procurement
|
||||
'raw_material_production_id': production.id,
|
||||
#this saves us a browse in create()
|
||||
'price_unit': product.standard_price,
|
||||
|
||||
@@ -1128,11 +1128,16 @@ class pos_order(osv.osv):
|
||||
if not grouped_data[key]:
|
||||
grouped_data[key].append(values)
|
||||
else:
|
||||
current_value = grouped_data[key][0]
|
||||
current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
|
||||
current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
|
||||
current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
|
||||
current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0)
|
||||
for line in grouped_data[key]:
|
||||
if line.get('tax_code_id') == values.get('tax_code_id'):
|
||||
current_value = line
|
||||
current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
|
||||
current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
|
||||
current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
|
||||
current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0)
|
||||
break
|
||||
else:
|
||||
grouped_data[key].append(values)
|
||||
else:
|
||||
grouped_data[key].append(values)
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
var s = new openerp.init();
|
||||
var wc = new s.web.WebClient();
|
||||
|
||||
wc._title_changed = function() {};
|
||||
wc.show_application = function() {
|
||||
wc.action_manager.do_action("pos.ui");
|
||||
};
|
||||
|
||||
@@ -791,6 +791,12 @@ class product_template(osv.osv):
|
||||
return super(product_template, self).name_get(cr, user, ids, context)
|
||||
|
||||
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
|
||||
# Only use the product.product heuristics if there is a search term and the domain
|
||||
# does not specify a match on `product.template` IDs.
|
||||
if not name or any(term[0] == 'id' for term in (args or [])):
|
||||
return super(product_template, self).name_search(
|
||||
cr, user, name=name, args=args, operator=operator, context=context, limit=limit)
|
||||
|
||||
product_product = self.pool['product.product']
|
||||
results = product_product.name_search(
|
||||
cr, user, name, args, operator=operator, context=context, limit=limit)
|
||||
@@ -798,8 +804,11 @@ class product_template(osv.osv):
|
||||
template_ids = [p.product_tmpl_id.id
|
||||
for p in product_product.browse(
|
||||
cr, user, product_ids, context=context)]
|
||||
uniq_ids = OrderedDict.fromkeys(template_ids).keys()
|
||||
return self.name_get(cr, user, uniq_ids, context=context)
|
||||
|
||||
# re-apply product.template order + name_get
|
||||
return super(product_template, self).name_search(
|
||||
cr, user, '', args=[('id', 'in', template_ids)],
|
||||
operator=operator, context=context, limit=limit)
|
||||
|
||||
class product_product(osv.osv):
|
||||
_name = "product.product"
|
||||
|
||||
@@ -1019,9 +1019,9 @@ class purchase_order_line(osv.osv):
|
||||
if line.state not in ['draft', 'cancel']:
|
||||
raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
procurement_ids_to_cancel = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
|
||||
if procurement_ids_to_cancel:
|
||||
self.pool['procurement.order'].cancel(cr, uid, procurement_ids_to_cancel)
|
||||
procurement_ids_to_except = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
|
||||
if procurement_ids_to_except:
|
||||
self.pool['procurement.order'].write(cr, uid, procurement_ids_to_except, {'state': 'exception'}, context=context)
|
||||
return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
|
||||
|
||||
@@ -550,7 +550,7 @@
|
||||
<field name="name">On Purchase Order Lines</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">purchase.order.line</field>
|
||||
<field name="domain">[('order_id.invoice_method','<>','picking'), ('state','in',('confirmed','done')), ('invoiced', '=', False)]</field>
|
||||
<field name="domain">[('order_id.invoice_method','=','manual'), ('state','in',('confirmed','done')), ('invoiced', '=', False)]</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="purchase_order_line_search"/>
|
||||
|
||||
@@ -39,4 +39,4 @@
|
||||
procurement = self.browse(cr, uid, ref('procurement_order_testcase0'))
|
||||
procurement.purchase_line_id.unlink()
|
||||
procurement.refresh()
|
||||
assert procurement.state == 'cancel', 'Procurement should be cancelled'
|
||||
assert procurement.state == 'exception', 'Procurement should be in exception'
|
||||
|
||||
@@ -578,11 +578,13 @@ class sale_order(osv.osv):
|
||||
context = {}
|
||||
sale_order_line_obj = self.pool.get('sale.order.line')
|
||||
account_invoice_obj = self.pool.get('account.invoice')
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
for sale in self.browse(cr, uid, ids, context=context):
|
||||
for inv in sale.invoice_ids:
|
||||
if inv.state not in ('draft', 'cancel'):
|
||||
raise UserError(_('Cannot cancel this sales order!') + ':' + _('First cancel all invoices attached to this sales order.'))
|
||||
inv.signal_workflow('invoice_cancel')
|
||||
procurement_obj.cancel(cr, uid, sum([l.procurement_ids.ids for l in sale.order_line],[]))
|
||||
sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
|
||||
{'state': 'cancel'})
|
||||
self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
|
||||
@@ -155,19 +155,6 @@ class sale_order(osv.osv):
|
||||
move_obj.write(cr, uid, [x.id for x in picking.move_lines], {'invoice_state': 'invoiced'}, context=context)
|
||||
return res
|
||||
|
||||
def action_cancel(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
sale_order_line_obj = self.pool.get('sale.order.line')
|
||||
proc_obj = self.pool.get('procurement.order')
|
||||
stock_obj = self.pool.get('stock.picking')
|
||||
for sale in self.browse(cr, uid, ids, context=context):
|
||||
for pick in sale.picking_ids:
|
||||
if pick.state not in ('draft', 'cancel'):
|
||||
raise UserError(_('Cannot cancel sales order!') + '\n' + _('You must first cancel all delivery order(s) attached to this sales order.'))
|
||||
stock_obj.signal_workflow(cr, uid, [p.id for p in sale.picking_ids], 'button_cancel')
|
||||
return super(sale_order, self).action_cancel(cr, uid, ids, context=context)
|
||||
|
||||
def action_wait(self, cr, uid, ids, context=None):
|
||||
res = super(sale_order, self).action_wait(cr, uid, ids, context=context)
|
||||
for o in self.browse(cr, uid, ids):
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<field name="name">Shared Group</field>
|
||||
<field name="comment">
|
||||
This group is a template group for shared users, giving basic access needs, the menus for instance.</field>
|
||||
<field name="category_id" ref="module_category_share"/>
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
<field name="share">1</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
@@ -186,7 +186,7 @@ class product_product(osv.osv):
|
||||
operator = '=='
|
||||
|
||||
ids = []
|
||||
if name == 'qty_available':
|
||||
if name == 'qty_available' and (value != 0.0 or operator not in ('==', '>=', '<=')):
|
||||
res.append(('id', 'in', self._search_qty_available(cr, uid, operator, value, context)))
|
||||
else:
|
||||
product_ids = self.search(cr, uid, [], context=context)
|
||||
|
||||
+24
-41
@@ -2660,14 +2660,6 @@ class stock_inventory(osv.osv):
|
||||
self.pool.get('stock.inventory.line').write(cr, uid, line_ids, {'product_qty': 0})
|
||||
return True
|
||||
|
||||
def _inventory_line_hook(self, cr, uid, inventory_line, move_vals):
|
||||
""" Creates a stock move from an inventory line
|
||||
@param inventory_line:
|
||||
@param move_vals:
|
||||
@return:
|
||||
"""
|
||||
return self.pool.get('stock.move').create(cr, uid, move_vals)
|
||||
|
||||
def action_done(self, cr, uid, ids, context=None):
|
||||
""" Finish the inventory
|
||||
@return: True
|
||||
@@ -2686,35 +2678,7 @@ class stock_inventory(osv.osv):
|
||||
#as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior
|
||||
#as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want).
|
||||
move_obj = self.pool.get('stock.move')
|
||||
move_obj.action_done(cr, uid, [x.id for x in inv.move_ids], context=context)
|
||||
|
||||
def _create_stock_move(self, cr, uid, inventory, todo_line, context=None):
|
||||
stock_move_obj = self.pool.get('stock.move')
|
||||
product_obj = self.pool.get('product.product')
|
||||
inventory_location_id = product_obj.browse(cr, uid, todo_line['product_id'], context=context).property_stock_inventory.id
|
||||
vals = {
|
||||
'name': _('INV:') + (inventory.name or ''),
|
||||
'product_id': todo_line['product_id'],
|
||||
'product_uom': todo_line['product_uom_id'],
|
||||
'date': inventory.date,
|
||||
'company_id': inventory.company_id.id,
|
||||
'inventory_id': inventory.id,
|
||||
'state': 'assigned',
|
||||
'restrict_lot_id': todo_line.get('prod_lot_id'),
|
||||
'restrict_partner_id': todo_line.get('partner_id'),
|
||||
}
|
||||
|
||||
if todo_line['product_qty'] < 0:
|
||||
#found more than expected
|
||||
vals['location_id'] = inventory_location_id
|
||||
vals['location_dest_id'] = todo_line['location_id']
|
||||
vals['product_uom_qty'] = -todo_line['product_qty']
|
||||
else:
|
||||
#found less than expected
|
||||
vals['location_id'] = todo_line['location_id']
|
||||
vals['location_dest_id'] = inventory_location_id
|
||||
vals['product_uom_qty'] = todo_line['product_qty']
|
||||
return stock_move_obj.create(cr, uid, vals, context=context)
|
||||
move_obj.action_done(cr, uid, [x.id for x in inv.move_ids if x.state != 'done'], context=context)
|
||||
|
||||
def action_check(self, cr, uid, ids, context=None):
|
||||
""" Checks the inventory and computes the stock move to do
|
||||
@@ -2728,13 +2692,14 @@ class stock_inventory(osv.osv):
|
||||
stock_move_obj.unlink(cr, uid, move_ids, context=context)
|
||||
for line in inventory.line_ids:
|
||||
#compare the checked quantities on inventory lines to the theorical one
|
||||
inventory_line_obj._resolve_inventory_line(cr, uid, line, context=context)
|
||||
stock_move = inventory_line_obj._resolve_inventory_line(cr, uid, line, context=context)
|
||||
|
||||
def action_cancel_draft(self, cr, uid, ids, context=None):
|
||||
""" Cancels the stock move and change inventory state to draft.
|
||||
@return: True
|
||||
"""
|
||||
for inv in self.browse(cr, uid, ids, context=context):
|
||||
self.write(cr, uid, [inv.id], {'line_ids': [(5,)]}, context=context)
|
||||
self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context=context)
|
||||
self.write(cr, uid, [inv.id], {'state': 'draft'}, context=context)
|
||||
return True
|
||||
@@ -2862,7 +2827,7 @@ class stock_inventory_line(osv.osv):
|
||||
|
||||
def _get_quants(self, cr, uid, line, context=None):
|
||||
quant_obj = self.pool["stock.quant"]
|
||||
dom = [('company_id', '=', line.company_id.id), ('location_id', 'child_of', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id),
|
||||
dom = [('company_id', '=', line.company_id.id), ('location_id', '=', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id),
|
||||
('product_id','=', line.product_id.id), ('owner_id', '=', line.partner_id.id), ('package_id', '=', line.package_id.id)]
|
||||
quants = quant_obj.search(cr, uid, dom, context=context)
|
||||
return quants
|
||||
@@ -2882,7 +2847,7 @@ class stock_inventory_line(osv.osv):
|
||||
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
|
||||
if not company_id:
|
||||
company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
|
||||
dom = [('company_id', '=', company_id), ('location_id', 'child_of', location_id), ('lot_id', '=', prod_lot_id),
|
||||
dom = [('company_id', '=', company_id), ('location_id', '=', location_id), ('lot_id', '=', prod_lot_id),
|
||||
('product_id','=', product_id), ('owner_id', '=', partner_id), ('package_id', '=', package_id)]
|
||||
quants = quant_obj.search(cr, uid, dom, context=context)
|
||||
th_qty = sum([x.qty for x in quant_obj.browse(cr, uid, quants, context=context)])
|
||||
@@ -2893,6 +2858,7 @@ class stock_inventory_line(osv.osv):
|
||||
|
||||
def _resolve_inventory_line(self, cr, uid, inventory_line, context=None):
|
||||
stock_move_obj = self.pool.get('stock.move')
|
||||
quant_obj = self.pool.get('stock.quant')
|
||||
diff = inventory_line.theoretical_qty - inventory_line.product_qty
|
||||
if not diff:
|
||||
return
|
||||
@@ -2919,7 +2885,24 @@ class stock_inventory_line(osv.osv):
|
||||
vals['location_id'] = inventory_line.location_id.id
|
||||
vals['location_dest_id'] = inventory_location_id
|
||||
vals['product_uom_qty'] = diff
|
||||
return stock_move_obj.create(cr, uid, vals, context=context)
|
||||
move_id = stock_move_obj.create(cr, uid, vals, context=context)
|
||||
move = stock_move_obj.browse(cr, uid, move_id, context=context)
|
||||
if diff > 0:
|
||||
domain = [('qty', '>', 0.0), ('package_id', '=', inventory_line.package_id.id), ('lot_id', '=', inventory_line.prod_lot_id.id), ('location_id', '=', inventory_line.location_id.id)]
|
||||
preferred_domain_list = [[('reservation_id', '=', False)], [('reservation_id.inventory_id', '!=', inventory_line.inventory_id.id)]]
|
||||
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, move.product_qty, domain=domain, prefered_domain_list=preferred_domain_list, restrict_partner_id=move.restrict_partner_id.id, context=context)
|
||||
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
|
||||
elif inventory_line.package_id:
|
||||
stock_move_obj.action_done(cr, uid, move_id, context=context)
|
||||
quants = [x.id for x in move.quant_ids]
|
||||
quant_obj.write(cr, uid, quants, {'package_id': inventory_line.package_id.id}, context=context)
|
||||
res = quant_obj.search(cr, uid, [('qty', '<', 0.0), ('product_id', '=', move.product_id.id),
|
||||
('location_id', '=', move.location_dest_id.id), ('package_id', '!=', False)], limit=1, context=context)
|
||||
if res:
|
||||
for quant in move.quant_ids:
|
||||
if quant.location_id.id == move.location_dest_id.id: #To avoid we take a quant that was reconcile already
|
||||
quant_obj._quant_reconcile_negative(cr, uid, quant, move, context=context)
|
||||
return move_id
|
||||
|
||||
# Should be left out in next version
|
||||
def restrict_change(self, cr, uid, ids, theoretical_qty, context=None):
|
||||
|
||||
@@ -1095,3 +1095,55 @@ class TestStockFlow(TestStockCommon):
|
||||
total_qty = [quant.qty for quant in quants]
|
||||
self.assertEqual(sum(total_qty), 4000, 'Expecting 4000 kg , got %.4f on location stock!' % (sum(total_qty)))
|
||||
self.assertEqual(productKG.qty_available, 4000, 'Expecting 4000 kg , got %.4f of quantity available!' % (productKG.qty_available))
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# TEST PARTIAL INVENTORY WITH PACKS and LOTS
|
||||
#---------------------------------------------------------
|
||||
|
||||
packproduct = self.ProductObj.create({'name': 'Pack Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id})
|
||||
lotproduct = self.ProductObj.create({'name': 'Lot Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id})
|
||||
inventory = self.InvObj.create({'name': 'Test Partial and Pack',
|
||||
'filter': 'partial',
|
||||
'location_id': self.stock_location})
|
||||
inventory.prepare_inventory()
|
||||
pack_obj = self.env['stock.quant.package']
|
||||
lot_obj = self.env['stock.production.lot']
|
||||
pack1 = pack_obj.create({'name': 'PACK00TEST1'})
|
||||
pack2 = pack_obj.create({'name': 'PACK00TEST2'})
|
||||
lot1 = lot_obj.create({'name': 'Lot001', 'product_id': lotproduct.id})
|
||||
move = self.MoveObj.search([('product_id', '=', productKG.id), ('inventory_id', '=', inventory.id)], limit=1)
|
||||
self.assertEqual(len(move), 0, "Partial filter should not create a lines upon prepare")
|
||||
|
||||
line_vals = []
|
||||
line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 10, 'product_uom_id': packproduct.uom_id.id}]
|
||||
line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 20, 'product_uom_id': packproduct.uom_id.id, 'package_id': pack1.id}]
|
||||
line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 30, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': lot1.id}]
|
||||
line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 25, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': False}]
|
||||
inventory.write({'line_ids': [(0, 0, x) for x in line_vals]})
|
||||
inventory.action_done()
|
||||
self.assertEqual(packproduct.qty_available, 30, "Wrong qty available for packproduct")
|
||||
self.assertEqual(lotproduct.qty_available, 55, "Wrong qty available for lotproduct")
|
||||
quants = self.StockQuantObj.search([('product_id', '=', packproduct.id), ('location_id', '=', self.stock_location), ('package_id', '=', pack1.id)])
|
||||
total_qty = sum([quant.qty for quant in quants])
|
||||
self.assertEqual(total_qty, 20, 'Expecting 20 units on package 1 of packproduct, but we got %.4f on location stock!' % (total_qty))
|
||||
|
||||
#Create an inventory that will put the lots without lot to 0 and check that taking without pack will not take it from the pack
|
||||
inventory2 = self.InvObj.create({'name': 'Test Partial Lot and Pack2',
|
||||
'filter': 'partial',
|
||||
'location_id': self.stock_location})
|
||||
inventory2.prepare_inventory()
|
||||
line_vals = []
|
||||
line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 20, 'product_uom_id': packproduct.uom_id.id}]
|
||||
line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 0, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': False}]
|
||||
line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 10, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': lot1.id}]
|
||||
inventory2.write({'line_ids': [(0, 0, x) for x in line_vals]})
|
||||
inventory2.action_done()
|
||||
self.assertEqual(packproduct.qty_available, 40, "Wrong qty available for packproduct")
|
||||
self.assertEqual(lotproduct.qty_available, 10, "Wrong qty available for lotproduct")
|
||||
quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', lot1.id)])
|
||||
total_qty = sum([quant.qty for quant in quants])
|
||||
self.assertEqual(total_qty, 10, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty))
|
||||
quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', False)])
|
||||
total_qty = sum([quant.qty for quant in quants])
|
||||
self.assertEqual(total_qty, 0, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty))
|
||||
@@ -83,15 +83,24 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.move_lines" t-as="move">
|
||||
<td><span t-field="move.product_id"/></td>
|
||||
<td class="text-right"><span t-field="move.product_uom_qty"/> <span t-field="move.product_uom" groups="product.group_uom"/></td>
|
||||
<t t-if="o.picking_type_id.code != 'incoming'"><td><span t-field="move.location_id"/></td></t>
|
||||
<td>
|
||||
<span t-if="move.product_id and move.product_id.barcode">
|
||||
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Barcode', move.product_id.barcode, 600, 100)" style="width:300px;height:50px"/>
|
||||
</span>
|
||||
</td>
|
||||
<t t-if="o.picking_type_id.code != 'outgoing'"><td><span t-field="move.location_dest_id"/></td></t>
|
||||
<t t-if="(o.state != 'partially_available') or (move.reserved_availability > 0)">
|
||||
<td><span t-field="move.product_id"/></td>
|
||||
<td class="text-right">
|
||||
<t t-if="o.state == 'partially_available'">
|
||||
<span t-field="move.reserved_availability"/> <span t-field="move.product_id.uom_id" groups="product.group_uom"/>
|
||||
</t>
|
||||
<t t-if="o.state != 'partially_available'">
|
||||
<span t-field="move.product_uom_qty"/> <span t-field="move.product_uom" groups="product.group_uom"/>
|
||||
</t>
|
||||
</td>
|
||||
<t t-if="o.picking_type_id.code != 'incoming'"><td><span t-field="move.location_id"/></td></t>
|
||||
<td>
|
||||
<span t-if="move.product_id and move.product_id.barcode">
|
||||
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Barcode', move.product_id.barcode, 600, 100)" style="width:300px;height:50px"/>
|
||||
</span>
|
||||
</td>
|
||||
<t t-if="o.picking_type_id.code != 'outgoing'"><td><span t-field="move.location_dest_id"/></td></t>
|
||||
</t>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -28,6 +28,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
||||
this.group_by_field = {};
|
||||
this.grouped_by_m2o = false;
|
||||
this.many2manys = [];
|
||||
this.m2m_context = {};
|
||||
this.state = {
|
||||
groups : {},
|
||||
records : {}
|
||||
@@ -107,6 +108,11 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
||||
this.transform_qweb_template(child);
|
||||
this.qweb.add_template(instance.web.json_node_to_xml(child));
|
||||
break;
|
||||
} else if (child.tag === 'field') {
|
||||
var ftype = child.attrs.widget || this.fields_view.fields[child.attrs.name].type;
|
||||
if(ftype == "many2many" && "context" in child.attrs) {
|
||||
this.m2m_context[child.attrs.name] = child.attrs.context;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -570,7 +576,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
||||
return;
|
||||
}
|
||||
if (!relations[field.relation]) {
|
||||
relations[field.relation] = { ids: [], elements: {}};
|
||||
relations[field.relation] = { ids: [], elements: {}, context: self.m2m_context[name]};
|
||||
}
|
||||
var rel = relations[field.relation];
|
||||
field.raw_value.forEach(function(id) {
|
||||
@@ -584,7 +590,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
||||
});
|
||||
});
|
||||
_.each(relations, function(rel, rel_name) {
|
||||
var dataset = new instance.web.DataSetSearch(self, rel_name, self.dataset.get_context());
|
||||
var dataset = new instance.web.DataSetSearch(self, rel_name, self.dataset.get_context(rel.context));
|
||||
dataset.name_get(_.uniq(rel.ids)).done(function(result) {
|
||||
result.forEach(function(nameget) {
|
||||
$(rel.elements[nameget[0]]).append('<span class="oe_tag">' + _.str.escapeHTML(nameget[1]) + '</span>');
|
||||
|
||||
@@ -480,7 +480,10 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
#------------------------------------------------------
|
||||
# Server actions
|
||||
#------------------------------------------------------
|
||||
@http.route('/website/action/<path_or_xml_id_or_id>', type='http', auth="public", website=True)
|
||||
@http.route([
|
||||
'/website/action/<path_or_xml_id_or_id>',
|
||||
'/website/action/<path_or_xml_id_or_id>/<path:path>',
|
||||
], type='http', auth="public", website=True)
|
||||
def actions_server(self, path_or_xml_id_or_id, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
res, action_id, action = None, None, None
|
||||
|
||||
@@ -323,7 +323,9 @@
|
||||
function treat_node(node){
|
||||
if(node.nodeType === 3) {
|
||||
if(node.nodeValue.match(/\S/)){
|
||||
node.nodeValue = openerp._t($.trim(node.nodeValue));
|
||||
var text_value = $.trim(node.nodeValue);
|
||||
var spaces = node.nodeValue.split(text_value);
|
||||
node.nodeValue = spaces[0] + openerp._t(text_value) + spaces[1];
|
||||
}
|
||||
}
|
||||
else if(node.nodeType === 1 && node.hasChildNodes()) {
|
||||
|
||||
@@ -28,6 +28,7 @@ class Website(Website):
|
||||
@route()
|
||||
def customize_template_get(self, key, full=False, bundles=False):
|
||||
res = super(Website, self).customize_template_get(key, full=full, bundles=bundles)
|
||||
if full and request.session.get('report_view_ids'):
|
||||
res += request.session['report_view_ids']
|
||||
if full:
|
||||
for r in request.session.get('report_view_ids', []):
|
||||
res += super(Website, self).customize_template_get(r.get('xml_id'), full=full)
|
||||
return res
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
Germany 2015-02-25
|
||||
|
||||
bloopark systems GmbH & Co. KG agrees to the terms of the Odoo Corporate
|
||||
Contributor License Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Jens Holze jholze@bloopark.de https://github.com/jholze
|
||||
|
||||
List of contributors:
|
||||
|
||||
Jens Holze jholze@bloopark.de https://github.com/jholze
|
||||
Christoph Giesel cgiesel@bloopark.de https://github.com/christophlsa
|
||||
Benjamin Bachmann bBachmann@bloopark.de https://github.com/benniphx
|
||||
Robert Rübner rruebner@bloopark.de https://github.com/rruebner
|
||||
Florian Fischer ffischer@bloopark.de https://github.com/florianfischer
|
||||
Mercerdes Scenna mscenna@bloopark.de https://github.com/mscenna
|
||||
@@ -0,0 +1,21 @@
|
||||
Netherlands, 2015-03-09
|
||||
|
||||
Therp BV agrees to the terms of the Odoo Corporate
|
||||
Contributor License Agreement v1.0.
|
||||
|
||||
We declare that we are authorized and able to make this agreement and sign
|
||||
this declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
* Ronald Portier rportier@therp.nl https://github.com/nl66278
|
||||
* Stefan Rijnhart stefan@therp.nl https://github.com/stefanrijnhart
|
||||
|
||||
List of contributors:
|
||||
|
||||
* Giovanni Capalbo giovanni@therp.nl https://github.com/gfcapalbo
|
||||
* Holger Brunn hbrunn@therp.nl https://github.com/hbrunn
|
||||
* Lara Freeke lfreeke@therp.nl https://github.com/lfreeke
|
||||
* Ronald Portier rportier@therp.nl https://github.com/nl66278
|
||||
* Stefan Rijnhart stefan@therp.nl https://github.com/stefanrijnhart
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Italy, 2015-02-18
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Lorenzo Battistini lorenzo.battistini@agilebg.com https://github.com/eLBati
|
||||
@@ -0,0 +1,11 @@
|
||||
United Kingdom, 13.03.2015
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Matthieu Choplin choplin.mat@gmail.com https://github.com/matthieuchoplin
|
||||
@@ -1086,7 +1086,7 @@ class ir_server_object_lines(osv.osv):
|
||||
_sequence = 'ir_actions_id_seq'
|
||||
|
||||
_columns = {
|
||||
'server_id': fields.many2one('ir.actions.server', 'Related Server Action'),
|
||||
'server_id': fields.many2one('ir.actions.server', 'Related Server Action', ondelete='cascade'),
|
||||
'col1': fields.many2one('ir.model.fields', 'Field', required=True),
|
||||
'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
|
||||
"When Formula type is selected, this field may be a Python expression "
|
||||
|
||||
@@ -382,13 +382,13 @@ class ir_model_fields(osv.osv):
|
||||
if vals.get('relation',False) and not self.pool['ir.model'].search(cr, user, [('model','=',vals['relation'])]):
|
||||
raise UserError(_("Model %s does not exist!") % vals['relation'])
|
||||
|
||||
self.pool.clear_manual_fields()
|
||||
|
||||
if vals['model'] in self.pool:
|
||||
model = self.pool[vals['model']]
|
||||
if vals['model'].startswith('x_') and vals['name'] == 'x_name':
|
||||
model._rec_name = 'x_name'
|
||||
|
||||
self.pool.clear_manual_fields()
|
||||
|
||||
# re-initialize model in registry
|
||||
model.__init__(self.pool, cr)
|
||||
self.pool.setup_models(cr, partial=(not self.pool.ready))
|
||||
|
||||
@@ -179,7 +179,7 @@ class ir_sequence(models.Model):
|
||||
if new_implementation in ('standard', None):
|
||||
# Implementation has NOT changed.
|
||||
# Only change sequence if really requested.
|
||||
if seq.number_next != n:
|
||||
if values.get('number_next'):
|
||||
_alter_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, number_next=n)
|
||||
if seq.number_increment != i:
|
||||
_alter_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, number_increment=i)
|
||||
|
||||
@@ -196,5 +196,33 @@ class test_ir_sequence_generate(unittest2.TestCase):
|
||||
drop_sequence('test_sequence_type_6')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
class Test_ir_sequence_init(common.TransactionCase):
|
||||
|
||||
def test_00(self):
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
|
||||
# test if read statement return the good number_next value (from postgreSQL sequence and not ir_sequence value)
|
||||
sequence = registry('ir.sequence')
|
||||
# first creation of sequence (normal)
|
||||
values = {'number_next': 1,
|
||||
'company_id': 1,
|
||||
'padding': 4,
|
||||
'number_increment': 1,
|
||||
'implementation': 'standard',
|
||||
'name': 'test-sequence-00'}
|
||||
seq_id = sequence.create(cr, uid, values)
|
||||
# Call get next 4 times
|
||||
sequence.next_by_id(cr, uid, seq_id)
|
||||
sequence.next_by_id(cr, uid, seq_id)
|
||||
sequence.next_by_id(cr, uid, seq_id)
|
||||
read_sequence = sequence.next_by_id(cr, uid, seq_id)
|
||||
# Read the value of the current sequence
|
||||
assert read_sequence == "0004", 'The actual sequence value must be 4. reading : %s' % read_sequence
|
||||
# reset sequence to 1 by write method calling
|
||||
sequence.write(cr, uid, [seq_id], {'number_next': 1})
|
||||
# Read the value of the current sequence
|
||||
read_sequence = sequence.next_by_id(cr, uid, seq_id)
|
||||
assert read_sequence == "0001", 'The actual sequence value must be 1. reading : %s' % read_sequence
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest2.main()
|
||||
|
||||
@@ -177,7 +177,7 @@ class wkf_workitem(osv.osv):
|
||||
_columns = {
|
||||
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
|
||||
'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
|
||||
'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade", select=True),
|
||||
'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="set null", select=True),
|
||||
'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),
|
||||
'state': fields.char('Status', select=True),
|
||||
}
|
||||
|
||||
+3
-5
@@ -1267,20 +1267,18 @@ class Selection(Field):
|
||||
def set_class_name(self, cls, name):
|
||||
super(Selection, self).set_class_name(cls, name)
|
||||
# determine selection (applying 'selection_add' extensions)
|
||||
selection = None
|
||||
for field in resolve_all_mro(cls, name, reverse=True):
|
||||
if isinstance(field, type(self)):
|
||||
# We cannot use field.selection or field.selection_add here
|
||||
# because those attributes are overridden by `set_class_name`.
|
||||
if 'selection' in field._attrs:
|
||||
selection = field._attrs['selection']
|
||||
self.selection = field._attrs['selection']
|
||||
if 'selection_add' in field._attrs:
|
||||
# use an OrderedDict to update existing values
|
||||
selection_add = field._attrs['selection_add']
|
||||
selection = OrderedDict(selection + selection_add).items()
|
||||
self.selection = OrderedDict(self.selection + selection_add).items()
|
||||
else:
|
||||
selection = None
|
||||
self.selection = selection
|
||||
self.selection = None
|
||||
|
||||
def _description_selection(self, env):
|
||||
""" return the selection list (pairs (value, label)); labels are
|
||||
|
||||
+8
-1
@@ -1599,6 +1599,7 @@ class BaseModel(object):
|
||||
'views': [(view_id, 'form')],
|
||||
'target': 'current',
|
||||
'res_id': id,
|
||||
'context': context,
|
||||
}
|
||||
|
||||
def get_access_action(self, cr, uid, id, context=None):
|
||||
@@ -2472,7 +2473,9 @@ class BaseModel(object):
|
||||
self._o2m_raise_on_missing_reference(cr, f)
|
||||
|
||||
elif isinstance(f, fields.many2many):
|
||||
self._m2m_raise_or_create_relation(cr, f)
|
||||
res = self._m2m_raise_or_create_relation(cr, f)
|
||||
if res and self._fields[k].depends:
|
||||
stored_fields.append(self._fields[k])
|
||||
|
||||
else:
|
||||
res = column_data.get(k)
|
||||
@@ -2772,6 +2775,9 @@ class BaseModel(object):
|
||||
raise UserError(_("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj))
|
||||
|
||||
def _m2m_raise_or_create_relation(self, cr, f):
|
||||
""" Create the table for the relation if necessary.
|
||||
Return ``True`` if the relation had to be created.
|
||||
"""
|
||||
m2m_tbl, col1, col2 = f._sql_names(self)
|
||||
# do not create relations for custom fields as they do not belong to a module
|
||||
# they will be automatically removed when dropping the corresponding ir.model.field
|
||||
@@ -2798,6 +2804,7 @@ class BaseModel(object):
|
||||
cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (m2m_tbl, self._table, ref))
|
||||
cr.commit()
|
||||
_schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref)
|
||||
return True
|
||||
|
||||
|
||||
def _add_sql_constraints(self, cr):
|
||||
|
||||
@@ -533,7 +533,7 @@ ALL_LANGUAGES = {
|
||||
'mn_MN': u'Mongolian / монгол',
|
||||
'nb_NO': u'Norwegian Bokmål / Norsk bokmål',
|
||||
'nl_NL': u'Dutch / Nederlands',
|
||||
'nl_BE': u'Flemish (BE) / Vlaams (BE)',
|
||||
'nl_BE': u'Dutch (BE) / Nederlands (BE)',
|
||||
'oc_FR': u'Occitan (FR, post 1500) / Occitan',
|
||||
'pl_PL': u'Polish / Język polski',
|
||||
'pt_BR': u'Portuguese (BR) / Português (BR)',
|
||||
|
||||
Reference in New Issue
Block a user