[MERGE] merged with main trunk
authorQuentin (OpenERP) <qdp-launchpad@openerp.com>
Wed, 11 Dec 2013 10:03:54 +0000 (11:03 +0100)
committerQuentin (OpenERP) <qdp-launchpad@openerp.com>
Wed, 11 Dec 2013 10:03:54 +0000 (11:03 +0100)
bzr revid: qdp-launchpad@openerp.com-20131211100354-hwlt65vk8p3xih9u

1  2 
addons/account_anglo_saxon/stock.py
addons/purchase/purchase.py
addons/purchase/purchase_view.xml
addons/sale/sale.py
addons/sale_stock/report/sale_report.py
addons/sale_stock/res_config.py
addons/sale_stock/sale_stock.py
addons/sale_stock/stock_view.xml

Simple merge
@@@ -929,10 -891,10 +937,11 @@@ class purchase_order_line(osv.osv)
          '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
                          </group>
                          <group>
                              <field name="date_order"/>
 -                            <field name="origin" attrs="{'invisible': [('origin','=',False)]}"/>
 -                            <field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
 +                            <field name="origin" attr="{'invisible': [('origin','=',False)]}"/>
                              <field name="company_id" groups="base.group_multi_company" widget="selection"/>
-                             <field name="picking_type_id" on_change="onchange_picking_type_id(picking_type_id, context)" domain="[('code','=','incoming')]" widget="selection" context="{'special_shortened_wh_name': True}"/>
++                            <field name="picking_type_id" on_change="onchange_picking_type_id(picking_type_id, context)" domain="[('code','=','incoming')]" widget="selection" context="{'special_shortened_wh_name': True}" groups="stock.group_locations"/>
 +                            <field name="related_location_id" invisible="1"/>
 +                            <field name="dest_address_id" string="Customer Address" on_change="onchange_dest_address_id(dest_address_id)"
 +                                attrs="{'invisible':['|', ('picking_type_id','=',False), ('related_location_id','!=', False)], 
 +                                'required': [('picking_type_id','!=',False), ('related_location_id','=', False)]}" 
 +                                groups="stock.group_locations"/>
                          </group>
                      </group>
                      <notebook>
@@@ -317,9 -315,12 +317,12 @@@ class sale_order(osv.osv)
  
      def create(self, cr, uid, vals, context=None):
          if context is None:
 -            context = {}
 +            context = {}        
          if vals.get('name', '/') == '/':
              vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'sale.order') or '/'
+         if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
+             defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context)['value']
+             vals = dict(defaults, **vals)
          context.update({'mail_create_nolog': True})
          new_id = super(sale_order, self).create(cr, uid, vals, context=context)
          self.message_post(cr, uid, [new_id], body=_("Quotation created"), context=context)
@@@ -39,57 -39,11 +39,10 @@@ class sale_report(osv.osv)
              ('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:
@@@ -79,11 -80,10 +83,10 @@@ class sale_configuration(osv.osv_memory
          wizard = self.browse(cr, uid, ids)[0]
  
          default_picking_policy = 'one' if wizard.default_picking_policy else 'direct'
-         ir_values.set_default(cr, uid, 'sale.order', 'picking_policy', default_picking_policy)
+         ir_values.set_default(cr, SUPERUSER_ID, 'sale.order', 'picking_policy', default_picking_policy)
          res = super(sale_configuration, self).set_sale_defaults(cr, uid, ids, context)
          return res
 -    
 +
      def onchange_invoice_methods(self, cr, uid, ids, group_invoice_so_lines, group_invoice_deli_orders, context=None):
          if not group_invoice_deli_orders:
              return {'value': {'default_order_policy': 'manual'}}
@@@ -44,53 -70,63 +44,53 @@@ class sale_order(osv.osv)
          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)]},
@@@ -346,44 -614,14 +346,44 @@@ class sale_order_line(osv.osv)
          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:
Simple merge