コード例 #1
0
ファイル: purchase_order.py プロジェクト: detian08/bsp_addons
class PurchaseOrderLine(models.Model):
    _inherit = "purchase.order.line"

    @api.depends('discount2', 'discount3')
    def _compute_amount(self):
        super(PurchaseOrderLine, self)._compute_amount()

    discount2 = fields.Float(
        'Disc. 2 (%)',
        digits=dp.get_precision('Discount'),
    )

    discount3 = fields.Float(
        'Disc. 3 (%)',
        digits=dp.get_precision('Discount'),
    )

    _sql_constraints = [
        ('discount2_limit', 'CHECK (discount2 <= 100.0)',
         'Discount 2 must be lower than 100%.'),
        ('discount3_limit', 'CHECK (discount3 <= 100.0)',
         'Discount 3 must be lower than 100%.'),
    ]

    def _get_discounted_price_unit(self):
        price_unit = super(PurchaseOrderLine,
                           self)._get_discounted_price_unit()
        if self.discount2:
            price_unit *= (1 - self.discount2 / 100.0)
        if self.discount3:
            price_unit *= (1 - self.discount3 / 100.0)
        return price_unit

    @api.onchange('product_qty', 'product_uom')
    def _onchange_quantity(self):
        """
        Check if a discount is defined into the supplier info and if so then
        apply it to the current purchase order line
        """
        res = super(PurchaseOrderLine, self)._onchange_quantity()
        if self.product_id:
            date = None
            if self.order_id.date_order:
                date = fields.Date.to_string(
                    fields.Date.from_string(self.order_id.date_order))
            product_supplierinfo = self.product_id._select_seller(
                partner_id=self.partner_id,
                quantity=self.product_qty,
                date=date,
                uom_id=self.product_uom)
            if product_supplierinfo:
                self.discount2 = product_supplierinfo.discount2
                self.discount3 = product_supplierinfo.discount3
        return res
コード例 #2
0
class ResPartner(models.Model):
    _inherit = "res.partner"

    default_supplierinfo_discount2 = fields.Float(
        string='Default Supplier Discount 2 (%)',
        digits=dp.get_precision('Discount'),
        help="This value will be used as the default one, for each new "
             "supplierinfo line depending on that supplier.",
    )
    default_supplierinfo_discount3 = fields.Float(
        string='Default Supplier Discount 3 (%)',
        digits=dp.get_precision('Discount'),
        help="This value will be used as the default one, for each new "
             "supplierinfo line depending on that supplier.",
    )
コード例 #3
0
class PurchaseOrderRecommendationLine(models.TransientModel):
    _inherit = 'purchase.order.recommendation.line'

    secondary_uom_id = fields.Many2one(
        comodel_name='product.secondary.unit',
        related='product_id.purchase_secondary_uom_id',
        readonly=True,
    )
    secondary_uom_qty = fields.Float(
        string='Secondary Qty',
        digits=dp.get_precision('Product Unit of Measure'),
    )

    @api.onchange('secondary_uom_id', 'secondary_uom_qty')
    def _onchange_secondary_uom(self):
        if not self.secondary_uom_id:
            return
        factor = self.secondary_uom_id.factor * self.product_id.uom_id.factor
        qty = float_round(
            self.secondary_uom_qty * factor,
            precision_rounding=self.product_id.uom_id.rounding)
        if float_compare(
                self.units_included,
                qty,
                precision_rounding=self.product_id.uom_id.rounding) != 0:
            self.units_included = qty

    @api.onchange('units_included')
    def _onchange_units_included_purchase_order_secondary_unit(self):
        if not self.secondary_uom_id:
            return
        factor = self.secondary_uom_id.factor * self.product_id.uom_id.factor
        qty = float_round(
            self.units_included / (factor or 1.0),
            precision_rounding=self.secondary_uom_id.uom_id.rounding)
        if float_compare(
                self.secondary_uom_qty,
                qty,
                precision_rounding=self.secondary_uom_id.uom_id.rounding) != 0:
            self.secondary_uom_qty = qty

    @api.multi
    def _prepare_update_po_line(self):
        res = super()._prepare_update_po_line()
        if self.secondary_uom_id and self.secondary_uom_qty:
            res.update({
                'secondary_uom_id': self.secondary_uom_id.id,
                'secondary_uom_qty': self.secondary_uom_qty,
            })
        return res

    @api.multi
    def _prepare_new_po_line(self, sequence):
        res = super()._prepare_new_po_line(sequence)
        if self.secondary_uom_id and self.secondary_uom_qty:
            res.update({
                'secondary_uom_id': self.secondary_uom_id.id,
                'secondary_uom_qty': self.secondary_uom_qty,
            })
        return res
コード例 #4
0
ファイル: product.py プロジェクト: vlim-git/odoo-addons
class product_attribute_value(models.Model):
    _inherit = "product.attribute.value"

    lst_price_brut = fields.Float(
        string='Gross selling price',
        digits_compute=dp.get_precision('Product Price'),
    )

    @api.multi
    def write(self, vals):
        if self.env.context.get('active_id', False):
            price_att = self.env['product.attribute.price'].search([
                ('product_tmpl_id', '=', self.env.context['active_id']),
                ('value_id', '=', self.id)
            ])
            if price_att:
                template = price_att.product_tmpl_id
                if 'price_extra' in vals:
                    vals['lst_price_brut'] = template.lst_price_brut + vals[
                        'price_extra'] * template.brut_net_factor
                elif 'lst_price_brut' in vals:
                    vals['price_extra'] = (
                        vals['lst_price_brut'] -
                        template.lst_price_brut) / template.brut_net_factor
        elif 'lst_price_brut' in vals:
            del vals['lst_price_brut']
        return super(product_attribute_value, self).write(vals)
コード例 #5
0
class AccountInvoiceLine(models.Model):
    _inherit = "account.invoice.line"

    discount2 = fields.Float(
        'Discount 2 (%)',
        digits=dp.get_precision('Discount'),
        default=0.0,
    )
    discount3 = fields.Float(
        'Discount 3 (%)',
        digits=dp.get_precision('Discount'),
        default=0.0,
    )

    def _get_discounted_price_unit(self):
        """Inheritable method for getting the unit price after applying
        discount(s).

        :returns: Unit price after discount(s).
        :rtype: float
        """
        self.ensure_one()
        price_unit = self.price_unit
        if self.discount:
            price_unit *= (1 - self.discount / 100)
        if self.discount2:
            price_unit *= (1 - self.discount2 / 100.0)
        if self.discount3:
            price_unit *= (1 - self.discount3 / 100.0)
        return price_unit

    @api.multi
    @api.depends('discount2', 'discount3')
    def _compute_price(self):
        for line in self:
            prev_price_unit = line.price_unit
            prev_discount = line.discount
            price_unit = line._get_discounted_price_unit()
            line.update({
                'price_unit': price_unit,
                'discount': 0.0,
            })
            super(AccountInvoiceLine, line)._compute_price()
            line.update({
                'price_unit': prev_price_unit,
                'discount': prev_discount,
            })
コード例 #6
0
class ResPartner(models.Model):
    _inherit = 'res.partner'

    purchase_general_discount = fields.Float(
        digits=dp.get_precision('Discount'),
        string='Purchase General Discount (%)',
        company_dependent=True,
    )
コード例 #7
0
class mass_mo_line(osv.osv_memory):
    _name = "mo.batch.line"
    _description = "Manufacturing Orders Line"
    _columns = {
        'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('internal_type','=','Finish'),('bom_ids','!=',False),('bom_ids.bom_id','=',False)]),
        'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
        'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
        'product_uos_qty': fields.float('Product UoS Quantity'),
        'product_uos': fields.many2one('product.uom', 'Product UoS'),
        'bom_id': fields.many2one('mrp.bom', 'Blend (BoM)', required=True, domain=[('bom_id','=',False)], help="Bill of Materials allow you to define the list of required raw materials to make a finished product."),
        'date_planned': fields.datetime('Scheduled Date', required=True, select=1),
        'company_id': fields.many2one('res.company', 'Company', required=True),
        'mo_batch_id': fields.many2one('mo.batch', 'MO'),
    }
    _defaults = {
        'date_planned': lambda *a: time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
        'product_qty':  lambda *a: 1.0,
        'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
    }
    
    def product_id_change(self, cr, uid, ids, product_id, context=None):
        """ Finds UoM of changed product.
        @param product_id: Id of changed product.
        @return: Dictionary of values.
        """
        if not product_id:
            return {'value': {
                'product_uom': False,
                'bom_id': False,
            }}
        bom_obj = self.pool.get('mrp.bom')
        product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
        product_uom_id = product.uom_id and product.uom_id.id or False
        bom_id = bom_obj._bom_find(cr, uid, product.id, product_uom_id, [])
        result = {
            'product_uom': product_uom_id,
            'bom_id': bom_id,
        }
        return {'value': result}

    def _prepare_mo_batch_line(self, cr, uid, line, context=None):
        vals = {
            'origin': 'Create from batch',
            'product_id': line.product_id.id,
            'product_qty': line.product_qty,
            'product_uom': line.product_uom.id,
            'product_uos_qty': line.product_uos and line.product_uos_qty or False,
            'product_uos': line.product_uos and line.product_uos.id or False,
            'bom_id': line.bom_id and line.bom_id.id or False,
            'date_planned': line.date_planned,
            'user_id': line.mo_batch_id.user_id.id,
            'company_id': line.company_id.id,
        }
        return vals
コード例 #8
0
ファイル: purchase_order.py プロジェクト: detian08/bsp_addons
class PurchaseOrderLine(models.Model):
    _inherit = 'purchase.order.line'

    secondary_uom_qty = fields.Float(
        string='Secondary Qty',
        digits=dp.get_precision('Product Unit of Measure'),
    )
    secondary_uom_id = fields.Many2one(
        comodel_name='product.secondary.unit',
        string='Secondary uom',
        ondelete='restrict',
    )

    @api.onchange('secondary_uom_id', 'secondary_uom_qty')
    def _onchange_secondary_uom(self):
        if not self.secondary_uom_id:
            return
        factor = self.secondary_uom_id.factor * self.product_uom.factor
        qty = float_round(self.secondary_uom_qty * factor,
                          precision_rounding=self.product_uom.rounding)
        if float_compare(self.product_qty,
                         qty,
                         precision_rounding=self.product_uom.rounding) != 0:
            self.product_qty = qty

    @api.onchange('product_qty')
    def _onchange_product_qty_purchase_order_secondary_unit(self):
        if not self.secondary_uom_id:
            return
        factor = self.secondary_uom_id.factor * self.product_uom.factor
        qty = float_round(
            self.product_qty / (factor or 1.0),
            precision_rounding=self.secondary_uom_id.uom_id.rounding)
        if float_compare(
                self.secondary_uom_qty,
                qty,
                precision_rounding=self.secondary_uom_id.uom_id.rounding) != 0:
            self.secondary_uom_qty = qty

    @api.onchange('product_uom')
    def _onchange_product_uom_purchase_order_secondary_unit(self):
        if not self.secondary_uom_id:
            return
        factor = self.product_uom.factor * self.secondary_uom_id.factor
        qty = float_round(self.product_qty / (factor or 1.0),
                          precision_rounding=self.product_uom.rounding)
        if float_compare(self.secondary_uom_qty,
                         qty,
                         precision_rounding=self.product_uom.rounding) != 0:
            self.secondary_uom_qty = qty

    @api.onchange('product_id')
    def _onchange_product_id_purchase_order_secondary_unit(self):
        self.secondary_uom_id = self.product_id.purchase_secondary_uom_id
コード例 #9
0
class StockSecondaryUnitMixin(models.AbstractModel):
    _name = 'stock.secondary.unit.mixin'

    secondary_uom_id = fields.Many2one(
        comodel_name='product.secondary.unit',
        string='Second unit',
    )
    secondary_uom_qty = fields.Float(
        string='Secondary Qty',
        digits=dp.get_precision('Product Unit of Measure'),
    )
コード例 #10
0
class GlobalDiscount(models.Model):
    _name = 'global.discount'
    _description = 'Global Discount'
    _order = "sequence, id desc"

    sequence = fields.Integer(help='Gives the order to apply discounts', )
    name = fields.Char(
        string='Discount Name',
        required=True,
    )
    discount = fields.Float(
        digits=dp.get_precision('Discount'),
        required=True,
        default=0.0,
    )
    discount_scope = fields.Selection(
        selection=[
            ('sale', 'Sales'),
            ('purchase', 'Purchases'),
        ],
        default='sale',
        required='True',
        string='Discount Scope',
    )
    company_id = fields.Many2one(
        comodel_name='res.company',
        string='Company',
        default=lambda self: self.env.user.company_id,
    )

    def name_get(self):
        result = []
        for one in self:
            result.append(
                (one.id, '{} ({:.2f}%)'.format(one.name, one.discount)))
        return result

    def _get_global_discount_vals(self, base, **kwargs):
        """ Prepare the dict of values to create to obtain the discounted
            amount

           :param float base: the amount to discount
           :return: dict with the discounted amount
        """
        self.ensure_one()
        return {
            'global_discount': self,
            'base': base,
            'base_discounted': base * (1 - (self.discount / 100)),
        }
コード例 #11
0
ファイル: product.py プロジェクト: detian08/bsp_addons
class StockProductSecondaryUnit(models.AbstractModel):
    _name = 'stock.product.secondary.unit'

    secondary_unit_qty_available = fields.Float(
        string='Quantity On Hand (2Unit)',
        compute='_compute_secondary_unit_qty_available',
        digits=dp.get_precision('Product Unit of Measure'),
    )

    def _compute_secondary_unit_qty_available(self):
        for product in self.filtered('stock_secondary_uom_id'):
            qty = product.qty_available / (
                product.stock_secondary_uom_id.factor or 1.0)
            product.secondary_unit_qty_available = float_round(
                qty, precision_rounding=product.uom_id.rounding)
コード例 #12
0
ファイル: product.py プロジェクト: vlim-git/odoo-addons
class product_template(models.Model):
    _inherit = 'product.template'

    list_price = fields.Float(compute='_compute_net_price', )
    lst_price_brut = fields.Float(
        string='Gross selling price',
        digits_compute=dp.get_precision('Product Price'),
    )

    brut_net_factor = fields.Float(string='Gross/Net Ratio', default=1)

    def get_list_price_factor(self, product, request):
        cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
        orm_user = registry.get('res.users')
        partner = orm_user.browse(cr, SUPERUSER_ID, request.uid,
                                  context).partner_id
        factor = 1
        if hasattr(partner.property_account_position, 'b2c_fiscal_position'
                   ) and partner.property_account_position.b2c_fiscal_position:
            if product.brut_net_factor > 0:
                factor = product.brut_net_factor
        return factor

    @api.onchange('categ_id')
    def onchange_product_category(self):
        if self.categ_id.brut_net_factor > 0:
            self.brut_net_factor = self.categ_id.brut_net_factor

    @api.one
    @api.depends('lst_price_brut', 'brut_net_factor')
    def _compute_net_price(self):
        if self.brut_net_factor:
            self.list_price = self.lst_price_brut / self.brut_net_factor

        if 'request' not in self.env.context and 'uid' in self.env.context:
            variants_attribute_prices = self.env[
                'product.attribute.price'].search([('product_tmpl_id', '=',
                                                    self.id)])
            for attribute_price in variants_attribute_prices:
                value = attribute_price.value_id
                _logger.debug("Variant: %s", value.name)
                if value:
                    price_extra = value.with_context(
                        active_id=self.id).price_extra
                    value.with_context(source='template').sudo().write({
                        'lst_price_brut':
                        (self.list_price + price_extra) * self.brut_net_factor
                    })
コード例 #13
0
class ProductConfiguratorAttribute(models.Model):
    _name = 'product.configurator.attribute'

    owner_id = fields.Integer(
        string="Owner",
        required=True,
        # ondelete is required since the owner_id is declared as inverse
        # of the field product_attribute_ids of the abstract model
        # product.configurator
        ondelete='cascade')
    owner_model = fields.Char(required=True)
    product_tmpl_id = fields.Many2one(comodel_name='product.template',
                                      string='Product Template',
                                      required=True)
    attribute_id = fields.Many2one(comodel_name='product.attribute',
                                   string='Attribute',
                                   readonly=True)
    value_id = fields.Many2one(comodel_name='product.attribute.value',
                               domain="[('attribute_id', '=', attribute_id), "
                               " ('id', 'in', possible_value_ids)]",
                               string='Value')
    possible_value_ids = fields.Many2many(
        comodel_name='product.attribute.value',
        compute='_compute_possible_value_ids',
        readonly=True)

    price_extra = fields.Float(
        compute='_compute_price_extra',
        digits=dp.get_precision('Product Price'),
        help="Price Extra: Extra price for the variant with this attribute "
        "value on sale price. eg. 200 price extra, 1000 + 200 = 1200.")

    @api.depends('attribute_id')
    def _compute_possible_value_ids(self):
        for record in self:
            # This should be unique due to the new constraint added
            attribute = record.product_tmpl_id.attribute_line_ids.filtered(
                lambda x: x.attribute_id == record.attribute_id)
            record.possible_value_ids = attribute.value_ids.sorted()

    @api.depends('value_id')
    def _compute_price_extra(self):
        for record in self:
            record.price_extra = sum(
                record.value_id.price_ids.filtered(lambda x: (
                    x.product_tmpl_id == record.product_tmpl_id)).mapped(
                        'price_extra'))
コード例 #14
0
ファイル: product.py プロジェクト: detian08/bsp_addons
class ProductProduct(models.Model):
    _inherit = "product.product"

    extra_weight = fields.Float(
        'Extra Weight',
        digits=dp.get_precision('Stock Weight'),
        help="Extra weight (in Kg.) given by packaging, etc.",
    )

    @api.onchange('extra_weight')
    def _onchange_extra_weight(self):
        if self.is_weight_uom:
            self.weight = self.uom_id.factor_inv + self.extra_weight
        else:
            self.extra_weight = 0

    @api.onchange('uom_id')
    def _onchange_uom_product_weight_through_uom(self):
        # When a UoM is in Weight category, we set the product weight to its
        # corresponding conversion to Kg.
        if self.is_weight_uom:
            self.weight = self.uom_id.factor_inv + self.extra_weight

    @api.multi
    def write(self, vals):
        if vals.get('uom_id') or vals.get('extra_weight'):
            uom_id = (self.env['product.uom'].browse(vals.get('uom_id')) or
                      self.uom_id)
            if self.is_weight_uom:
                vals['weight'] = uom_id.factor_inv + (
                    vals.get('extra_weight') or self.extra_weight)
        return super().write(vals)

    @api.model
    def create(self, vals):
        template = self.env['product.template'].browse(
            vals.get('product_tmpl_id'))
        uom_id = template.is_weight_uom and template.uom_id
        if not template:
            uom_id = (self.env['product.uom'].browse(vals.get('uom_id')))
        if uom_id and uom_id.category_id == self.env.ref(
                'product.product_uom_categ_kgm'):
            vals['weight'] = uom_id.factor_inv + (
                vals.get('extra_weight') or template.extra_weight)
        return super().create(vals)
コード例 #15
0
class StockInventoryLine(models.Model):
    _inherit = 'stock.inventory.line'

    discrepancy_qty = fields.Float(
        string='Discrepancy',
        compute='_compute_discrepancy',
        help="The difference between the actual qty counted and the "
        "theoretical quantity on hand.",
        digits=dp.get_precision('Product Unit of Measure'),
        default=0)
    discrepancy_percent = fields.Float(
        string='Discrepancy percent (%)',
        compute='_compute_discrepancy',
        digits=(3, 2),
        help="The discrepancy expressed in percent with theoretical quantity "
        "as basis")
    discrepancy_threshold = fields.Float(
        string='Threshold (%)',
        digits=(3, 2),
        help="Maximum Discrepancy Rate Threshold",
        compute='_compute_discrepancy_threshold')

    @api.multi
    @api.depends('theoretical_qty', 'product_qty')
    def _compute_discrepancy(self):
        for line in self:
            line.discrepancy_qty = line.product_qty - line.theoretical_qty
            if line.theoretical_qty:
                line.discrepancy_percent = 100 * abs(
                    (line.product_qty - line.theoretical_qty) /
                    line.theoretical_qty)
            elif not line.theoretical_qty and line.product_qty:
                line.discrepancy_percent = 100.0

    @api.multi
    def _compute_discrepancy_threshold(self):
        for line in self:
            whs = line.location_id.get_warehouse()
            if line.location_id.discrepancy_threshold > 0.0:
                line.discrepancy_threshold = line.location_id.\
                    discrepancy_threshold
            elif whs.discrepancy_threshold > 0.0:
                line.discrepancy_threshold = whs.discrepancy_threshold
            else:
                line.discrepancy_threshold = False
コード例 #16
0
class StockInventoryEmptyLines(models.Model):
    _name = 'stock.inventory.line.empty'

    product_code = fields.Char(
        string='Product Code',
        required=True,
    )
    product_qty = fields.Float(
        string='Quantity',
        required=True,
        default=1.0,
        digits=dp.get_precision('Product Unit of Measure'),
    )
    inventory_id = fields.Many2one(
        comodel_name='stock.inventory',
        string='Inventory',
        required=True,
        ondelete="cascade",
    )
コード例 #17
0
class PurchaseCostDistributionLineExpense(models.Model):
    _name = "purchase.cost.distribution.line.expense"
    _description = "Purchase cost distribution line expense"

    distribution_line = fields.Many2one(
        comodel_name='purchase.cost.distribution.line',
        string='Cost distribution line',
        ondelete="cascade",
    )
    picking_id = fields.Many2one(
        comodel_name="stock.picking",
        store=True,
        readonly=True,
        related="distribution_line.picking_id",
    )
    picking_date_done = fields.Datetime(
        related="picking_id.date_done",
        store=True,
        readonly=True,
    )
    distribution_expense = fields.Many2one(
        comodel_name='purchase.cost.distribution.expense',
        string='Distribution expense',
        ondelete="cascade",
    )
    type = fields.Many2one(
        'purchase.expense.type',
        string='Expense type',
        readonly=True,
        related='distribution_expense.type',
        store=True,
    )
    expense_amount = fields.Float(
        string='Expense amount',
        digits=dp.get_precision('Account'),
    )
    cost_ratio = fields.Float('Unit cost')
    company_id = fields.Many2one(
        comodel_name="res.company",
        related="distribution_line.company_id",
        store=True,
        readonly=True,
    )
コード例 #18
0
class StockPickingScrapLine(models.TransientModel):
    _name = "wiz.stock.picking.scrap.line"
    _rec_name = 'product_id'

    product_id = fields.Many2one(
        comodel_name='product.product',
        string='Product',
        readonly=True,
    )
    lot_id = fields.Many2one(
        comodel_name='stock.production.lot',
        string='Lot',
        readonly=True,
    )
    package_id = fields.Many2one(
        comodel_name='stock.quant.package',
        string='Package',
        readonly=True,
    )
    owner_id = fields.Many2one(
        comodel_name='res.partner',
        string='Owner',
        readonly=True,
    )
    quantity = fields.Float(
        string='Quantity',
        digits=dp.get_precision('Product Unit of Measure'),
        required=True,
    )
    uom_id = fields.Many2one(
        comodel_name='product.uom',
        string='Unit of Measure',
        readonly=True,
    )
    wizard_id = fields.Many2one(
        comodel_name='wiz.stock.picking.scrap',
        string='Wizard',
    )
    move_line_id = fields.Many2one(
        comodel_name='stock.move.line',
        string='Move Line',
    )
コード例 #19
0
class CXOrderpointTemplate(models.Model):
    _name = "cx.orderpoint.template"
    _description = "Minimum Inventory Rule Template"

    name = fields.Char(string="Name", compute='name_compose',
                       store=False)
    active = fields.Boolean(string='Active', default=True)
    category_id = fields.Many2one(string='Product Category', comodel_name='product.category', copy=False,
                                  ondelete='cascade')
    attr_val_ids = fields.Many2many(string="Attribute Values",
                                    comodel_name='product.attribute.value',
                                    relation='cx_op_tpl_pr_attr_val_rel',
                                    column1='cx_op_template_id',
                                    column2='attr_val_id')
    product_min_qty = fields.Float(string='Minimum Quantity', digits=dp.get_precision('Product Unit of Measure'),
                                   required=True)
    product_max_qty = fields.Float(string='Maximum Quantity', digits=dp.get_precision('Product Unit of Measure'),
                                   required=True)
    qty_multiple = fields.Float(string='Qty Multiple', digits=dp.get_precision('Product Unit of Measure'),
                                default=1, required=True)
    group_id = fields.Many2one('procurement.group', 'Procurement Group', copy=False)
    company_id = fields.Many2one(string='Company', comodel_name='res.company', required=True,
                                 default=lambda self: self.env.user.company_id.id)
    lead_days = fields.Integer(sting='Lead Time', default=1)
    lead_type = fields.Selection(
        [('net', 'Day(s) to get the products'), ('supplier', 'Day(s) to purchase')], 'Lead Type',
        required=True, default='supplier')
    warehouse_id = fields.Many2one(comodel_name='stock.warehouse', string='Warehouse',
                                   ondelete="cascade", required=False)
    location_id = fields.Many2one(comodel_name='stock.location', string='Location',
                                  ondelete="cascade", required=False)
    rule_ids = fields.One2many(string="Related Rules", comodel_name='stock.warehouse.orderpoint',
                               inverse_name='template_id')
    rule_ids_count = fields.Integer(string="Rules Count", compute="_rule_ids_count")

    _sql_constraints = [('tmpl_name_uniq', 'unique(category_id, company_id)',
                         'Such template already exists for this company!')]

# -- Count related rules
    @api.depends('rule_ids')
    @api.multi
    def _rule_ids_count(self):
        for rec in self:
            rec.rule_ids_count = len(rec.rule_ids)

# -- Compose name
    @api.depends('category_id')
    @api.multi
    def name_compose(self):
        for rec in self:
            prefix = rec.category_id.name_get()[0][1] if rec.category_id else False
            if prefix:
                rec.name = prefix
            else:
                rec.name = _('Global')

# -- Compose name -- PRO
    @api.depends('category_id', 'attr_val_ids')
    @api.multi
    def name_compose_pro(self):
        self.ensure_one()
        prefix = self.category_id.name if self.category_id else False
        suffix = [attr_val_id.name for attr_val_id in self.attr_val_ids] if len(self.attr_val_ids) > 0 else False
        if not prefix and not suffix:
            self.name = False
        elif prefix and suffix:
            self.name = '%s > %s' % (prefix, suffix)
        elif prefix:
            self.name = prefix
        else:
            self.name = suffix

# -- Apply template
    @api.multi
    def apply_template(self):
        for rec in self:
            is_global = False
            if rec.category_id:
                # Local templates
                products = self.env['product.product'].search([('categ_id', '=', rec.category_id.id),
                                                               ('type', '=', 'product')])
            else:
                # Global template
                products = self.env['product.product'].search([('type', '=', 'product')])
                is_global = True

            # Apply template to products
            for product in products:
                # Get existing rules
                existing_rules = self.env['stock.warehouse.orderpoint'].search([('product_id', '=', product.id)])
                len_existing_rules = len(existing_rules)

                # Do not apply Global template if rules exist
                if is_global and len_existing_rules > 0 and len(existing_rules.filtered(lambda t: not t.template_id)) == 0:
                    continue

                vals = {
                    'product_id': product.id,
                    'template_id': rec.id,
                    'product_min_qty': rec.product_min_qty,
                    'product_max_qty': rec.product_max_qty,
                    'qty_multiple': rec.qty_multiple,
                    'group_id': rec.group_id.id,
                    'lead_days': rec.lead_days,
                    'lead_type': rec.lead_type,
                }
                if rec.warehouse_id:
                    vals.update({'warehouse_id': rec.warehouse_id.id})
                if rec.location_id:
                    vals.update({'location_id': rec.location_id.id})

                # Update existing reordering rules or create new
                if len_existing_rules > 0:
                    existing_rules.filtered("template_control").write(vals)
                else:
                    vals.update({'template_control': True})
                    self.env['stock.warehouse.orderpoint'].create(vals)

# -- Create
    @api.model
    def create(self, vals):

        res = super(CXOrderpointTemplate, self).create(vals)
        res.apply_template()
        return res

# -- Write
    @api.multi
    def write(self, vals):

        # Remove category and attribute values
        vals.pop('category_id', False)
        vals.pop('attr_val_ids', False)

        # Write
        res = super(CXOrderpointTemplate, self).write(vals)

        # Get new vals for rules
        rule_vals = {}

        for key in TEMPLATE_FIELDS:
            if key in vals:
                rule_vals.update({key: vals.get(key, False)})

        # Update all related rules
        if len(rule_vals) > 0:
            self.env['stock.warehouse.orderpoint'].search([('template_id', 'in', self.ids)]).write(rule_vals)

        return res

# -- Delete
    @api.multi
    def unlink(self):

        # Get products
        product_ids = self.mapped('rule_ids').mapped('product_id')

        # Delete templates
        res = super(CXOrderpointTemplate, self).unlink()

        # Re-apply template
        if len(product_ids) > 0:
            product_ids.reorder_rules_update()

        return res
コード例 #20
0
class account_transfer(osv.osv):
    def _get_balance(self, src_journal, dst_journal, company):
        src_balance = dst_balance = 0.0
        #import pdb; pdb.set_trace()
        if src_journal.default_credit_account_id.id == src_journal.default_debit_account_id.id:
            if not src_journal.currency or company.currency_id.id == src_journal.currency.id:
                src_balance = src_journal.default_credit_account_id.balance
            else:
                src_balance = src_journal.default_credit_account_id.foreign_balance
        else:
            if not src_journal.currency or company.currency_id.id == src_journal.currency.id:
                src_balance = src_journal.default_debit_account_id.balance - src_journal.default_credit_account_id.balance
            else:
                src_balance = src_journal.default_debit_account_id.foreign_balance - src_journal.default_credit_account_id.foreign_balance
        if dst_journal.default_credit_account_id.id == dst_journal.default_debit_account_id.id:
            if not dst_journal.currency or company.currency_id.id == dst_journal.currency.id:
                dst_balance = dst_journal.default_credit_account_id.balance
            else:
                dst_balance = dst_journal.default_credit_account_id.foreign_balance
        else:
            if not dst_journal.currency or company.currency_id.id == dst_journal.currency.id:
                dst_balance = dst_journal.default_debit_account_id.balance - dst_journal.default_credit_account_id.balance
            else:
                dst_balance = dst_journal.default_debit_account_id.foreign_balance - dst_journal.default_credit_account_id.foreign_balance

        return (src_balance, dst_balance)

    def _balance(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        for trans in self.browse(cr, uid, ids, context=context):
            src_balance, dst_balance = self._get_balance(
                trans.src_journal_id, trans.dst_journal_id, trans.company_id)
            exchange = False
            if trans.dst_journal_id.currency.id != trans.src_journal_id.currency.id:
                exchange = True
            res[trans.id] = {
                'src_balance':
                src_balance,
                'dst_balance':
                dst_balance,
                'exchange':
                exchange,
                'exchange_inv':
                (trans.exchange_rate and 1.0 / trans.exchange_rate or 0.0)
            }
        return res

    def _get_type(self, cr, uid, context=None):
        return [('transfer', 'Transfer')]

    STATE_SELECTION = [
        ('draft', 'Draft'),
        ('confirm', 'Confirm'),
        ('done', 'Done'),
        ('cancel', 'Cancel'),
    ]

    _columns = {
        'company_id':
        fields.many2one('res.company',
                        'Company',
                        required=True,
                        readonly=True,
                        states={'draft': [('readonly', False)]}),
        'type':
        fields.selection(_get_type,
                         'Type',
                         required=True,
                         readonly=True,
                         states={'draft': [('readonly', False)]}),
        'name':
        fields.char('Number',
                    size=32,
                    required=True,
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'date':
        fields.date('Date',
                    required=True,
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'origin':
        fields.char('Origin',
                    size=128,
                    readonly=True,
                    states={'draft': [('readonly', False)]},
                    help="Origin Document"),
        'account_analytic_id':
        fields.many2one('account.analytic.account',
                        'Analytic Account',
                        readonly=True,
                        states={'draft': [('readonly', False)]}),
        'voucher_ids':
        fields.one2many('account.voucher',
                        'transfer_id',
                        string='Payments',
                        readonly=True,
                        states={
                            'draft': [('readonly', False)],
                            'confirm': [('readonly', False)]
                        }),
        'src_journal_id':
        fields.many2one('account.journal',
                        'Source Journal',
                        required=True,
                        domain=[('type', 'in', ['cash', 'bank'])],
                        select=True,
                        readonly=True,
                        states={'draft': [('readonly', False)]}),
        'src_partner_id':
        fields.many2one('res.partner', 'Source Partner', select=True),
        'src_balance':
        fields.function(
            _balance,
            digits_compute=dp.get_precision('Account'),
            string='Current Source Balance',
            type='float',
            readonly=True,
            multi='balance',
            help="Include all account moves in draft and confirmed state"),
        'src_amount':
        fields.float('Source Amount',
                     required=True,
                     readonly=True,
                     states={'draft': [('readonly', False)]}),
        'src_have_partner':
        fields.related('src_journal_id',
                       'have_partner',
                       type='boolean',
                       string='Have Partner',
                       readonly=True),
        'dst_journal_id':
        fields.many2one('account.journal',
                        'Destinity Journal',
                        required=True,
                        domain=[('type', 'in', ['cash', 'bank'])],
                        select=True,
                        readonly=True,
                        states={'draft': [('readonly', False)]}),
        'dst_partner_id':
        fields.many2one('res.partner', 'Destinity Partner', select=True),
        'dst_balance':
        fields.function(
            _balance,
            digits_compute=dp.get_precision('Account'),
            string='Current Destinity Balance',
            type='float',
            readonly=True,
            multi='balance',
            help="Include all account moves in draft and confirmed state"),
        'dst_amount':
        fields.float('Destinity Amount',
                     required=True,
                     readonly=True,
                     states={'draft': [('readonly', False)]}),
        'dst_have_partner':
        fields.related('dst_journal_id',
                       'have_partner',
                       type='boolean',
                       string='Have Partner',
                       readonly=True),
        'exchange_rate':
        fields.float('Exchange Rate',
                     digits_compute=dp.get_precision('Exchange'),
                     readonly=True,
                     states={'draft': [('readonly', False)]}),
        'exchange':
        fields.function(_balance,
                        string='Have Exchange',
                        type='boolean',
                        readonly=True,
                        multi='balance'),
        'exchange_inv':
        fields.function(_balance,
                        string='1 / Exchange Rate',
                        type='float',
                        digits_compute=dp.get_precision('Exchange'),
                        readonly=True,
                        multi='balance'),
        'adjust_move':
        fields.many2one(
            'account.move',
            'Adjust Move',
            readonly=True,
            help="Adjust move usually by difference in the money exchange"),
        'state':
        fields.selection(STATE_SELECTION,
                         string='State',
                         track_visibility='onchange',
                         readonly=True),
    }
    _defaults = {
        'type':
        'transfer',
        'name':
        lambda s, cr, u, c: s.pool.get('ir.sequence').get(
            cr, u, 'account.transfer'),
        'company_id':
        lambda s, cr, u, c: s.pool.get('res.users').browse(cr, u, u).company_id
        .id,
        'date':
        lambda *a: time.strftime('%Y-%m-%d'),
        'exchange_rate':
        1.0,
        'exchange_inv':
        1.0,
        'state':
        'draft',
    }
    _sql_constraints = [('name_unique', 'unique(company_id,name)',
                         _('The number must be unique!'))]
    _name = 'account.transfer'
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _description = 'Account Cash and Bank Transfer'
    _order = 'name desc'

    def voucher_get(self, cr, uid, trans, context=None):
        res = {}
        res['transfer_id'] = trans.id
        res['type'] = 'transfer'
        res['company_id'] = trans.company_id.id
        res['reference'] = trans.name + str(trans.origin and
                                            (' - ' + trans.origin) or '')
        res['line_ids'] = [(0, 0, {})]
        res['line_ids'][0][2][
            'account_analytic_id'] = trans.account_analytic_id and trans.account_analytic_id.id or 0
        res['line_ids'][0][2]['name'] = trans.origin
        return res

    def unlink(self, cr, uid, ids, context=None):
        for trans in self.browse(cr, uid, ids, context=context):
            if trans.state not in ('draft'):
                raise osv.except_osv(
                    _('User Error!'),
                    _('You cannot delete a not draft transfer "%s"') %
                    trans.name)
        return super(account_transfer, self).unlink(cr,
                                                    uid,
                                                    ids,
                                                    context=context)

    def copy(self, cr, uid, id, defaults, context=None):
        defaults['name'] = self.pool.get('ir.sequence').get(
            cr, uid, 'account.transfer')
        defaults['voucher_ids'] = []
        return super(account_transfer, self).copy(cr,
                                                  uid,
                                                  id,
                                                  defaults,
                                                  context=context)

    def onchange_amount(self, cr, uid, ids, field, src_amount, dst_amount,
                        exchange_rate):
        res = {'value': {}}
        if field == 'src_amount':
            res['value']['src_amount'] = src_amount
            res['value']['dst_amount'] = src_amount * exchange_rate
            res['value']['exchange_rate'] = exchange_rate
            res['value'][
                'exchange_inv'] = exchange_rate and 1.0 / exchange_rate or 0.0
        elif field == 'dst_amount':
            res['value'][
                'src_amount'] = exchange_rate and dst_amount / exchange_rate or 0.0
            res['value']['dst_amount'] = dst_amount
            res['value']['exchange_rate'] = exchange_rate
            res['value'][
                'exchange_inv'] = exchange_rate and 1.0 / exchange_rate or 0.0
        elif field == 'exchange_rate':
            res['value']['src_amount'] = src_amount
            res['value']['dst_amount'] = src_amount * exchange_rate
            res['value']['exchange_rate'] = exchange_rate
            res['value'][
                'exchange_inv'] = exchange_rate and 1.0 / exchange_rate or 0.0
        return res

    def onchange_journal(self, cr, uid, ids, src_journal_id, dst_journal_id,
                         date, exchange_rate, src_amount):
        res = {'value': {}}
        if not (src_journal_id and dst_journal_id):
            return res
        src_journal = self.pool.get('account.journal').browse(
            cr, uid, src_journal_id)
        dst_journal = self.pool.get('account.journal').browse(
            cr, uid, dst_journal_id)
        res['value']['src_balance'], res['value'][
            'dst_balance'] = self._get_balance(src_journal, dst_journal,
                                               src_journal.company_id)
        res['value']['exchange'] = (src_journal.currency.id !=
                                    dst_journal.currency.id)
        res['value']['src_have_partner'], res['value'][
            'dst_have_partner'] = src_journal.have_partner, dst_journal.have_partner
        res['value']['exchange_rate'] = exchange_rate
        if res['value']['exchange']:
            res['value']['exchange_rate'] = (
                src_journal.currency and src_journal.currency.rate
                or src_journal.company_id.currency_id.rate) and (
                    (dst_journal.currency and dst_journal.currency.rate
                     or dst_journal.company_id.currency_id.rate) /
                    (src_journal.currency and src_journal.currency.rate
                     or src_journal.company_id.currency_id.rate)) or 0.0
        else:
            res['value']['exchange_rate'] = 1.0
        res['value']['exchange_inv'] = res['value']['exchange_rate'] and (
            1.0 / res['value']['exchange_rate']) or 0.0
        res['value']['dst_amount'] = res['value']['exchange_rate'] * src_amount
        return res

    def action_confirm(self, cr, uid, ids, context=None):
        voucher_obj = self.pool.get('account.voucher')
        for trans in self.browse(cr, uid, ids, context=context):
            sval = self.voucher_get(cr, uid, trans, context=context)
            dval = self.voucher_get(cr, uid, trans, context=context)
            sval['journal_id'] = trans.src_journal_id.id
            dval['journal_id'] = trans.dst_journal_id.id
            sval[
                'account_id'] = trans.src_journal_id.default_credit_account_id.id
            dval[
                'account_id'] = trans.dst_journal_id.default_debit_account_id.id
            sval[
                'payment_rate'] = trans.src_journal_id.currency.id and trans.company_id.currency_id.id <> trans.src_journal_id.currency.id and trans.exchange_rate or 1.0
            dval[
                'payment_rate'] = trans.dst_journal_id.currency.id and trans.company_id.currency_id.id <> trans.dst_journal_id.currency.id and trans.exchange_inv or 1.0
            dval[
                'payment_rate_currency_id'] = trans.dst_journal_id.currency.id or trans.company_id.currency_id.id
            sval[
                'payment_rate_currency_id'] = trans.src_journal_id.currency.id or trans.company_id.currency_id.id
            #import pdb; pdb.set_trace()
            sval['line_ids'][0][2]['amount'] = sval[
                'amount'] = trans.src_amount
            dval['line_ids'][0][2]['amount'] = dval[
                'amount'] = trans.dst_amount
            sval['line_ids'][0][2]['type'] = 'dr'
            dval['line_ids'][0][2]['type'] = 'cr'
            sval['line_ids'][0][2][
                'account_id'] = trans.dst_journal_id.default_debit_account_id.id
            if trans.src_partner_id.id ^ trans.dst_partner_id.id:
                sval[
                    'partner_id'] = trans.src_have_partner and trans.src_partner_id.id or trans.dst_partner_id.id
            else:
                sval[
                    'partner_id'] = trans.src_have_partner and trans.src_partner_id.id or trans.company_id.partner_id.id
                dval[
                    'partner_id'] = trans.dst_have_partner and trans.dst_partner_id.id or trans.company_id.partner_id.id
                sval['line_ids'][0][2]['account_id'] = dval['line_ids'][0][2][
                    'account_id'] = trans.src_journal_id.account_transit.id
                voucher_obj.create(cr, uid, dval, context=context)
            voucher_obj.create(cr, uid, sval, context=context)
        return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)

    def action_done(self, cr, uid, ids, context=None):
        voucher_obj = self.pool.get('account.voucher')
        move_obj = self.pool.get('account.move')
        for trans in self.browse(cr, uid, ids, context=context):
            paid_amount = []
            for voucher in trans.voucher_ids:
                (voucher.state == 'draft') and voucher_obj.proforma_voucher(
                    cr, uid, [voucher.id], context=context)
                sign = (voucher.journal_id.id
                        == trans.src_journal_id.id) and 1 or -1
                paid_amount.append(
                    sign * voucher_obj._paid_amount_in_company_currency(
                        cr, uid, [voucher.id], '', '')[voucher.id])
                #paid_amount.append(sign * voucher.paid_amount_in_company_currency)
            sum_amount = sum(paid_amount)
            if len(paid_amount) > 1 and sum_amount != 0.0:
                periods = self.pool.get('account.period').find(cr, uid)
                move = {}
                move['journal_id'] = trans.dst_journal_id.id
                move['period_id'] = periods and periods[0] or False
                move['ref'] = trans.name + str(trans.origin and
                                               (' - ' + trans.origin) or '')
                move['date'] = trans.date
                move['line_id'] = [(0, 0, {}), (0, 0, {})]
                move['line_id'][0][2]['name'] = trans.name
                move['line_id'][1][2]['name'] = trans.name
                if sum_amount > 0:
                    move['line_id'][0][2][
                        'account_id'] = trans.dst_journal_id.default_debit_account_id.id
                    move['line_id'][1][2][
                        'account_id'] = trans.src_journal_id.account_transit.id  #trans.company_id.income_currency_exchange_account_id.id
                    move['line_id'][0][2]['debit'] = sum_amount
                    move['line_id'][1][2]['credit'] = sum_amount
                else:
                    move['line_id'][0][2][
                        'account_id'] = trans.dst_journal_id.default_credit_account_id.id
                    move['line_id'][1][2][
                        'account_id'] = trans.src_journal_id.account_transit.id  #trans.company_id.expense_currency_exchange_account_id.id
                    move['line_id'][1][2]['debit'] = -1 * sum_amount
                    move['line_id'][0][2]['credit'] = -1 * sum_amount
                move_id = move_obj.create(cr, uid, move, context=context)
                self.write(cr,
                           uid, [trans.id], {'adjust_move': move_id},
                           context=context)
        return self.write(cr, uid, ids, {'state': 'done'}, context=context)

    def action_cancel(self, cr, uid, ids, context=None):
        voucher_obj = self.pool.get('account.voucher')
        move_obj = self.pool.get('account.move')
        #import pdb; pdb.set_trace()
        for trans in self.browse(cr, uid, ids, context=context):
            for voucher in trans.voucher_ids:
                voucher_obj.unlink(cr, uid, [voucher.id], context=context)
            trans.adjust_move and move_obj.unlink(
                cr, uid, [trans.adjust_move.id], context=context)
        return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)

    def action_draft(self, cr, uid, ids, context=None):
        self.write(cr, uid, ids, {'state': 'draft'})
        #        wf_service = netsvc.LocalService("workflow")
        #        for trans_id in ids:
        #            wf_service.trg_delete(uid, 'account.transfer', trans_id, cr)
        #            wf_service.trg_create(uid, 'account.transfer', trans_id, cr)
        return True
コード例 #21
0
class account_expense(osv.Model):
    _inherit = 'account.expense'
    _columns = {
        'amount_planed':
        fields.float('Planed Amount',
                     digits_compute=dp.get_precision('Account'),
                     readonly=True,
                     states={'draft': [('readonly', False)]},
                     help="Planed incomes to receive"),
        'date_end':
        fields.date('Date End',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'description':
        fields.html('Description',
                    readonly=True,
                    states={'draft': [('readonly', False)]}),
        'property_bank_journal_id':
        fields.property(
            'account.journal',
            type='many2one',
            view_load=True,
            relation='account.journal',
            string='Bank Journal',
            readonly=True,
            states={'draft': [('readonly', False)]},
            help="Default bank journal to make the automated cash transfers"),
    }

    def action_pre_approve(self, cr, uid, ids, context=None):
        for expense in self.browse(cr, uid, ids, context=context):
            if not expense.invoice_ids:
                raise osv.except_osv(
                    _('User Error!'),
                    _('You must register almost one invoice on invoices tab'))
        return super(account_expense, self).action_pre_approve(cr,
                                                               uid,
                                                               ids,
                                                               context=context)

    def action_approve(self, cr, uid, ids, context=None):
        for expense in self.browse(cr, uid, ids, context=context):
            if expense.type == 'expenditure' and not self.pool.get(
                    'res.currency').is_zero(cr, uid, expense.currency_id,
                                            expense.amount_balance):
                raise osv.except_osv(
                    _('User Error!'),
                    _('Te balance amount must be zero (current balance:%d)') %
                    (expense.amount_balance))
        return super(account_expense, self).action_approve(cr,
                                                           uid,
                                                           ids,
                                                           context=context)

    def action_generate_transfer(self, cr, uid, ids, context=None):
        context = context or {}
        transfer_obj = self.pool.get('account.transfer')
        for expense in self.browse(cr, uid, ids, context=context):
            if not expense.property_bank_journal_id:
                raise openerp.exceptions.Warning(
                    _("You must configure a new property (property bank journal_id) for the current company, on default bank journal!"
                      ))
            amount = expense.amount_planed + expense.amount_transfer
            if amount <= 0.01:
                continue
            src_journal = expense.property_bank_journal_id
            dst_journal = expense.journal_id
            dst_partner_id = expense.partner_id.id
            value = transfer_obj.onchange_journal(cr, uid, ids, src_journal.id,
                                                  dst_journal.id,
                                                  time.strftime('%Y-%m-%d'),
                                                  1.0, amount)['value']
            value.update(
                transfer_obj.onchange_amount(cr, uid, ids, 'dst_amount', 0.0,
                                             amount,
                                             value['exchange_rate'])['value'])
            transfer_obj.create(cr,
                                uid, {
                                    'company_id': expense.company_id.id,
                                    'type': 'expense',
                                    'origin': expense.name,
                                    'src_journal_id': src_journal.id,
                                    'src_amount': value['src_amount'],
                                    'dst_journal_id': dst_journal.id,
                                    'dst_partner_id': dst_partner_id,
                                    'dst_amount': amount,
                                    'exchange_rate': value['exchange_rate'],
                                    'expense_id': expense.id,
                                },
                                context=context)
        return super(account_expense,
                     self).action_generate_transfer(cr,
                                                    uid,
                                                    ids,
                                                    context=context)
コード例 #22
0
class ProductProduct(models.Model):

    """ Add a field for the stock available to promise.
    Useful implementations need to be installed through the Settings menu or by
    installing one of the modules stock_available_*
    """
    _inherit = 'product.product'

    @api.multi
    def _compute_available_quantities_dict(self):
        stock_dict = self._compute_quantities_dict(
            self._context.get('lot_id'),
            self._context.get('owner_id'),
            self._context.get('package_id'),
            self._context.get('from_date'),
            self._context.get('to_date'))
        res = {}
        for product in self:
            res[product.id] = {
                'immediately_usable_qty': stock_dict[product.id][
                    'virtual_available'],
                'potential_qty': 0.0
            }
        return res, stock_dict

    @api.multi
    @api.depends('virtual_available')
    def _compute_available_quantities(self):
        res, _ = self._compute_available_quantities_dict()
        for product in self:
            for key, value in res[product.id].items():
                if hasattr(product, key):
                    product[key] = value

    immediately_usable_qty = fields.Float(
        digits=dp.get_precision('Product Unit of Measure'),
        compute='_compute_available_quantities',
        search="_search_immediately_usable_qty",
        string='Available to promise',
        help="Stock for this Product that can be safely proposed "
             "for sale to Customers.\n"
             "The definition of this value can be configured to suit "
             "your needs.")
    potential_qty = fields.Float(
        compute='_compute_available_quantities',
        digits=dp.get_precision('Product Unit of Measure'),
        string='Potential',
        help="Quantity of this Product that could be produced using "
             "the materials already at hand.")

    @api.model
    def _search_immediately_usable_qty(self, operator, value):
        """ Search function for the immediately_usable_qty field.
        The search is quite similar to the Odoo search about quantity available
        (addons/stock/models/product.py,253; _search_product_quantity function)
        :param operator: str
        :param value: str
        :return: list of tuple (domain)
        """
        products = self.search([])
        # Force prefetch
        products.mapped("immediately_usable_qty")
        product_ids = []
        for product in products:
            if OPERATORS[operator](product.immediately_usable_qty, value):
                product_ids.append(product.id)
        return [('id', 'in', product_ids)]
コード例 #23
0
class PurchaseCostDistributionExpense(models.Model):
    _name = "purchase.cost.distribution.expense"
    _description = "Purchase cost distribution expense"

    @api.multi
    @api.depends('distribution', 'distribution.cost_lines')
    def _get_imported_lines(self):
        for record in self:
            record.imported_lines = record.env[
                'purchase.cost.distribution.line']
            record.imported_lines |= record.distribution.cost_lines

    distribution = fields.Many2one(comodel_name='purchase.cost.distribution',
                                   string='Cost distribution',
                                   index=True,
                                   ondelete="cascade",
                                   required=True)
    ref = fields.Char(string="Reference")
    type = fields.Many2one(comodel_name='purchase.expense.type',
                           string='Expense type',
                           index=True,
                           ondelete="restrict")
    calculation_method = fields.Selection(string='Calculation method',
                                          related='type.calculation_method',
                                          readonly=True)
    imported_lines = fields.Many2many(
        comodel_name='purchase.cost.distribution.line',
        string='Imported lines',
        compute='_get_imported_lines')
    affected_lines = fields.Many2many(
        comodel_name='purchase.cost.distribution.line',
        column1="expense_id",
        relation="distribution_expense_aff_rel",
        column2="line_id",
        string='Affected lines',
        help="Put here specific lines that this expense is going to be "
        "distributed across. Leave it blank to use all imported lines.",
        domain="[('id', 'in', imported_lines)]")
    expense_amount = fields.Float(string='Expense amount',
                                  digits=dp.get_precision('Account'),
                                  required=True)
    invoice_line = fields.Many2one(
        comodel_name='account.invoice.line',
        string="Supplier invoice line",
        domain="[('invoice_id.type', '=', 'in_invoice'),"
        "('invoice_id.state', 'in', ('open', 'paid'))]")
    invoice_id = fields.Many2one(comodel_name='account.invoice',
                                 string="Invoice")
    display_name = fields.Char(compute="_compute_display_name", store=True)
    company_id = fields.Many2one(
        comodel_name="res.company",
        related="distribution.company_id",
        store=True,
    )

    @api.multi
    @api.depends('distribution', 'type', 'expense_amount', 'ref')
    def _compute_display_name(self):
        for record in self:
            record.display_name = "%s: %s - %s (%s)" % (
                record.distribution.name, record.type.name, record.ref,
                formatLang(record.env,
                           record.expense_amount,
                           currency_obj=record.distribution.currency_id))

    @api.onchange('type')
    def onchange_type(self):
        if self.type and self.type.default_amount:
            self.expense_amount = self.type.default_amount

    @api.onchange('invoice_line')
    def onchange_invoice_line(self):
        self.invoice_id = self.invoice_line.invoice_id.id
        self.expense_amount = self.invoice_line.price_subtotal

    @api.multi
    def button_duplicate(self):
        for expense in self:
            expense.copy()
コード例 #24
0
class PurchaseCostDistributionLine(models.Model):
    _name = "purchase.cost.distribution.line"
    _description = "Purchase cost distribution Line"

    @api.multi
    @api.depends('product_price_unit', 'product_qty')
    def _compute_total_amount(self):
        for dist_line in self:
            dist_line.total_amount = dist_line.product_price_unit * \
                dist_line.product_qty

    @api.multi
    @api.depends('product_id', 'product_qty')
    def _compute_total_weight(self):
        for dist_line in self:
            dist_line.total_weight = dist_line.product_weight * \
                dist_line.product_qty

    @api.multi
    @api.depends('product_id', 'product_qty')
    def _compute_total_volume(self):
        for dist_line in self:
            dist_line.total_volume = dist_line.product_volume * \
                dist_line.product_qty

    @api.multi
    @api.depends('expense_lines', 'expense_lines.cost_ratio')
    def _compute_cost_ratio(self):
        for dist_line in self:
            dist_line.cost_ratio = sum(
                [x.cost_ratio for x in dist_line.expense_lines])

    @api.multi
    @api.depends('expense_lines', 'expense_lines.expense_amount')
    def _compute_expense_amount(self):
        for dist_line in self:
            dist_line.expense_amount = sum(
                [x.expense_amount for x in dist_line.expense_lines])

    @api.multi
    @api.depends('standard_price_old', 'cost_ratio')
    def _compute_standard_price_new(self):
        for dist_line in self:
            dist_line.standard_price_new = dist_line.standard_price_old + \
                dist_line.cost_ratio

    @api.multi
    @api.depends('distribution', 'distribution.name', 'picking_id',
                 'picking_id.name', 'product_id', 'product_id.display_name')
    def _compute_name(self):
        for dist_line in self:
            dist_line.name = "%s: %s / %s" % (
                dist_line.distribution.name,
                dist_line.picking_id.name,
                dist_line.product_id.display_name,
            )

    @api.multi
    @api.depends('move_id', 'move_id.product_id')
    def _compute_product_id(self):
        for dist_line in self:
            # Cannot be done via related
            # field due to strange bug in update chain
            dist_line.product_id = dist_line.move_id.product_id.id

    @api.multi
    @api.depends('move_id', 'move_id.product_qty')
    def _get_product_qty(self):
        for dist_line in self:
            # Cannot be done via related
            #  field due to strange bug in update chain
            dist_line.product_qty = dist_line.move_id.product_qty

    @api.multi
    @api.depends('move_id')
    def _compute_standard_price_old(self):
        for dist_line in self:
            dist_line.standard_price_old = (
                dist_line.move_id and dist_line.move_id._get_price_unit()
                or 0.0)

    name = fields.Char(
        string='Name',
        compute='_compute_name',
        store=True,
    )
    distribution = fields.Many2one(comodel_name='purchase.cost.distribution',
                                   string='Cost distribution',
                                   ondelete='cascade',
                                   required=True)
    move_id = fields.Many2one(comodel_name='stock.move',
                              string='Picking line',
                              ondelete="restrict",
                              required=True)
    purchase_line_id = fields.Many2one(comodel_name='purchase.order.line',
                                       string='Purchase order line',
                                       related='move_id.purchase_line_id')
    purchase_id = fields.Many2one(comodel_name='purchase.order',
                                  string='Purchase order',
                                  readonly=True,
                                  related='move_id.purchase_line_id.order_id',
                                  store=True)
    partner = fields.Many2one(
        comodel_name='res.partner',
        string='Supplier',
        readonly=True,
        related='move_id.purchase_line_id.order_id.partner_id')
    picking_id = fields.Many2one('stock.picking',
                                 string='Picking',
                                 related='move_id.picking_id',
                                 store=True)
    product_id = fields.Many2one(comodel_name='product.product',
                                 string='Product',
                                 store=True,
                                 compute='_compute_product_id')
    product_qty = fields.Float(string='Quantity',
                               compute='_get_product_qty',
                               store=True)
    product_uom = fields.Many2one(comodel_name='product.uom',
                                  string='Unit of measure',
                                  related='move_id.product_uom')
    product_price_unit = fields.Float(string='Unit price',
                                      related='move_id.price_unit')
    expense_lines = fields.One2many(
        comodel_name='purchase.cost.distribution.line.expense',
        inverse_name='distribution_line',
        string='Expenses distribution lines')
    product_volume = fields.Float(string='Volume',
                                  help="The volume in m3.",
                                  related='product_id.product_tmpl_id.volume')
    product_weight = fields.Float(string='Gross weight',
                                  related='product_id.product_tmpl_id.weight',
                                  help="The gross weight in Kg.")
    standard_price_old = fields.Float(string='Previous cost',
                                      compute="_compute_standard_price_old",
                                      store=True,
                                      digits=dp.get_precision('Product Price'))
    expense_amount = fields.Float(string='Cost amount',
                                  digits=dp.get_precision('Account'),
                                  compute='_compute_expense_amount')
    cost_ratio = fields.Float(string='Unit cost',
                              compute='_compute_cost_ratio')
    standard_price_new = fields.Float(string='New cost',
                                      digits=dp.get_precision('Product Price'),
                                      compute='_compute_standard_price_new')
    total_amount = fields.Float(compute=_compute_total_amount,
                                string='Amount line',
                                digits=dp.get_precision('Account'))
    total_weight = fields.Float(compute=_compute_total_weight,
                                string="Line weight",
                                store=True,
                                digits=dp.get_precision('Stock Weight'),
                                help="The line gross weight in Kg.")
    total_volume = fields.Float(compute=_compute_total_volume,
                                string='Line volume',
                                store=True,
                                help="The line volume in m3.")
    company_id = fields.Many2one(
        comodel_name="res.company",
        related="distribution.company_id",
        store=True,
    )
コード例 #25
0
class PurchaseCostDistribution(models.Model):
    _name = "purchase.cost.distribution"
    _description = "Purchase landed costs distribution"
    _order = 'name desc'

    @api.multi
    @api.depends('total_expense', 'total_purchase')
    def _compute_amount_total(self):
        for distribution in self:
            distribution.amount_total = distribution.total_purchase + \
                distribution.total_expense

    @api.multi
    @api.depends('cost_lines', 'cost_lines.total_amount')
    def _compute_total_purchase(self):
        for distribution in self:
            distribution.total_purchase = sum(
                [x.total_amount for x in distribution.cost_lines])

    @api.multi
    @api.depends('cost_lines', 'cost_lines.product_price_unit')
    def _compute_total_price_unit(self):
        for distribution in self:
            distribution.total_price_unit = sum(
                [x.product_price_unit for x in distribution.cost_lines])

    @api.multi
    @api.depends('cost_lines', 'cost_lines.product_qty')
    def _compute_total_uom_qty(self):
        for distribution in self:
            distribution.total_uom_qty = sum(
                [x.product_qty for x in distribution.cost_lines])

    @api.multi
    @api.depends('cost_lines', 'cost_lines.total_weight')
    def _compute_total_weight(self):
        for distribution in self:
            distribution.total_weight = sum(
                [x.total_weight for x in distribution.cost_lines])

    @api.multi
    @api.depends('cost_lines', 'cost_lines.total_volume')
    def _compute_total_volume(self):
        for distribution in self:
            distribution.total_volume = sum(
                [x.total_volume for x in distribution.cost_lines])

    @api.multi
    @api.depends('expense_lines', 'expense_lines.expense_amount')
    def _compute_total_expense(self):
        for distribution in self:
            distribution.total_expense = sum(
                [x.expense_amount for x in distribution.expense_lines])

    def _expense_lines_default(self):
        expenses = self.env['purchase.expense.type'].search([
            ('default_expense', '=', True)
        ])
        return [{
            'type': x,
            'expense_amount': x.default_amount
        } for x in expenses]

    name = fields.Char(string='Distribution number',
                       required=True,
                       index=True,
                       default='/')
    company_id = fields.Many2one(
        comodel_name='res.company',
        string='Company',
        required=True,
        default=(lambda self: self.env['res.company']._company_default_get(
            'purchase.cost.distribution')))
    currency_id = fields.Many2one(comodel_name='res.currency',
                                  string='Currency',
                                  related="company_id.currency_id")
    state = fields.Selection([('draft', 'Draft'), ('calculated', 'Calculated'),
                              ('done', 'Done'), ('error', 'Error'),
                              ('cancel', 'Cancel')],
                             string='Status',
                             readonly=True,
                             default='draft')
    cost_update_type = fields.Selection([('direct', 'Direct Update')],
                                        string='Cost Update Type',
                                        default='direct',
                                        required=True)
    date = fields.Date(string='Date',
                       required=True,
                       readonly=True,
                       index=True,
                       states={'draft': [('readonly', False)]},
                       default=fields.Date.context_today)
    total_uom_qty = fields.Float(compute=_compute_total_uom_qty,
                                 readonly=True,
                                 digits=dp.get_precision('Product UoS'),
                                 string='Total quantity')
    total_weight = fields.Float(compute=_compute_total_weight,
                                string='Total gross weight',
                                readonly=True,
                                digits=dp.get_precision('Stock Weight'))
    total_volume = fields.Float(compute=_compute_total_volume,
                                string='Total volume',
                                readonly=True)
    total_purchase = fields.Float(compute=_compute_total_purchase,
                                  digits=dp.get_precision('Account'),
                                  string='Total purchase')
    total_price_unit = fields.Float(compute=_compute_total_price_unit,
                                    string='Total price unit',
                                    digits=dp.get_precision('Product Price'))
    amount_total = fields.Float(compute=_compute_amount_total,
                                digits=dp.get_precision('Account'),
                                string='Total')
    total_expense = fields.Float(compute=_compute_total_expense,
                                 digits=dp.get_precision('Account'),
                                 string='Total expenses')
    note = fields.Text(string='Documentation for this order')
    cost_lines = fields.One2many(
        comodel_name='purchase.cost.distribution.line',
        ondelete="cascade",
        inverse_name='distribution',
        string='Distribution lines')
    expense_lines = fields.One2many(
        comodel_name='purchase.cost.distribution.expense',
        ondelete="cascade",
        inverse_name='distribution',
        string='Expenses',
        default=_expense_lines_default)

    @api.multi
    def unlink(self):
        for record in self:
            if record.state not in ('draft', 'calculated'):
                raise UserError(
                    _("You can't delete a confirmed cost distribution"))
        return super(PurchaseCostDistribution, self).unlink()

    @api.model
    def create(self, vals):
        if vals.get('name', '/') == '/':
            vals['name'] = self.env['ir.sequence'].next_by_code(
                'purchase.cost.distribution')
        return super(PurchaseCostDistribution, self).create(vals)

    @api.multi
    def write(self, vals):
        for command in vals.get('cost_lines', []):
            if command[0] in (2, 3, 5):
                if command[0] == 5:
                    to_check = self.mapped('cost_lines').ids
                else:
                    to_check = [command[1]]
                lines = self.mapped('expense_lines.affected_lines').ids
                if any(i in lines for i in to_check):
                    raise UserError(
                        _("You can't delete a cost line if it's an "
                          "affected line of any expense line."))
        return super(PurchaseCostDistribution, self).write(vals)

    @api.model
    def _prepare_expense_line(self, expense_line, cost_line):
        distribution = cost_line.distribution
        if expense_line.type.calculation_method == 'amount':
            multiplier = cost_line.total_amount
            if expense_line.affected_lines:
                divisor = sum(
                    [x.total_amount for x in expense_line.affected_lines])
            else:
                divisor = distribution.total_purchase
        elif expense_line.type.calculation_method == 'price':
            multiplier = cost_line.product_price_unit
            if expense_line.affected_lines:
                divisor = sum([
                    x.product_price_unit for x in expense_line.affected_lines
                ])
            else:
                divisor = distribution.total_price_unit
        elif expense_line.type.calculation_method == 'qty':
            multiplier = cost_line.product_qty
            if expense_line.affected_lines:
                divisor = sum(
                    [x.product_qty for x in expense_line.affected_lines])
            else:
                divisor = distribution.total_uom_qty
        elif expense_line.type.calculation_method == 'weight':
            multiplier = cost_line.total_weight
            if expense_line.affected_lines:
                divisor = sum(
                    [x.total_weight for x in expense_line.affected_lines])
            else:
                divisor = distribution.total_weight
        elif expense_line.type.calculation_method == 'volume':
            multiplier = cost_line.total_volume
            if expense_line.affected_lines:
                divisor = sum(
                    [x.total_volume for x in expense_line.affected_lines])
            else:
                divisor = distribution.total_volume
        elif expense_line.type.calculation_method == 'equal':
            multiplier = 1
            divisor = (len(expense_line.affected_lines)
                       or len(distribution.cost_lines))
        else:
            raise UserError(_('No valid distribution type.'))
        if divisor:
            expense_amount = (expense_line.expense_amount * multiplier /
                              divisor)
        else:
            raise UserError(
                _("The cost for the line '%s' can't be "
                  "distributed because the calculation method "
                  "doesn't provide valid data" % expense_line.type.name))
        return {
            'distribution_expense': expense_line.id,
            'expense_amount': expense_amount,
            'cost_ratio': expense_amount / cost_line.product_qty,
        }

    @api.multi
    def action_calculate(self):
        for distribution in self:
            # Check expense lines for amount 0
            if any([not x.expense_amount for x in distribution.expense_lines]):
                raise UserError(
                    _('Please enter an amount for all the expenses'))
            # Check if exist lines in distribution
            if not distribution.cost_lines:
                raise UserError(
                    _('There is no picking lines in the distribution'))
            # Calculating expense line
            for cost_line in distribution.cost_lines:
                cost_line.expense_lines.unlink()
                expense_lines = []
                for expense in distribution.expense_lines:
                    if (expense.affected_lines
                            and cost_line not in expense.affected_lines):
                        continue
                    expense_lines.append(
                        self._prepare_expense_line(expense, cost_line))
                cost_line.expense_lines = [(0, 0, x) for x in expense_lines]
            distribution.state = 'calculated'
        return True

    def _product_price_update(self, product, vals_list):
        """Method that mimicks stock.move's product_price_update_before_done
        method behaviour, but taking into account that calculations are made
        on an already done moves, and prices sources are given as parameters.
        """
        moves_total_qty = 0
        moves_total_diff_price = 0
        for move, price_diff in vals_list:
            moves_total_qty += move.product_qty
            moves_total_diff_price += move.product_qty * price_diff
        prev_qty_available = product.qty_available - moves_total_qty
        if prev_qty_available <= 0:
            prev_qty_available = 0
        total_available = prev_qty_available + moves_total_qty
        new_std_price = ((total_available * product.standard_price +
                          moves_total_diff_price) / total_available)
        # Write the standard price, as SUPERUSER_ID, because a
        # warehouse manager may not have the right to write on products
        product.sudo().write({'standard_price': new_std_price})

    @api.multi
    def action_done(self):
        """Perform all moves that touch the same product in batch."""
        self.ensure_one()
        if self.cost_update_type != 'direct':
            return
        d = {}
        for line in self.cost_lines:
            product = line.move_id.product_id
            if (product.cost_method != 'average'
                    or line.move_id.location_id.usage != 'supplier'):
                continue
            d.setdefault(product, [])
            d[product].append(
                (line.move_id,
                 line.standard_price_new - line.standard_price_old), )
        for product, vals_list in d.items():
            self._product_price_update(product, vals_list)
            for move, price_diff in vals_list:
                move.price_unit += price_diff
                move.value = move.product_uom_qty * move.price_unit
        self.state = 'done'

    @api.multi
    def action_draft(self):
        self.write({'state': 'draft'})
        return True

    @api.multi
    def action_cancel(self):
        """Perform all moves that touch the same product in batch."""
        self.ensure_one()
        self.state = 'draft'
        if self.cost_update_type != 'direct':
            return
        d = {}
        for line in self.cost_lines:
            product = line.move_id.product_id
            if (product.cost_method != 'average'
                    or line.move_id.location_id.usage != 'supplier'):
                continue
            if self.currency_id.compare_amounts(line.move_id.price_unit,
                                                line.standard_price_new) != 0:
                raise UserError(
                    _('Cost update cannot be undone because there has '
                      'been a later update. Restore correct price and try '
                      'again.'))
            d.setdefault(product, [])
            d[product].append(
                (line.move_id,
                 line.standard_price_old - line.standard_price_new), )
        for product, vals_list in d.items():
            self._product_price_update(product, vals_list)
            for move, price_diff in vals_list:
                move.price_unit += price_diff
                move._run_valuation()
コード例 #26
0
class StockRequest(models.AbstractModel):
    _name = "stock.request.abstract"
    _description = "Stock Request Template"
    _inherit = ['mail.thread', 'mail.activity.mixin']

    @api.model
    def default_get(self, fields):
        res = super(StockRequest, self).default_get(fields)
        warehouse = None
        if 'warehouse_id' not in res and res.get('company_id'):
            warehouse = self.env['stock.warehouse'].search(
                [('company_id', '=', res['company_id'])], limit=1)
        if warehouse:
            res['warehouse_id'] = warehouse.id
            res['location_id'] = warehouse.lot_stock_id.id
        return res

    @api.depends('product_id', 'product_uom_id', 'product_uom_qty',
                 'product_id.product_tmpl_id.uom_id')
    def _compute_product_qty(self):
        for rec in self:
            rec.product_qty = rec.product_uom_id._compute_quantity(
                rec.product_uom_qty, rec.product_id.product_tmpl_id.uom_id)

    name = fields.Char('Name',
                       copy=False,
                       required=True,
                       readonly=True,
                       default='/')
    warehouse_id = fields.Many2one(
        'stock.warehouse',
        'Warehouse',
        ondelete="cascade",
        required=True,
    )
    location_id = fields.Many2one(
        'stock.location',
        'Location',
        domain=[('usage', 'in', ['internal', 'transit'])],
        ondelete="cascade",
        required=True,
    )
    product_id = fields.Many2one(
        'product.product',
        'Product',
        domain=[('type', 'in', ['product', 'consu'])],
        ondelete='cascade',
        required=True,
    )
    allow_virtual_location = fields.Boolean(
        related='company_id.stock_request_allow_virtual_loc',
        readonly=True,
    )
    product_uom_id = fields.Many2one(
        'product.uom',
        'Product Unit of Measure',
        required=True,
        default=lambda self: self._context.get('product_uom_id', False),
    )
    product_uom_qty = fields.Float(
        'Quantity',
        digits=dp.get_precision('Product Unit of Measure'),
        required=True,
        help="Quantity, specified in the unit of measure indicated in the "
        "request.",
    )
    product_qty = fields.Float(
        'Real Quantity',
        compute='_compute_product_qty',
        store=True,
        copy=False,
        digits=dp.get_precision('Product Unit of Measure'),
        help='Quantity in the default UoM of the product',
    )
    procurement_group_id = fields.Many2one(
        'procurement.group',
        'Procurement Group',
        help="Moves created through this stock request will be put in this "
        "procurement group. If none is given, the moves generated by "
        "procurement rules will be grouped into one big picking.",
    )
    company_id = fields.Many2one(
        'res.company',
        'Company',
        required=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'stock.request'),
    )
    route_id = fields.Many2one('stock.location.route',
                               string='Route',
                               domain="[('id', 'in', route_ids)]",
                               ondelete='restrict')

    route_ids = fields.Many2many(
        'stock.location.route',
        string='Route',
        compute='_compute_route_ids',
        readonly=True,
    )

    _sql_constraints = [
        ('name_uniq', 'unique(name, company_id)', 'Name must be unique'),
    ]

    @api.depends('product_id', 'warehouse_id', 'location_id')
    def _compute_route_ids(self):
        route_obj = self.env['stock.location.route']
        for wh in self.mapped('warehouse_id'):
            wh_routes = route_obj.search([('warehouse_ids', '=', wh.id)])
            for record in self.filtered(lambda r: r.warehouse_id == wh):
                routes = route_obj
                if record.product_id:
                    routes += record.product_id.mapped(
                        'route_ids') | record.product_id.mapped(
                            'categ_id').mapped('total_route_ids')
                if record.warehouse_id:
                    routes |= wh_routes
                parents = record.get_parents().ids
                record.route_ids = routes.filtered(lambda r: any(
                    p.location_id.id in parents for p in r.pull_ids))

    def get_parents(self):
        location = self.location_id
        result = location
        while location.location_id:
            location = location.location_id
            result |= location
        return result

    @api.constrains('company_id', 'product_id', 'warehouse_id', 'location_id',
                    'route_id')
    def _check_company_constrains(self):
        """ Check if the related models have the same company """
        for rec in self:
            if rec.product_id.company_id and \
                    rec.product_id.company_id != rec.company_id:
                raise ValidationError(
                    _('You have entered a product that is assigned '
                      'to another company.'))
            if rec.location_id.company_id and \
                    rec.location_id.company_id != rec.company_id:
                raise ValidationError(
                    _('You have entered a location that is '
                      'assigned to another company.'))
            if rec.warehouse_id.company_id != rec.company_id:
                raise ValidationError(
                    _('You have entered a warehouse that is '
                      'assigned to another company.'))
            if rec.route_id and rec.route_id.company_id and \
                    rec.route_id.company_id != rec.company_id:
                raise ValidationError(
                    _('You have entered a route that is '
                      'assigned to another company.'))

    @api.constrains('product_id')
    def _check_product_uom(self):
        ''' Check if the UoM has the same category as the
        product standard UoM '''
        if any(request.product_id.uom_id.category_id !=
               request.product_uom_id.category_id for request in self):
            raise ValidationError(
                _('You have to select a product unit of measure in the '
                  'same category than the default unit '
                  'of measure of the product'))

    @api.onchange('warehouse_id')
    def onchange_warehouse_id(self):
        """ Finds location id for changed warehouse. """
        res = {'domain': {}}
        if self._name == 'stock.request' and self.order_id:
            # When the stock request is created from an order the wh and
            # location are taken from the order and we rely on it to change
            # all request associated. Thus, no need to apply
            # the onchange, as it could lead to inconsistencies.
            return res
        if self.warehouse_id:
            loc_wh = self.location_id.sudo().get_warehouse()
            if self.warehouse_id != loc_wh:
                self.location_id = self.warehouse_id.lot_stock_id.id
            if self.warehouse_id.company_id != self.company_id:
                self.company_id = self.warehouse_id.company_id
        return res

    @api.onchange('location_id')
    def onchange_location_id(self):
        if self.location_id:
            loc_wh = self.location_id.sudo().get_warehouse()
            if loc_wh and self.warehouse_id != loc_wh:
                self.warehouse_id = loc_wh
                self.with_context(
                    no_change_childs=True).onchange_warehouse_id()

    @api.onchange('allow_virtual_location')
    def onchange_allow_virtual_location(self):
        if self.allow_virtual_location:
            return {'domain': {'location_id': []}}

    @api.onchange('company_id')
    def onchange_company_id(self):
        """ Sets a default warehouse when the company is changed and limits
        the user selection of warehouses. """
        if self.company_id and (
                not self.warehouse_id
                or self.warehouse_id.company_id != self.company_id):
            self.warehouse_id = self.env['stock.warehouse'].search(
                [('company_id', '=', self.company_id.id)], limit=1)
            self.onchange_warehouse_id()

        return {
            'domain': {
                'warehouse_id': [('company_id', '=', self.company_id.id)]
            }
        }

    @api.onchange('product_id')
    def onchange_product_id(self):
        res = {'domain': {}}
        if self.product_id:
            self.product_uom_id = self.product_id.uom_id.id
            res['domain']['product_uom_id'] = [
                ('category_id', '=', self.product_id.uom_id.category_id.id)
            ]
            return res
        res['domain']['product_uom_id'] = []
        return res
コード例 #27
0
class AssignManualQuantsLines(models.TransientModel):
    _name = 'assign.manual.quants.lines'
    _rec_name = 'quant_id'

    assign_wizard = fields.Many2one(comodel_name='assign.manual.quants',
                                    string='Move',
                                    required=True,
                                    ondelete='cascade')
    quant_id = fields.Many2one(comodel_name='stock.quant',
                               string='Quant',
                               required=True,
                               ondelete='cascade',
                               oldname='quant')
    location_id = fields.Many2one(comodel_name='stock.location',
                                  string='Location',
                                  related='quant_id.location_id',
                                  readonly=True,
                                  groups="stock.group_stock_multi_locations")
    lot_id = fields.Many2one(comodel_name='stock.production.lot',
                             string='Lot',
                             related='quant_id.lot_id',
                             readonly=True,
                             groups="stock.group_production_lot")
    package_id = fields.Many2one(comodel_name='stock.quant.package',
                                 string='Package',
                                 related='quant_id.package_id',
                                 readonly=True,
                                 groups="stock.group_tracking_lot")
    owner_id = fields.Many2one(
        comodel_name='res.partner',
        string='Owner',
        related='quant_id.owner_id',
        readonly=True,
        groups="stock.group_tracking_owner",
    )
    # This is not correctly shown as related or computed, so we make it regular
    on_hand = fields.Float(
        readonly=True,
        string='On Hand',
        digits=dp.get_precision('Product Unit of Measure'),
    )
    reserved = fields.Float(string='Others Reserved',
                            digits=dp.get_precision('Product Unit of Measure'))
    selected = fields.Boolean(string='Select')
    qty = fields.Float(string='QTY',
                       digits=dp.get_precision('Product Unit of Measure'))

    @api.onchange('selected')
    def _onchange_selected(self):
        for record in self:
            if not record.selected:
                record.qty = 0
            elif not record.qty:
                # This takes current "snapshot" situation, so that we don't
                # have to compute each time if current reserved quantity is
                # for this current move. If other operations change available
                # quantity on quant, a constraint would be raised later on
                # validation.
                quant_qty = record.on_hand - record.reserved
                remaining_qty = record.assign_wizard.move_qty
                record.qty = min(quant_qty, remaining_qty)

    @api.multi
    @api.constrains('qty')
    def _check_qty(self):
        precision_digits = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        for record in self.filtered('qty'):
            quant = record.quant_id
            move_lines = record.assign_wizard.move_id.move_line_ids.filtered(
                lambda ml: (ml.location_id == quant.location_id and ml.lot_id
                            == quant.lot_id))
            reserved = (quant.reserved_quantity -
                        sum(move_lines.mapped('ordered_qty')))
            if float_compare(record.qty,
                             record.quant_id.quantity - reserved,
                             precision_digits=precision_digits) > 0:
                raise UserError(
                    _('Selected line quantity is higher than the available '
                      'one. Maybe an operation with this product has been '
                      'done meanwhile or you have manually increased the '
                      'suggested value.'))

    def _assign_quant_line(self):
        self.ensure_one()
        quant = self.env['stock.quant']
        precision_digits = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        move = self.assign_wizard.move_id
        if float_compare(self.qty, 0.0, precision_digits=precision_digits) > 0:
            available_quantity = quant._get_available_quantity(
                move.product_id,
                self.location_id,
                lot_id=self.lot_id,
                package_id=self.package_id,
                owner_id=self.owner_id,
            )
            if float_compare(available_quantity,
                             0.0,
                             precision_digits=precision_digits) <= 0:
                return
            move._update_reserved_quantity(self.qty,
                                           available_quantity,
                                           self.location_id,
                                           lot_id=self.lot_id,
                                           package_id=self.package_id,
                                           owner_id=self.owner_id,
                                           strict=True)
コード例 #28
0
class AssignManualQuants(models.TransientModel):
    _name = 'assign.manual.quants'

    @api.multi
    @api.constrains('quants_lines')
    def _check_qty(self):
        precision_digits = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        for record in self.filtered('quants_lines'):
            if float_compare(record.lines_qty,
                             record.move_id.product_qty,
                             precision_digits=precision_digits) > 0:
                raise UserError(_('Quantity is higher than the needed one'))

    @api.depends('move_id', 'quants_lines', 'quants_lines.qty')
    def _compute_qties(self):
        for record in self:
            record.lines_qty = sum(
                record.quants_lines.filtered('selected').mapped('qty'))
            record.move_qty = record.move_id.product_qty - record.lines_qty

    lines_qty = fields.Float(
        string='Reserved qty',
        compute='_compute_qties',
        digits=dp.get_precision('Product Unit of Measure'))
    move_qty = fields.Float(string='Remaining qty',
                            compute='_compute_qties',
                            digits=dp.get_precision('Product Unit of Measure'))
    quants_lines = fields.One2many('assign.manual.quants.lines',
                                   'assign_wizard',
                                   string='Quants')
    move_id = fields.Many2one(
        comodel_name='stock.move',
        string='Move',
    )

    @api.multi
    def assign_quants(self):
        move = self.move_id
        move._do_unreserve()
        for line in self.quants_lines:
            line._assign_quant_line()
        # Auto-fill all lines as done
        for ml in move.move_line_ids:
            ml.qty_done = ml.product_qty
        move._recompute_state()
        move.mapped('picking_id')._compute_state()
        return {}

    @api.model
    def default_get(self, fields):
        res = super(AssignManualQuants, self).default_get(fields)
        move = self.env['stock.move'].browse(self.env.context['active_id'])
        available_quants = self.env['stock.quant'].search([
            ('location_id', 'child_of', move.location_id.id),
            ('product_id', '=', move.product_id.id),
            ('quantity', '>', 0),
        ])
        quants_lines = []
        for quant in available_quants:
            line = {}
            line['quant_id'] = quant.id
            line['on_hand'] = quant.quantity
            line['location_id'] = quant.location_id.id
            line['lot_id'] = quant.lot_id.id
            line['package_id'] = quant.package_id.id
            line['owner_id'] = quant.owner_id.id
            line['selected'] = False
            move_lines = move.move_line_ids.filtered(
                lambda ml: (ml.location_id == quant.location_id and ml.lot_id
                            == quant.lot_id and ml.owner_id == quant.owner_id
                            and ml.package_id == quant.package_id))
            line['qty'] = sum(move_lines.mapped('ordered_qty'))
            line['selected'] = bool(line['qty'])
            line['reserved'] = quant.reserved_quantity - line['qty']
            quants_lines.append(line)
        res.update({
            'quants_lines': [(0, 0, x) for x in quants_lines],
            'move_id': move.id,
        })
        return res
コード例 #29
0
# Copyright 2018 Camptocamp SA
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
# Copyright 2016 Eficent Business and IT Consulting Services S.L.
#   (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import api, fields, models, _
from addons import decimal_precision as dp
from odoo.addons.stock.models.product import OPERATORS
from odoo.tools.float_utils import float_round
from odoo.exceptions import UserError

UNIT = dp.get_precision('Product Unit of Measure')


class ProductTemplate(models.Model):
    _inherit = "product.template"

    qty_available_not_res = fields.Float(
        string='Quantity On Hand Unreserved',
        digits=UNIT,
        compute='_compute_product_available_not_res',
        search='_search_quantity_unreserved',
    )

    @api.multi
    @api.depends('product_variant_ids.qty_available_not_res')
    def _compute_product_available_not_res(self):
        for tmpl in self:
            if isinstance(tmpl.id, models.NewId):
                continue
コード例 #30
0
class PurchaseRequestLine(models.Model):

    _name = "purchase.request.line"
    _description = "Purchase Request Line"
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char('Description', track_visibility='onchange')
    product_uom_id = fields.Many2one('product.uom', 'Product Unit of Measure',
                                     track_visibility='onchange')
    product_qty = fields.Float('Quantity', track_visibility='onchange',
                               digits=dp.get_precision(
                                   'Product Unit of Measure'))
    request_id = fields.Many2one('purchase.request',
                                 'Purchase Request',
                                 ondelete='cascade', readonly=True)
    company_id = fields.Many2one('res.company',
                                 related='request_id.company_id',
                                 string='Company',
                                 store=True, readonly=True)
    analytic_account_id = fields.Many2one('account.analytic.account',
                                          'Analytic Account',
                                          track_visibility='onchange')
    requested_by = fields.Many2one('res.users',
                                   related='request_id.requested_by',
                                   string='Requested by',
                                   store=True, readonly=True)
    assigned_to = fields.Many2one('res.users',
                                  related='request_id.assigned_to',
                                  string='Assigned to',
                                  store=True, readonly=True)
    date_start = fields.Date(related='request_id.date_start',
                             string='Request Date', readonly=True,
                             store=True)
    description = fields.Text(related='request_id.description',
                              string='Description', readonly=True,
                              store=True)
    origin = fields.Char(related='request_id.origin',
                         string='Source Document', readonly=True, store=True)
    date_required = fields.Date(string='Request Date', required=True,
                                track_visibility='onchange',
                                default=fields.Date.context_today)
    is_editable = fields.Boolean(string='Is editable',
                                 compute="_compute_is_editable",
                                 readonly=True)
    specifications = fields.Text(string='Specifications')
    request_state = fields.Selection(string='Request state',
                                     readonly=True,
                                     related='request_id.state',
                                     selection=_STATES,
                                     store=True)
    supplier_id = fields.Many2one('res.partner',
                                  string='Preferred supplier',
                                  compute="_compute_supplier_id")
    cancelled = fields.Boolean(
        string="Cancelled", readonly=True, default=False, copy=False)

    purchased_qty = fields.Float(string='Quantity in RFQ or PO',
                                 compute="_compute_purchased_qty")
    purchase_lines = fields.Many2many(
        'purchase.order.line', 'purchase_request_purchase_order_line_rel',
        'purchase_request_line_id',
        'purchase_order_line_id', 'Purchase Order Lines',
        readonly=True, copy=False)
    purchase_state = fields.Selection(
        compute="_compute_purchase_state",
        string="Purchase Status",
        selection=lambda self:
        self.env['purchase.order']._fields['state'].selection,
        store=True,
    )
    move_dest_ids = fields.One2many('stock.move',
                                    'created_purchase_request_line_id',
                                    'Downstream Moves')

    orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint')

    estimated_cost = fields.Monetary(
        string='Estimated Cost', currency_field='currency_id', default=0.0,
        help='Estimated cost of Purchase Request Line, not propagated to PO.')
    currency_id = fields.Many2one(
        related='company_id.currency_id',
        readonly=True,
    )

    @api.multi
    @api.depends('product_id', 'name', 'product_uom_id', 'product_qty',
                 'analytic_account_id', 'date_required', 'specifications',
                 'purchase_lines')
    def _compute_is_editable(self):
        for rec in self:
            if rec.request_id.state in ('to_approve', 'approved', 'rejected',
                                        'done'):
                rec.is_editable = False
            else:
                rec.is_editable = True
        for rec in self.filtered(lambda p: p.purchase_lines):
            rec.is_editable = False

    @api.multi
    def _compute_supplier_id(self):
        for rec in self:
            if rec.product_id:
                if rec.product_id.seller_ids:
                    rec.supplier_id = rec.product_id.seller_ids[0].name

    product_id = fields.Many2one(
        'product.product', 'Product',
        domain=[('purchase_ok', '=', True)],
        track_visibility='onchange')

    @api.onchange('product_id')
    def onchange_product_id(self):
        if self.product_id:
            name = self.product_id.name
            if self.product_id.code:
                name = '[%s] %s' % (name, self.product_id.code)
            if self.product_id.description_purchase:
                name += '\n' + self.product_id.description_purchase
            self.product_uom_id = self.product_id.uom_id.id
            self.product_qty = 1
            self.name = name

    @api.multi
    def do_cancel(self):
        """Actions to perform when cancelling a purchase request line."""
        self.write({'cancelled': True})

    @api.multi
    def do_uncancel(self):
        """Actions to perform when uncancelling a purchase request line."""
        self.write({'cancelled': False})

    @api.multi
    def write(self, vals):
        res = super(PurchaseRequestLine, self).write(vals)
        if vals.get('cancelled'):
            requests = self.mapped('request_id')
            requests.check_auto_reject()
        return res

    @api.multi
    def _compute_purchased_qty(self):
        for rec in self:
            rec.purchased_qty = 0.0
            for line in rec.purchase_lines.filtered(
                    lambda x: x.state != 'cancel'):
                if rec.product_uom_id and\
                        line.product_uom != rec.product_uom_id:
                    rec.purchased_qty += line.product_uom._compute_quantity(
                        line.product_qty, rec.product_uom_id)
                else:
                    rec.purchased_qty += line.product_qty

    @api.multi
    @api.depends('purchase_lines.state', 'purchase_lines.order_id.state')
    def _compute_purchase_state(self):
        for rec in self:
            temp_purchase_state = False
            if rec.purchase_lines:
                if any([po_line.state == 'done' for po_line in
                        rec.purchase_lines]):
                    temp_purchase_state = 'done'
                elif all([po_line.state == 'cancel' for po_line in
                          rec.purchase_lines]):
                    temp_purchase_state = 'cancel'
                elif any([po_line.state == 'purchase' for po_line in
                          rec.purchase_lines]):
                    temp_purchase_state = 'purchase'
                elif any([po_line.state == 'to approve' for po_line in
                          rec.purchase_lines]):
                    temp_purchase_state = 'to approve'
                elif any([po_line.state == 'sent' for po_line in
                          rec.purchase_lines]):
                    temp_purchase_state = 'sent'
                elif all([po_line.state in ('draft', 'cancel') for po_line in
                          rec.purchase_lines]):
                    temp_purchase_state = 'draft'
            rec.purchase_state = temp_purchase_state

    @api.model
    def _planned_date(self, request_line, delay=0.0):
        company = request_line.company_id
        date_planned = datetime.strptime(
            request_line.date_required, '%Y-%m-%d') - \
            relativedelta(days=company.po_lead)
        if delay:
            date_planned -= relativedelta(days=delay)
        return date_planned and date_planned.strftime('%Y-%m-%d') \
            or False

    @api.model
    def _get_supplier_min_qty(self, product, partner_id=False):
        seller_min_qty = 0.0
        if partner_id:
            seller = product.seller_ids \
                .filtered(lambda r: r.name == partner_id) \
                .sorted(key=lambda r: r.min_qty)
        else:
            seller = product.seller_ids.sorted(key=lambda r: r.min_qty)
        if seller:
            seller_min_qty = seller[0].min_qty
        return seller_min_qty

    @api.model
    def _calc_new_qty(self, request_line, po_line=None,
                      new_pr_line=False):
        purchase_uom = po_line.product_uom or request_line.product_id.uom_po_id
        uom = request_line.product_uom_id
        qty = uom._compute_quantity(request_line.product_qty, purchase_uom)
        # Make sure we use the minimum quantity of the partner corresponding
        # to the PO. This does not apply in case of dropshipping
        supplierinfo_min_qty = 0.0
        if not po_line.order_id.dest_address_id:
            supplierinfo_min_qty = self._get_supplier_min_qty(
                po_line.product_id, po_line.order_id.partner_id)

        rl_qty = 0.0
        # Recompute quantity by adding existing running procurements.
        for rl in po_line.purchase_request_lines:
            rl_qty += rl.product_uom_id._compute_quantity(
                rl.product_qty, purchase_uom)
        qty = max(rl_qty, supplierinfo_min_qty)
        return qty

    @api.model
    def _calc_new_qty_purchase_requisition(self, request_line, po_line=None,
                      new_pr_line=False):
        purchase_uom = po_line.product_uom_id or request_line.product_id.uom_po_id
        uom = request_line.product_uom_id
        qty = uom._compute_quantity(request_line.product_qty, purchase_uom)
        # Make sure we use the minimum quantity of the partner corresponding
        # to the PO.
        supplierinfo_min_qty = 0.0
        supplierinfo_min_qty = self._get_supplier_min_qty(
                po_line.product_id, po_line.requisition_id.vendor_id)

        rl_qty = 0.0
        # Recompute quantity by adding existing running procurements.
        for rl in po_line.purchase_request_lines:
            rl_qty += rl.product_uom_id._compute_quantity(
                rl.product_qty, purchase_uom)
        qty = max(rl_qty, supplierinfo_min_qty)
        return qty

    @api.multi
    def unlink(self):
        if self.mapped('purchase_lines'):
            raise UserError(
                _('You cannot delete a record that refers to purchase '
                  'lines!'))
        return super(PurchaseRequestLine, self).unlink()