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'))
class SaleQuoteLine(models.Model): _name = "sale.quote.line" _description = "Quotation Template Lines" _order = 'sequence, id' sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of sale quote lines.", default=10) quote_id = fields.Many2one('sale.quote.template', 'Quotation Template Reference', required=True, ondelete='cascade', index=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('Line Description', related='product_id.product_tmpl_id.quote_description', 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'), default=0.0) product_uom_qty = fields.Float('Quantity', required=True, digits=dp.get_precision('Product UoS'), default=1) product_uom_id = fields.Many2one('product.uom', 'Unit of Measure ', required=True) @api.onchange('product_id') def _onchange_product_id(self): self.ensure_one() if self.product_id: name = self.product_id.name_get()[0][1] if self.product_id.description_sale: name += '\n' + self.product_id.description_sale self.name = name self.price_unit = self.product_id.lst_price self.product_uom_id = self.product_id.uom_id.id self.website_description = self.product_id.quote_description or self.product_id.website_description or '' domain = {'product_uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]} return {'domain': domain} @api.onchange('product_uom_id') def _onchange_product_uom(self): if self.product_id and self.product_uom_id: self.price_unit = self.product_id.uom_id._compute_price(self.product_id.lst_price, self.product_uom_id) @api.model def create(self, values): values = self._inject_quote_description(values) return super(SaleQuoteLine, self).create(values) @api.multi def write(self, values): values = self._inject_quote_description(values) return super(SaleQuoteLine, self).write(values) def _inject_quote_description(self, values): values = dict(values or {}) if not values.get('website_description') and values.get('product_id'): product = self.env['product.product'].browse(values['product_id']) values['website_description'] = product.quote_description or product.website_description or '' return values
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')
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()
class WizIndentLine(models.TransientModel): _name = 'wiz.indent.line' _description = "Wizard Indent Line" @api.depends('purchase_indent_line_id', 'purchase_indent_line_id.product_qty', 'purchase_indent_line_id.requisition_qty') @api.multi def _compute_get_rem_qty(self): for line_id in self: remaining_qty = 0.0 if line_id.purchase_indent_line_id: remaining_qty = \ line_id.purchase_indent_line_id.product_qty - \ line_id.purchase_indent_line_id.requisition_qty line_id.remaining_qty = remaining_qty purchase_indent_ids = fields.Many2many('purchase.indent', string='Purchase Indent') name = fields.Text(string='Description', required=True) sequence = fields.Integer(string='Sequence', default=10) product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Discount')) expected_date = fields.Datetime(string='Expected Date', index=True) product_uom = fields.Many2one('product.uom', string='Product Unit of Measure') product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True) requisition_qty = fields.Float(string="Requisition Quantity", digits=dp.get_precision('Discount')) wizard_indent_id = fields.Many2one('wiz.requisition.request', 'Wiz Requisition Request') partner_id = fields.Many2one('res.partner', string='Partner') price_unit = fields.Float(string='Unit Price', digits=dp.get_precision('Product Price')) taxes_id = fields.Many2many( 'account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)]) purchase_indent_line_id = fields.Many2one('purchase.indent.line', string="Indent Line Ref") remaining_qty = fields.Float(compute='_compute_get_rem_qty', string='Remaining Quantity', store=True) order_type = fields.Selection(related='wizard_indent_id.order_type', string='Order Type')
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)
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
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 ''
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
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])
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}
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)
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)
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'))
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)
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'}
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")
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)
class SaleOrder(models.Model): _inherit = "sale.order" margin = fields.Monetary( compute='_product_margin', help= "It gives profitability by calculating the difference between the Unit Price and the cost.", currency_field='currency_id', digits=dp.get_precision('Product Price'), store=True) @api.depends('order_line.margin') def _product_margin(self): for order in self: order.margin = sum( order.order_line.filtered( lambda r: r.state != 'cancel').mapped('margin'))
class SupplierInfo(models.Model): _name = "product.supplierinfo" _description = "Information about a product vendor" _order = 'sequence, min_qty desc, price' name = fields.Many2one( 'res.partner', 'Vendor', domain=[('supplier', '=', True)], ondelete='cascade', required=True, help="Vendor of this product") product_name = fields.Char( 'Vendor Product Name', help="This vendor's product name will be used when printing a request for quotation. Keep empty to use the internal one.") product_code = fields.Char( 'Vendor Product Code', help="This vendor's product code will be used when printing a request for quotation. Keep empty to use the internal one.") sequence = fields.Integer( 'Sequence', default=1, help="Assigns the priority to the list of product vendor.") product_uom = fields.Many2one( 'product.uom', 'Vendor Unit of Measure', readonly="1", related='product_tmpl_id.uom_po_id', help="This comes from the product form.") min_qty = fields.Float( 'Minimal Quantity', default=0.0, required=True, help="The minimal quantity to purchase from this vendor, expressed in the vendor Product Unit of Measure if not any, in the default unit of measure of the product otherwise.") price = fields.Float( 'Price', default=0.0, digits=dp.get_precision('Product Price'), required=True, help="The price to purchase a product") company_id = fields.Many2one( 'res.company', 'Company', default=lambda self: self.env.user.company_id.id, index=1) currency_id = fields.Many2one( 'res.currency', 'Currency', default=lambda self: self.env.user.company_id.currency_id.id, required=True) date_start = fields.Date('Start Date', help="Start date for this vendor price") date_end = fields.Date('End Date', help="End date for this vendor price") product_id = fields.Many2one( 'product.product', 'Product Variant', help="If not set, the vendor price will apply to all variants of this products.") product_tmpl_id = fields.Many2one( 'product.template', 'Product Template', index=True, ondelete='cascade', oldname='product_id') product_variant_count = fields.Integer('Variant Count', related='product_tmpl_id.product_variant_count') delay = fields.Integer( 'Delivery Lead Time', default=1, required=True, help="Lead time in days between the confirmation of the purchase order and the receipt of the products in your warehouse. Used by the scheduler for automatic computation of the purchase order planning.")
class MembershipInvoice(models.TransientModel): _name = "membership.invoice" _description = "Membership Invoice" product_id = fields.Many2one('product.product', string='Membership', required=True) member_price = fields.Float(string='Member Price', digits=dp.get_precision('Product Price'), required=True) @api.onchange('product_id') def onchange_product(self): """This function returns value of product's member price based on product id. """ price_dict = self.product_id.price_compute('list_price') self.member_price = price_dict.get(self.product_id.id) or False @api.multi def membership_invoice(self): if self: datas = { 'membership_product_id': self.product_id.id, 'amount': self.member_price } invoice_list = self.env['res.partner'].browse( self._context.get('active_ids')).create_membership_invoice( datas=datas) search_view_ref = self.env.ref('account.view_account_invoice_filter', False) form_view_ref = self.env.ref('account.invoice_form', False) tree_view_ref = self.env.ref('account.invoice_tree', False) return { 'domain': [('id', 'in', invoice_list)], 'name': 'Membership Invoices', 'res_model': 'account.invoice', 'type': 'ir.actions.act_window', 'views': [(tree_view_ref.id, 'tree'), (form_view_ref.id, 'form')], 'search_view_id': search_view_ref and search_view_ref.id, }
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
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 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'] 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): workorders += self._workorders_create(bom, bom_data) 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
class DummyWizIndentLine(models.TransientModel): _name = 'dummy.wiz.indent.line' _description = "Dummy Wizard Indent Line" @api.depends('purchase_indent_line_id', 'purchase_indent_line_id.product_qty', 'purchase_indent_line_id.requisition_qty') @api.multi def _compute_get_rem_qty(self): for line_id in self: remaining_qty = 0.0 if line_id.purchase_indent_line_id: remaining_qty = \ line_id.purchase_indent_line_id.product_qty - \ line_id.purchase_indent_line_id.requisition_qty line_id.remaining_qty = remaining_qty purchase_indent_id = fields.Many2one( 'purchase.indent', 'Purchase Indent', domain="[('id', '=', purchase_indent_id)]") name = fields.Text(string='Description') sequence = fields.Integer(string='Sequence', default=10) product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Discount')) dummy_product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Discount')) expected_date = fields.Datetime(string='Expected Date', index=True) product_uom = fields.Many2one('product.uom', string='Product Unit of Measure') product_id = fields.Many2one( 'product.product', string='Product', domain="[('purchase_ok', '=', True), ('id', '=', product_id)]", change_default=True, required=True) company_id = fields.Many2one('res.company', related='purchase_indent_id.company_id', string='Company', store=True, readonly=True) requisition_qty = fields.Float( string="Requisition Quantity", digits=dp.get_precision('Product Unit of Measure')) wizard_indent_id = fields.Many2one('wiz.requisition.request', 'Wiz Requisition Request') partner_id = fields.Many2one('res.partner', related='purchase_indent_id.partner_id', string='Partner', readonly=True, store=True) indent_requ_date = fields.Date(related='purchase_indent_id.request_date', string='Request Date', readonly=True, store=True) purchase_indent_line_id = fields.Many2one('purchase.indent.line', string="Indent Line Ref") remaining_qty = fields.Float(compute='_compute_get_rem_qty', string='Remaining Quantity', store=True) @api.onchange('requisition_qty') def onchange_requisition_qty(self): warning = {} if self.requisition_qty < 0: warning.update({ 'title': _("Warning"), 'message': _("Requisition Quantity (%s) can not be \ Negative!") % (formatLang(self.env, self.requisition_qty, digits=2)) }) self.requisition_qty = False return {'warning': warning} @api.model def create(self, values): if values.get('dummy_product_qty', False): values.update({'product_qty': values.get('dummy_product_qty')}) res = super(DummyWizIndentLine, self).create(values) return res @api.model def write(self, values): if values.get('dummy_product_qty', False): values.update({'product_qty': values.get('dummy_product_qty')}) res = super(DummyWizIndentLine, self).write(values) return res
class SaleOrderLine(models.Model): _inherit = "sale.order.line" coupon_code_id = fields.Many2one('coupon.code', 'Coupon Ref') check_coupon = fields.Boolean('Apply Coupon') dummy_discount = fields.Float( string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) @api.multi def get_line_percentage(self): self._onchange_discount() discount = self.discount if discount > 100: self.dummy_discount = discount discount = 100 self.discount = discount return discount @api.multi def set_line_amount(self): discount, product_price = self.get_rule_discount() if product_price: discount = product_price * (discount) / 100 self.price_unit = product_price - discount @api.multi def get_total_coupon_code(self): return self.env['coupon.code'].get_coupon_discount(self, False) def _get_real_price_currency_advance(self, product, uom, pricelist_id, price_unit): currency_id = pricelist_id.currency_id product_currency = \ (product.company_id and product.company_id.currency_id ) or self.env.user.company_id.currency_id if currency_id.id == product_currency.id: cur_factor = 1.0 else: cur_factor = currency_id._get_conversion_rate( product_currency, currency_id) product_uom = self.env.context.get('uom') or product.uom_id.id if uom and uom.id != product_uom: uom_factor = uom._compute_price(1.0, product.uom_id) else: uom_factor = 1.0 return price_unit * uom_factor * cur_factor, currency_id.id @api.multi def get_rule_discount(self): date = fields.Date.context_today(self) rules = self.env['price.rule'].get_rules( self.order_id.pricelist_id, date) max_dis_price = [] min_dis_price = [] discount_per = 0.0 apply_method = self.order_id.pricelist_id.apply_method discount = 0.0 context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order) pricelist_context = dict(context_partner, uom=self.product_uom.id, order_id=self.order_id, price_unit=self.price_unit) product_price = 0.0 for rule in rules: adv_price, adv_rule_id = \ self.order_id.pricelist_id.with_context( pricelist_context).get_product_price_rule_advance( self.product_id, self.product_uom_qty, self.order_id.partner_id) rule_line_id = self.env['rule.line'].browse(adv_rule_id) adv_new_price = 0.0 currency_id = False if not rule_line_id: adv_new_price, currency_id = self.with_context( context_partner)._get_real_price_currency( self.product_id, False, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) else: if rule_line_id.rule_type == 'percent': adv_new_price, currency_id = self.with_context( context_partner)._get_real_price_currency( self.product_id, False, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) elif rule_line_id.rule_type == 'fixed_amount': adv_new_price, currency_id = self.with_context( context_partner)._get_real_price_currency_advance( self.product_id, self.product_uom, self.order_id.pricelist_id, self.price_unit) if adv_new_price != 0: if self.order_id.pricelist_id.currency_id.id != currency_id: adv_new_price = self.env['res.currency'].browse( currency_id).with_context(context_partner).compute( adv_new_price, rule.pricelist_id.currency_id) if not product_price: product_price = adv_new_price discount_per =\ (adv_new_price - adv_price) / adv_new_price * 100 if apply_method == 'first_matched_rule': discount += discount_per break elif apply_method == 'all_matched_rules': discount += discount_per elif apply_method == 'smallest_discount' and adv_rule_id: min_dis_price.append(discount_per) else: max_dis_price.append(discount_per) if min_dis_price: discount += min(min_dis_price) if max_dis_price: discount += max(max_dis_price) return discount, product_price # Overrides Function @api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id') def _onchange_discount(self): self.discount = 0.0 if not (self.product_id and self.product_uom and self.order_id.partner_id and self.order_id.pricelist_id and self.order_id.pricelist_id.discount_policy == 'without_discount' and self.env.user.has_group('sale.group_discount_per_so_line')): return discount = 0.0 context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order) pricelist_context = dict(context_partner, uom=self.product_uom.id) if self.order_id.pricelist_id.pricelist_type == 'basic': price, rule_id = self.order_id.pricelist_id.with_context( pricelist_context).get_product_price_rule( self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) new_list_price, currency_id = self.with_context( context_partner)._get_real_price_currency( self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) if new_list_price != 0: if self.order_id.pricelist_id.currency_id.id != currency_id: new_list_price = self.env['res.currency'].browse( currency_id).with_context(context_partner).compute( new_list_price, self.order_id.pricelist_id.currency_id) discount = (new_list_price - price) / new_list_price * 100 if discount > 0: self.discount = discount else: if self.coupon_code_id and (self._context.get( 'quantity', False) or self._context.get( 'price_unit', False) or self._context.get('tax', False)): raise Warning(_('You can not change order line. ' 'Please remove coupon code first!')) discount, product_price = self.get_rule_discount() if discount > 0: self.discount = discount if self.order_id.have_coupon_code and self.coupon_code_id: self.get_total_coupon_code()
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}
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
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') branch_id = fields.Many2one('res.branch', related='voucher_id.branch_id', string='Branch', readonly=True, store=True) @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': {}}
class PricelistItem(models.Model): _name = "product.pricelist.item" _description = "Pricelist item" _order = "applied_on, min_quantity desc, categ_id desc, id" product_tmpl_id = fields.Many2one( 'product.template', 'Product Template', ondelete='cascade', help= "Specify a template if this rule only applies to one product template. Keep empty otherwise." ) product_id = fields.Many2one( 'product.product', 'Product', ondelete='cascade', help= "Specify a product if this rule only applies to one product. Keep empty otherwise." ) categ_id = fields.Many2one( 'product.category', 'Product Category', ondelete='cascade', help= "Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise." ) min_quantity = fields.Integer( 'Min. Quantity', default=0, help="For the rule to apply, bought/sold quantity must be greater " "than or equal to the minimum quantity specified in this field.\n" "Expressed in the default unit of measure of the product.") applied_on = fields.Selection( [('3_global', 'Global'), ('2_product_category', ' Product Category'), ('1_product', 'Product'), ('0_product_variant', 'Product Variant')], "Apply On", default='3_global', required=True, help='Pricelist Item applicable on selected option') base = fields.Selection( [('list_price', 'Public Price'), ('standard_price', 'Cost'), ('pricelist', 'Other Pricelist')], "Based on", default='list_price', required=True, help='Base price for computation.\n' 'Public Price: The base price will be the Sale/public Price.\n' 'Cost Price : The base price will be the cost price.\n' 'Other Pricelist : Computation of the base price based on another Pricelist.' ) base_pricelist_id = fields.Many2one('product.pricelist', 'Other Pricelist') pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', index=True, ondelete='cascade') price_surcharge = fields.Float( 'Price Surcharge', digits=dp.get_precision('Product Price'), help= 'Specify the fixed amount to add or substract(if negative) to the amount calculated with the discount.' ) price_discount = fields.Float('Price Discount', default=0, digits=(16, 2)) price_round = fields.Float( 'Price Rounding', digits=dp.get_precision('Product Price'), help="Sets the price so that it is a multiple of this value.\n" "Rounding is applied after the discount and before the surcharge.\n" "To have prices that end in 9.99, set rounding 10, surcharge -0.01") price_min_margin = fields.Float( 'Min. Price Margin', digits=dp.get_precision('Product Price'), help='Specify the minimum amount of margin over the base price.') price_max_margin = fields.Float( 'Max. Price Margin', digits=dp.get_precision('Product Price'), help='Specify the maximum amount of margin over the base price.') company_id = fields.Many2one('res.company', 'Company', readonly=True, related='pricelist_id.company_id', store=True) currency_id = fields.Many2one('res.currency', 'Currency', readonly=True, related='pricelist_id.currency_id', store=True) date_start = fields.Date( 'Start Date', help="Starting date for the pricelist item validation") date_end = fields.Date( 'End Date', help="Ending valid for the pricelist item validation") compute_price = fields.Selection([('fixed', 'Fix Price'), ('percentage', 'Percentage (discount)'), ('formula', 'Formula')], index=True, default='fixed') fixed_price = fields.Float('Fixed Price', digits=dp.get_precision('Product Price')) percent_price = fields.Float('Percentage Price') # functional fields used for usability purposes name = fields.Char('Name', compute='_get_pricelist_item_name_price', help="Explicit rule name for this pricelist line.") price = fields.Char('Price', compute='_get_pricelist_item_name_price', help="Explicit rule name for this pricelist line.") @api.constrains('base_pricelist_id', 'pricelist_id', 'base') def _check_recursion(self): if any(item.base == 'pricelist' and item.pricelist_id and item.pricelist_id == item.base_pricelist_id for item in self): raise ValidationError( _('Error! You cannot assign the Main Pricelist as Other Pricelist in PriceList Item!' )) return True @api.constrains('price_min_margin', 'price_max_margin') def _check_margin(self): if any(item.price_min_margin > item.price_max_margin for item in self): raise ValidationError( _('Error! The minimum margin should be lower than the maximum margin.' )) return True @api.one @api.depends('categ_id', 'product_tmpl_id', 'product_id', 'compute_price', 'fixed_price', \ 'pricelist_id', 'percent_price', 'price_discount', 'price_surcharge') def _get_pricelist_item_name_price(self): if self.categ_id: self.name = _("Category: %s") % (self.categ_id.name) elif self.product_tmpl_id: self.name = self.product_tmpl_id.name elif self.product_id: self.name = self.product_id.display_name.replace( '[%s]' % self.product_id.code, '') else: self.name = _("All Products") if self.compute_price == 'fixed': self.price = ("%s %s") % (self.fixed_price, self.pricelist_id.currency_id.name) elif self.compute_price == 'percentage': self.price = _("%s %% discount") % (self.percent_price) else: self.price = _("%s %% discount and %s surcharge") % ( self.price_discount, self.price_surcharge) @api.onchange('applied_on') def _onchange_applied_on(self): if self.applied_on != '0_product_variant': self.product_id = False if self.applied_on != '1_product': self.product_tmpl_id = False if self.applied_on != '2_product_category': self.categ_id = False @api.onchange('compute_price') def _onchange_compute_price(self): if self.compute_price != 'fixed': self.fixed_price = 0.0 if self.compute_price != 'percentage': self.percent_price = 0.0 if self.compute_price != 'formula': self.update({ 'price_discount': 0.0, 'price_surcharge': 0.0, 'price_round': 0.0, 'price_min_margin': 0.0, 'price_max_margin': 0.0, })
class StockPicking(models.Model): _inherit = 'stock.picking' def _default_uom(self): weight_uom_id = self.env.ref('product.product_uom_kgm', raise_if_not_found=False) if not weight_uom_id: uom_categ_id = self.env.ref('product.product_uom_categ_kgm').id weight_uom_id = self.env['product.uom'].search( [('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1) return weight_uom_id @api.one @api.depends('move_line_ids') def _compute_packages(self): self.ensure_one() packs = set() for move_line in self.move_line_ids: if move_line.result_package_id: packs.add(move_line.result_package_id.id) self.package_ids = list(packs) @api.one @api.depends('move_line_ids') def _compute_bulk_weight(self): weight = 0.0 for move_line in self.move_line_ids: if move_line.product_id and not move_line.result_package_id: weight += move_line.product_uom_id._compute_quantity( move_line.qty_done, move_line.product_id.uom_id) * move_line.product_id.weight self.weight_bulk = weight @api.one @api.depends('package_ids', 'weight_bulk') def _compute_shipping_weight(self): self.shipping_weight = self.weight_bulk + sum( [pack.shipping_weight for pack in self.package_ids]) carrier_price = fields.Float(string="Shipping Cost") delivery_type = fields.Selection(related='carrier_id.delivery_type', readonly=True) carrier_id = fields.Many2one("delivery.carrier", string="Carrier") volume = fields.Float(copy=False) weight = fields.Float(compute='_cal_weight', digits=dp.get_precision('Stock Weight'), store=True) carrier_tracking_ref = fields.Char(string='Tracking Reference', copy=False) carrier_tracking_url = fields.Char(string='Tracking URL', compute='_compute_carrier_tracking_url') number_of_packages = fields.Integer(string='Number of Packages', copy=False) weight_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly="1", help="Unit of measurement for Weight", default=_default_uom) package_ids = fields.Many2many('stock.quant.package', compute='_compute_packages', string='Packages') weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_weight') shipping_weight = fields.Float("Weight for Shipping", compute='_compute_shipping_weight') @api.depends('carrier_id', 'carrier_tracking_ref') def _compute_carrier_tracking_url(self): for picking in self: picking.carrier_tracking_url = picking.carrier_id.get_tracking_link( picking ) if picking.carrier_id and picking.carrier_tracking_ref else False @api.depends('product_id', 'move_lines') def _cal_weight(self): for picking in self: picking.weight = sum(move.weight for move in picking.move_lines if move.state != 'cancel') @api.multi def action_done(self): res = super(StockPicking, self).action_done() for pick in self: if pick.carrier_id: if pick.carrier_id.integration_level == 'rate_and_ship': pick.send_to_shipper() pick._add_delivery_cost_to_so() return res @api.multi def put_in_pack(self): if self.carrier_id and self.carrier_id.delivery_type not in [ 'base_on_rule', 'fixed' ]: view_id = self.env.ref( 'delivery.choose_delivery_package_view_form').id return { 'name': _('Package Details'), 'type': 'ir.actions.act_window', 'view_mode': 'form', 'res_model': 'choose.delivery.package', 'view_id': view_id, 'views': [(view_id, 'form')], 'target': 'new', 'context': { 'current_package_carrier_type': self.carrier_id.delivery_type, } } else: return self._put_in_pack() @api.multi def action_send_confirmation_email(self): self.ensure_one() delivery_template_id = self.env.ref( 'delivery.mail_template_data_delivery_confirmation').id compose_form_id = self.env.ref( 'mail.email_compose_message_wizard_form').id ctx = dict( default_composition_mode='comment', default_res_id=self.id, default_model='stock.picking', default_use_template=bool(delivery_template_id), default_template_id=delivery_template_id, custom_layout='delivery.mail_template_data_delivery_notification') return { 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'mail.compose.message', 'view_id': compose_form_id, 'target': 'new', 'context': ctx, } @api.multi def send_to_shipper(self): self.ensure_one() res = self.carrier_id.send_shipping(self)[0] self.carrier_price = res['exact_price'] self.carrier_tracking_ref = res['tracking_number'] order_currency = self.sale_id.currency_id or self.company_id.currency_id msg = _( "Shipment sent to carrier %s for shipping with tracking number %s<br/>Cost: %.2f %s" ) % (self.carrier_id.name, self.carrier_tracking_ref, self.carrier_price, order_currency.name) self.message_post(body=msg) @api.multi def _add_delivery_cost_to_so(self): self.ensure_one() sale_order = self.sale_id if sale_order.invoice_shipping_on_delivery: sale_order._create_delivery_line(self.carrier_id, self.carrier_price) @api.multi def open_website_url(self): self.ensure_one() if not self.carrier_tracking_url: raise UserError( _("Your delivery method has no redirect on courier provider's website to track this order." )) client_action = { 'type': 'ir.actions.act_url', 'name': "Shipment Tracking Page", 'target': 'new', 'url': self.carrier_tracking_url, } return client_action @api.one def cancel_shipment(self): self.carrier_id.cancel_shipment(self) msg = "Shipment %s cancelled" % self.carrier_tracking_ref self.message_post(body=msg) self.carrier_tracking_ref = False @api.multi def check_packages_are_identical(self): '''Some shippers require identical packages in the same shipment. This utility checks it.''' self.ensure_one() if self.package_ids: packages = [p.packaging_id for p in self.package_ids] if len(set(packages)) != 1: package_names = ', '.join([str(p.name) for p in packages]) raise UserError( _('You are shipping different packaging types in the same shipment.\nPackaging Types: %s' % package_names)) return True