Пример #1
0
class SaleOrderLine(models.Model):
    _inherit = "sale.order.line"

    margin = fields.Float(compute='_product_margin', digits=dp.get_precision('Product Price'), store=True)
    purchase_price = fields.Float(string='Cost', digits=dp.get_precision('Product Price'))

    def _compute_margin(self, order_id, product_id, product_uom_id):
        frm_cur = self.env.user.company_id.currency_id
        to_cur = order_id.pricelist_id.currency_id
        purchase_price = product_id.standard_price
        if product_uom_id != product_id.uom_id:
            purchase_price = product_id.uom_id._compute_price(purchase_price, product_uom_id)
        ctx = self.env.context.copy()
        ctx['date'] = order_id.date_order
        price = frm_cur.with_context(ctx).compute(purchase_price, to_cur, round=False)
        return price

    @api.model
    def _get_purchase_price(self, pricelist, product, product_uom, date):
        frm_cur = self.env.user.company_id.currency_id
        to_cur = pricelist.currency_id
        purchase_price = product.standard_price
        if product_uom != product.uom_id:
            purchase_price = product.uom_id._compute_price(purchase_price, product_uom)
        ctx = self.env.context.copy()
        ctx['date'] = date
        price = frm_cur.with_context(ctx).compute(purchase_price, to_cur, round=False)
        return {'purchase_price': price}

    @api.onchange('product_id', 'product_uom')
    def product_id_change_margin(self):
        if not self.order_id.pricelist_id or not self.product_id or not self.product_uom:
            return
        self.purchase_price = self._compute_margin(self.order_id, self.product_id, self.product_uom)

    @api.model
    def create(self, vals):
        vals.update(self._prepare_add_missing_fields(vals))

        # Calculation of the margin for programmatic creation of a SO line. It is therefore not
        # necessary to call product_id_change_margin manually
        if 'purchase_price' not in vals:
            order_id = self.env['sale.order'].browse(vals['order_id'])
            product_id = self.env['product.product'].browse(vals['product_id'])
            product_uom_id = self.env['product.uom'].browse(vals['product_uom'])

            vals['purchase_price'] = self._compute_margin(order_id, product_id, product_uom_id)

        return super(SaleOrderLine, self).create(vals)

    @api.depends('product_id', 'purchase_price', 'product_uom_qty', 'price_unit', 'price_subtotal')
    def _product_margin(self):
        for line in self:
            currency = line.order_id.pricelist_id.currency_id
            price = line.purchase_price
            if not price:
                from_cur = line.env.user.company_id.currency_id.with_context(date=line.order_id.date_order)
                price = from_cur.compute(line.product_id.standard_price, currency, round=False)
 
            line.margin = currency.round(line.price_subtotal - (price * line.product_uom_qty))
Пример #2
0
class HrContract(models.Model):
    """
    Employee contract allows to add different values in fields.
    Fields are used in salary rule computation.
    """
    _inherit = 'hr.contract'

    tds = fields.Float(string='TDS',
                       digits=dp.get_precision('Payroll'),
                       help='Amount for Tax Deduction at Source')
    driver_salay = fields.Boolean(
        string='Driver Salary',
        help='Check this box if you provide allowance for driver')
    medical_insurance = fields.Float(
        string='Medical Insurance',
        digits=dp.get_precision('Payroll'),
        help='Deduction towards company provided medical insurance')
    voluntary_provident_fund = fields.Float(
        string='Voluntary Provident Fund (%)',
        digits=dp.get_precision('Payroll'),
        help=
        'VPF is a safe option wherein you can contribute more than the PF ceiling of 12% that has been mandated by the government and VPF computed as percentage(%)'
    )
    house_rent_allowance_metro_nonmetro = fields.Float(
        string='House Rent Allowance (%)',
        digits=dp.get_precision('Payroll'),
        help=
        'HRA is an allowance given by the employer to the employee for taking care of his rental or accommodation expenses for metro city it is 50% and for non metro 40%. \nHRA computed as percentage(%)'
    )
    supplementary_allowance = fields.Float(string='Supplementary Allowance',
                                           digits=dp.get_precision('Payroll'))
Пример #3
0
class SaleQuoteOption(models.Model):
    _name = "sale.quote.option"
    _description = "Quotation Option"

    template_id = fields.Many2one('sale.quote.template',
                                  'Quotation Template Reference',
                                  ondelete='cascade',
                                  index=True,
                                  required=True)
    name = fields.Text('Description', required=True, translate=True)
    product_id = fields.Many2one('product.product',
                                 'Product',
                                 domain=[('sale_ok', '=', True)],
                                 required=True)
    layout_category_id = fields.Many2one('sale.layout_category',
                                         string='Section')
    website_description = fields.Html('Option Description',
                                      translate=html_translate,
                                      sanitize_attributes=False)
    price_unit = fields.Float('Unit Price',
                              required=True,
                              digits=dp.get_precision('Product Price'))
    discount = fields.Float('Discount (%)',
                            digits=dp.get_precision('Discount'))
    uom_id = fields.Many2one('product.uom', 'Unit of Measure ', required=True)
    quantity = fields.Float('Quantity',
                            required=True,
                            digits=dp.get_precision('Product UoS'),
                            default=1)

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if not self.product_id:
            return
        product = self.product_id
        self.price_unit = product.list_price
        self.website_description = product.product_tmpl_id.quote_description
        self.name = product.name
        self.uom_id = product.uom_id
        domain = {
            'uom_id':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        return {'domain': domain}

    @api.onchange('uom_id')
    def _onchange_product_uom(self):
        if not self.product_id:
            return
        if not self.uom_id:
            self.price_unit = 0.0
            return
        if self.uom_id.id != self.product_id.uom_id.id:
            self.price_unit = self.product_id.uom_id._compute_price(
                self.price_unit, self.uom_id)
Пример #4
0
class PriceRule(models.Model):
    _name = "delivery.price.rule"
    _description = "Delivery Price Rules"
    _order = 'sequence, list_price, id'

    @api.depends('variable', 'operator', 'max_value', 'list_base_price',
                 'list_price', 'variable_factor')
    def _compute_name(self):
        for rule in self:
            name = 'if %s %s %s then' % (rule.variable, rule.operator,
                                         rule.max_value)
            if rule.list_base_price and not rule.list_price:
                name = '%s fixed price %s' % (name, rule.list_base_price)
            elif rule.list_price and not rule.list_base_price:
                name = '%s %s times %s' % (name, rule.list_price,
                                           rule.variable_factor)
            else:
                name = '%s fixed price %s plus %s times %s' % (
                    name, rule.list_base_price, rule.list_price,
                    rule.variable_factor)
            rule.name = name

    name = fields.Char(compute='_compute_name')
    sequence = fields.Integer(required=True, default=10)
    carrier_id = fields.Many2one('delivery.carrier',
                                 'Carrier',
                                 required=True,
                                 ondelete='cascade')

    variable = fields.Selection([('weight', 'Weight'), ('volume', 'Volume'),
                                 ('wv', 'Weight * Volume'), ('price', 'Price'),
                                 ('quantity', 'Quantity')],
                                required=True,
                                default='weight')
    operator = fields.Selection([('==', '='), ('<=', '<='), ('<', '<'),
                                 ('>=', '>='), ('>', '>')],
                                required=True,
                                default='<=')
    max_value = fields.Float('Maximum Value', required=True)
    list_base_price = fields.Float(string='Sale Base Price',
                                   digits=dp.get_precision('Product Price'),
                                   required=True,
                                   default=0.0)
    list_price = fields.Float('Sale Price',
                              digits=dp.get_precision('Product Price'),
                              required=True,
                              default=0.0)
    variable_factor = fields.Selection([('weight', 'Weight'),
                                        ('volume', 'Volume'),
                                        ('wv', 'Weight * Volume'),
                                        ('price', 'Price'),
                                        ('quantity', 'Quantity')],
                                       'Variable Factor',
                                       required=True,
                                       default='weight')
Пример #5
0
class ResCompany(models.Model):
    _inherit = 'res.company'

    plafond_secu = fields.Float(string='Plafond de la Securite Sociale',
                                digits=dp.get_precision('Payroll'))
    nombre_employes = fields.Integer(string='Nombre d\'employes')
    cotisation_prevoyance = fields.Float(
        string='Cotisation Patronale Prevoyance',
        digits=dp.get_precision('Payroll'))
    org_ss = fields.Char(string='Organisme de securite sociale')
    conv_coll = fields.Char(string='Convention collective')
Пример #6
0
class HrPayslipLine(models.Model):
    _name = 'hr.payslip.line'
    _inherit = 'hr.salary.rule'
    _description = 'Payslip Line'
    _order = 'contract_id, sequence'

    slip_id = fields.Many2one('hr.payslip',
                              string='Pay Slip',
                              required=True,
                              ondelete='cascade')
    salary_rule_id = fields.Many2one('hr.salary.rule',
                                     string='Rule',
                                     required=True)
    employee_id = fields.Many2one('hr.employee',
                                  string='Employee',
                                  required=True)
    contract_id = fields.Many2one('hr.contract',
                                  string='Contract',
                                  required=True,
                                  index=True)
    rate = fields.Float(string='Rate (%)',
                        digits=dp.get_precision('Payroll Rate'),
                        default=100.0)
    amount = fields.Float(digits=dp.get_precision('Payroll'))
    quantity = fields.Float(digits=dp.get_precision('Payroll'), default=1.0)
    total = fields.Float(compute='_compute_total',
                         string='Total',
                         digits=dp.get_precision('Payroll'),
                         store=True)

    @api.depends('quantity', 'amount', 'rate')
    def _compute_total(self):
        for line in self:
            line.total = float(line.quantity) * line.amount * line.rate / 100

    @api.model
    def create(self, values):
        if 'employee_id' not in values or 'contract_id' not in values:
            payslip = self.env['hr.payslip'].browse(values.get('slip_id'))
            values['employee_id'] = values.get(
                'employee_id') or payslip.employee_id.id
            values['contract_id'] = values.get(
                'contract_id'
            ) or payslip.contract_id and payslip.contract_id.id
            if not values['contract_id']:
                raise UserError(
                    _('You must set a contract to create a payslip line.'))
        return super(HrPayslipLine, self).create(values)
Пример #7
0
class StockMove(models.Model):
    _inherit = 'stock.move'

    def _default_uom(self):
        uom_categ_id = self.env.ref('product.product_uom_categ_kgm').id
        return self.env['product.uom'].search(
            [('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1)

    weight = fields.Float(compute='_cal_move_weight',
                          digits=dp.get_precision('Stock Weight'),
                          store=True)
    weight_uom_id = fields.Many2one(
        'product.uom',
        string='Weight Unit of Measure',
        required=True,
        readonly=True,
        help=
        "Unit of Measure (Unit of Measure) is the unit of measurement for Weight",
        default=_default_uom)

    @api.depends('product_id', 'product_uom_qty', 'product_uom')
    def _cal_move_weight(self):
        for move in self.filtered(
                lambda moves: moves.product_id.weight > 0.00):
            move.weight = (move.product_qty * move.product_id.weight)

    def _get_new_picking_values(self):
        vals = super(StockMove, self)._get_new_picking_values()
        vals['carrier_id'] = self.sale_line_id.order_id.carrier_id.id
        return vals
Пример #8
0
class LandedCostLine(models.Model):
    _name = 'stock.landed.cost.lines'
    _description = 'Stock Landed Cost Lines'

    name = fields.Char('Description')
    cost_id = fields.Many2one('stock.landed.cost',
                              'Landed Cost',
                              required=True,
                              ondelete='cascade')
    product_id = fields.Many2one('product.product', 'Product', required=True)
    price_unit = fields.Float('Cost',
                              digits=dp.get_precision('Product Price'),
                              required=True)
    split_method = fields.Selection(product.SPLIT_METHOD,
                                    string='Split Method',
                                    required=True)
    account_id = fields.Many2one('account.account',
                                 'Account',
                                 domain=[('deprecated', '=', False)])

    @api.onchange('product_id')
    def onchange_product_id(self):
        if not self.product_id:
            self.quantity = 0.0
        self.name = self.product_id.name or ''
        self.split_method = self.product_id.split_method or 'equal'
        self.price_unit = self.product_id.standard_price or 0.0
        self.account_id = self.product_id.property_account_expense_id.id or self.product_id.categ_id.property_account_expense_categ_id.id
Пример #9
0
class HrPayrollAdviceLine(models.Model):
    '''
    Bank Advice Lines
    '''
    _name = 'hr.payroll.advice.line'
    _description = 'Bank Advice Lines'

    advice_id = fields.Many2one('hr.payroll.advice', string='Bank Advice')
    name = fields.Char('Bank Account No.', required=True)
    ifsc_code = fields.Char(string='IFSC Code')
    employee_id = fields.Many2one('hr.employee',
                                  string='Employee',
                                  required=True)
    bysal = fields.Float(string='By Salary',
                         digits=dp.get_precision('Payroll'))
    debit_credit = fields.Char(string='C/D', default='C')
    company_id = fields.Many2one('res.company',
                                 related='advice_id.company_id',
                                 string='Company',
                                 store=True)
    ifsc = fields.Boolean(related='advice_id.neft', string='IFSC')

    @api.onchange('employee_id')
    def onchange_employee_id(self):
        self.name = self.employee_id.bank_account_id.acc_number
        self.ifsc_code = self.employee_id.bank_account_id.bank_bic or ''
Пример #10
0
class Product(models.Model):
    _inherit = "product.product"

    website_price = fields.Float('Website price', compute='_website_price', digits=dp.get_precision('Product Price'))
    website_public_price = fields.Float('Website public price', compute='_website_price', digits=dp.get_precision('Product Price'))
    website_price_difference = fields.Boolean('Website price difference', compute='_website_price')

    def _website_price(self):
        qty = self._context.get('quantity', 1.0)
        partner = self.env.user.partner_id
        current_website = self.env['website'].get_current_website()
        pricelist = current_website.get_current_pricelist()
        company_id = current_website.company_id

        context = dict(self._context, pricelist=pricelist.id, partner=partner)
        self2 = self.with_context(context) if self._context != context else self

        ret = self.env.user.has_group('sale.group_show_price_subtotal') and 'total_excluded' or 'total_included'

        for p, p2 in pycompat.izip(self, self2):
            taxes = partner.property_account_position_id.map_tax(p.sudo().taxes_id.filtered(lambda x: x.company_id == company_id))
            p.website_price = taxes.compute_all(p2.price, pricelist.currency_id, quantity=qty, product=p2, partner=partner)[ret]
            price_without_pricelist = taxes.compute_all(p.list_price, pricelist.currency_id)[ret]
            p.website_price_difference = False if float_is_zero(price_without_pricelist - p.website_price, precision_rounding=pricelist.currency_id.rounding) else True
            p.website_public_price = taxes.compute_all(p2.lst_price, quantity=qty, product=p2, partner=partner)[ret]

    @api.multi
    def website_publish_button(self):
        self.ensure_one()
        return self.product_tmpl_id.website_publish_button()
Пример #11
0
class ProductAttributevalue(models.Model):
    _name = "product.attribute.value"
    _order = 'sequence, attribute_id, id'

    name = fields.Char('Value', required=True, translate=True)
    sequence = fields.Integer('Sequence', help="Determine the display order")
    attribute_id = fields.Many2one('product.attribute', 'Attribute', ondelete='cascade', required=True)
    product_ids = fields.Many2many('product.product', string='Variants', readonly=True)
    price_extra = fields.Float(
        'Attribute Price Extra', compute='_compute_price_extra', inverse='_set_price_extra',
        default=0.0, 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.")
    price_ids = fields.One2many('product.attribute.price', 'value_id', 'Attribute Prices', readonly=True)

    _sql_constraints = [
        ('value_company_uniq', 'unique (name,attribute_id)', 'This attribute value already exists !')
    ]

    @api.one
    def _compute_price_extra(self):
        if self._context.get('active_id'):
            price = self.price_ids.filtered(lambda price: price.product_tmpl_id.id == self._context['active_id'])
            self.price_extra = price.price_extra
        else:
            self.price_extra = 0.0

    def _set_price_extra(self):
        if not self._context.get('active_id'):
            return

        AttributePrice = self.env['product.attribute.price']
        prices = AttributePrice.search([('value_id', 'in', self.ids), ('product_tmpl_id', '=', self._context['active_id'])])
        updated = prices.mapped('value_id')
        if prices:
            prices.write({'price_extra': self.price_extra})
        else:
            for value in self - updated:
                AttributePrice.create({
                    'product_tmpl_id': self._context['active_id'],
                    'value_id': value.id,
                    'price_extra': self.price_extra,
                })

    @api.multi
    def name_get(self):
        if not self._context.get('show_attribute', True):  # TDE FIXME: not used
            return super(ProductAttributevalue, self).name_get()
        return [(value.id, "%s: %s" % (value.attribute_id.name, value.name)) for value in self]

    @api.multi
    def unlink(self):
        linked_products = self.env['product.product'].with_context(active_test=False).search([('attribute_value_ids', 'in', self.ids)])
        if linked_products:
            raise UserError(_('The operation cannot be completed:\nYou are trying to delete an attribute value with a reference on a product variant.'))
        return super(ProductAttributevalue, self).unlink()

    @api.multi
    def _variant_name(self, variable_attributes):
        return ", ".join([v.name for v in self if v.attribute_id in variable_attributes])
Пример #12
0
class ReturnPickingLine(models.TransientModel):
    _name = "stock.return.picking.line"
    _rec_name = 'product_id'

    product_id = fields.Many2one('product.product', string="Product", required=True, domain="[('id', '=', product_id)]")
    quantity = fields.Float("Quantity", digits=dp.get_precision('Product Unit of Measure'), required=True)
    uom_id = fields.Many2one('product.uom', string='Unit of Measure', related='move_id.product_uom')
    wizard_id = fields.Many2one('stock.return.picking', string="Wizard")
    move_id = fields.Many2one('stock.move', "Move")
Пример #13
0
class MrpProductProduceLine(models.TransientModel):
    _name = "mrp.product.produce.line"
    _description = "Record Production Line"

    product_produce_id = fields.Many2one('mrp.product.produce')
    product_id = fields.Many2one('product.product', 'Product')
    lot_id = fields.Many2one('stock.production.lot', 'Lot')
    qty_to_consume = fields.Float(
        'To Consume', digits=dp.get_precision('Product Unit of Measure'))
    product_uom_id = fields.Many2one('product.uom', 'Unit of Measure')
    qty_done = fields.Float('Done',
                            digits=dp.get_precision('Product Unit of Measure'))
    move_id = fields.Many2one('stock.move')

    @api.onchange('lot_id')
    def _onchange_lot_id(self):
        """ When the user is encoding a produce line for a tracked product, we apply some logic to
        help him. This onchange will automatically switch `qty_done` to 1.0.
        """
        res = {}
        if self.product_id.tracking == 'serial':
            self.qty_done = 1
        return res

    @api.onchange('qty_done')
    def _onchange_qty_done(self):
        """ When the user is encoding a produce line for a tracked product, we apply some logic to
        help him. This onchange will warn him if he set `qty_done` to a non-supported value.
        """
        res = {}
        if self.product_id.tracking == 'serial':
            if float_compare(self.qty_done,
                             1.0,
                             precision_rounding=self.move_id.product_id.uom_id.
                             rounding) != 0:
                message = _(
                    'You can only process 1.0 %s for products with unique serial number.'
                ) % self.product_id.uom_id.name
                res['warning'] = {'title': _('Warning'), 'message': message}
        return res

    @api.onchange('product_id')
    def _onchange_product_id(self):
        self.product_uom_id = self.product_id.uom_id.id
Пример #14
0
class LunchProduct(models.Model):
    """ Products available to order. A product is linked to a specific vendor. """
    _name = 'lunch.product'
    _description = 'lunch product'

    name = fields.Char('Product', required=True)
    category_id = fields.Many2one('lunch.product.category', 'Category', required=True)
    description = fields.Text('Description')
    price = fields.Float('Price', digits=dp.get_precision('Account'))
    supplier = fields.Many2one('res.partner', 'Vendor')
    active = fields.Boolean(default=True)
Пример #15
0
class StockMoveLine(models.Model):
    _inherit = 'stock.move.line'

    workorder_id = fields.Many2one('mrp.workorder', 'Work Order')
    production_id = fields.Many2one('mrp.production', 'Production Order')
    lot_produced_id = fields.Many2one('stock.production.lot', 'Finished Lot')
    lot_produced_qty = fields.Float(
        'Quantity Finished Product',
        digits=dp.get_precision('Product Unit of Measure'),
        help="Informative, not used in matching")
    done_wo = fields.Boolean(
        'Done for Work Order',
        default=True,
        help=
        "Technical Field which is False when temporarily filled in in work order"
    )  # TDE FIXME: naming
    done_move = fields.Boolean('Move Done',
                               related='move_id.is_done',
                               store=True)  # TDE FIXME: naming

    def _get_similar_move_lines(self):
        lines = super(StockMoveLine, self)._get_similar_move_lines()
        if self.move_id.production_id:
            finished_moves = self.move_id.production_id.move_finished_ids
            finished_move_lines = finished_moves.mapped('move_line_ids')
            lines |= finished_move_lines.filtered(
                lambda ml: ml.product_id == self.product_id and
                (ml.lot_id or ml.lot_name) and ml.done_wo == self.done_wo)
        if self.move_id.raw_material_production_id:
            raw_moves = self.move_id.raw_material_production_id.move_raw_ids
            raw_moves_lines = raw_moves.mapped('move_line_ids')
            raw_moves_lines |= self.move_id.active_move_line_ids
            lines |= raw_moves_lines.filtered(
                lambda ml: ml.product_id == self.product_id and
                (ml.lot_id or ml.lot_name) and ml.done_wo == self.done_wo)
        return lines

    @api.multi
    def write(self, vals):
        for move_line in self:
            if move_line.move_id.production_id and 'lot_id' in vals:
                move_line.production_id.move_raw_ids.mapped('move_line_ids')\
                    .filtered(lambda r: r.done_wo and not r.done_move and r.lot_produced_id == move_line.lot_id)\
                    .write({'lot_produced_id': vals['lot_id']})
            production = move_line.move_id.production_id or move_line.move_id.raw_material_production_id
            if production and move_line.state == 'done' and any(
                    field in vals
                    for field in ('lot_id', 'location_id', 'qty_done')):
                move_line._log_message(production, move_line,
                                       'mrp.track_production_move_template',
                                       vals)
        return super(StockMoveLine, self).write(vals)
Пример #16
0
class StockChangeStandardPrice(models.TransientModel):
    _name = "stock.change.standard.price"
    _description = "Change Standard Price"

    new_price = fields.Float(
        'Price',
        digits=dp.get_precision('Product Price'),
        required=True,
        help=
        "If cost price is increased, stock variation account will be debited "
        "and stock output account will be credited with the value = (difference of amount * quantity available).\n"
        "If cost price is decreased, stock variation account will be creadited and stock input account will be debited."
    )
    counterpart_account_id = fields.Many2one('account.account',
                                             string="Counter-Part Account",
                                             domain=[('deprecated', '=', False)
                                                     ])
    counterpart_account_id_required = fields.Boolean(
        string="Counter-Part Account Required")

    @api.model
    def default_get(self, fields):
        res = super(StockChangeStandardPrice, self).default_get(fields)

        product_or_template = self.env[self._context['active_model']].browse(
            self._context['active_id'])
        if 'new_price' in fields and 'new_price' not in res:
            res['new_price'] = product_or_template.standard_price
        if 'counterpart_account_id' in fields and 'counterpart_account_id' not in res:
            res['counterpart_account_id'] = product_or_template.property_account_expense_id.id or product_or_template.categ_id.property_account_expense_categ_id.id
        res['counterpart_account_id_required'] = bool(
            product_or_template.valuation == 'real_time')
        return res

    @api.multi
    def change_price(self):
        """ Changes the Standard Price of Product and creates an account move accordingly. """
        self.ensure_one()
        if self._context['active_model'] == 'product.template':
            products = self.env['product.template'].browse(
                self._context['active_id']).product_variant_ids
        else:
            products = self.env['product.product'].browse(
                self._context['active_id'])

        products.do_change_standard_price(self.new_price,
                                          self.counterpart_account_id.id)
        return {'type': 'ir.actions.act_window_close'}
Пример #17
0
class SaleOrderLine(models.Model):
    _inherit = 'sale.order.line'

    is_delivery = fields.Boolean(string="Is a Delivery", default=False)
    product_qty = fields.Float(
        compute='_compute_product_qty',
        string='Quantity',
        digits=dp.get_precision('Product Unit of Measure'))

    @api.depends('product_id', 'product_uom', 'product_uom_qty')
    def _compute_product_qty(self):
        for line in self:
            if not line.product_id or not line.product_uom or not line.product_uom_qty:
                return 0.0
            line.product_qty = line.product_uom._compute_quantity(
                line.product_uom_qty, line.product_id.uom_id)
Пример #18
0
class ProductPriceHistory(models.Model):
    """ Keep track of the ``product.template`` standard prices as they are changed. """
    _name = 'product.price.history'
    _rec_name = 'datetime'
    _order = 'datetime desc'

    def _get_default_company_id(self):
        return self._context.get('force_company', self.env.user.company_id.id)

    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=_get_default_company_id,
                                 required=True)
    product_id = fields.Many2one('product.product',
                                 'Product',
                                 ondelete='cascade',
                                 required=True)
    datetime = fields.Datetime('Date', default=fields.Datetime.now)
    cost = fields.Float('Cost', digits=dp.get_precision('Product Price'))
Пример #19
0
class MrpSubProduct(models.Model):
    _name = 'mrp.subproduct'
    _description = 'Byproduct'

    product_id = fields.Many2one('product.product', 'Product', required=True)
    product_qty = fields.Float(
        'Product Qty',
        default=1.0,
        digits=dp.get_precision('Product Unit of Measure'),
        required=True)
    product_uom_id = fields.Many2one('product.uom',
                                     'Unit of Measure',
                                     required=True)
    bom_id = fields.Many2one('mrp.bom', 'BoM', ondelete='cascade')
    operation_id = fields.Many2one('mrp.routing.workcenter',
                                   'Produced at Operation')

    @api.onchange('product_id')
    def onchange_product_id(self):
        """ Changes UoM if product_id changes. """
        if self.product_id:
            self.product_uom_id = self.product_id.uom_id.id

    @api.onchange('product_uom_id')
    def onchange_uom(self):
        res = {}
        if self.product_uom_id and self.product_id and self.product_uom_id.category_id != self.product_id.uom_id.category_id:
            res['warning'] = {
                'title':
                _('Warning'),
                'message':
                _('The Product Unit of Measure you chose has a different category than in the product form.'
                  )
            }
            self.product_uom_id = self.product_id.uom_id.id
        return res
Пример #20
0
class HrSalaryRule(models.Model):
    _name = 'hr.salary.rule'

    name = fields.Char(required=True, translate=True)
    code = fields.Char(
        required=True,
        help=
        "The code of salary rules can be used as reference in computation of other rules. "
        "In that case, it is case sensitive.")
    sequence = fields.Integer(required=True,
                              index=True,
                              default=5,
                              help='Use to arrange calculation sequence')
    quantity = fields.Char(
        default='1.0',
        help="It is used in computation for percentage and fixed amount. "
        "For e.g. A rule for Meal Voucher having fixed amount of "
        u"1€ per worked day can have its quantity defined in expression "
        "like worked_days.WORK100.number_of_days.")
    category_id = fields.Many2one('hr.salary.rule.category',
                                  string='Category',
                                  required=True)
    active = fields.Boolean(
        default=True,
        help=
        "If the active field is set to false, it will allow you to hide the salary rule without removing it."
    )
    appears_on_payslip = fields.Boolean(
        string='Appears on Payslip',
        default=True,
        help="Used to display the salary rule on payslip.")
    parent_rule_id = fields.Many2one('hr.salary.rule',
                                     string='Parent Salary Rule',
                                     index=True)
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        default=lambda self: self.env['res.company']._company_default_get())
    condition_select = fields.Selection([('none', 'Always True'),
                                         ('range', 'Range'),
                                         ('python', 'Python Expression')],
                                        string="Condition Based on",
                                        default='none',
                                        required=True)
    condition_range = fields.Char(
        string='Range Based on',
        default='contract.wage',
        help=
        'This will be used to compute the % fields values; in general it is on basic, '
        'but you can also use categories code fields in lowercase as a variable names '
        '(hra, ma, lta, etc.) and the variable basic.')
    condition_python = fields.Text(
        string='Python Condition',
        required=True,
        default='''
                    # Available variables:
                    #----------------------
                    # payslip: object containing the payslips
                    # employee: hr.employee object
                    # contract: hr.contract object
                    # rules: object containing the rules code (previously computed)
                    # categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
                    # worked_days: object containing the computed worked days
                    # inputs: object containing the computed inputs

                    # Note: returned value have to be set in the variable 'result'

                    result = rules.NET > categories.NET * 0.10''',
        help=
        'Applied this rule for calculation if condition is true. You can specify condition like basic > 1000.'
    )
    condition_range_min = fields.Float(
        string='Minimum Range',
        help="The minimum amount, applied for this rule.")
    condition_range_max = fields.Float(
        string='Maximum Range',
        help="The maximum amount, applied for this rule.")
    amount_select = fields.Selection(
        [
            ('percentage', 'Percentage (%)'),
            ('fix', 'Fixed Amount'),
            ('code', 'Python Code'),
        ],
        string='Amount Type',
        index=True,
        required=True,
        default='fix',
        help="The computation method for the rule amount.")
    amount_fix = fields.Float(string='Fixed Amount',
                              digits=dp.get_precision('Payroll'))
    amount_percentage = fields.Float(
        string='Percentage (%)',
        digits=dp.get_precision('Payroll Rate'),
        help='For example, enter 50.0 to apply a percentage of 50%')
    amount_python_compute = fields.Text(string='Python Code',
                                        default='''
                    # Available variables:
                    #----------------------
                    # payslip: object containing the payslips
                    # employee: hr.employee object
                    # contract: hr.contract object
                    # rules: object containing the rules code (previously computed)
                    # categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
                    # worked_days: object containing the computed worked days.
                    # inputs: object containing the computed inputs.

                    # Note: returned value have to be set in the variable 'result'

                    result = contract.wage * 0.10''')
    amount_percentage_base = fields.Char(
        string='Percentage based on',
        help='result will be affected to a variable')
    child_ids = fields.One2many('hr.salary.rule',
                                'parent_rule_id',
                                string='Child Salary Rule',
                                copy=True)
    register_id = fields.Many2one(
        'hr.contribution.register',
        string='Contribution Register',
        help=
        "Eventual third party involved in the salary payment of the employees."
    )
    input_ids = fields.One2many('hr.rule.input',
                                'input_id',
                                string='Inputs',
                                copy=True)
    note = fields.Text(string='Description')

    @api.multi
    def _recursive_search_of_rules(self):
        """
        @return: returns a list of tuple (id, sequence) which are all the children of the passed rule_ids
        """
        children_rules = []
        for rule in self.filtered(lambda rule: rule.child_ids):
            children_rules += rule.child_ids._recursive_search_of_rules()
        return [(rule.id, rule.sequence) for rule in self] + children_rules

    #TODO should add some checks on the type of result (should be float)
    @api.multi
    def _compute_rule(self, localdict):
        """
        :param localdict: dictionary containing the environement in which to compute the rule
        :return: returns a tuple build as the base/amount computed, the quantity and the rate
        :rtype: (float, float, float)
        """
        self.ensure_one()
        if self.amount_select == 'fix':
            try:
                return self.amount_fix, float(
                    safe_eval(self.quantity, localdict)), 100.0
            except:
                raise UserError(
                    _('Wrong quantity defined for salary rule %s (%s).') %
                    (self.name, self.code))
        elif self.amount_select == 'percentage':
            try:
                return (float(safe_eval(self.amount_percentage_base,
                                        localdict)),
                        float(safe_eval(self.quantity,
                                        localdict)), self.amount_percentage)
            except:
                raise UserError(
                    _('Wrong percentage base or quantity defined for salary rule %s (%s).'
                      ) % (self.name, self.code))
        else:
            try:
                safe_eval(self.amount_python_compute,
                          localdict,
                          mode='exec',
                          nocopy=True)
                return float(
                    localdict['result']
                ), 'result_qty' in localdict and localdict[
                    'result_qty'] or 1.0, 'result_rate' in localdict and localdict[
                        'result_rate'] or 100.0
            except:
                raise UserError(
                    _('Wrong python code defined for salary rule %s (%s).') %
                    (self.name, self.code))

    @api.multi
    def _satisfy_condition(self, localdict):
        """
        @param contract_id: id of hr.contract to be tested
        @return: returns True if the given rule match the condition for the given contract. Return False otherwise.
        """
        self.ensure_one()

        if self.condition_select == 'none':
            return True
        elif self.condition_select == 'range':
            try:
                result = safe_eval(self.condition_range, localdict)
                return self.condition_range_min <= result and result <= self.condition_range_max or False
            except:
                raise UserError(
                    _('Wrong range condition defined for salary rule %s (%s).')
                    % (self.name, self.code))
        else:  # python code
            try:
                safe_eval(self.condition_python,
                          localdict,
                          mode='exec',
                          nocopy=True)
                return 'result' in localdict and localdict['result'] or False
            except:
                raise UserError(
                    _('Wrong python condition defined for salary rule %s (%s).'
                      ) % (self.name, self.code))
Пример #21
0
class RepairFee(models.Model):
    _name = 'mrp.repair.fee'
    _description = 'Repair Fees Line'

    repair_id = fields.Many2one('mrp.repair',
                                'Repair Order Reference',
                                index=True,
                                ondelete='cascade',
                                required=True)
    name = fields.Char('Description', index=True, required=True)
    product_id = fields.Many2one('product.product', 'Product')
    product_uom_qty = fields.Float(
        'Quantity',
        digits=dp.get_precision('Product Unit of Measure'),
        required=True,
        default=1.0)
    price_unit = fields.Float('Unit Price', required=True)
    product_uom = fields.Many2one('product.uom',
                                  'Product Unit of Measure',
                                  required=True)
    price_subtotal = fields.Float('Subtotal',
                                  compute='_compute_price_subtotal',
                                  digits=0)
    tax_id = fields.Many2many('account.tax', 'repair_fee_line_tax',
                              'repair_fee_line_id', 'tax_id', 'Taxes')
    invoice_line_id = fields.Many2one('account.invoice.line',
                                      'Invoice Line',
                                      copy=False,
                                      readonly=True)
    invoiced = fields.Boolean('Invoiced', copy=False, readonly=True)

    @api.one
    @api.depends('price_unit', 'repair_id', 'product_uom_qty', 'product_id')
    def _compute_price_subtotal(self):
        taxes = self.tax_id.compute_all(
            self.price_unit, self.repair_id.pricelist_id.currency_id,
            self.product_uom_qty, self.product_id, self.repair_id.partner_id)
        self.price_subtotal = taxes['total_excluded']

    @api.onchange('repair_id', 'product_id', 'product_uom_qty')
    def onchange_product_id(self):
        """ On change of product it sets product quantity, tax account, name,
        uom of product, unit price and price subtotal. """
        if not self.product_id:
            return

        partner = self.repair_id.partner_id
        pricelist = self.repair_id.pricelist_id

        if partner and self.product_id:
            self.tax_id = partner.property_account_position_id.map_tax(
                self.product_id.taxes_id, self.product_id, partner).ids
        if self.product_id:
            self.name = self.product_id.display_name
            self.product_uom = self.product_id.uom_id.id

        warning = False
        if not pricelist:
            warning = {
                'title':
                _('No Pricelist!'),
                'message':
                _('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.'
                  )
            }
        else:
            price = pricelist.get_product_price(self.product_id,
                                                self.product_uom_qty, partner)
            if price is False:
                warning = {
                    'title':
                    _('No valid pricelist line found !'),
                    'message':
                    _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist."
                      )
                }
            else:
                self.price_unit = price
        if warning:
            return {'warning': warning}
Пример #22
0
class RepairLine(models.Model):
    _name = 'mrp.repair.line'
    _description = 'Repair Line'

    name = fields.Char('Description', required=True)
    repair_id = fields.Many2one('mrp.repair',
                                'Repair Order Reference',
                                index=True,
                                ondelete='cascade')
    type = fields.Selection([('add', 'Add'), ('remove', 'Remove')],
                            'Type',
                            required=True)
    product_id = fields.Many2one('product.product', 'Product', required=True)
    invoiced = fields.Boolean('Invoiced', copy=False, readonly=True)
    price_unit = fields.Float('Unit Price',
                              required=True,
                              digits=dp.get_precision('Product Price'))
    price_subtotal = fields.Float('Subtotal',
                                  compute='_compute_price_subtotal',
                                  digits=0)
    tax_id = fields.Many2many('account.tax', 'repair_operation_line_tax',
                              'repair_operation_line_id', 'tax_id', 'Taxes')
    product_uom_qty = fields.Float(
        'Quantity',
        default=1.0,
        digits=dp.get_precision('Product Unit of Measure'),
        required=True)
    product_uom = fields.Many2one('product.uom',
                                  'Product Unit of Measure',
                                  required=True)
    invoice_line_id = fields.Many2one('account.invoice.line',
                                      'Invoice Line',
                                      copy=False,
                                      readonly=True)
    location_id = fields.Many2one('stock.location',
                                  'Source Location',
                                  index=True,
                                  required=True)
    location_dest_id = fields.Many2one('stock.location',
                                       'Dest. Location',
                                       index=True,
                                       required=True)
    move_id = fields.Many2one('stock.move',
                              'Inventory Move',
                              copy=False,
                              readonly=True)
    lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial')
    state = fields.Selection(
        [('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'),
         ('cancel', 'Cancelled')],
        'Status',
        default='draft',
        copy=False,
        readonly=True,
        required=True,
        help=
        'The status of a repair line is set automatically to the one of the linked repair order.'
    )

    @api.constrains('lot_id', 'product_id')
    def constrain_lot_id(self):
        for line in self.filtered(
                lambda x: x.product_id.tracking != 'none' and not x.lot_id):
            raise ValidationError(
                _("Serial number is required for operation line with product '%s'"
                  ) % (line.product_id.name))

    @api.one
    @api.depends('price_unit', 'repair_id', 'product_uom_qty', 'product_id',
                 'repair_id.invoice_method')
    def _compute_price_subtotal(self):
        taxes = self.tax_id.compute_all(
            self.price_unit, self.repair_id.pricelist_id.currency_id,
            self.product_uom_qty, self.product_id, self.repair_id.partner_id)
        self.price_subtotal = taxes['total_excluded']

    @api.onchange('type', 'repair_id')
    def onchange_operation_type(self):
        """ On change of operation type it sets source location, destination location
        and to invoice field.
        @param product: Changed operation type.
        @param guarantee_limit: Guarantee limit of current record.
        @return: Dictionary of values.
        """
        if not self.type:
            self.location_id = False
            self.location_dest_id = False
        elif self.type == 'add':
            self.onchange_product_id()
            args = self.repair_id.company_id and [
                ('company_id', '=', self.repair_id.company_id.id)
            ] or []
            warehouse = self.env['stock.warehouse'].search(args, limit=1)
            self.location_id = warehouse.lot_stock_id
            self.location_dest_id = self.env['stock.location'].search(
                [('usage', '=', 'production')], limit=1).id
        else:
            self.price_unit = 0.0
            self.tax_id = False
            self.location_id = self.env['stock.location'].search(
                [('usage', '=', 'production')], limit=1).id
            self.location_dest_id = self.env['stock.location'].search(
                [('scrap_location', '=', True)], limit=1).id

    @api.onchange('repair_id', 'product_id', 'product_uom_qty')
    def onchange_product_id(self):
        """ On change of product it sets product quantity, tax account, name,
        uom of product, unit price and price subtotal. """
        partner = self.repair_id.partner_id
        pricelist = self.repair_id.pricelist_id
        if not self.product_id or not self.product_uom_qty:
            return
        if self.product_id:
            if partner:
                self.name = self.product_id.with_context(
                    lang=partner.lang).display_name
            else:
                self.name = self.product_id.display_name
            self.product_uom = self.product_id.uom_id.id
        if self.type != 'remove':
            if partner and self.product_id:
                self.tax_id = partner.property_account_position_id.map_tax(
                    self.product_id.taxes_id, self.product_id, partner).ids
            warning = False
            if not pricelist:
                warning = {
                    'title':
                    _('No Pricelist!'),
                    'message':
                    _('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.'
                      )
                }
            else:
                price = pricelist.get_product_price(self.product_id,
                                                    self.product_uom_qty,
                                                    partner)
                if price is False:
                    warning = {
                        'title':
                        _('No valid pricelist line found !'),
                        'message':
                        _("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist."
                          )
                    }
                else:
                    self.price_unit = price
            if warning:
                return {'warning': warning}
Пример #23
0
class Repair(models.Model):
    _name = 'mrp.repair'
    _description = 'Repair Order'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'create_date desc'

    @api.model
    def _default_stock_location(self):
        warehouse = self.env['stock.warehouse'].search([], limit=1)
        if warehouse:
            return warehouse.lot_stock_id.id
        return False

    name = fields.Char('Repair Reference',
                       default=lambda self: self.env['ir.sequence'].
                       next_by_code('mrp.repair'),
                       copy=False,
                       required=True,
                       states={'confirmed': [('readonly', True)]})
    product_id = fields.Many2one('product.product',
                                 string='Product to Repair',
                                 readonly=True,
                                 required=True,
                                 states={'draft': [('readonly', False)]})
    product_qty = fields.Float(
        'Product Quantity',
        default=1.0,
        digits=dp.get_precision('Product Unit of Measure'),
        readonly=True,
        required=True,
        states={'draft': [('readonly', False)]})
    product_uom = fields.Many2one('product.uom',
                                  'Product Unit of Measure',
                                  readonly=True,
                                  required=True,
                                  states={'draft': [('readonly', False)]})
    partner_id = fields.Many2one(
        'res.partner',
        'Customer',
        index=True,
        states={'confirmed': [('readonly', True)]},
        help='Choose partner for whom the order will be invoiced and delivered.'
    )
    address_id = fields.Many2one('res.partner',
                                 'Delivery Address',
                                 domain="[('parent_id','=',partner_id)]",
                                 states={'confirmed': [('readonly', True)]})
    default_address_id = fields.Many2one('res.partner',
                                         compute='_compute_default_address_id')
    state = fields.Selection(
        [('draft', 'Quotation'), ('cancel', 'Cancelled'),
         ('confirmed', 'Confirmed'), ('under_repair', 'Under Repair'),
         ('ready', 'Ready to Repair'), ('2binvoiced', 'To be Invoiced'),
         ('invoice_except', 'Invoice Exception'), ('done', 'Repaired')],
        string='Status',
        copy=False,
        default='draft',
        readonly=True,
        track_visibility='onchange',
        help=
        "* The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order.\n"
        "* The \'Confirmed\' status is used when a user confirms the repair order.\n"
        "* The \'Ready to Repair\' status is used to start to repairing, user can start repairing only after repair order is confirmed.\n"
        "* The \'To be Invoiced\' status is used to generate the invoice before or after repairing done.\n"
        "* The \'Done\' status is set when repairing is completed.\n"
        "* The \'Cancelled\' status is used when user cancel repair order.")
    location_id = fields.Many2one('stock.location',
                                  'Current Location',
                                  default=_default_stock_location,
                                  index=True,
                                  readonly=True,
                                  required=True,
                                  states={
                                      'draft': [('readonly', False)],
                                      'confirmed': [('readonly', True)]
                                  })
    location_dest_id = fields.Many2one('stock.location',
                                       'Delivery Location',
                                       readonly=True,
                                       required=True,
                                       states={
                                           'draft': [('readonly', False)],
                                           'confirmed': [('readonly', True)]
                                       })
    lot_id = fields.Many2one(
        'stock.production.lot',
        'Lot/Serial',
        domain="[('product_id','=', product_id)]",
        help="Products repaired are all belonging to this lot",
        oldname="prodlot_id")
    guarantee_limit = fields.Date('Warranty Expiration',
                                  states={'confirmed': [('readonly', True)]})
    operations = fields.One2many('mrp.repair.line',
                                 'repair_id',
                                 'Parts',
                                 copy=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})
    pricelist_id = fields.Many2one(
        'product.pricelist',
        'Pricelist',
        default=lambda self: self.env['product.pricelist'].search([], limit=1
                                                                  ).id,
        help='Pricelist of the selected partner.')
    partner_invoice_id = fields.Many2one('res.partner', 'Invoicing Address')
    invoice_method = fields.Selection(
        [("none", "No Invoice"), ("b4repair", "Before Repair"),
         ("after_repair", "After Repair")],
        string="Invoice Method",
        default='none',
        index=True,
        readonly=True,
        required=True,
        states={'draft': [('readonly', False)]},
        help=
        'Selecting \'Before Repair\' or \'After Repair\' will allow you to generate invoice before or after the repair is done respectively. \'No invoice\' means you don\'t want to generate invoice for this repair order.'
    )
    invoice_id = fields.Many2one('account.invoice',
                                 'Invoice',
                                 copy=False,
                                 readonly=True,
                                 track_visibility="onchange")
    move_id = fields.Many2one('stock.move',
                              'Move',
                              copy=False,
                              readonly=True,
                              track_visibility="onchange",
                              help="Move created by the repair order")
    fees_lines = fields.One2many('mrp.repair.fee',
                                 'repair_id',
                                 'Operations',
                                 copy=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})
    internal_notes = fields.Text('Internal Notes')
    quotation_notes = fields.Text('Quotation Notes')
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('mrp.repair'))
    invoiced = fields.Boolean('Invoiced', copy=False, readonly=True)
    repaired = fields.Boolean('Repaired', copy=False, readonly=True)
    amount_untaxed = fields.Float('Untaxed Amount',
                                  compute='_amount_untaxed',
                                  store=True)
    amount_tax = fields.Float('Taxes', compute='_amount_tax', store=True)
    amount_total = fields.Float('Total', compute='_amount_total', store=True)
    tracking = fields.Selection('Product Tracking',
                                related="product_id.tracking")

    @api.one
    @api.depends('partner_id')
    def _compute_default_address_id(self):
        if self.partner_id:
            self.default_address_id = self.partner_id.address_get(
                ['contact'])['contact']

    @api.one
    @api.depends('operations.price_subtotal', 'invoice_method',
                 'fees_lines.price_subtotal', 'pricelist_id.currency_id')
    def _amount_untaxed(self):
        total = sum(operation.price_subtotal for operation in self.operations)
        total += sum(fee.price_subtotal for fee in self.fees_lines)
        self.amount_untaxed = self.pricelist_id.currency_id.round(total)

    @api.one
    @api.depends('operations.price_unit', 'operations.product_uom_qty',
                 'operations.product_id', 'fees_lines.price_unit',
                 'fees_lines.product_uom_qty', 'fees_lines.product_id',
                 'pricelist_id.currency_id', 'partner_id')
    def _amount_tax(self):
        val = 0.0
        for operation in self.operations:
            if operation.tax_id:
                tax_calculate = operation.tax_id.compute_all(
                    operation.price_unit, self.pricelist_id.currency_id,
                    operation.product_uom_qty, operation.product_id,
                    self.partner_id)
                for c in tax_calculate['taxes']:
                    val += c['amount']
        for fee in self.fees_lines:
            if fee.tax_id:
                tax_calculate = fee.tax_id.compute_all(
                    fee.price_unit, self.pricelist_id.currency_id,
                    fee.product_uom_qty, fee.product_id, self.partner_id)
                for c in tax_calculate['taxes']:
                    val += c['amount']
        self.amount_tax = val

    @api.one
    @api.depends('amount_untaxed', 'amount_tax')
    def _amount_total(self):
        self.amount_total = self.pricelist_id.currency_id.round(
            self.amount_untaxed + self.amount_tax)

    _sql_constraints = [
        ('name', 'unique (name)',
         'The name of the Repair Order must be unique!'),
    ]

    @api.onchange('product_id')
    def onchange_product_id(self):
        self.guarantee_limit = False
        self.lot_id = False
        if self.product_id:
            self.product_uom = self.product_id.uom_id.id

    @api.onchange('product_uom')
    def onchange_product_uom(self):
        res = {}
        if not self.product_id or not self.product_uom:
            return res
        if self.product_uom.category_id != self.product_id.uom_id.category_id:
            res['warning'] = {
                'title':
                _('Warning'),
                'message':
                _('The Product Unit of Measure you chose has a different category than in the product form.'
                  )
            }
            self.product_uom = self.product_id.uom_id.id
        return res

    @api.onchange('location_id')
    def onchange_location_id(self):
        self.location_dest_id = self.location_id.id

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        if not self.partner_id:
            self.address_id = False
            self.partner_invoice_id = False
            self.pricelist_id = self.env['product.pricelist'].search(
                [], limit=1).id
        else:
            addresses = self.partner_id.address_get(
                ['delivery', 'invoice', 'contact'])
            self.address_id = addresses['delivery'] or addresses['contact']
            self.partner_invoice_id = addresses['invoice']
            self.pricelist_id = self.partner_id.property_product_pricelist.id

    @api.multi
    def button_dummy(self):
        # TDE FIXME: this button is very interesting
        return True

    @api.multi
    def action_repair_cancel_draft(self):
        if self.filtered(lambda repair: repair.state != 'cancel'):
            raise UserError(
                _("Repair must be canceled in order to reset it to draft."))
        self.mapped('operations').write({'state': 'draft'})
        return self.write({'state': 'draft'})

    def action_validate(self):
        self.ensure_one()
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        available_qty = self.env['stock.quant']._get_available_quantity(
            self.product_id, self.location_id, self.lot_id, strict=True)
        if float_compare(available_qty,
                         self.product_qty,
                         precision_digits=precision) >= 0:
            return self.action_repair_confirm()
        else:
            return {
                'name':
                _('Insufficient Quantity'),
                'view_type':
                'form',
                'view_mode':
                'form',
                'res_model':
                'stock.warn.insufficient.qty.repair',
                'view_id':
                self.env.ref(
                    'mrp_repair.stock_warn_insufficient_qty_repair_form_view').
                id,
                'type':
                'ir.actions.act_window',
                'context': {
                    'default_product_id': self.product_id.id,
                    'default_location_id': self.location_id.id,
                    'default_repair_id': self.id
                },
                'target':
                'new'
            }

    @api.multi
    def action_repair_confirm(self):
        """ Repair order state is set to 'To be invoiced' when invoice method
        is 'Before repair' else state becomes 'Confirmed'.
        @param *arg: Arguments
        @return: True
        """
        if self.filtered(lambda repair: repair.state != 'draft'):
            raise UserError(_("Can only confirm draft repairs."))
        before_repair = self.filtered(
            lambda repair: repair.invoice_method == 'b4repair')
        before_repair.write({'state': '2binvoiced'})
        to_confirm = self - before_repair
        to_confirm_operations = to_confirm.mapped('operations')
        to_confirm_operations.write({'state': 'confirmed'})
        to_confirm.write({'state': 'confirmed'})
        return True

    @api.multi
    def action_repair_cancel(self):
        if self.filtered(lambda repair: repair.state == 'done'):
            raise UserError(_("Cannot cancel completed repairs."))
        if any(repair.invoiced for repair in self):
            raise UserError(_('Repair order is already invoiced.'))
        self.mapped('operations').write({'state': 'cancel'})
        return self.write({'state': 'cancel'})

    @api.multi
    def action_send_mail(self):
        self.ensure_one()
        template_id = self.env.ref(
            'mrp_repair.mail_template_mrp_repair_quotation').id
        ctx = {
            'default_model': 'mrp.repair',
            'default_res_id': self.id,
            'default_use_template': bool(template_id),
            'default_template_id': template_id,
            'default_composition_mode': 'comment'
        }
        return {
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'target': 'new',
            'context': ctx,
        }

    @api.multi
    def print_repair_order(self):
        return self.env.ref(
            'mrp_repair.action_report_mrp_repair_order').report_action(self)

    def action_repair_invoice_create(self):
        for repair in self:
            repair.action_invoice_create()
            if repair.invoice_method == 'b4repair':
                repair.action_repair_ready()
            elif repair.invoice_method == 'after_repair':
                repair.write({'state': 'done'})
        return True

    @api.multi
    def action_invoice_create(self, group=False):
        """ Creates invoice(s) for repair order.
        @param group: It is set to true when group invoice is to be generated.
        @return: Invoice Ids.
        """
        res = dict.fromkeys(self.ids, False)
        invoices_group = {}
        InvoiceLine = self.env['account.invoice.line']
        Invoice = self.env['account.invoice']
        for repair in self.filtered(lambda repair: repair.state not in (
                'draft', 'cancel') and not repair.invoice_id):
            if not repair.partner_id.id and not repair.partner_invoice_id.id:
                raise UserError(
                    _('You have to select a Partner Invoice Address in the repair form!'
                      ))
            comment = repair.quotation_notes
            if repair.invoice_method != 'none':
                if group and repair.partner_invoice_id.id in invoices_group:
                    invoice = invoices_group[repair.partner_invoice_id.id]
                    invoice.write({
                        'name':
                        invoice.name + ', ' + repair.name,
                        'origin':
                        invoice.origin + ', ' + repair.name,
                        'comment':
                        (comment and
                         (invoice.comment and invoice.comment + "\n" + comment
                          or comment))
                        or (invoice.comment and invoice.comment or ''),
                    })
                else:
                    if not repair.partner_id.property_account_receivable_id:
                        raise UserError(
                            _('No account defined for partner "%s".') %
                            repair.partner_id.name)
                    invoice = Invoice.create({
                        'name':
                        repair.name,
                        'origin':
                        repair.name,
                        'type':
                        'out_invoice',
                        'account_id':
                        repair.partner_id.property_account_receivable_id.id,
                        'partner_id':
                        repair.partner_invoice_id.id or repair.partner_id.id,
                        'currency_id':
                        repair.pricelist_id.currency_id.id,
                        'comment':
                        repair.quotation_notes,
                        'fiscal_position_id':
                        repair.partner_id.property_account_position_id.id
                    })
                    invoices_group[repair.partner_invoice_id.id] = invoice
                repair.write({'invoiced': True, 'invoice_id': invoice.id})

                for operation in repair.operations:
                    if operation.type == 'add':
                        if group:
                            name = repair.name + '-' + operation.name
                        else:
                            name = operation.name

                        if operation.product_id.property_account_income_id:
                            account_id = operation.product_id.property_account_income_id.id
                        elif operation.product_id.categ_id.property_account_income_categ_id:
                            account_id = operation.product_id.categ_id.property_account_income_categ_id.id
                        else:
                            raise UserError(
                                _('No account defined for product "%s".') %
                                operation.product_id.name)

                        invoice_line = InvoiceLine.create({
                            'invoice_id':
                            invoice.id,
                            'name':
                            name,
                            'origin':
                            repair.name,
                            'account_id':
                            account_id,
                            'quantity':
                            operation.product_uom_qty,
                            'invoice_line_tax_ids':
                            [(6, 0, [x.id for x in operation.tax_id])],
                            'uom_id':
                            operation.product_uom.id,
                            'price_unit':
                            operation.price_unit,
                            'price_subtotal':
                            operation.product_uom_qty * operation.price_unit,
                            'product_id':
                            operation.product_id and operation.product_id.id
                            or False
                        })
                        operation.write({
                            'invoiced': True,
                            'invoice_line_id': invoice_line.id
                        })
                for fee in repair.fees_lines:
                    if group:
                        name = repair.name + '-' + fee.name
                    else:
                        name = fee.name
                    if not fee.product_id:
                        raise UserError(_('No product defined on Fees!'))

                    if fee.product_id.property_account_income_id:
                        account_id = fee.product_id.property_account_income_id.id
                    elif fee.product_id.categ_id.property_account_income_categ_id:
                        account_id = fee.product_id.categ_id.property_account_income_categ_id.id
                    else:
                        raise UserError(
                            _('No account defined for product "%s".') %
                            fee.product_id.name)

                    invoice_line = InvoiceLine.create({
                        'invoice_id':
                        invoice.id,
                        'name':
                        name,
                        'origin':
                        repair.name,
                        'account_id':
                        account_id,
                        'quantity':
                        fee.product_uom_qty,
                        'invoice_line_tax_ids':
                        [(6, 0, [x.id for x in fee.tax_id])],
                        'uom_id':
                        fee.product_uom.id,
                        'product_id':
                        fee.product_id and fee.product_id.id or False,
                        'price_unit':
                        fee.price_unit,
                        'price_subtotal':
                        fee.product_uom_qty * fee.price_unit
                    })
                    fee.write({
                        'invoiced': True,
                        'invoice_line_id': invoice_line.id
                    })
                invoice.compute_taxes()
                res[repair.id] = invoice.id
        return res

    @api.multi
    def action_created_invoice(self):
        self.ensure_one()
        return {
            'name': _('Invoice created'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'account.invoice',
            'view_id': self.env.ref('account.invoice_form').id,
            'target': 'current',
            'res_id': self.invoice_id.id,
        }

    def action_repair_ready(self):
        self.mapped('operations').write({'state': 'confirmed'})
        return self.write({'state': 'ready'})

    @api.multi
    def action_repair_start(self):
        """ Writes repair order state to 'Under Repair'
        @return: True
        """
        if self.filtered(
                lambda repair: repair.state not in ['confirmed', 'ready']):
            raise UserError(
                _("Repair must be confirmed before starting reparation."))
        self.mapped('operations').write({'state': 'confirmed'})
        return self.write({'state': 'under_repair'})

    @api.multi
    def action_repair_end(self):
        """ Writes repair order state to 'To be invoiced' if invoice method is
        After repair else state is set to 'Ready'.
        @return: True
        """
        if self.filtered(lambda repair: repair.state != 'under_repair'):
            raise UserError(
                _("Repair must be under repair in order to end reparation."))
        for repair in self:
            repair.write({'repaired': True})
            vals = {'state': 'done'}
            vals['move_id'] = repair.action_repair_done().get(repair.id)
            if not repair.invoiced and repair.invoice_method == 'after_repair':
                vals['state'] = '2binvoiced'
            repair.write(vals)
        return True

    @api.multi
    def action_repair_done(self):
        """ Creates stock move for operation and stock move for final product of repair order.
        @return: Move ids of final products

        """
        if self.filtered(lambda repair: not repair.repaired):
            raise UserError(
                _("Repair must be repaired in order to make the product moves."
                  ))
        res = {}
        Move = self.env['stock.move']
        for repair in self:
            moves = self.env['stock.move']
            for operation in repair.operations:
                move = Move.create({
                    'name':
                    repair.name,
                    'product_id':
                    operation.product_id.id,
                    'product_uom_qty':
                    operation.product_uom_qty,
                    'product_uom':
                    operation.product_uom.id,
                    'partner_id':
                    repair.address_id.id,
                    'location_id':
                    operation.location_id.id,
                    'location_dest_id':
                    operation.location_dest_id.id,
                    'move_line_ids': [(
                        0,
                        0,
                        {
                            'product_id': operation.product_id.id,
                            'lot_id': operation.lot_id.id,
                            'product_uom_qty': 0,  # bypass reservation here
                            'product_uom_id': operation.product_uom.id,
                            'qty_done': operation.product_uom_qty,
                            'package_id': False,
                            'result_package_id': False,
                            'location_id':
                            operation.location_id.id,  #TODO: owner stuff
                            'location_dest_id': operation.location_dest_id.id,
                        })],
                    'repair_id':
                    repair.id,
                    'origin':
                    repair.name,
                })
                moves |= move
                operation.write({'move_id': move.id, 'state': 'done'})
            move = Move.create({
                'name':
                repair.name,
                'product_id':
                repair.product_id.id,
                'product_uom':
                repair.product_uom.id or repair.product_id.uom_id.id,
                'product_uom_qty':
                repair.product_qty,
                'partner_id':
                repair.address_id.id,
                'location_id':
                repair.location_id.id,
                'location_dest_id':
                repair.location_dest_id.id,
                'move_line_ids': [(
                    0,
                    0,
                    {
                        'product_id':
                        repair.product_id.id,
                        'lot_id':
                        repair.lot_id.id,
                        'product_uom_qty':
                        0,  # bypass reservation here
                        'product_uom_id':
                        repair.product_uom.id or repair.product_id.uom_id.id,
                        'qty_done':
                        repair.product_qty,
                        'package_id':
                        False,
                        'result_package_id':
                        False,
                        'location_id':
                        repair.location_id.id,  #TODO: owner stuff
                        'location_dest_id':
                        repair.location_dest_id.id,
                    })],
                'repair_id':
                repair.id,
                'origin':
                repair.name,
            })
            consumed_lines = moves.mapped('move_line_ids')
            produced_lines = move.move_line_ids
            moves |= move
            moves._action_done()
            produced_lines.write(
                {'consume_line_ids': [(6, 0, consumed_lines.ids)]})
            res[repair.id] = move.id
        return res
Пример #24
0
class AccountVoucherLine(models.Model):
    _name = 'account.voucher.line'
    _description = 'Voucher Lines'

    name = fields.Text(string='Description', required=True)
    sequence = fields.Integer(
        default=10,
        help="Gives the sequence of this line when displaying the voucher.")
    voucher_id = fields.Many2one('account.voucher',
                                 'Voucher',
                                 required=1,
                                 ondelete='cascade')
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 ondelete='set null',
                                 index=True)
    account_id = fields.Many2one(
        'account.account',
        string='Account',
        required=True,
        domain=[('deprecated', '=', False)],
        help="The income or expense account related to the selected product.")
    price_unit = fields.Float(string='Unit Price',
                              required=True,
                              digits=dp.get_precision('Product Price'),
                              oldname='amount')
    price_subtotal = fields.Monetary(string='Amount',
                                     store=True,
                                     readonly=True,
                                     compute='_compute_subtotal')
    quantity = fields.Float(digits=dp.get_precision('Product Unit of Measure'),
                            required=True,
                            default=1)
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          'Analytic Account')
    company_id = fields.Many2one('res.company',
                                 related='voucher_id.company_id',
                                 string='Company',
                                 store=True,
                                 readonly=True)
    tax_ids = fields.Many2many('account.tax',
                               string='Tax',
                               help="Only for tax excluded from price")
    currency_id = fields.Many2one('res.currency',
                                  related='voucher_id.currency_id')

    @api.one
    @api.depends('price_unit', 'tax_ids', 'quantity', 'product_id',
                 'voucher_id.currency_id')
    def _compute_subtotal(self):
        self.price_subtotal = self.quantity * self.price_unit
        if self.tax_ids:
            taxes = self.tax_ids.compute_all(
                self.price_unit,
                self.voucher_id.currency_id,
                self.quantity,
                product=self.product_id,
                partner=self.voucher_id.partner_id)
            self.price_subtotal = taxes['total_excluded']

    @api.onchange('product_id', 'voucher_id', 'price_unit', 'company_id')
    def _onchange_line_details(self):
        if not self.voucher_id or not self.product_id or not self.voucher_id.partner_id:
            return
        onchange_res = self.product_id_change(self.product_id.id,
                                              self.voucher_id.partner_id.id,
                                              self.price_unit,
                                              self.company_id.id,
                                              self.voucher_id.currency_id.id,
                                              self.voucher_id.voucher_type)
        for fname, fvalue in onchange_res['value'].items():
            setattr(self, fname, fvalue)

    def _get_account(self, product, fpos, type):
        accounts = product.product_tmpl_id.get_product_accounts(fpos)
        if type == 'sale':
            return accounts['income']
        return accounts['expense']

    @api.multi
    def product_id_change(self,
                          product_id,
                          partner_id=False,
                          price_unit=False,
                          company_id=None,
                          currency_id=None,
                          type=None):
        # TDE note: mix of old and new onchange badly written in 9, multi but does not use record set
        context = self._context
        company_id = company_id if company_id is not None else context.get(
            'company_id', False)
        company = self.env['res.company'].browse(company_id)
        currency = self.env['res.currency'].browse(currency_id)
        if not partner_id:
            raise UserError(_("You must first select a partner!"))
        part = self.env['res.partner'].browse(partner_id)
        if part.lang:
            self = self.with_context(lang=part.lang)

        product = self.env['product.product'].browse(product_id)
        fpos = part.property_account_position_id
        account = self._get_account(product, fpos, type)
        values = {
            'name': product.partner_ref,
            'account_id': account.id,
        }

        if type == 'purchase':
            values['price_unit'] = price_unit or product.standard_price
            taxes = product.supplier_taxes_id or account.tax_ids
            if product.description_purchase:
                values['name'] += '\n' + product.description_purchase
        else:
            values['price_unit'] = price_unit or product.lst_price
            taxes = product.taxes_id or account.tax_ids
            if product.description_sale:
                values['name'] += '\n' + product.description_sale

        values['tax_ids'] = taxes.ids

        if company and currency:
            if company.currency_id != currency:
                if type == 'purchase':
                    values['price_unit'] = price_unit or product.standard_price
                values['price_unit'] = values['price_unit'] * currency.rate

        return {'value': values, 'domain': {}}
Пример #25
0
class ProductAttributePrice(models.Model):
    _name = "product.attribute.price"

    product_tmpl_id = fields.Many2one('product.template', 'Product Template', ondelete='cascade', required=True)
    value_id = fields.Many2one('product.attribute.value', 'Product Attribute Value', ondelete='cascade', required=True)
    price_extra = fields.Float('Price Extra', digits=dp.get_precision('Product Price'))
Пример #26
0
class EventTicket(models.Model):
    _name = 'event.event.ticket'
    _description = 'Event Ticket'

    def _default_product_id(self):
        return self.env.ref('event_sale.product_product_event', raise_if_not_found=False)

    name = fields.Char(string='Name', required=True, translate=True)
    event_type_id = fields.Many2one('event.type', string='Event Category', ondelete='cascade')
    event_id = fields.Many2one('event.event', string="Event", ondelete='cascade')
    product_id = fields.Many2one('product.product', string='Product',
        required=True, domain=[("event_ok", "=", True)],
        default=_default_product_id)
    registration_ids = fields.One2many('event.registration', 'event_ticket_id', string='Registrations')
    price = fields.Float(string='Price', digits=dp.get_precision('Product Price'))
    deadline = fields.Date(string="Sales End")
    is_expired = fields.Boolean(string='Is Expired', compute='_compute_is_expired')

    price_reduce = fields.Float(string="Price Reduce", compute="_compute_price_reduce", digits=dp.get_precision('Product Price'))
    price_reduce_taxinc = fields.Float(compute='_get_price_reduce_tax', string='Price Reduce Tax inc')
    # seats fields
    seats_availability = fields.Selection([('limited', 'Limited'), ('unlimited', 'Unlimited')],
        string='Available Seat', required=True, store=True, compute='_compute_seats', default="limited")
    seats_max = fields.Integer(string='Maximum Available Seats',
       help="Define the number of available tickets. If you have too much registrations you will "
            "not be able to sell tickets anymore. Set 0 to ignore this rule set as unlimited.")
    seats_reserved = fields.Integer(string='Reserved Seats', compute='_compute_seats', store=True)
    seats_available = fields.Integer(string='Available Seats', compute='_compute_seats', store=True)
    seats_unconfirmed = fields.Integer(string='Unconfirmed Seat Reservations', compute='_compute_seats', store=True)
    seats_used = fields.Integer(compute='_compute_seats', store=True)

    @api.multi
    def _compute_is_expired(self):
        for record in self:
            if record.deadline:
                current_date = fields.Date.context_today(record.with_context({'tz': record.event_id.date_tz}))
                record.is_expired = record.deadline < current_date
            else:
                record.is_expired = False

    @api.multi
    def _compute_price_reduce(self):
        for record in self:
            product = record.product_id
            discount = product.lst_price and (product.lst_price - product.price) / product.lst_price or 0.0
            record.price_reduce = (1.0 - discount) * record.price

    def _get_price_reduce_tax(self):
        for record in self:
            # sudo necessary here since the field is most probably accessed through the website
            tax_ids = record.sudo().product_id.taxes_id.filtered(lambda r: r.company_id == record.event_id.company_id)
            taxes = tax_ids.compute_all(record.price_reduce, record.event_id.company_id.currency_id, 1.0, product=record.product_id)
            record.price_reduce_taxinc = taxes['total_included']

    @api.multi
    @api.depends('seats_max', 'registration_ids.state')
    def _compute_seats(self):
        """ Determine reserved, available, reserved but unconfirmed and used seats. """
        # initialize fields to 0 + compute seats availability
        for ticket in self:
            ticket.seats_availability = 'unlimited' if ticket.seats_max == 0 else 'limited'
            ticket.seats_unconfirmed = ticket.seats_reserved = ticket.seats_used = ticket.seats_available = 0
        # aggregate registrations by ticket and by state
        if self.ids:
            state_field = {
                'draft': 'seats_unconfirmed',
                'open': 'seats_reserved',
                'done': 'seats_used',
            }
            query = """ SELECT event_ticket_id, state, count(event_id)
                        FROM event_registration
                        WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done')
                        GROUP BY event_ticket_id, state
                    """
            self.env.cr.execute(query, (tuple(self.ids),))
            for event_ticket_id, state, num in self.env.cr.fetchall():
                ticket = self.browse(event_ticket_id)
                ticket[state_field[state]] += num
        # compute seats_available
        for ticket in self:
            if ticket.seats_max > 0:
                ticket.seats_available = ticket.seats_max - (ticket.seats_reserved + ticket.seats_used)

    @api.multi
    @api.constrains('registration_ids', 'seats_max')
    def _check_seats_limit(self):
        for record in self:
            if record.seats_max and record.seats_available < 0:
                raise ValidationError(_('No more available seats for the ticket'))

    @api.constrains('event_type_id', 'event_id')
    def _constrains_event(self):
        if any(ticket.event_type_id and ticket.event_id for ticket in self):
            raise UserError(_('Ticket should belong to either event category or event but not both'))

    @api.onchange('product_id')
    def _onchange_product_id(self):
        self.price = self.product_id.list_price or 0
Пример #27
0
class MrpProduction(models.Model):
    """ Manufacturing Orders """
    _name = 'mrp.production'
    _description = 'Manufacturing Order'
    _date_name = 'date_planned_start'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'date_planned_start asc,id'

    @api.model
    def _get_default_picking_type(self):
        return self.env['stock.picking.type'].search([
            ('code', '=', 'mrp_operation'),
            ('warehouse_id.company_id', 'in', [self.env.context.get('company_id', self.env.user.company_id.id), False])],
            limit=1).id

    @api.model
    def _get_default_location_src_id(self):
        location = False
        if self._context.get('default_picking_type_id'):
            location = self.env['stock.picking.type'].browse(self.env.context['default_picking_type_id']).default_location_src_id
        if not location:
            location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
        return location and location.id or False

    @api.model
    def _get_default_location_dest_id(self):
        location = False
        if self._context.get('default_picking_type_id'):
            location = self.env['stock.picking.type'].browse(self.env.context['default_picking_type_id']).default_location_dest_id
        if not location:
            location = self.env.ref('stock.stock_location_stock', raise_if_not_found=False)
        return location and location.id or False

    name = fields.Char(
        'Reference', copy=False, readonly=True, default=lambda x: _('New'))
    origin = fields.Char(
        'Source', copy=False,
        help="Reference of the document that generated this production order request.")

    product_id = fields.Many2one(
        'product.product', 'Product',
        domain=[('type', 'in', ['product', 'consu'])],
        readonly=True, required=True,
        states={'confirmed': [('readonly', False)]})
    product_tmpl_id = fields.Many2one('product.template', 'Product Template', related='product_id.product_tmpl_id')
    product_qty = fields.Float(
        'Quantity To Produce',
        default=1.0, digits=dp.get_precision('Product Unit of Measure'),
        readonly=True, required=True, track_visibility='onchange',
        states={'confirmed': [('readonly', False)]})
    product_uom_id = fields.Many2one(
        'product.uom', 'Product Unit of Measure',
        oldname='product_uom', readonly=True, required=True,
        states={'confirmed': [('readonly', False)]})
    picking_type_id = fields.Many2one(
        'stock.picking.type', 'Operation Type',
        default=_get_default_picking_type, required=True)
    location_src_id = fields.Many2one(
        'stock.location', 'Raw Materials Location',
        default=_get_default_location_src_id,
        readonly=True,  required=True,
        states={'confirmed': [('readonly', False)]},
        help="Location where the system will look for components.")
    location_dest_id = fields.Many2one(
        'stock.location', 'Finished Products Location',
        default=_get_default_location_dest_id,
        readonly=True,  required=True,
        states={'confirmed': [('readonly', False)]},
        help="Location where the system will stock the finished products.")
    date_planned_start = fields.Datetime(
        'Deadline Start', copy=False, default=fields.Datetime.now,
        index=True, required=True,
        states={'confirmed': [('readonly', False)]}, oldname="date_planned")
    date_planned_finished = fields.Datetime(
        'Deadline End', copy=False, default=fields.Datetime.now,
        index=True, 
        states={'confirmed': [('readonly', False)]})
    date_start = fields.Datetime('Start Date', copy=False, index=True, readonly=True)
    date_finished = fields.Datetime('End Date', copy=False, index=True, readonly=True)
    bom_id = fields.Many2one(
        'mrp.bom', 'Bill of Material',
        readonly=True, states={'confirmed': [('readonly', False)]},
        help="Bill of Materials allow you to define the list of required raw materials to make a finished product.")
    routing_id = fields.Many2one(
        'mrp.routing', 'Routing',
        readonly=True, compute='_compute_routing', store=True,
        help="The list of operations (list of work centers) to produce the finished product. The routing "
             "is mainly used to compute work center costs during operations and to plan future loads on "
             "work centers based on production planning.")
    move_raw_ids = fields.One2many(
        'stock.move', 'raw_material_production_id', 'Raw Materials', oldname='move_lines',
        copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, 
        domain=[('scrapped', '=', False)])
    move_finished_ids = fields.One2many(
        'stock.move', 'production_id', 'Finished Products',
        copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, 
        domain=[('scrapped', '=', False)])
    finished_move_line_ids = fields.One2many(
        'stock.move.line', compute='_compute_lines', inverse='_inverse_lines', string="Finished Product"
        )
    workorder_ids = fields.One2many(
        'mrp.workorder', 'production_id', 'Work Orders',
        copy=False, oldname='workcenter_lines', readonly=True)
    workorder_count = fields.Integer('# Work Orders', compute='_compute_workorder_count')
    workorder_done_count = fields.Integer('# Done Work Orders', compute='_compute_workorder_done_count')
    move_dest_ids = fields.One2many('stock.move', 'created_production_id',
        string="Stock Movements of Produced Goods")

    state = fields.Selection([
        ('confirmed', 'Confirmed'),
        ('planned', 'Planned'),
        ('progress', 'In Progress'),
        ('done', 'Done'),
        ('cancel', 'Cancelled')], string='State',
        copy=False, default='confirmed', track_visibility='onchange')
    availability = fields.Selection([
        ('assigned', 'Available'),
        ('partially_available', 'Partially Available'),
        ('waiting', 'Waiting'),
        ('none', 'None')], string='Materials Availability',
        compute='_compute_availability', store=True)

    unreserve_visible = fields.Boolean(
        'Allowed to Unreserve Inventory', compute='_compute_unreserve_visible',
        help='Technical field to check when we can unreserve')
    post_visible = fields.Boolean(
        'Allowed to Post Inventory', compute='_compute_post_visible',
        help='Technical field to check when we can post')
    consumed_less_than_planned = fields.Boolean(
        compute='_compute_consumed_less_than_planned',
        help='Technical field used to see if we have to display a warning or not when confirming an order.')

    user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self._uid)
    company_id = fields.Many2one(
        'res.company', 'Company',
        default=lambda self: self.env['res.company']._company_default_get('mrp.production'),
        required=True)

    check_to_done = fields.Boolean(compute="_get_produced_qty", string="Check Produced Qty", 
        help="Technical Field to see if we can show 'Mark as Done' button")
    qty_produced = fields.Float(compute="_get_produced_qty", string="Quantity Produced")
    procurement_group_id = fields.Many2one(
        'procurement.group', 'Procurement Group',
        copy=False)
    propagate = fields.Boolean(
        'Propagate cancel and split',
        help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too')
    has_moves = fields.Boolean(compute='_has_moves')
    scrap_ids = fields.One2many('stock.scrap', 'production_id', 'Scraps')
    scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
    priority = fields.Selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority',
                                readonly=True, states={'confirmed': [('readonly', False)]}, default='1')
    is_locked = fields.Boolean('Is Locked', default=True, copy=False)
    show_final_lots = fields.Boolean('Show Final Lots', compute='_compute_show_lots')
    production_location_id = fields.Many2one('stock.location', "Production Location", related='product_id.property_stock_production')

    @api.depends('product_id.tracking')
    def _compute_show_lots(self):
        for production in self:
            production.show_final_lots = production.product_id.tracking != 'none'

    def _inverse_lines(self):
        """ Little hack to make sure that when you change something on these objects, it gets saved"""
        pass

    @api.depends('move_finished_ids.move_line_ids')
    def _compute_lines(self):
        for production in self:
            production.finished_move_line_ids = production.move_finished_ids.mapped('move_line_ids')

    @api.multi
    @api.depends('bom_id.routing_id', 'bom_id.routing_id.operation_ids')
    def _compute_routing(self):
        for production in self:
            if production.bom_id.routing_id.operation_ids:
                production.routing_id = production.bom_id.routing_id.id
            else:
                production.routing_id = False

    @api.multi
    @api.depends('workorder_ids')
    def _compute_workorder_count(self):
        data = self.env['mrp.workorder'].read_group([('production_id', 'in', self.ids)], ['production_id'], ['production_id'])
        count_data = dict((item['production_id'][0], item['production_id_count']) for item in data)
        for production in self:
            production.workorder_count = count_data.get(production.id, 0)

    @api.multi
    @api.depends('workorder_ids.state')
    def _compute_workorder_done_count(self):
        data = self.env['mrp.workorder'].read_group([
            ('production_id', 'in', self.ids),
            ('state', '=', 'done')], ['production_id'], ['production_id'])
        count_data = dict((item['production_id'][0], item['production_id_count']) for item in data)
        for production in self:
            production.workorder_done_count = count_data.get(production.id, 0)

    @api.multi
    @api.depends('move_raw_ids.state', 'workorder_ids.move_raw_ids', 'bom_id.ready_to_produce')
    def _compute_availability(self):
        for order in self:
            if not order.move_raw_ids:
                order.availability = 'none'
                continue
            if order.bom_id.ready_to_produce == 'all_available':
                order.availability = any(move.state not in ('assigned', 'done', 'cancel') for move in order.move_raw_ids) and 'waiting' or 'assigned'
            else:
                move_raw_ids = order.move_raw_ids.filtered(lambda m: m.product_qty)
                partial_list = [x.state in ('partially_available', 'assigned') for x in move_raw_ids]
                assigned_list = [x.state in ('assigned', 'done', 'cancel') for x in move_raw_ids]
                order.availability = (all(assigned_list) and 'assigned') or (any(partial_list) and 'partially_available') or 'waiting'

    @api.depends('move_raw_ids', 'is_locked', 'state', 'move_raw_ids.quantity_done')
    def _compute_unreserve_visible(self):
        for order in self:
            already_reserved = order.is_locked and order.state not in ('done', 'cancel') and order.mapped('move_raw_ids.move_line_ids')
            any_quantity_done = any([m.quantity_done > 0 for m in order.move_raw_ids])
            order.unreserve_visible = not any_quantity_done and already_reserved

    @api.multi
    @api.depends('move_raw_ids.quantity_done', 'move_finished_ids.quantity_done', 'is_locked')
    def _compute_post_visible(self):
        for order in self:
            if order.product_tmpl_id._is_cost_method_standard():
                order.post_visible = order.is_locked and any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids | order.move_finished_ids)
            else:
                order.post_visible = order.is_locked and any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_finished_ids)

    @api.multi
    @api.depends('move_raw_ids.quantity_done', 'move_raw_ids.product_uom_qty')
    def _compute_consumed_less_than_planned(self):
        for order in self:
            order.consumed_less_than_planned = any(order.move_raw_ids.filtered(
                lambda move: float_compare(move.quantity_done,
                                           move.product_uom_qty,
                                           precision_rounding=move.product_uom.rounding) == -1)
            )

    @api.multi
    @api.depends('workorder_ids.state', 'move_finished_ids', 'is_locked')
    def _get_produced_qty(self):
        for production in self:
            done_moves = production.move_finished_ids.filtered(lambda x: x.state != 'cancel' and x.product_id.id == production.product_id.id)
            qty_produced = sum(done_moves.mapped('quantity_done'))
            wo_done = True
            if any([x.state not in ('done', 'cancel') for x in production.workorder_ids]):
                wo_done = False
            production.check_to_done = production.is_locked and done_moves and (qty_produced >= production.product_qty) and (production.state not in ('done', 'cancel')) and wo_done
            production.qty_produced = qty_produced
        return True

    @api.multi
    @api.depends('move_raw_ids')
    def _has_moves(self):
        for mo in self:
            mo.has_moves = any(mo.move_raw_ids)

    @api.multi
    def _compute_scrap_move_count(self):
        data = self.env['stock.scrap'].read_group([('production_id', 'in', self.ids)], ['production_id'], ['production_id'])
        count_data = dict((item['production_id'][0], item['production_id_count']) for item in data)
        for production in self:
            production.scrap_count = count_data.get(production.id, 0)


    _sql_constraints = [
        ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per Company!'),
        ('qty_positive', 'check (product_qty > 0)', 'The quantity to produce must be positive!'),
    ]

    @api.onchange('product_id', 'picking_type_id', 'company_id')
    def onchange_product_id(self):
        """ Finds UoM of changed product. """
        if not self.product_id:
            self.bom_id = False
        else:
            bom = self.env['mrp.bom']._bom_find(product=self.product_id, picking_type=self.picking_type_id, company_id=self.company_id.id)
            if bom.type == 'normal':
                self.bom_id = bom.id
            else:
                self.bom_id = False
            self.product_uom_id = self.product_id.uom_id.id
            return {'domain': {'product_uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]}}

    @api.onchange('picking_type_id')
    def onchange_picking_type(self):
        location = self.env.ref('stock.stock_location_stock')
        self.location_src_id = self.picking_type_id.default_location_src_id.id or location.id
        self.location_dest_id = self.picking_type_id.default_location_dest_id.id or location.id

    @api.multi
    def write (self, vals):
        res = super(MrpProduction, self).write(vals)
        if 'date_planned_start' in vals:
            moves = (self.mapped('move_raw_ids') + self.mapped('move_finished_ids')).filtered(
                lambda r: r.state not in ['done', 'cancel'])
            moves.write({
                'date_expected': vals['date_planned_start'],
            })
        return res

    @api.model
    def create(self, values):
        if not values.get('name', False) or values['name'] == _('New'):
            if values.get('picking_type_id'):
                values['name'] = self.env['stock.picking.type'].browse(values['picking_type_id']).sequence_id.next_by_id()
            else:
                values['name'] = self.env['ir.sequence'].next_by_code('mrp.production') or _('New')
        if not values.get('procurement_group_id'):
            values['procurement_group_id'] = self.env["procurement.group"].create({'name': values['name']}).id
        production = super(MrpProduction, self).create(values)
        production._generate_moves()
        return production

    @api.multi
    def unlink(self):
        if any(production.state != 'cancel' for production in self):
            raise UserError(_('Cannot delete a manufacturing order not in cancel state'))
        return super(MrpProduction, self).unlink()

    def action_toggle_is_locked(self):
        self.ensure_one()
        self.is_locked = not self.is_locked
        return True

    @api.multi
    def _generate_moves(self):
        for production in self:
            production._generate_finished_moves()
            factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id) / production.bom_id.product_qty
            boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
            production._generate_raw_moves(lines)
            # Check for all draft moves whether they are mto or not
            production._adjust_procure_method()
            production.move_raw_ids._action_confirm()
        return True

    def _generate_finished_moves(self):
        move = self.env['stock.move'].create({
            'name': self.name,
            'date': self.date_planned_start,
            'date_expected': self.date_planned_start,
            'product_id': self.product_id.id,
            'product_uom': self.product_uom_id.id,
            'product_uom_qty': self.product_qty,
            'location_id': self.product_id.property_stock_production.id,
            'location_dest_id': self.location_dest_id.id,
            'company_id': self.company_id.id,
            'production_id': self.id,
            'origin': self.name,
            'group_id': self.procurement_group_id.id,
            'propagate': self.propagate,
            'move_dest_ids': [(4, x.id) for x in self.move_dest_ids],
        })
        move._action_confirm()
        return move

    def _generate_raw_moves(self, exploded_lines):
        self.ensure_one()
        moves = self.env['stock.move']
        for bom_line, line_data in exploded_lines:
            moves += self._generate_raw_move(bom_line, line_data)
        return moves

    def _generate_raw_move(self, bom_line, line_data):
        quantity = line_data['qty']
        # alt_op needed for the case when you explode phantom bom and all the lines will be consumed in the operation given by the parent bom line
        alt_op = line_data['parent_line'] and line_data['parent_line'].operation_id.id or False
        if bom_line.child_bom_id and bom_line.child_bom_id.type == 'phantom':
            return self.env['stock.move']
        if bom_line.product_id.type not in ['product', 'consu']:
            return self.env['stock.move']
        if self.routing_id:
            routing = self.routing_id
        else:
            routing = self.bom_id.routing_id
        if routing and routing.location_id:
            source_location = routing.location_id
        else:
            source_location = self.location_src_id
        original_quantity = (self.product_qty - self.qty_produced) or 1.0
        data = {
            'sequence': bom_line.sequence,
            'name': self.name,
            'date': self.date_planned_start,
            'date_expected': self.date_planned_start,
            'bom_line_id': bom_line.id,
            'product_id': bom_line.product_id.id,
            'product_uom_qty': quantity,
            'product_uom': bom_line.product_uom_id.id,
            'location_id': source_location.id,
            'location_dest_id': self.product_id.property_stock_production.id,
            'raw_material_production_id': self.id,
            'company_id': self.company_id.id,
            'operation_id': bom_line.operation_id.id or alt_op,
            'price_unit': bom_line.product_id.standard_price,
            'procure_method': 'make_to_stock',
            'origin': self.name,
            'warehouse_id': source_location.get_warehouse().id,
            'group_id': self.procurement_group_id.id,
            'propagate': self.propagate,
            'unit_factor': quantity / original_quantity,
        }
        return self.env['stock.move'].create(data)

    @api.multi
    def _adjust_procure_method(self):
        try:
            mto_route = self.env['stock.warehouse']._get_mto_route()
        except:
            mto_route = False
        for move in self.move_raw_ids:
            product = move.product_id
            routes = product.route_ids + product.route_from_categ_ids
            # TODO: optimize with read_group?
            pull = self.env['procurement.rule'].search([('route_id', 'in', [x.id for x in routes]), ('location_src_id', '=', move.location_id.id),
                                                        ('location_id', '=', move.location_dest_id.id)], limit=1)
            if pull and (pull.procure_method == 'make_to_order'):
                move.procure_method = pull.procure_method
            elif not pull: # If there is no make_to_stock rule either
                if mto_route and mto_route.id in [x.id for x in routes]:
                    move.procure_method = 'make_to_order'

    @api.multi
    def _update_raw_move(self, bom_line, line_data):
        quantity = line_data['qty']
        self.ensure_one()
        move = self.move_raw_ids.filtered(lambda x: x.bom_line_id.id == bom_line.id and x.state not in ('done', 'cancel'))
        if move:
            if quantity > 0:
                move[0].write({'product_uom_qty': quantity})
            elif quantity < 0:  # Do not remove 0 lines
                if move[0].quantity_done > 0:
                    raise UserError(_('Lines need to be deleted, but can not as you still have some quantities to consume in them. '))
                move[0]._action_cancel()
                move[0].unlink()
            return move
        else:
            self._generate_raw_move(bom_line, line_data)

    @api.multi
    def action_assign(self):
        for production in self:
            production.move_raw_ids._action_assign()
        return True

    @api.multi
    def open_produce_product(self):
        self.ensure_one()
        action = self.env.ref('mrp.act_mrp_product_produce').read()[0]
        return action

    @api.multi
    def button_plan(self):
        """ Create work orders. And probably do stuff, like things. """
        orders_to_plan = self.filtered(lambda order: order.routing_id and order.state == 'confirmed')
        for order in orders_to_plan:
            quantity = order.product_uom_id._compute_quantity(order.product_qty, order.bom_id.product_uom_id) / order.bom_id.product_qty
            boms, lines = order.bom_id.explode(order.product_id, quantity, picking_type=order.bom_id.picking_type_id)
            order._generate_workorders(boms)
        return orders_to_plan.write({'state': 'planned'})

    @api.multi
    def _generate_workorders(self, exploded_boms):
        workorders = self.env['mrp.workorder']
        original_one = False
        for bom, bom_data in exploded_boms:
            # If the routing of the parent BoM and phantom BoM are the same, don't recreate work orders, but use one master routing
            if bom.routing_id.id and (not bom_data['parent_line'] or bom_data['parent_line'].bom_id.routing_id.id != bom.routing_id.id):
                temp_workorders = self._workorders_create(bom, bom_data)
                workorders += temp_workorders
                if temp_workorders: # In order to avoid two "ending work orders"
                    if original_one:
                        temp_workorders[-1].next_work_order_id = original_one
                    original_one = temp_workorders[0]
        return workorders

    def _workorders_create(self, bom, bom_data):
        """
        :param bom: in case of recursive boms: we could create work orders for child
                    BoMs
        """
        workorders = self.env['mrp.workorder']
        bom_qty = bom_data['qty']

        # Initial qty producing
        if self.product_id.tracking == 'serial':
            quantity = 1.0
        else:
            quantity = self.product_qty - sum(self.move_finished_ids.mapped('quantity_done'))
            quantity = quantity if (quantity > 0) else 0

        for operation in bom.routing_id.operation_ids:
            # create workorder
            cycle_number = math.ceil(bom_qty / operation.workcenter_id.capacity)  # TODO: float_round UP
            duration_expected = (operation.workcenter_id.time_start +
                                 operation.workcenter_id.time_stop +
                                 cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency)
            workorder = workorders.create({
                'name': operation.name,
                'production_id': self.id,
                'workcenter_id': operation.workcenter_id.id,
                'operation_id': operation.id,
                'duration_expected': duration_expected,
                'state': len(workorders) == 0 and 'ready' or 'pending',
                'qty_producing': quantity,
                'capacity': operation.workcenter_id.capacity,
            })
            if workorders:
                workorders[-1].next_work_order_id = workorder.id
            workorders += workorder

            # assign moves; last operation receive all unassigned moves (which case ?)
            moves_raw = self.move_raw_ids.filtered(lambda move: move.operation_id == operation)
            if len(workorders) == len(bom.routing_id.operation_ids):
                moves_raw |= self.move_raw_ids.filtered(lambda move: not move.operation_id)
            moves_finished = self.move_finished_ids.filtered(lambda move: move.operation_id == operation) #TODO: code does nothing, unless maybe by_products?
            moves_raw.mapped('move_line_ids').write({'workorder_id': workorder.id})
            (moves_finished + moves_raw).write({'workorder_id': workorder.id})

            workorder._generate_lot_ids()
        return workorders

    @api.multi
    def action_cancel(self):
        """ Cancels production order, unfinished stock moves and set procurement
        orders in exception """
        if any(workorder.state == 'progress' for workorder in self.mapped('workorder_ids')):
            raise UserError(_('You can not cancel production order, a work order is still in progress.'))
        for production in self:
            production.workorder_ids.filtered(lambda x: x.state != 'cancel').action_cancel()

            finish_moves = production.move_finished_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
            raw_moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
            (finish_moves | raw_moves)._action_cancel()

        self.write({'state': 'cancel', 'is_locked': True})
        return True

    def _cal_price(self, consumed_moves):
        self.ensure_one()
        return True

    @api.multi
    def post_inventory(self):
        for order in self:
            moves_not_to_do = order.move_raw_ids.filtered(lambda x: x.state == 'done')
            moves_to_do = order.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
            for move in moves_to_do.filtered(lambda m: m.product_qty == 0.0 and m.quantity_done > 0):
                move.product_uom_qty = move.quantity_done
            moves_to_do._action_done()
            moves_to_do = order.move_raw_ids.filtered(lambda x: x.state == 'done') - moves_not_to_do
            order._cal_price(moves_to_do)
            moves_to_finish = order.move_finished_ids.filtered(lambda x: x.state not in ('done','cancel'))
            moves_to_finish._action_done()
            order.action_assign()
            consume_move_lines = moves_to_do.mapped('active_move_line_ids')
            for moveline in moves_to_finish.mapped('active_move_line_ids'):
                if moveline.product_id == order.product_id and moveline.move_id.has_tracking != 'none':
                    if any([not ml.lot_produced_id for ml in consume_move_lines]):
                        raise UserError(_('You can not consume without telling for which lot you consumed it'))
                    # Link all movelines in the consumed with same lot_produced_id false or the correct lot_produced_id
                    filtered_lines = consume_move_lines.filtered(lambda x: x.lot_produced_id == moveline.lot_id)
                    moveline.write({'consume_line_ids': [(6, 0, [x for x in filtered_lines.ids])]})
                else:
                    # Link with everything
                    moveline.write({'consume_line_ids': [(6, 0, [x for x in consume_move_lines.ids])]})
        return True

    @api.multi
    def button_mark_done(self):
        self.ensure_one()
        for wo in self.workorder_ids:
            if wo.time_ids.filtered(lambda x: (not x.date_end) and (x.loss_type in ('productive', 'performance'))):
                raise UserError(_('Work order %s is still running') % wo.name)
        self.post_inventory()
        moves_to_cancel = (self.move_raw_ids | self.move_finished_ids).filtered(lambda x: x.state not in ('done', 'cancel'))
        moves_to_cancel._action_cancel()
        self.write({'state': 'done', 'date_finished': fields.Datetime.now()})
        return self.write({'state': 'done'})

    @api.multi
    def do_unreserve(self):
        for production in self:
            production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))._do_unreserve()
        return True

    @api.multi
    def button_unreserve(self):
        self.ensure_one()
        self.do_unreserve()
        return True

    @api.multi
    def button_scrap(self):
        self.ensure_one()
        return {
            'name': _('Scrap'),
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'stock.scrap',
            'view_id': self.env.ref('stock.stock_scrap_form_view2').id,
            'type': 'ir.actions.act_window',
            'context': {'default_production_id': self.id,
                        'product_ids': (self.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) | self.move_finished_ids.filtered(lambda x: x.state == 'done')).mapped('product_id').ids,
                        },
            'target': 'new',
        }

    @api.multi
    def action_see_move_scrap(self):
        self.ensure_one()
        action = self.env.ref('stock.action_stock_scrap').read()[0]
        action['domain'] = [('production_id', '=', self.id)]
        return action
Пример #28
0
class SaleOrderOption(models.Model):
    _name = "sale.order.option"
    _description = "Sale Options"
    _order = 'sequence, id'

    order_id = fields.Many2one('sale.order',
                               'Sales Order Reference',
                               ondelete='cascade',
                               index=True)
    line_id = fields.Many2one('sale.order.line', on_delete="set null")
    name = fields.Text('Description', required=True)
    product_id = fields.Many2one('product.product',
                                 'Product',
                                 domain=[('sale_ok', '=', True)])
    layout_category_id = fields.Many2one('sale.layout_category',
                                         string='Section')
    website_description = fields.Html('Line Description',
                                      sanitize_attributes=False,
                                      translate=html_translate)
    price_unit = fields.Float('Unit Price',
                              required=True,
                              digits=dp.get_precision('Product Price'))
    discount = fields.Float('Discount (%)',
                            digits=dp.get_precision('Discount'))
    uom_id = fields.Many2one('product.uom', 'Unit of Measure ', required=True)
    quantity = fields.Float('Quantity',
                            required=True,
                            digits=dp.get_precision('Product UoS'),
                            default=1)
    sequence = fields.Integer(
        'Sequence',
        help=
        "Gives the sequence order when displaying a list of suggested product."
    )

    @api.onchange('product_id', 'uom_id')
    def _onchange_product_id(self):
        if not self.product_id:
            return
        product = self.product_id.with_context(
            lang=self.order_id.partner_id.lang)
        self.price_unit = product.list_price
        self.website_description = product.quote_description or product.website_description
        self.name = product.name
        if product.description_sale:
            self.name += '\n' + product.description_sale
        self.uom_id = self.uom_id or product.uom_id
        pricelist = self.order_id.pricelist_id
        if pricelist and product:
            partner_id = self.order_id.partner_id.id
            self.price_unit = pricelist.with_context(
                uom=self.uom_id.id).get_product_price(product, self.quantity,
                                                      partner_id)
        domain = {
            'uom_id':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        return {'domain': domain}

    @api.multi
    def button_add_to_order(self):
        self.ensure_one()
        order = self.order_id
        if order.state not in ['draft', 'sent']:
            return False

        order_line = order.order_line.filtered(
            lambda line: line.product_id == self.product_id)
        if order_line:
            order_line = order_line[0]
            order_line.product_uom_qty += 1
        else:
            vals = {
                'price_unit': self.price_unit,
                'website_description': self.website_description,
                'name': self.name,
                'order_id': order.id,
                'product_id': self.product_id.id,
                'layout_category_id': self.layout_category_id.id,
                'product_uom_qty': self.quantity,
                'product_uom': self.uom_id.id,
                'discount': self.discount,
            }
            order_line = self.env['sale.order.line'].create(vals)
            order_line._compute_tax_id()

        self.write({'line_id': order_line.id})
        return {'type': 'ir.actions.client', 'tag': 'reload'}
Пример #29
0
class ChangeProductionQty(models.TransientModel):
    _name = 'change.production.qty'
    _description = 'Change Quantity of Products'

    # TDE FIXME: add production_id field
    mo_id = fields.Many2one('mrp.production',
                            'Manufacturing Order',
                            required=True)
    product_qty = fields.Float(
        'Quantity To Produce',
        digits=dp.get_precision('Product Unit of Measure'),
        required=True)

    @api.model
    def default_get(self, fields):
        res = super(ChangeProductionQty, self).default_get(fields)
        if 'mo_id' in fields and not res.get('mo_id') and self._context.get(
                'active_model') == 'mrp.production' and self._context.get(
                    'active_id'):
            res['mo_id'] = self._context['active_id']
        if 'product_qty' in fields and not res.get('product_qty') and res.get(
                'mo_id'):
            res['product_qty'] = self.env['mrp.production'].browse(
                res['mo_id']).product_qty
        return res

    @api.model
    def _update_product_to_produce(self, production, qty):
        production_move = production.move_finished_ids.filtered(
            lambda x: x.product_id.id == production.product_id.id and x.state
            not in ('done', 'cancel'))
        if production_move:
            production_move.write({'product_uom_qty': qty})
        else:
            production_move = production._generate_finished_moves()
            production_move = production.move_finished_ids.filtered(
                lambda x: x.state not in ('done', 'cancel') and production.
                product_id.id == x.product_id.id)
            production_move.write({'product_uom_qty': qty})

    @api.multi
    def change_prod_qty(self):
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        for wizard in self:
            production = wizard.mo_id
            produced = sum(
                production.move_finished_ids.filtered(
                    lambda m: m.product_id == production.product_id).mapped(
                        'quantity_done'))
            if wizard.product_qty < produced:
                format_qty = '%.{precision}f'.format(precision=precision)
                raise UserError(
                    _("You have already processed %s. Please input a quantity higher than %s "
                      ) % (format_qty % produced, format_qty % produced))
            production.write({'product_qty': wizard.product_qty})
            done_moves = production.move_finished_ids.filtered(
                lambda x: x.state == 'done' and x.product_id == production.
                product_id)
            qty_produced = production.product_id.uom_id._compute_quantity(
                sum(done_moves.mapped('product_qty')),
                production.product_uom_id)
            factor = production.product_uom_id._compute_quantity(
                production.product_qty - qty_produced, production.bom_id.
                product_uom_id) / production.bom_id.product_qty
            boms, lines = production.bom_id.explode(
                production.product_id,
                factor,
                picking_type=production.bom_id.picking_type_id)
            for line, line_data in lines:
                production._update_raw_move(line, line_data)
            operation_bom_qty = {}
            for bom, bom_data in boms:
                for operation in bom.routing_id.operation_ids:
                    operation_bom_qty[operation.id] = bom_data['qty']
            self._update_product_to_produce(
                production, production.product_qty - qty_produced)
            moves = production.move_raw_ids.filtered(lambda x: x.state not in
                                                     ('done', 'cancel'))
            moves._action_assign()
            for wo in production.workorder_ids:
                operation = wo.operation_id
                if operation_bom_qty.get(operation.id):
                    cycle_number = math.ceil(operation_bom_qty[operation.id] /
                                             operation.workcenter_id.capacity
                                             )  # TODO: float_round UP
                    wo.duration_expected = (
                        operation.workcenter_id.time_start +
                        operation.workcenter_id.time_stop +
                        cycle_number * operation.time_cycle * 100.0 /
                        operation.workcenter_id.time_efficiency)
                quantity = wo.qty_production - wo.qty_produced
                if production.product_id.tracking == 'serial':
                    quantity = 1.0 if not float_is_zero(
                        quantity, precision_digits=precision) else 0.0
                else:
                    quantity = quantity if (quantity > 0) else 0
                if float_is_zero(quantity, precision_digits=precision):
                    wo.final_lot_id = False
                    wo.active_move_line_ids.unlink()
                wo.qty_producing = quantity
                if wo.qty_produced < wo.qty_production and wo.state == 'done':
                    wo.state = 'progress'
                # assign moves; last operation receive all unassigned moves
                # TODO: following could be put in a function as it is similar as code in _workorders_create
                # TODO: only needed when creating new moves
                moves_raw = production.move_raw_ids.filtered(
                    lambda move: move.operation_id == operation and move.state
                    not in ('done', 'cancel'))
                if wo == production.workorder_ids[-1]:
                    moves_raw |= production.move_raw_ids.filtered(
                        lambda move: not move.operation_id)
                moves_finished = production.move_finished_ids.filtered(
                    lambda move: move.operation_id == operation
                )  #TODO: code does nothing, unless maybe by_products?
                moves_raw.mapped('move_line_ids').write(
                    {'workorder_id': wo.id})
                (moves_finished + moves_raw).write({'workorder_id': wo.id})
                if quantity > 0 and wo.move_raw_ids.filtered(
                        lambda x: x.product_id.tracking != 'none'
                ) and not wo.active_move_line_ids:
                    wo._generate_lot_ids()
        return {}
Пример #30
0
class HrExpenseSheet(models.Model):

    _name = "hr.expense.sheet"
    _inherit = ['mail.thread']
    _description = "Expense Report"
    _order = "accounting_date desc, id desc"

    name = fields.Char(string='Expense Report Summary', required=True)
    expense_line_ids = fields.One2many('hr.expense',
                                       'sheet_id',
                                       string='Expense Lines',
                                       states={
                                           'approve': [('readonly', True)],
                                           'done': [('readonly', True)],
                                           'post': [('readonly', True)]
                                       },
                                       copy=False)
    state = fields.Selection([('submit', 'Submitted'), ('approve', 'Approved'),
                              ('post', 'Posted'), ('done', 'Paid'),
                              ('cancel', 'Refused')],
                             string='Status',
                             index=True,
                             readonly=True,
                             track_visibility='onchange',
                             copy=False,
                             default='submit',
                             required=True,
                             help='Expense Report State')
    employee_id = fields.Many2one(
        'hr.employee',
        string="Employee",
        required=True,
        readonly=True,
        states={'submit': [('readonly', False)]},
        default=lambda self: self.env['hr.employee'].search(
            [('user_id', '=', self.env.uid)], limit=1))
    address_id = fields.Many2one('res.partner', string="Employee Home Address")
    payment_mode = fields.Selection(
        [("own_account", "Employee (to reimburse)"),
         ("company_account", "Company")],
        related='expense_line_ids.payment_mode',
        default='own_account',
        readonly=True,
        string="Payment By")
    responsible_id = fields.Many2one('res.users',
                                     'Validation By',
                                     readonly=True,
                                     copy=False,
                                     states={
                                         'submit': [('readonly', False)],
                                         'submit': [('readonly', False)]
                                     })
    total_amount = fields.Float(string='Total Amount',
                                store=True,
                                compute='_compute_amount',
                                digits=dp.get_precision('Account'))
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 states={'submit': [('readonly', False)]},
                                 default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        readonly=True,
        states={'submit': [('readonly', False)]},
        default=lambda self: self.env.user.company_id.currency_id)
    attachment_number = fields.Integer(compute='_compute_attachment_number',
                                       string='Number of Attachments')
    journal_id = fields.Many2one(
        'account.journal',
        string='Expense Journal',
        states={
            'done': [('readonly', True)],
            'post': [('readonly', True)]
        },
        default=lambda self: self.env['ir.model.data'].
        xmlid_to_object('hr_expense.hr_expense_account_journal') or self.env[
            'account.journal'].search([('type', '=', 'purchase')], limit=1),
        help="The journal used when the expense is done.")
    bank_journal_id = fields.Many2one(
        'account.journal',
        string='Bank Journal',
        states={
            'done': [('readonly', True)],
            'post': [('readonly', True)]
        },
        default=lambda self: self.env['account.journal'].search(
            [('type', 'in', ['cash', 'bank'])], limit=1),
        help="The payment method used when the expense is paid by the company."
    )
    accounting_date = fields.Date(string="Date")
    account_move_id = fields.Many2one('account.move',
                                      string='Journal Entry',
                                      ondelete='restrict',
                                      copy=False)
    department_id = fields.Many2one('hr.department',
                                    string='Department',
                                    states={
                                        'post': [('readonly', True)],
                                        'done': [('readonly', True)]
                                    })

    @api.multi
    def check_consistency(self):
        for rec in self:
            expense_lines = rec.expense_line_ids
            if not expense_lines:
                continue
            if any(expense.employee_id != rec.employee_id
                   for expense in expense_lines):
                raise UserError(
                    _("Expenses must belong to the same Employee."))
            if any(expense.payment_mode != expense_lines[0].payment_mode
                   for expense in expense_lines):
                raise UserError(
                    _("Expenses must have been paid by the same entity (Company or employee)"
                      ))

    @api.model
    def create(self, vals):
        self._create_set_followers(vals)
        sheet = super(HrExpenseSheet, self).create(vals)
        sheet.check_consistency()
        return sheet

    @api.multi
    def write(self, vals):
        res = super(HrExpenseSheet, self).write(vals)
        self.check_consistency()
        if vals.get('employee_id'):
            self._add_followers()
        return res

    @api.multi
    def unlink(self):
        for expense in self:
            if expense.state in ['post', 'done']:
                raise UserError(
                    _('You cannot delete a posted or paid expense.'))
        super(HrExpenseSheet, self).unlink()

    @api.multi
    def set_to_paid(self):
        self.write({'state': 'done'})

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'approve':
            return 'hr_expense.mt_expense_approved'
        elif 'state' in init_values and self.state == 'submit':
            return 'hr_expense.mt_expense_confirmed'
        elif 'state' in init_values and self.state == 'cancel':
            return 'hr_expense.mt_expense_refused'
        elif 'state' in init_values and self.state == 'done':
            return 'hr_expense.mt_expense_paid'
        return super(HrExpenseSheet, self)._track_subtype(init_values)

    def _get_users_to_subscribe(self, employee=False):
        users = self.env['res.users']
        employee = employee or self.employee_id
        if employee.user_id:
            users |= employee.user_id
        if employee.parent_id:
            users |= employee.parent_id.user_id
        if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id:
            users |= employee.department_id.manager_id.user_id
        return users

    def _add_followers(self):
        users = self._get_users_to_subscribe()
        self.message_subscribe_users(user_ids=users.ids)

    @api.model
    def _create_set_followers(self, values):
        # Add the followers at creation, so they can be notified
        employee_id = values.get('employee_id')
        if not employee_id:
            return

        employee = self.env['hr.employee'].browse(employee_id)
        users = self._get_users_to_subscribe(employee=employee) - self.env.user
        values['message_follower_ids'] = []
        MailFollowers = self.env['mail.followers']
        for partner in users.mapped('partner_id'):
            values[
                'message_follower_ids'] += MailFollowers._add_follower_command(
                    self._name, [], {partner.id: None}, {})[0]

    @api.onchange('employee_id')
    def _onchange_employee_id(self):
        self.address_id = self.employee_id.sudo().address_home_id
        self.department_id = self.employee_id.department_id

    @api.one
    @api.depends('expense_line_ids', 'expense_line_ids.total_amount',
                 'expense_line_ids.currency_id')
    def _compute_amount(self):
        total_amount = 0.0
        for expense in self.expense_line_ids:
            total_amount += expense.currency_id.with_context(
                date=expense.date, company_id=expense.company_id.id).compute(
                    expense.total_amount, self.currency_id)
        self.total_amount = total_amount

    @api.one
    def _compute_attachment_number(self):
        self.attachment_number = sum(
            self.expense_line_ids.mapped('attachment_number'))

    @api.multi
    def refuse_sheet(self, reason):
        if not self.user_has_groups('hr_expense.group_hr_expense_user'):
            raise UserError(_("Only HR Officers can refuse expenses"))
        self.write({'state': 'cancel'})
        for sheet in self:
            sheet.message_post_with_view(
                'hr_expense.hr_expense_template_refuse_reason',
                values={
                    'reason': reason,
                    'is_sheet': True,
                    'name': self.name
                })

    @api.multi
    def approve_expense_sheets(self):
        if not self.user_has_groups('hr_expense.group_hr_expense_user'):
            raise UserError(_("Only HR Officers can approve expenses"))
        self.write({'state': 'approve', 'responsible_id': self.env.user.id})

    @api.multi
    def paid_expense_sheets(self):
        self.write({'state': 'done'})

    @api.multi
    def reset_expense_sheets(self):
        self.mapped('expense_line_ids').write({'is_refused': False})
        return self.write({'state': 'submit'})

    @api.multi
    def action_sheet_move_create(self):
        if any(sheet.state != 'approve' for sheet in self):
            raise UserError(
                _("You can only generate accounting entry for approved expense(s)."
                  ))

        if any(not sheet.journal_id for sheet in self):
            raise UserError(
                _("Expenses must have an expense journal specified to generate accounting entries."
                  ))

        expense_line_ids = self.mapped('expense_line_ids')\
            .filtered(lambda r: not float_is_zero(r.total_amount, precision_rounding=(r.currency_id or self.env.user.company_id.currency_id).rounding))
        res = expense_line_ids.action_move_create()

        if not self.accounting_date:
            self.accounting_date = self.account_move_id.date

        if self.payment_mode == 'own_account' and expense_line_ids:
            self.write({'state': 'post'})
        else:
            self.write({'state': 'done'})
        return res

    @api.multi
    def action_get_attachment_view(self):
        res = self.env['ir.actions.act_window'].for_xml_id(
            'base', 'action_attachment')
        res['domain'] = [('res_model', '=', 'hr.expense'),
                         ('res_id', 'in', self.expense_line_ids.ids)]
        res['context'] = {
            'default_res_model': 'hr.expense.sheet',
            'default_res_id': self.id,
            'create': False,
            'edit': False,
        }
        return res

    @api.one
    @api.constrains('expense_line_ids', 'employee_id')
    def _check_employee(self):
        employee_ids = self.expense_line_ids.mapped('employee_id')
        if len(employee_ids) > 1 or (len(employee_ids) == 1
                                     and employee_ids != self.employee_id):
            raise ValidationError(
                _('You cannot add expense lines of another employee.'))

    @api.one
    @api.constrains('expense_line_ids')
    def _check_payment_mode(self):
        payment_mode = set(self.expense_line_ids.mapped('payment_mode'))
        if len(payment_mode) > 1:
            raise ValidationError(
                _('You cannot report expenses with different payment modes.'))