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.
Related
I want to get a product's location and display it on a custom report table:
and on the "Warehouse" cell it should be all the product's location, so if that product has multiple it should be displayed there. Just for clarification this is the location I'm talking about:
In order to put that there I tried this code:
class StockInventoryValuationReport(models.TransientModel):
_name = 'report.stock.inventory.valuation.report'
_description = 'Stock Inventory Valuation Report'
location_id = fields.Many2one('stock.location')
# filter domain wizard
#api.multi
def _compute_results(self):
self.ensure_one()
stockquant_obj = self.env['stock.quant'].search([("location_id", "=", self.location_id.id)])
print(stockquant_obj.location_id)
line = {
'name': product.name,
'reference': product.default_code,
'barcode': product.barcode,
'qty_at_date': product.qty_at_date,
'uom_id': product.uom_id,
'currency_id': product.currency_id,
'cost_currency_id': product.cost_currency_id,
'standard_price': standard_price,
'stock_value': product.qty_at_date * standard_price,
'cost_method': product.cost_method,
'taxes_id': product.taxes_id,
'location_id': stockquant_obj.location_id,
}
if product.qty_at_date != 0:
self.results += ReportLine.new(line)
but when I'm printing stockquant_obj.location_id it is an empty recordset basically its not finding any locations. Can someone please hint me on anything?
I actually managed to get the product's locations using this code:
class StockInventoryValuationReport(models.TransientModel):
_name = 'report.stock.inventory.valuation.report'
_description = 'Stock Inventory Valuation Report'
location_id = fields.Many2one('stock.location')
# filter domain wizard
#api.multi
def _compute_results(self):
self.ensure_one()
stockquant_obj = self.env['stock.quant'].search([("location_id", "=", self.location_id.id)])
for xyz in stockquant_obj:
line = {
'name': product.name,
'reference': product.default_code,
'barcode': product.barcode,
'qty_at_date': product.qty_at_date,
'uom_id': product.uom_id,
'currency_id': product.currency_id,
'cost_currency_id': product.cost_currency_id,
'standard_price': standard_price,
'stock_value': product.qty_at_date * standard_price,
'cost_method': product.cost_method,
'taxes_id': product.taxes_id,
'location_id': xyz.location_id,
}
if product.qty_at_date != 0:
self.results += ReportLine.new(line)
I debugged further discovering that now stock.quant() could get some record-set but odoo was expecting a singleton when on my old code was stockquant_obj.location_id so since I have seen from other people that the solution to singleton is a for loop and for that reason I added it.
The problem with this is that now not only the warehouse would be added but the same product would repeat as times as long the recordset is. How can I dodge this? How to tell python that I only need to loop through stockquant_obj and xyz should be inside the line variable?
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)
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
My goal is to get all items from parent quotation, to the wizard window.
I don't know i do this right way or not, but for now i can get all the products from quotation, and don't understand how to fill them into my wizard items line.
Quotation example
Wizard on that quotation
I remove the pieces of code which not matter.
from odoo import fields, models, api
import logging
class back_to_back_order(models.Model):
_name = "back.to.back.order"
_description = "Back to Back Order"
line_ids = fields.One2many('back.to.back.order.line','back_order_id', 'Order Lines', required=True)
def get_items_from_quotation(self, context):
items = []
quotation_id = context['id']
current_quotation = self.env['sale.order'].search([('id','=',quotation_id)])
if quotation_id == current_quotation.id:
for line in current_quotation.order_line:
item = {
'product_id': line.product_id,
'qty': line.product_uom_qty,
'price': line.price_unit,
'subtotal': line.price_unit*line.product_uom_qty
}
items.append(item)
class back_to_back_order_line(models.Model):
_name = "back.to.back.order.line"
_description = "Back to Back Order"
product_id = fields.Many2one('product.product', 'Product')
back_order_id = fields.Many2one('back.to.back.order', 'Back Order')
qty = fields.Float('Quantity')
price = fields.Float('Unit Price')
subtotal = fields.Float('Subtotal')
Firstly, if you are creating a wizard, then it's very likely you should be using models.TransientModel and not model.Model for your classes.
class back_to_back_order(models.TransientModel):
...
Wizard records are not meant to be persistent; they are automatically deleted from the database after a certain time. This is why they are called transient.
You mentioned you are already able to get the Sales Order Lines within your wizard, but you aren't sure how to fill your Back to Back Order Lines with the data.
One2many and Many2many use a special "commands" format to manipulate the set of records stored in/associated with the field.
I originally found these commands on another answer but they are also covered in the Documentation.
As for your specific application, you should be able to simply create your back.to.back.order.line records and they will be linked as long as you provide the back_order_id.
#api.multi
def get_items_from_quotation(self, context):
self.ensure_one()
b2b_line_obj = self.env['back.to.back.order.line']
quotation = self.env['sale.order'].browse(context['id'])
if quotation:
back_order_id = self.id
for line in quotation.order_line:
b2b_line_obj.create({
'back_order_id': back_order_id,
'product_id': line.product_id.id,
'qty': line.product_uom_qty,
'price': line.price_unit,
'subtotal': line.price_unit * line.product_uom_qty,
})
I was trying to add custom field into Invoice number sequence.
So the field I added was a many2one field or selection field whose value needs to be appended to the Invoice sequence.
The field is in account.invoice class
seq_pat = fields.Many2one('account.sequence.new','Sequence Pattern')
Other way I was trying is by overriding ir.sequence class method which creates the legends in sequences.
class ir_sequence(osv.osv):
_inherit = 'ir.sequence'
def _interpolation_dict_context(self, context=None):
if context is None:
context = {}
test1 = self.pool.get('account.invoice').browse()
test = test1.seq_pat.name
t = datetime.now(pytz.timezone(context.get('tz') or 'UTC'))
sequences = {
'year': '%Y', 'month': '%m', 'day': '%d', 'y': '%y', 'doy': '%j', 'woy': '%W',
'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S',
'pattern': '%P'
}
return {key: t.strftime(sequence) for key, sequence in sequences.iteritems()}
which succeeded in making it to the legends section of Odoo.
But I am stuck with how to get my field recognised by ir.sequence.
Anyone with any other idea to achieve this would be really helpful.