Пример #1
0
    def get_date_range(self, start_date, end_date):
        min_time = datetime.min.time()
        date_list = []
        single_month = False

        # Calculate the last day of month of start date
        month_end = end_of(start_date, 'month')
        if start_date != end_date and end_date > month_end:
            date_list.append((start_date, month_end))
            next_month_start = add(month_end, days=1)
            end_month_start = start_of(end_date, 'month')
            if end_month_start == end_date:
                previous_month_end = end_month_start
            elif next_month_start > add(end_month_start, days=-1):
                previous_month_end = end_month_start
            else:
                previous_month_end = add(end_month_start, days=-1)
        else:
            date_list.append((start_date, end_date))
            next_month_start = start_date
            end_month_start = start_of(end_date, 'month')
            previous_month_end = end_date
            single_month = True

        if not single_month and next_month_start != previous_month_end:
            # Loop over "date_range" odoo library function
            for start_day_month in date_range(datetime.combine(next_month_start, min_time),
                                              datetime.combine(previous_month_end, min_time)):
                end_day_month = end_of(start_day_month.date(), 'month')
                if end_day_month <= end_date:
                    date_list.append((start_day_month.date(), end_day_month))

        if end_month_start > start_date and end_month_start != end_date:
            date_list.append((end_month_start, end_date))
        elif end_month_start > start_date and end_month_start == end_date:
            date_list.append((end_date, end_date))
        return date_list
Пример #2
0
    def _get_orderpoint_action(self):
        """Create manual orderpoints for missing product in each warehouses. It also removes
        orderpoints that have been replenish. In order to do it:
        - It uses the report.stock.quantity to find missing quantity per product/warehouse
        - It checks if orderpoint already exist to refill this location.
        - It checks if it exists other sources (e.g RFQ) tha refill the warehouse.
        - It creates the orderpoints for missing quantity that were not refill by an upper option.

        return replenish report ir.actions.act_window
        """
        action = self.env["ir.actions.actions"]._for_xml_id("stock.action_orderpoint_replenish")
        action['context'] = self.env.context
        # Search also with archived ones to avoid to trigger product_location_check SQL constraints later
        # It means that when there will be a archived orderpoint on a location + product, the replenishment
        # report won't take in account this location + product and it won't create any manual orderpoint
        # In master: the active field should be remove
        orderpoints = self.env['stock.warehouse.orderpoint'].with_context(active_test=False).search([])
        # Remove previous automatically created orderpoint that has been refilled.
        to_remove = orderpoints.filtered(lambda o: o.create_uid.id == SUPERUSER_ID and o.qty_to_order <= 0.0 and o.trigger == 'manual')
        to_remove.unlink()
        orderpoints = orderpoints - to_remove
        to_refill = defaultdict(float)
        all_product_ids = []
        all_warehouse_ids = []
        # Take 3 months since it's the max for the forecast report
        to_date = add(fields.date.today(), months=3)
        qty_by_product_warehouse = self.env['report.stock.quantity'].read_group(
            [('date', '=', to_date), ('state', '=', 'forecast')],
            ['product_id', 'product_qty', 'warehouse_id'],
            ['product_id', 'warehouse_id'], lazy=False)
        for group in qty_by_product_warehouse:
            warehouse_id = group.get('warehouse_id') and group['warehouse_id'][0]
            if group['product_qty'] >= 0.0 or not warehouse_id:
                continue
            all_product_ids.append(group['product_id'][0])
            all_warehouse_ids.append(warehouse_id)
            to_refill[(group['product_id'][0], warehouse_id)] = group['product_qty']
        if not to_refill:
            return action

        # Recompute the forecasted quantity for missing product today but at this time
        # with their real lead days.
        key_to_remove = []

        # group product by lead_days and warehouse in order to read virtual_available
        # in batch
        pwh_per_day = defaultdict(list)
        for (product, warehouse), quantity in to_refill.items():
            product = self.env['product.product'].browse(product).with_prefetch(all_product_ids)
            warehouse = self.env['stock.warehouse'].browse(warehouse).with_prefetch(all_warehouse_ids)
            rules = product._get_rules_from_location(warehouse.lot_stock_id)
            lead_days = rules.with_context(bypass_delay_description=True)._get_lead_days(product)[0]
            pwh_per_day[(lead_days, warehouse)].append(product.id)
        for (days, warehouse), p_ids in pwh_per_day.items():
            products = self.env['product.product'].browse(p_ids)
            qties = products.with_context(
                warehouse=warehouse.id,
                to_date=fields.datetime.now() + relativedelta.relativedelta(days=days)
            ).read(['virtual_available'])
            for qty in qties:
                if float_compare(qty['virtual_available'], 0, precision_rounding=product.uom_id.rounding) >= 0:
                    key_to_remove.append((qty['id'], warehouse.id))
                else:
                    to_refill[(qty['id'], warehouse.id)] = qty['virtual_available']

        for key in key_to_remove:
            del to_refill[key]
        if not to_refill:
            return action

        # Remove incoming quantity from other origin than moves (e.g RFQ)
        product_ids, warehouse_ids = zip(*to_refill)
        dummy, qty_by_product_wh = self.env['product.product'].browse(product_ids)._get_quantity_in_progress(warehouse_ids=warehouse_ids)
        rounding = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        # Group orderpoint by product-warehouse
        orderpoint_by_product_warehouse = self.env['stock.warehouse.orderpoint'].read_group(
            [('id', 'in', orderpoints.ids)],
            ['product_id', 'warehouse_id', 'qty_to_order:sum'],
            ['product_id', 'warehouse_id'], lazy=False)
        orderpoint_by_product_warehouse = {
            (record.get('product_id')[0], record.get('warehouse_id')[0]): record.get('qty_to_order')
            for record in orderpoint_by_product_warehouse
        }
        for (product, warehouse), product_qty in to_refill.items():
            qty_in_progress = qty_by_product_wh.get((product, warehouse)) or 0.0
            qty_in_progress += orderpoint_by_product_warehouse.get((product, warehouse), 0.0)
            # Add qty to order for other orderpoint under this warehouse.
            if not qty_in_progress:
                continue
            to_refill[(product, warehouse)] = product_qty + qty_in_progress
        to_refill = {k: v for k, v in to_refill.items() if float_compare(
            v, 0.0, precision_digits=rounding) < 0.0}

        lot_stock_id_by_warehouse = self.env['stock.warehouse'].search_read([
            ('id', 'in', [g[1] for g in to_refill.keys()])
        ], ['lot_stock_id'])
        lot_stock_id_by_warehouse = {w['id']: w['lot_stock_id'][0] for w in lot_stock_id_by_warehouse}

        # With archived ones to avoid `product_location_check` SQL constraints
        orderpoint_by_product_location = self.env['stock.warehouse.orderpoint'].with_context(active_test=False).read_group(
            [('id', 'in', orderpoints.ids)],
            ['product_id', 'location_id', 'ids:array_agg(id)'],
            ['product_id', 'location_id'], lazy=False)
        orderpoint_by_product_location = {
            (record.get('product_id')[0], record.get('location_id')[0]): record.get('ids')[0]
            for record in orderpoint_by_product_location
        }

        orderpoint_values_list = []
        for (product, warehouse), product_qty in to_refill.items():
            lot_stock_id = lot_stock_id_by_warehouse[warehouse]
            orderpoint_id = orderpoint_by_product_location.get((product, lot_stock_id))
            if orderpoint_id:
                self.env['stock.warehouse.orderpoint'].browse(orderpoint_id).qty_forecast += product_qty
            else:
                orderpoint_values = self.env['stock.warehouse.orderpoint']._get_orderpoint_values(product, lot_stock_id)
                orderpoint_values.update({
                    'name': _('Replenishment Report'),
                    'warehouse_id': warehouse,
                    'company_id': self.env['stock.warehouse'].browse(warehouse).company_id.id,
                })
                orderpoint_values_list.append(orderpoint_values)

        orderpoints = self.env['stock.warehouse.orderpoint'].with_user(SUPERUSER_ID).create(orderpoint_values_list)
        for orderpoint in orderpoints:
            orderpoint.route_id = orderpoint.product_id.route_ids[:1]
        orderpoints.filtered(lambda o: not o.route_id)._set_default_route_id()
        return action