Odoo 10 - Get actual row or call function after saving - odoo-10

I'm working on a table that's inside a form. That table is filled automatically with the input of the first field that the user has to do. In the table there is only one column that the user has to enter data in and I made it possible for them to directly enter datas in the table instead of opening a new form.
Those datas need to be checked using values hidden in the table, I just have to check if the value entered by the user respect some tolerances.
Now my problem is, for checking the values I need to know the ids of the values to be able to check with the rights tolerances but I can't get them if the user doesn't save the form.
So I have two questions :
Is it possible to call a function just after saving ? I know I can override the "create" function but that's not what I want, I want a function to be called right after saving, thanks to that I would be able to know the ids and it would be possible to check the values.
If that's not possible, my second question is :
Is it possible to get the row number when the user enters its value in ? It would be useful to get the row number with api.onchange so I could simply loop into the rows until I find the right one and then use the datas that are there.
Thanks for your time
EDIT1 : Here the code I made to loop through the datas :
class ControlMeasure(models.Model):
"""
Modèle pour la mesure d'une pièce
"""
# Nom de la table
_name = 'spc.control.measure'
# Champs de la table
name = fields.Many2one(string='Name', related='control_point_id.measure_type_id', readonly=True)
value = fields.Float(string='Measured value')
validity = fields.Boolean(string='Validity', readonly=True)
nominal_value = fields.Float(string='Nominal value', related='control_point_id.nominal_value', readonly=True)
unit_id = fields.Many2one('product.uom', string='Unit', related='control_point_id.product_uom_id', readonly=True)
control_part_id = fields.Many2one('spc.control.part', string='Controled piece')
control_point_id = fields.Many2one('spc.control.point', string='Control point')
# Champs visuels
control_device_id = fields.Many2one('spc.control.device', string='Used device', related='control_point_id.control_device_id')
# Vérifie si la valeur mesurée est dans la tolérance
#api.multi
#api.onchange('value')
def is_valid(self):
# raise Exception('Appel réussi')
sql= """
SELECT p.nominal_value, p.inferior_tolerance, p.superior_tolerance FROM spc_control_point p
WHERE p.control_plan_id = %(ctrlid)s
"""
if self.control_part_id.control_plan_id == False:
raise Exception('False ' + str(self.control_part_id.control_plan_id))
else:
self.env.cr.execute(sql, {'ctrlid' : self.control_part_id.control_plan_id.id})
row = self.env.cr.fetchall()
for i in range(0, len(row)):
#raise Exception(str(self.value) + ' < ' + str(row[i][0]) + ' - ' + str(abs(row[i][1])) + ' or ' + str(self.value) + ' > ' + str(row[i][0]) + ' + ' + str(abs(row[i][2])))
if self.value < row[i][0] - abs(row[i][1]) or self.value > row[i][0] + abs(row[i][2]):
self.validity = False
else:
self.validity = True
Here the whole code works how I want except the loop that I don't know how to make it properly, I checked if the datas which are tested are the right ones and they are, the test itself works as I want, but as you see everytime I check all the rows and the end, only the last one count.
That's why I came with the idea to get the row number where the user clicked but I don't know if it's possible to do it or even call a function after saving which would be easy because I would know the ids and I could check the datas after saving.

Two possible solutions come up on my mind:
As you mentioned, you can create a method with #api.onchange decorator, to check the entered data each time user passed it in, without actually saving it or any other manipulations.
#api.multi
#api.onchange('your_field', 'your_field2')
def _onchange_your_fields(self):
for record in self:
if record.your_field != record.validation_field:
raise exceptions.ValidationError(_('Sorry, pal, try again'))
The good thing about this, is that it will validate the field's value rapidly: after the user will put a cursor on the next field. Although, it can be irritating from a user's perspective.
You can try #api.constrains decorator. It behaves slightly different from the #api.onchange decorator, but it's a common practice to use it for a validation.
#api.multi
#api.constrains('your_field', 'your_field2')
def _check_your_fields(self):
for record in self:
if record.your_field != record.validation_field:
raise exceptions.ValidationError(_('Sorry, pal, try again'))
The difference is that it will be called after you'll hit "Save" button, but before actually executing create or write functions.
Hope, that will help.
UPDATE 1:
# Vérifie si la valeur mesurée est dans la tolérance
#api.multi
#api.onchange('value')
def validate(self):
# Because we are using #api.multi decorator, our 'self' is actually a recordset.
# That means, that we cannot treat it as a regular record, so "self.control_part_id...." would be an odd code
for record in self:
# So now we took a one record from recordset, and now we can continue on validating it.
if not record.control_part_id.control_plan_id:
raise exceptions.UserError(_('False %s' % str(record.control_part_id.control_plan_id)))
# You executed here a SQL query, which is actually takes our records from database, not from cache.
# But we haven't write(or create) our changes yet, and that makes no sense.
# What we wanna do, is to take values from cache and validate them. You can simply do it just via model's field
validation_record = record.control_point_id
if record.value < validation_row.nominal_value - validation_row.inferior_tolerance \
or record.value > validation_row.nominal_value + validation_row.superior_tolerance:
record.validity = True
else:
# You can get rid of this line by simply defining the default value of record.validity as "False"
record.validity = False
# I recommend you to check the final validation this way
#api.multi
#api.constrains('validity')
def check_valid(self):
for record in self:
if not record.validity:
raise exceptions.ValidationError(_("Cannot pass a validation rest"))

Related

Sorting in an cl_gui_alv_grid by column?

I'm generating an internal table with cl_gui_alv_grid that has five columns and I can't seem to figure out how to sort by column 1 once the table is loaded. I managed to do it with cl_salv_table, however not with the grid. Any idea please? I suppose it's done somehow with the CALL METHOD go_alv->set_sort_criteria however I'm getting a crash when triggering the table view.
DATA: lt_sort TYPE lvc_t_sort,
ls_sort TYPE lvc_s_sort.
FORM sort_data.
REFRESH: lt_sort.
CLEAR: ls_sort.
ls_sort-spos = '1'.
ls_sort-fieldname = 'Column1'.
ls_sort-up = abap_true.
APPEND ls_sort TO lt_sort.
ENDFORM.
FORM first_display.
PERFORM sort_data.
CALL METHOD go_alv->set_table_for_first_display
EXPORTING
i_structure_name = 'TABLE_STRUCTURE'
is_layout = gs_layout
CHANGING
it_outtab = gt_salv_table
ENDFORM.
CALL METHOD go_alv->set_sort_criteria
EXPORTING
it_sort = lt_sort
* EXCEPTIONS
* no_fieldcatalog_available = 1
* others = 2.
Do you call the set_sort_criteria before the set_table_for_first_display? It looks like there is no fieldcatalouge yet, this is why it does not work.
I think more straighforward is: The set_table_for_first_display method has a changing parameter it_sort, just use that one for importing the sort table into the class, no need to call set_sort_criteria:
CALL METHOD go_alv->set_table_for_first_display
EXPORTING
i_structure_name = 'TABLE_STRUCTURE'
is_layout = gs_layout
CHANGING
it_outtab = gt_salv_table
it_sort = lt_sort
EXCEPTIONS
...

Both sql and python constraints are not working in odoo 9

I have been battling with this for a while. none of the two options are working neither were they giving errors. i commented the pythonic constrain method for you to see.
code snippet:
class house_development(models.Model):
_name = 'house.development'
_description = 'Development'
_inherit = ["mail.thread"]
name = fields.Char(string="Name", required=True, track_visibility='onchange')
description = fields.Text(string="Description", track_visibility='onchange')
# #api.one
# #api.constrains('name')
# def _identify_same_name(self):
# for record in self:
# if record.name in self:
# raise exceptions.ValidationError("There is another development/project with the same name: %s" % record.name)
_sql_constraints = [
('name_unique',
'UNIQUE(name)',
"There is another development/project with the same name"),
]
It should be like that,
#api.multi
#api.constrains('name')
def _identify_same_name(self):
for record in self:
obj = self.search([('name','=ilike',record.name),('id','!=',record.id)])
if obj:
raise exceptions.ValidationError("There is another development/project with the same name: %s" % record.name)
You need to search for the same name but not with the same id.
And for database unique constrains you may add like that.
_sql_constraints = [
('name_unique', 'unique(name)', 'There is another development/project with the same name!!!'),
]
Database constrains will not be added if there are duplicate name in
table. Sql constrains will apply only if the rule is not violated by
the existing data. I think in your case that's the point with Sql
constrains. And make sure for that you need to upgrade module. first
check duplicate records in database by firing that query.
Select name, count(*) from table_name
group by name
having count(*) > 1;
Available operators in domain in odoo Click to see more.

How to write this domain to be sure at 100 % that we will get the right stock pack operation for each invoice line?

This post should be a little more complex than usual.
We have created a new field for an account.invoice.line : pack_operation. With this field, we can print serial/lot number for each line on the PDF invoice (this part works well).
Many hours passed trying to write the domain to select the EXACT and ONLY stock pack operation for each invoice line.
In the code below, we used the domain [('id','=', 31)] to make our tests printing the PDF.
Ho to write this domain to be sure at 100 % that we will get the right stock pack operation for each invoice line?
I really need your help here... Too complex for my brain.
Our code :
class AccountInvoiceLine(models.Model):
_inherit = "account.invoice.line"
pack_operation = fields.Many2one(comodel_name='stock.pack.operation', compute='compute_stock_pack_operation_id')
def compute_stock_pack_operation_id(self):
stock_operation_obj = self.env['stock.pack.operation']
stock_operation = stock_operation_obj.search( [('id','=', 31)] )
self.pack_operation = stock_operation[0]
EDIT#1
I know that you won't like my code. But, this one seems to work. I take any comments and improvements with pleasure.
class AccountInvoiceLine(models.Model):
_inherit = "account.invoice.line"
pack_operation = fields.Many2one(comodel_name='stock.pack.operation', compute='compute_stock_pack_operation_id')#api.one
def compute_stock_pack_operation_id(self):
procurement_order_obj = self.env['procurement.order']
stock_operation_obj = self.env['stock.pack.operation']
all_picking_ids_for_this_invoice_line = []
for saleorderline in self.sale_line_ids:
for procurement in saleorderline.procurement_ids:
for stockmove in procurement.move_ids:
if stockmove.picking_id.id not in all_picking_ids_for_this_invoice_line
all_picking_ids_for_this_invoice_line.append(stockmove.picking_id.id)
all_picking_ids_for_this_invoice_line))
stock_operation = stock_operation_obj.search(
[ '&',
('picking_id','in',all_picking_ids_for_this_invoice_line),
('product_id','=',self.product_id.id)
]
)
self.pack_operation = stock_operation[0]
The pack_operation field is a computed field, that be default means that the field will not be saved on the database unless you set store=True when you define your field.
So, what you can do here is change:
pack_operation = fields.Many2one(comodel_name='stock.pack.operation', compute='compute_stock_pack_operation_id')
to:
pack_operation = fields.Many2one(comodel_name='stock.pack.operation', compute='compute_stock_pack_operation_id', store=True)
And try running your query again.

appending to rails field value

I need to find and update a number of records in a Rails 3.2, Ruby 2 application. The following code successfully finds the records I want. What I need to do though is add " x" (including the space) to the email address of every user and I can't figure out how to do it.
This finds the records
User.joins(:account)
.where("users.account_id NOT IN (?)", [1955, 3083, 3869])
.where("accounts.partner_id IN (?)", [23,50])
.where("users.staff = '0'")
.where("users.admin = '0'")
.where("users.api_user = '0'")
.where("users.partner_id is null")
.update_all(email: :email.to_s << " X")
but it's the last line I'm having problems with. Is this possible, or do I need to find the records another way?
The update_all method updates a collection of records, but unless you write your own SQL expression, it can only set one value. For example, if you wanted to overwrite all the email addresses with "X", you could do it easily:
User.joins(:account)
.where("users.account_id NOT IN (?)", [1955, 3083, 3869])
# ...other scopes...
.update_all(email: "X")
In your case, what you really need to do is make individual updates to all these records. One way to do it is to find the records, then loop over them and update them one at a time:
users_to_update = User.joins(:account)
.where("users.account_id NOT IN (?)", [1955, 3083, 3869])
.where("accounts.partner_id IN (?)", [23,50])
.where("users.staff = '0'")
.where("users.admin = '0'")
.where("users.api_user = '0'")
.where("users.partner_id is null")
users_to_update.each do |user|
user.update_attribute(:email, "#{user.email} X")
end
Another solution would be to use a SQL expression with update_all, as in Zoran's answer.
Try writing the last line like so:
.update_all("email = email || ' X'")
This uses SQL's string concatenation operator to append the X to the end of the emails.
Hope that helps!

VB6.0 with DataControl Database Programming

Hey, can I ask you something? I'm using VB6.0 and I have some problem with the connection of my Database through DataControl. I have a table named tblEmployee and the other one is tblPosition then I passed the value of the two tables to two DataControls respectively. How can I then get the value of a certain row of Position field. With my code, my Position field returns only the first row. Here's my code
Private Sub cmdSearchEmployee_Click()
With datEmployee.Recordset
datEmployee.Recordset.Index = "idxid"
datEmployee.Recordset.Seek "=", txtIDNumber.Text
If .NoMatch = True Then
MsgBox ("No Record Found!")
Else
Me.txtLastName.Text = .Fields("lname")
Me.txtFirstName.Text = .Fields("fname")
Me.txtMiddleName.Text = .Fields("mi")
With datPosition.Recordset
Me.txtPosition.Text = .Fields("position")
End With
End If
End With
End Sub
I can't see that you have "passed the value" to your DataControl named datPosition. Could this be the problem? e.g. where you have
With datPosition.Recordset
Me.txtPosition.Text = .Fields("position")
End With
...should be more like this:
With datPosition.Recordset
.Index = "some_index??"
.Seek "=", "some_value??"
Me.txtPosition.Text = .Fields("position")
End With
Also consider using the recordsets' Filter to remove the rows that do not match your criteria then RecordCount to loop through the rows that do match your criteria.
Further consider returning a single recordset by creating a join between tblEmployee and tblPosition, either in SQL code or returning a hierarchical recordset using the MsDataShape with its SHAPE syntax.

Resources