Show field based on value of another field - odoo

I've been trying to show order confirmation date in sale order tree view.
To show it I used 'date_order' like this:
<field name="date_order" string="Confirmation Date" optional="show" attrs="{'invisible':[['state','not in',['sale', 'done']]]}" />
In our setup orders are created manually but also synced from Woocommerce and in those synced orders from web shop date_order is always before the create date.
For example order is created (create_date) 10.08.2022 17:10:20 and confirmed automatically (date_order) 10.08.2022 17:10:11
Somehow, it is confirmed 9s before it is created. Now, i'd like to show date_order only when it's value is > create_date. In other cases i'd like to display create_date in it's place.
Tryed to use attrs for this, I'm not sure if it's even possible:
<field name="date_order" string="Confirmation Date" optional="show" attrs="{'invisible':['&', ['state','not in',['sale', 'done']], ['date_order'], '>', ['create_date']]}" />
The code above gives XMLSyntaxError. Not sure if it's possible to compare values in that manner - and finally how to get one or the other value - I guess my second approach is maybe better.
In second approach I tried to create compute field, like this:
date_order_mine = fields.Char("Potvrdjeeeno", compute="comp_date")
#api.depends('create_date', 'date_order', 'order_line')
def comp_date(self):
for order in self:
for line in order.order_line:
if line.create_date < line.date_order:
return line.date_order
else:
return line.create_date
This code gives me AttributeError: 'sale.order.line' object has no attribute 'date_order'
Since I'm so new to Odoo and Python dev I'm not sure what should I do here to compare values of this fields and to return one or other based on conditions - if someone can help I will appreciate it.

The domain used in attrs is not valid, domains should be a list of criteria, each criterion being a triple (either a list or a tuple) of:
(field_name, operator, value)
In your second approach, the date_order field is on the parent model sale.order (order) and to access the date order from order lines , use the order_id field like following:
line.order_id.date_order
To set the value of date_order_mine to create_date or date_order you do not need the order lines and also the computed method should assign the compute value:
Computed Fields
Fields can be computed (instead of read straight from the database) using the compute parameter. It must assign the computed value to the field.
Example:
date_order_mine = fields.Datetime("Potvrdjeeeno", compute="comp_date")
#api.depends('create_date', 'date_order')
def comp_date(self):
for order in self:
if order.create_date < order.date_order:
order.date_order_mine = order.date_order
else:
order.date_order_mine = order.create_date

Related

Django complex filter and order

I have 4 model like this
class Site(models.Model):
name = models.CharField(max_length=200)
def get_lowest_price(self, mm_date):
'''This method returns lowest product price on a site at a particular date'''
class Category(models.Model):
name = models.CharField(max_length=200)
site = models.ForeignKey(Site)
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category)
class Price(models.Model):
date = models.DateField()
price = models.IntegerField()
product = models.ForeignKey(Product)
Here every have many category, every category have many product. Now product price can change every day so price model will hold the product price and date.
My problem is I want list of site filter by price range. This price range will depends on the get_lowest_price method and can be sort Min to Max and Max to Min. Already I've used lambda expression to do that but I think it's not appropriate
sorted(Site.objects.all(), key=lambda x: x.get_lowest_price(the_date))
Also I can get all site within a price range by running a loop but this is also not a good idea. Please help my someone to do the query in right manner.
If you still need more clear view of the question please see the first comment from "Ishtiaque Khan", his assumption is 100% right.
*In these models writing frequency is low and reading frequency is high.
1. Using query
If you just wanna query using a specific date. Here is how:
q = Site.objects.filter(category__product__price__date=mm_date) \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
It will return a list of Site with lowest price on mm_date fall within range of min_price - max_price. You can also query for multiple date using query like so:
q = Site.objects.values('name', 'category__product__price__date') \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
2. Eager/pre-calculation, you can use post_save signal. Since the write frequency is low this will not be expensive
Create another Table to hold lowest prices per date. Like this:
class LowestPrice(models.Model):
date = models.DateField()
site = models.ForeignKey(Site)
lowest_price = models.IntegerField(default=0)
Use post_save signal to calculate and update this every time there. Sample code (not tested)
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Price)
def update_price(sender, instance, **kwargs):
cur_price = LowestPrice.objects.filter(site=instance.product.category.site, date=instance.date).first()
if not cur_price:
new_price = LowestPrice()
new_price.site = instance.product.category.site
new_price.date = instance.date
else:
new_price = cur_price
# update price only if needed
if instance.price<new_price.lowest_price:
new_price.lowest_price = instance.price
new_price.save()
Then just query directly from this table when needed:
LowestPrice.objects.filter(date=mm_date, lowest_price__gte=min_price, lowest_price__lte=max_price)
Solution:
from django.db.models import Min
Site.objects.annotate(
price_min=Min('categories__products__prices__price')
).filter(
categories__products__prices__date=the_date,
).distinct().order_by('price_min') # prefix '-' for descending order
For this to work, you need to modify the models by adding a related_name attribute to the ForeignKey fields.
Like this -
class Category(models.Model):
# rest of the fields
site = models.ForeignKey(Site, related_name='categories')
Similary, for Product and Price models, add related_name as products and prices in the ForeignKey fields.
Explanation:
Starting with related_name, it describes the reverse relation from one model to another.
After the reverse relationship is setup, you can use them to inner join the tables.
You can use the reverse relationships to get the price of a product of a category on a site and annotate the min price, filtered by the_date. I have used the annotated value to order by min price of the product, in ascending order. You can use '-' as a prefix character to do in descending order.
Do it with django queryset operations
Price.objects.all().order_by('price') #add [0] for only the first object
or
Price.objects.all().order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('-price') #add [0] for only the first object
I think this ORM query could do the job ...
from django.db.models import Min
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('price_min')
or /and for reversing the order :
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('-price_min')

Set sales order fields reference in picking - odoo

I would like to set Sales Team reference in picking directly when sales order confirm and picking is getting created.
But I didn't get enough hint how can I achieve this. Because the method which is called at the time of sales order confirmation is as follow.
def action_button_confirm(self, cr, uid, ids, context=None):
if not context:
context = {}
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
self.signal_workflow(cr, uid, ids, 'order_confirm')
if context.get('send_email'):
self.force_quotation_send(cr, uid, ids, context=context)
return True
Here there is no any hint how can I pass it to picking ?
Purpose:
My aim is to set sales team reference in picking / shipment.
It's not that easy. Odoo uses procurement.orders for creating stock.moves and for them stock.pickings. Problem: Maybe a picking has more than one sales orders as origin. So there could be more than one sales team referenced.
But try to use a computed field:
section_id = fields.Many2one(
comodel_name="crm.case.section", string="Sales Team",
compute="_compute_section_id")
#api.multi
def _compute_section_id(self):
for picking in self:
section_ids = set()
for move in picking.move_lines:
if move.sale_line_id.order_id.section_id
section_ids.add(move.sale_line_id.order_id.section_id.id)
if len(section_ids) == 1:
picking.section_id = section_ids.pop()
You could also use a related field, but that could have really bad side effects. Because Odoo will take the first move.
section_id = fields.Many2one(
comodel_name="crm.case.section", string="Sales Team",
related="move_lines.sale_line_id.order_id.section_id")
I got that method from where it create picking. So I have just inherited it and added my code. action_ship_create will always get called at the time of shipment creation from the sales order.
#api.cr_uid_ids_context
def action_ship_create(self,cr,uid,ids,context={}):
result=super(sale_order,self).action_ship_create(cr,uid,ids,context=context)
for order in self.browse(cr,uid,ids,context=context):
order.picking_ids.write({'section_id':order.section_id.id})
return result

Odoo 8 : how to link a Many2one field automatically with the parent model?

I created a new model 'sale.order.category' in order to group Sale Order Lines in specific subcategories (allowing to display subtotals, etc.)
class SaleOrderCategory(models.Model):
_name = 'sale.order.category'
name = fields.Char('Name', required=True)
line_ids = fields.One2many('sale.order.line', 'category_id', 'Order Lines in this category')
order_id = fields.Many2one('sale.order', 'Order', required=True, readonly=True)
class SaleOrder(models.Model):
_name = 'sale.order'
_inherit = 'sale.order'
order_category_ids = fields.One2many('sale.order.category', 'order_id', 'Categories in this order', readonly=True, copy=True)
Just for info, here is my Order lines tree view modification to add the Category column :
<!-- adds a category column in the order lines list -->
<xpath expr="//field[#name='order_line']/tree/field[#name='name']" position="after">
<field name="category_id"/>
</xpath>
My question is : how can I automatically populate the order_id field with the current Sales Order ID when I create a new Category through the Order Lines Tree (inside a Sales Order) ?
Many thanks,
Max
Preliminary remark: your use case seems related to what the official sale_layout module does, so you might want to have a look at it before going any further. Perhaps you could extend it instead of starting from scratch.
Next, the most basic answer to your question is to pass a default value for the order_id field of your sale.order.category model when you create it from the view. You can do that by setting a context with an appropriate default value on the many2one field from which you will create the value:
<xpath expr="//field[#name='order_line']/tree/field[#name='name']" position="after">
<field name="category_id" context="{'default_order_id': parent.id}"/>
</xpath>
Your category_id field is defined on the sale.order.line tree view, so parent will dynamically refer to the parent record inside the web client interface, here the sale.order.
However this option will not work well:
When you're creating a new sales order, you will have to create your categories before the sales order is even saved, so there is no possible value for order_id yet. For this reason, you cannot make order_id required, and you will have to set its value again later, or you will need to save your orders before starting to add the categories.
You already have an order_lines one2many field in your sale.order.category model. The order_id field is redundant with the line_ids field, because all lines presumably belong to the same order.
A simple alternative would be to entirely omit the order_id field (use lines_id[0].order_id when you need it), or to replace it with a related field that will be automatically computed from the lines (it will take the value from the first order line):
order_id = fields.Many2one('sale.order', related='line_ids.order_id', readonly=True)
What you should do depends on your requirements, it's difficult to say based only on your question.

Odoo custom filter domain field on load view

I'm making a module for reservations in Odoo 9 and one field of my model is populated based if it's reserved or no. Basically my model is:
class Reservation(models.Model):
....
room_id = fields.Many2one('reservation.room', string="Room")
I've defined an onchange function that return a domain to filter the room_ids that aren't reserved:
#api.onchange('date')
def _set_available_room(self):
.....
return {'domain': {'room_id': [('id', 'in', res)]}}
This works fine and when I set the date, the rooms are filtered ok. My problem is when I save a reservation and enter again to edit it. The room_id field show all values and only when I change the date the room_id is filtered.
I've tried using the domain attribute in the field definition like this, but it doesn't works:
room_id = fields.Many2one('reservation.room', string="Room", domain=lambda self: self._get_available_slots())
How can I filter this field on the load view using my function than search for available rooms?

Magento: Get Collection of Order Items for a product collection filtered by an attribute

I'm working on developing a category roll-up report for a Magento (1.6) store.
To that end, I want to get an Order Item collection for a subset of products - those product whose unique category id (that's a Magento product attribute that I created) match a particular value.
I can get the relevant result set by basing the collection on catalog/product.
$collection = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToFilter('unique_category_id', '75')
->joinTable('sales/order_item', 'product_id=entity_id', array('price'=>'price','qty_ordered' => 'qty_ordered'));
Magento doesn't like it, since there are duplicate entries for the same product id.
How do I craft the code to get this result set based on Order Items? Joining in the product collection filtered by an attribute is eluding me. This code isn't doing the trick, since it assumes that attribute is on the Order Item, and not the Product.
$collection = Mage::getModel('sales/order_item')
->getCollection()
->join('catalog/product', 'entity_id=product_id')
->addAttributeToFilter('unique_category_id', '75');
Any help is appreciated.
The only way to make cross entity selects work cleanly and efficiently is by building the SQL with the collections select object.
$attributeCode = 'unique_category_id';
$alias = $attributeCode.'_table';
$attribute = Mage::getSingleton('eav/config')
->getAttribute(Mage_Catalog_Model_Product::ENTITY, $attributeCode);
$collection = Mage::getResourceModel('sales/order_item_collection');
$select = $collection->getSelect()->join(
array($alias => $attribute->getBackendTable()),
"main_table.product_id = $alias.entity_id AND $alias.attribute_id={$attribute->getId()}",
array($attributeCode => 'value')
)
->where("$alias.value=?", 75);
This works quite well for me. I tend to skip going the full way of joining the eav_entity_type table, then eav_attribute, then the value table etc for performance reasons. Since the attribute_id is entity specific, that is all that is needed.
Depending on the scope of your attribute you might need to add in the store id, too.