'invoiced': fields.boolean('Invoiced', readonly=True),
'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date"),
-
+ 'procurement_ids': fields.one2many('procurement.order', 'purchase_line_id', string='Associated procurements'),
+ 'group_id': fields.related('procurement_ids', 'group_id', type='many2one', relation='procurement.group', string='Procurement Group'),
}
_defaults = {
+ 'product_uom' : _get_uom_id,
'product_qty': lambda *a: 1.0,
'state': lambda *args: 'draft',
'invoiced': lambda *a: 0,
"""
onchange handler of product_uom.
"""
+ if context is None:
+ context = {}
if not uom_id:
return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
+ context = dict(context, purchase_uom_check=True)
return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
- name=name, price_unit=price_unit, context=context)
+ name=name, price_unit=price_unit, state=state, context=context)
def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
"""Return the datetime value to use as Schedule Date (``date_planned``) for
('cancel', 'Cancelled')
], 'Order Status', readonly=True),
}
- def init(self, cr):
- tools.drop_view_if_exists(cr, 'sale_report')
- # TODO: make parent view extensible similarly to invoice analysis and
- # remove the duplication
- cr.execute("""
- create or replace view sale_report as (
- select
- min(l.id) as id,
- l.product_id as product_id,
- t.uom_id as product_uom,
- sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty,
- sum(l.product_uom_qty * l.price_unit * (100.0-l.discount) / 100.0) as price_total,
- count(*) as nbr,
- s.date_order as date,
- s.date_confirm as date_confirm,
- to_char(s.date_order, 'YYYY') as year,
- to_char(s.date_order, 'MM') as month,
- to_char(s.date_order, 'YYYY-MM-DD') as day,
- s.partner_id as partner_id,
- s.user_id as user_id,
- s.company_id as company_id,
- s.warehouse_id as warehouse_id,
- extract(epoch from avg(date_trunc('day',s.date_confirm)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay,
- s.state,
- t.categ_id as categ_id,
- s.shipped,
- s.shipped::integer as shipped_qty_1,
- s.pricelist_id as pricelist_id,
- s.project_id as analytic_account_id
- from
- sale_order_line l
- join sale_order s on (l.order_id=s.id)
- left join product_product p on (l.product_id=p.id)
- left join product_template t on (p.product_tmpl_id=t.id)
- left join product_uom u on (u.id=l.product_uom)
- left join product_uom u2 on (u2.id=t.uom_id)
- group by
- l.product_id,
- l.order_id,
- t.uom_id,
- t.categ_id,
- s.date_order,
- s.date_confirm,
- s.partner_id,
- s.user_id,
- s.warehouse_id,
- s.company_id,
- s.state,
- s.shipped,
- s.pricelist_id,
- s.project_id
- )
- """)
+
+ def _select(self):
+ return super(sale_report, self)._select() + ", s.warehouse_id as warehouse_id, s.shipped, s.shipped::integer as shipped_qty_1"
+
+ def _group_by(self):
+ return super(sale_report, self)._group_by() + ", s.warehouse_id, s.shipped"
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
warehouse_ids = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', company_id)], context=context)
if not warehouse_ids:
- raise osv.except_osv(_('Error!'), _('There is no warehouse defined for selected company.'))
+ return False
return warehouse_ids[0]
- # This is False
- def _picked_rate(self, cr, uid, ids, name, arg, context=None):
- if not ids:
- return {}
+ def _get_shipped(self, cr, uid, ids, name, args, context=None):
res = {}
- tmp = {}
- for id in ids:
- tmp[id] = {'picked': 0.0, 'total': 0.0}
- cr.execute('''SELECT
- p.sale_id as sale_order_id, sum(m.product_qty) as nbr, mp.state as procurement_state, m.state as move_state, p.type as picking_type
- FROM
- stock_move m
- LEFT JOIN
- stock_picking p on (p.id=m.picking_id)
- LEFT JOIN
- procurement_order mp on (mp.move_id=m.id)
- WHERE
- p.sale_id IN %s GROUP BY m.state, mp.state, p.sale_id, p.type''', (tuple(ids),))
-
- for item in cr.dictfetchall():
- if item['move_state'] == 'cancel':
- continue
-
- if item['picking_type'] == 'in':#this is a returned picking
- tmp[item['sale_order_id']]['total'] -= item['nbr'] or 0.0 # Deducting the return picking qty
- if item['procurement_state'] == 'done' or item['move_state'] == 'done':
- tmp[item['sale_order_id']]['picked'] -= item['nbr'] or 0.0
+ for sale in self.browse(cr, uid, ids, context=context):
+ group = sale.procurement_group_id
+ if group:
+ res[sale.id] = all([proc.state in ['cancel', 'done'] for proc in group.procurement_ids])
else:
- tmp[item['sale_order_id']]['total'] += item['nbr'] or 0.0
- if item['procurement_state'] == 'done' or item['move_state'] == 'done':
- tmp[item['sale_order_id']]['picked'] += item['nbr'] or 0.0
+ res[sale.id] = False
+ return res
- for order in self.browse(cr, uid, ids, context=context):
- if order.shipped:
- res[order.id] = 100.0
- else:
- res[order.id] = tmp[order.id]['total'] and (100.0 * tmp[order.id]['picked'] / tmp[order.id]['total']) or 0.0
+ def _get_orders(self, cr, uid, ids, context=None):
+ res = set()
+ for move in self.browse(cr, uid, ids, context=context):
+ if move.procurement_id and move.procurement_id.sale_line_id:
+ res.add(move.procurement_id.sale_line_id.order_id.id)
+ return list(res)
+
+ def _get_orders_procurements(self, cr, uid, ids, context=None):
+ res = set()
+ for proc in self.pool.get('procurement.order').browse(cr, uid, ids, context=context):
+ if proc.sale_line_id:
+ res.add(proc.sale_line_id.order_id.id)
+ return list(res)
+
+ def _get_picking_ids(self, cr, uid, ids, name, args, context=None):
+ res = {}
+ for sale in self.browse(cr, uid, ids, context=context):
+ if not sale.procurement_group_id:
+ res[sale.id] = []
+ continue
+ res[sale.id] = self.pool.get('stock.picking').search(cr, uid, [('group_id', '=', sale.procurement_group_id.id)], context=context)
return res
-
+
+ def _prepare_order_line_procurement(self, cr, uid, order, line, group_id=False, context=None):
+ vals = super(sale_order, self)._prepare_order_line_procurement(cr, uid, order, line, group_id=group_id, context=context)
+ location_id = order.partner_shipping_id.property_stock_customer.id
+ vals['location_id'] = location_id
+
+ routes = line.route_id and [(4, line.route_id.id)] or []
+ vals['route_ids'] = routes
+ vals['warehouse_id'] = order.warehouse_id and order.warehouse_id.id or False
+ return vals
+
_columns = {
- 'state': fields.selection([
- ('draft', 'Draft Quotation'),
- ('sent', 'Quotation Sent'),
- ('cancel', 'Cancelled'),
- ('waiting_date', 'Waiting Schedule'),
- ('progress', 'Sales Order'),
- ('manual', 'Sale to Invoice'),
- ('shipping_except', 'Shipping Exception'),
- ('invoice_except', 'Invoice Exception'),
- ('done', 'Done'),
- ], 'Status', readonly=True,help="Gives the status of the quotation or sales order.\
- \nThe exception status is automatically set when a cancel operation occurs \
- in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception).\nThe 'Waiting Schedule' status is set when the invoice is confirmed\
- but waiting for the scheduler to run on the order date.", select=True),
'incoterm': fields.many2one('stock.incoterms', 'Incoterm', help="International Commercial Terms are a series of predefined commercial terms used in international transactions."),
'picking_policy': fields.selection([('direct', 'Deliver each product when available'), ('one', 'Deliver all products at once')],
'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
res_packing = self.product_packaging_change(cr, uid, ids, pricelist, product, qty, uom, partner_id, packaging, context=context)
res['value'].update(res_packing.get('value', {}))
warning_msgs = res_packing.get('warning') and res_packing['warning']['message'] or ''
- compare_qty = float_compare(product_obj.virtual_available * uom2.factor, qty * product_obj.uom_id.factor, precision_rounding=product_obj.uom_id.rounding)
- if (product_obj.type=='product') and int(compare_qty) == -1 \
- and (product_obj.procure_method=='make_to_stock'):
- warn_msg = _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') % \
- (qty, uom2 and uom2.name or product_obj.uom_id.name,
- max(0,product_obj.virtual_available), product_obj.uom_id.name,
- max(0,product_obj.qty_available), product_obj.uom_id.name)
- warning_msgs += _("Not enough stock ! : ") + warn_msg + "\n\n"
+
+ #determine if the product is MTO or not (for a further check)
+ isMto = False
+ if warehouse_id:
+ warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
+ for product_route in product_obj.route_ids:
+ if warehouse.mto_pull_id and warehouse.mto_pull_id.route_id and warehouse.mto_pull_id.route_id.id == product_route.id:
+ isMto = True
+ break
+ else:
+ try:
+ mto_route_id = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'route_warehouse0_mto').id
+ except:
+ # if route MTO not found in ir_model_data, we treat the product as in MTS
+ mto_route_id = False
+ if mto_route_id:
+ for product_route in product_obj.route_ids:
+ if product_route.id == mto_route_id:
+ isMto = True
+ break
+
+ #check if product is available, and if not: raise a warning, but do this only for products that aren't processed in MTO
+ if not isMto:
+ uom2 = False
+ if uom:
- uom2 = product_uom_obj.browse(cr, uid, uom)
++ uom2 = product_uom_obj.browse(cr, uid, uom, context=context)
+ if product_obj.uom_id.category_id.id != uom2.category_id.id:
+ uom = False
+ if not uom2:
+ uom2 = product_obj.uom_id
+ compare_qty = float_compare(product_obj.virtual_available * uom2.factor, qty * product_obj.uom_id.factor, precision_rounding=product_obj.uom_id.rounding)
+ if (product_obj.type=='product') and int(compare_qty) == -1:
+ #and (product_obj.procure_method=='make_to_stock'): --> need to find alternative for procure_method
+ warn_msg = _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') % \
+ (qty, uom2 and uom2.name or product_obj.uom_id.name,
+ max(0,product_obj.virtual_available), product_obj.uom_id.name,
+ max(0,product_obj.qty_available), product_obj.uom_id.name)
+ warning_msgs += _("Not enough stock ! : ") + warn_msg + "\n\n"
#update of warning messages
if warning_msgs: