- (e.g. expenses@mycompany.flectra.com)
+ (e.g. expenses@mycompany.flectrahq.com)
@@ -243,7 +243,7 @@
Reimbursement
Once expense reports approved by managers, the accounting department checks accounts, products and taxes used. Then they
- post them into the books and proceed with the employee reimbursement .
+ post them into the books and proceed with the employee reimbursement .
To do:
@@ -258,7 +258,7 @@
Set Home Address to employees (used for reimbursement)
- See how to manage payables
+ See how to manage payables
@@ -267,7 +267,7 @@
Invoice Customers
- If you track expenses on customer projects, you can charge them back to your customers automatically . Here are some advises to avoid conflictual situations:
+ If you track expenses on customer projects, you can charge them back to your customers automatically . Here are some advises to avoid conflictual situations:
diff --git a/addons/hr_holidays/models/hr.py b/addons/hr_holidays/models/hr.py
index 80393e6a..b4387d5f 100644
--- a/addons/hr_holidays/models/hr.py
+++ b/addons/hr_holidays/models/hr.py
@@ -215,3 +215,15 @@ class Employee(models.Model):
('type', '=', 'remove')
])
return [('id', 'in', holidays.mapped('employee_id').ids)]
+
+ def write(self, values):
+ res = super(Employee, self).write(values)
+ if 'parent_id' in values or 'department_id' in values:
+ holidays = self.env['hr.holidays'].search([('state', 'in', ['draft', 'confirm']), ('employee_id', 'in', self.ids)])
+ hr_vals = {}
+ if values.get('parent_id') is not None:
+ hr_vals['manager_id'] = values['parent_id']
+ if values.get('department_id') is not None:
+ hr_vals['department_id'] = values['department_id']
+ holidays.write(hr_vals)
+ return res
diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py
index 9e79c561..05ba6297 100644
--- a/addons/hr_holidays/models/hr_holidays.py
+++ b/addons/hr_holidays/models/hr_holidays.py
@@ -180,7 +180,7 @@ class Holidays(models.Model):
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
employee_id = fields.Many2one('hr.employee', string='Employee', index=True, readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, track_visibility='onchange')
- manager_id = fields.Many2one('hr.employee', related='employee_id.parent_id', string='Manager', readonly=True, store=True)
+ manager_id = fields.Many2one('hr.employee', string='Manager', readonly=True)
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
number_of_days_temp = fields.Float(
'Allocation', copy=False, readonly=True,
@@ -195,9 +195,9 @@ class Holidays(models.Model):
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
help="Choose 'Leave Request' if someone wants to take an off-day. "
"\nChoose 'Allocation Request' if you want to increase the number of leaves available for someone")
- parent_id = fields.Many2one('hr.holidays', string='Parent')
+ parent_id = fields.Many2one('hr.holidays', string='Parent', copy=False)
linked_request_ids = fields.One2many('hr.holidays', 'parent_id', string='Linked Requests')
- department_id = fields.Many2one('hr.department', related='employee_id.department_id', string='Department', readonly=True, store=True)
+ department_id = fields.Many2one('hr.department', string='Department', readonly=True)
category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee')
holiday_type = fields.Selection([
@@ -248,7 +248,7 @@ class Holidays(models.Model):
if nholidays:
raise ValidationError(_('You can not have 2 leaves that overlaps on same day!'))
- @api.constrains('state', 'number_of_days_temp')
+ @api.constrains('state', 'number_of_days_temp', 'holiday_status_id')
def _check_holidays(self):
for holiday in self:
if holiday.holiday_type != 'employee' or holiday.type != 'remove' or not holiday.employee_id or holiday.holiday_status_id.limit:
@@ -274,7 +274,8 @@ class Holidays(models.Model):
self.employee_id = None
@api.onchange('employee_id')
- def _onchange_employee(self):
+ def _onchange_employee_id(self):
+ self.manager_id = self.employee_id and self.employee_id.parent_id
self.department_id = self.employee_id.department_id
def _get_number_of_days(self, date_from, date_to, employee_id):
@@ -358,6 +359,8 @@ class Holidays(models.Model):
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
holiday = super(Holidays, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values)
holiday.add_follower(employee_id)
+ if 'employee_id' in values:
+ holiday._onchange_employee_id()
return holiday
@api.multi
@@ -367,6 +370,8 @@ class Holidays(models.Model):
raise AccessError(_('You cannot set a leave request as \'%s\'. Contact a human resource manager.') % values.get('state'))
result = super(Holidays, self).write(values)
self.add_follower(employee_id)
+ if 'employee_id' in values:
+ self._onchange_employee_id()
return result
@api.multi
diff --git a/addons/hr_payroll/models/hr_salary_rule.py b/addons/hr_payroll/models/hr_salary_rule.py
index f651e025..85a99284 100644
--- a/addons/hr_payroll/models/hr_salary_rule.py
+++ b/addons/hr_payroll/models/hr_salary_rule.py
@@ -85,6 +85,11 @@ class HrSalaryRuleCategory(models.Model):
company_id = fields.Many2one('res.company', string='Company',
default=lambda self: self.env['res.company']._company_default_get())
+ @api.constrains('parent_id')
+ def _check_parent_id(self):
+ if not self._check_recursion():
+ raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rule Category.'))
+
class HrSalaryRule(models.Model):
_name = 'hr.salary.rule'
@@ -165,6 +170,11 @@ class HrSalaryRule(models.Model):
input_ids = fields.One2many('hr.rule.input', 'input_id', string='Inputs', copy=True)
note = fields.Text(string='Description')
+ @api.constrains('parent_rule_id')
+ def _check_parent_rule_id(self):
+ if not self._check_recursion(parent='parent_rule_id'):
+ raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rules.'))
+
@api.multi
def _recursive_search_of_rules(self):
"""
diff --git a/addons/l10n_ca/data/l10n_ca_chart_data.xml b/addons/l10n_ca/data/l10n_ca_chart_data.xml
index 53586a12..977a11bf 100644
--- a/addons/l10n_ca/data/l10n_ca_chart_data.xml
+++ b/addons/l10n_ca/data/l10n_ca_chart_data.xml
@@ -29,7 +29,7 @@
1181
-
+
GST receivable
@@ -37,7 +37,7 @@
1182
-
+
PST/QST receivable
@@ -45,7 +45,7 @@
11831
-
+
HST receivable - 13%
@@ -53,7 +53,7 @@
11832
-
+
HST receivable - 14%
@@ -61,7 +61,7 @@
11833
-
+
HST receivable - 15%
@@ -79,7 +79,7 @@
2131
-
+
GST to pay
@@ -87,7 +87,7 @@
2132
-
+
PST/QST to pay
@@ -95,7 +95,7 @@
21331
-
+
HST to pay - 13%
@@ -103,7 +103,7 @@
21332
-
+
HST to pay - 14%
@@ -111,7 +111,7 @@
21333
-
+
HST to pay - 15%
diff --git a/addons/l10n_ch/data/l10n_ch_chart_data.xml b/addons/l10n_ch/data/l10n_ch_chart_data.xml
index f507ad92..4a28e3ce 100644
--- a/addons/l10n_ch/data/l10n_ch_chart_data.xml
+++ b/addons/l10n_ch/data/l10n_ch_chart_data.xml
@@ -18,7 +18,7 @@
100
-
+
diff --git a/addons/product/models/product.py b/addons/product/models/product.py
index 1f85bad5..7c7d0e1c 100644
--- a/addons/product/models/product.py
+++ b/addons/product/models/product.py
@@ -149,6 +149,9 @@ class ProductProduct(models.Model):
('barcode_uniq', 'unique(barcode)', "A barcode can only be assigned to one product !"),
]
+ def _get_invoice_policy(self):
+ return False
+
def _compute_product_price(self):
prices = {}
pricelist_id_or_name = self._context.get('pricelist')
@@ -219,6 +222,7 @@ class ProductProduct(models.Model):
for supplier_info in self.seller_ids:
if supplier_info.name.id == self._context.get('partner_id'):
self.code = supplier_info.product_code or self.default_code
+ break
else:
self.code = self.default_code
@@ -227,9 +231,10 @@ class ProductProduct(models.Model):
for supplier_info in self.seller_ids:
if supplier_info.name.id == self._context.get('partner_id'):
product_name = supplier_info.product_name or self.default_code
+ self.partner_ref = '%s%s' % (self.code and '[%s] ' % self.code or '', product_name)
+ break
else:
- product_name = self.name
- self.partner_ref = '%s%s' % (self.code and '[%s] ' % self.code or '', product_name)
+ self.partner_ref = self.name_get()[0][1]
@api.one
@api.depends('image_variant', 'product_tmpl_id.image')
@@ -428,7 +433,12 @@ class ProductProduct(models.Model):
limit2 = (limit - len(products)) if limit else False
products += self.search(args + [('name', operator, name), ('id', 'not in', products.ids)], limit=limit2)
elif not products and operator in expression.NEGATIVE_TERM_OPERATORS:
- products = self.search(args + ['&', ('default_code', operator, name), ('name', operator, name)], limit=limit)
+ domain = expression.OR([
+ ['&', ('default_code', operator, name), ('name', operator, name)],
+ ['&', ('default_code', '=', False), ('name', operator, name)],
+ ])
+ domain = expression.AND([args, domain])
+ products = self.search(domain, limit=limit)
if not products and operator in positive_operators:
ptrn = re.compile('(\[(.*?)\])')
res = ptrn.search(name)
diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py
index 45368332..a9cce62a 100644
--- a/addons/product/models/product_attribute.py
+++ b/addons/product/models/product_attribute.py
@@ -4,6 +4,7 @@
from flectra import api, fields, models, _
from flectra.addons import decimal_precision as dp
from flectra.exceptions import UserError, ValidationError
+from flectra.osv import expression
class ProductAttribute(models.Model):
@@ -107,6 +108,6 @@ class ProductAttributeLine(models.Model):
# search on a m2o and one on a m2m, probably this will quickly become
# difficult to compute - check if performance optimization is required
if name and operator in ('=', 'ilike', '=ilike', 'like', '=like'):
- args = ['|', ('attribute_id', operator, name), ('value_ids', operator, name)]
+ args = expression.AND([['|', ('attribute_id', operator, name), ('value_ids', operator, name)], args])
return self.search(args, limit=limit).name_get()
return super(ProductAttributeLine, self).name_search(name=name, args=args, operator=operator, limit=limit)
diff --git a/addons/product/report/product_pricelist_templates.xml b/addons/product/report/product_pricelist_templates.xml
index e14465bd..614c360c 100644
--- a/addons/product/report/product_pricelist_templates.xml
+++ b/addons/product/report/product_pricelist_templates.xml
@@ -45,6 +45,11 @@
[ ]
+
+ -
+
+
+
-
+
+
+
diff --git a/addons/product/tests/__init__.py b/addons/product/tests/__init__.py
index 30885ae7..520d6f93 100644
--- a/addons/product/tests/__init__.py
+++ b/addons/product/tests/__init__.py
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
-from . import test_variants, test_uom, test_pricelist, test_product_pricelist
+from . import test_seller, test_variants, test_uom, test_pricelist, test_product_pricelist
diff --git a/addons/product/tests/test_seller.py b/addons/product/tests/test_seller.py
new file mode 100644
index 00000000..9cfeca5d
--- /dev/null
+++ b/addons/product/tests/test_seller.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
+
+from flectra.tests.common import TransactionCase
+
+
+class TestSeller(TransactionCase):
+
+ def setUp(self):
+ super(TestSeller, self).setUp()
+ self.product_service = self.env.ref('product.product_product_2')
+ self.product_service.default_code = 'DEFCODE'
+ self.asustec = self.env.ref('base.res_partner_1')
+ self.camptocamp = self.env.ref('base.res_partner_12')
+
+ def test_10_sellers(self):
+ self.product_service.write({'seller_ids': [
+ (0, 0, {'name': self.asustec.id, 'product_code': 'ASUCODE'}),
+ (0, 0, {'name': self.camptocamp.id, 'product_code': 'C2CCODE'}),
+ ]})
+
+ default_code = self.product_service.code
+ self.assertEqual("DEFCODE", default_code, "Default code not used in product name")
+
+ context_code = self.product_service\
+ .with_context(partner_id=self.camptocamp.id)\
+ .code
+ self.assertEqual('C2CCODE', context_code, "Partner's code not used in product name with context set")
diff --git a/addons/product/tests/test_variants.py b/addons/product/tests/test_variants.py
index 5e203c46..2ba7818e 100644
--- a/addons/product/tests/test_variants.py
+++ b/addons/product/tests/test_variants.py
@@ -40,6 +40,15 @@ class TestVariantsSearch(TransactionCase):
self.assertIn(self.product_shirt_template, search_value,
'Shirt should be found searching L')
+ def test_name_search(self):
+ self.product_slip_template = self.env['product.template'].create({
+ 'name': 'Slip',
+ })
+ res = self.env['product.product'].name_search('Shirt', [], 'not ilike', None)
+ res_ids = [r[0] for r in res]
+ self.assertIn(self.product_slip_template.product_variant_ids.id, res_ids,
+ 'Slip should be found searching \'not ilike\'')
+
class TestVariants(common.TestProductCommon):
diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml
index 9d0d0681..6537083b 100644
--- a/addons/product/views/product_views.xml
+++ b/addons/product/views/product_views.xml
@@ -90,6 +90,11 @@
+
+
+
+
+
@@ -202,7 +207,7 @@
-
+
All general settings about this product are managed on
diff --git a/addons/product_email_template/views/mail_template_views.xml b/addons/product_email_template/views/mail_template_views.xml
index f94c0272..55f75c15 100644
--- a/addons/product_email_template/views/mail_template_views.xml
+++ b/addons/product_email_template/views/mail_template_views.xml
@@ -11,7 +11,8 @@
Body
-
+