So I have a Sale model and SaleLine model. Sale model have a field sale_line_ids as One2many from SaleLine model.
Sale
class Sale(models.Model):
_name = 'store.sale'
_description = 'Store Sale'
...
sale_line_ids = fields.One2many('store.sale_line', 'sale_id', string='Sale Lines')
...
Sale Line
class SaleLine(models.Model):
_name = 'store.sale_line'
_description = 'Store Sale Line'
sale_id = fields.Many2one('store.sale', string='Sale Reference', ondelete='cascade', index=True)
product_id = fields.Many2one('store.product', string='Product', required=True)
quantity = fields.Integer(string='Quantity', required=True)
I want to create SaleLine model programmatically and add that model to sale_line_ids field. How can I do that?
This answer is what I actually want to implement. However, the model is immediately saved to a database. I need to create a SaleLine model using env[].create({}) method.
self.env['store.sale_line'].create({
'sale_id': rec.id,
'product_id': id_from_another_model,
'quantity': quantity_from_another_model,
})
After that, I need to commit in order to save the data in a database.
self.env.cr.commit()
UPDATE
The previous answer required record to store directly. The best answer to solve the problem is to create temporary record that only saved when user click the save button.
Syntax
(0, 0, { values })
First create sale_line list
sale_line = []
for data in list_of_data:
sale_line.append([0, 0, {
'sale_id': data['sale_id'],
'product_id': data['product_id'],
'quantity': data['quantity'],
}])
Assign sale_line list to sale_line_ids field
self.write({'sale_line_ids': sale_line})
And override the create method to commit the change
#api.model
def create(self, values):
self.env.cr.commit() # This is the important part
return super(Sale, self).create(values)
Related
I have inherited account.move model and added job_card_id field(many2one) in it, as shown as below :
Below given is Image of Selected Job Card :
Below given is code of inherited model :
class CustomInvoice(models.Model):
_inherit = "account.move"
job_card_id = fields.Many2one('job.card', string="Job Card", domain="[('customer_id','=',partner_id)]", tracking=True)
Below given is code of Job Card Line :
class JobCardLine(models.Model):
_name = "job.card.line"
job_card_id = fields.Many2one('job.card', string="Job Card Id", tracking=True)
product_id = fields.Many2one('product.template', string="Product", tracking=True)
quantity = fields.Integer(string="Quantity", tracking=True)
price = fields.Float(
'Sales Price',
digits='Product Price')
total = fields.Integer(string='Total', compute='_compute_total', tracking=True,
help="This field will be calculated from dob !")
employee_id = fields.Many2one('hr.employee', string="Employee", tracking=True)
Now I wanted to add product line of selected job card into Invoice product line automatically when I select the job card.
You need to create an onchange method depending on the job_card_id.
In this onchange, you will add a line to invoice_line_ids.
This will triggers all the other onchange and compute methods and set ups everything needed.
Here is a small example:
class CustomInvoice(models.Model):
_inherit = "account.move"
job_card_id = fields.Many2one('job.card', string="Job Card", domain="[('customer_id','=',partner_id)]", tracking=True)
#api.onchange('job_card_id')
def _onchange_job_card_id(self):
# creates your invoice lines vals according to your values
invoice_lines_vals = self.job_card_id.get_invoice_line_vals()
self.write({'invoice_line_ids': [(6, 0, 0)] + [(0, 0, vals) for vals in invoice_lines_vals]})
The 6 is a command that will deletes all previous invoice_line and the 0 is a creation command.
more info on ORM command
Now, you just need to create the get_invoice_line_vals method in your JobCard class.
It should return a list of vals.
Each vals should be a dict containing some information such as the quantity and the price_unit.
It depends mostly on how you want your data to be calculate.
You just need to return a list like this:
def get_invoice_line_vals(self):
vals_list = []
for job_card_line in self.line_ids:
vals_list.append({
'price_unit': job_card_line.price_unit,
'quantity': job_card_line.quantity
})
return vals_list
I want to prevent the selected record to show again in the combo box.
As you can see, the 710 - Maleo show again after I selected that record before.
Field declaration for One2many field
class RMReservationOrderLine(models.Model):
_name = "rm.reservation.order.line"
_description = "Reservation Order Line"
room_line_ids = fields.One2many('rm.reservation.room.line', 'order_id', string='Rooms')
Model class for One2many field
class RMReservationRoomLine(models.Model):
_name = "rm.reservation.room.line"
_description = "Reservation Room Line"
order_id = fields.Many2one('rm.reservation.order.line', string='Order', required=True, ondelete='cascade')
room_id = fields.Many2one('rm.room', string='Room', required=True)
UPDATE
Since my model class for the One2many field just have a single field, room_id, I just change the One2many field to Many2many. Because by default Many2many field prevent duplicate record.
But I still want to know how to prevent duplicate records if I use the One2many field, In case I have more than 1 field in the model class for One2many.
I think this is the same case as you want.
I already modified the Sales Order, so when the product in the sales order line it's already selected then the product will not be shown again in the selected product.
I used odoo-14 and inherited the sales.order.line and modified the function product_id_change() to become:
#api.onchange('product_id')
def product_id_change(self):
values = super(SaleOrderLine, self).product_id_change()
filter_product_ids = [data.product_id.id for data in self.order_id.order_line]
if values is None:
values = {}
values['domain'] = {'product_id' : [('id', 'not in', filter_product_ids)]}
return values
I have created new one2many field like order lines in sale after other info tab. Also,I have created a new one2many field like invoice lines in invoice form after other info tab.
What i need to do is, I have to pass values from sale one2many field to invoice one2many field while clicking create invoice button.
I have tried inheriting _prepare_invoice and _prepare_invoice_line function in default. It does not work for other one2many field.
Could anyone please help me to do this!
If you are using Odoo14/Odoo13/Odoo12/Odoo11 then you need to override the sale.order method i.e _prepare_invoice.
Below is the example of Odoo14
#api.model
def _prepare_invoice(self):
"""
Prepare the dict of values to create the new invoice for a sales order. This method may be
overridden to implement custom invoice generation (making sure to call super() to establish
a clean extension chain).
"""
self.ensure_one()
journal = self.env['account.move'].with_context(default_move_type='out_invoice')._get_default_journal()
if not journal:
raise UserError(_('Please define an accounting sales journal for the company %s (%s).') % (self.company_id.name, self.company_id.id))
invoice_vals = {
'ref': self.client_order_ref or '',
'move_type': 'out_invoice',
'narration': self.note,
'currency_id': self.pricelist_id.currency_id.id,
'campaign_id': self.campaign_id.id,
'medium_id': self.medium_id.id,
'source_id': self.source_id.id,
'invoice_user_id': self.user_id and self.user_id.id,
'team_id': self.team_id.id,
'partner_id': self.partner_invoice_id.id,
'partner_shipping_id': self.partner_shipping_id.id,
'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(self.partner_invoice_id.id)).id,
'partner_bank_id': self.company_id.partner_id.bank_ids[:1].id,
'journal_id': journal.id, # company comes from the journal
'invoice_origin': self.name,
'invoice_payment_term_id': self.payment_term_id.id,
'payment_reference': self.reference,
'transaction_ids': [(6, 0, self.transaction_ids.ids)],
'invoice_line_ids': [],
'company_id': self.company_id.id,
'tax_line': [(6, 0, self.tax_line.ids)] //This is my customize field.
}
return invoice_vals
You can't directly pass the o2M which will be entirely different ids.
'account_tax_line_ids': [(6, 0, self.sale_tax_line_ids.ids)] is not possible.
If you need to edit account_tax_line_ids again in the invoice form and
sale_tax_line_ids value should not change, then try below.
#api.multi
def _prepare_invoice(self):
self.ensure_one()
invoice_vals = super(SaleOrder, self)._prepare_invoice()
invoice_vals['account_tax_line_ids'] = [(0, 0, {'FIELD1': line.FIELD1_VALUE, ... } for line in self.sale_tax_line_ids]
return invoice_vals
Specify the fields in side Dictionary.
Read your comments and find out why it's not shown or not getting errors.
In the sale order field:
sale_tax_line_ids = fields.One2many('**sale.order.tax**', 'sale_id', string='Tax Lines', readonly=True, states={'draft': [('readonly', False)]}, copy=True)
And, in the account, move:
account_tax_line_ids = fields.One2many('**account.tax.line**', 'account_line_id', string='Taxes') and you set value like 'account_tax_line_ids': [(6, 0,**self.sale_tax_line_ids.ids**)]
So in the account_tax_line_ids fields, you should set the account.tax.line model records, but you are trying to set sale.order.tax model records.
Also, you can see in the odoo default, like in the sale order line, there is product_id, so it is related to the product.product model, and same with account: move line there product_id, which relates to product.product model. So it sets sale order line product to account move line product.
So Im creating a hotel management module . I have the option to filter rooms based on bed_type and tag. Tag contains different facilities like AC, TV etc. So an user will come and select a bed_type and the facilities he wants and in the third field it should show the rooms that have the given configuration if not available an error messages should come. SO i created a onchange function to do this , but i dint know how to include the tags in it. Im doing it on odoo v14. m
This is the model for the room
from odoo import api, fields, models, _
class HotelRoom(models.Model):
_name = 'hotel.room'
_description = 'hotel room'
_rec_name = 'room_number'
room_number = fields.Char('Room Number', required=True)
room_rent = fields.Monetary('Room Rent')
tag = fields.Many2many('hotel.tags', string='Facilities')
dormitory_count = fields.Integer('Dormitory count')
bed_type = fields.Selection([
('single', 'Single'),
('double', 'Double'),
('dormitory', 'Dormitory')
], required=True, default='other')
This is the model for the reception
class HotelAccommodation(models.Model):
_name = 'accommodation.room'
_description = 'Reception'
bed_type = fields.Selection([
('single', 'Single'),
('double', 'Double'),
('dormitory', 'Dormitory')
], required=True, string= 'Bed type')
state = fields.Selection([
('draft','Draft'),
('check-in','Check-In'),
('check-out', "Check-Out"),
('cancel','Cancel'),
], required=True, default='draft', tracking=True)
tag = fields.Many2many('hotel.tags', string='Facilities')
room_id = fields.Many2one('hotel.room', string='Room')
Using this I can filter the rooms based on the bed_type but I need to have the tags as well.I tried giving it inside the domain but its not working .
#api.onchange('bed_type')
def filter_room(self):
for rec in self:
return {'domain': {'room_id': [('bed_type', '=', rec.bed_type)]}}
You need also to add the tag field to the domain and to the onchange decorator, so the method will be called when the tag field is modified.
The method is invoked on a pseudo-record that contains the values present in the form, you do not need to loop over self.
Try the following example:
#api.onchange('bed_type', 'tag')
def filter_room(self):
return {'domain': {'room_id': [('bed_type', '=', self.bed_type), ('tag', 'in', self.tag.ids)]}}
You can use _compute fields with the store option = True
take a look https://www.odoo.com/documentation/14.0/reference/orm.html#computed-fields
I am adding custom One2many field in sale.order form view just below the sale.order.line.
I am computing values on_change it is displaying values but when I am going to save the sales order it is generating error that
ValueError: Wrong value for tax.lines.order_id: sale.order(24,)
Python:
class SaleOrderInherit(models.Model):
_inherit = ['sale.order']
tax_line = fields.One2many('tax.lines', 'order_id', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True, auto_join=True)
#on.change('partner_id')
def calculating_tax(self):
//After some code
self.env['tax.lines'].create({
'tax_id': tax['tid'],
'name': tax['name'],
'amount': tax['tax'],
'order_id': self.id
})
class TaxLines(models.Model):
_name = 'tax.lines'
tax_id = fields.Char('Tax Id')
name = fields.Char('Tax Name')
amount = fields.Char('Tax Amount')
order_id = fields.Many2one('sale.order', string='Tax Report', ondelete='cascade', index=True, copy=False)
Because I am creating one2many field before creating the order.
But is there any way to get rid of this problem.
Edit:
Error after replacing my code with Charif DZ code:
Never create records in onchange events they are immidiatly saved in database what if the user decided to cancel the order, instead of create use new with create an object but doesn't save it in database.
def calculating_tax(self):
//After some code
# to add record to your o2m use `|` oprator
# if you want to clear record before start adding new records make sure to empty your field first by an empty record set like this
# self.tax_line = self.env['tax.lines'] do this before the for loop that is used to fill-up the field not put it inside or you will get only the last record
self.tax_line |= self.env['tax.lines'].new({
'tax_id': tax['tid'],
'name': tax['name'],
'amount': tax['tax'],
# 'order_id': self.id remove the many2one because it's handled automaticly by the one2many
})
I hope this help you good luck