From 1109b4cb35a37ac09cca4754e9e822ea81d0ce07 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 4 Feb 2013 15:03:24 +0530
Subject: [PATCH 001/256] [IMP]add session tag for yaml and based on session
user whole yaml tested
bzr revid: sgo@tinyerp.com-20130204093324-6jdty26tepmvjhhs
---
.../addons/base/security/ir.model.access.csv | 2 +-
openerp/tools/yaml_import.py | 17 ++++++++++++++---
openerp/tools/yaml_tag.py | 13 +++++++++++++
3 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index 35d5b82bc41..3a29108d61e 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -17,7 +17,7 @@
"access_ir_model_constraint","ir_model_constraint","model_ir_model_constraint",,1,0,0,0
"access_ir_model_relation","ir_model_relation","model_ir_model_relation",,1,0,0,0
"access_ir_model_access_all","ir_model_access_all","model_ir_model_access",,1,0,0,0
-"access_ir_model_data_all","ir_model_data all","model_ir_model_data",,1,0,0,0
+"access_ir_model_data_all","ir_model_data all","model_ir_model_data",,1,1,1,1
"access_ir_model_fields_all","ir_model_fields all","model_ir_model_fields",,1,0,0,0
"access_ir_module_category_group_user","ir_module_category group_user","model_ir_module_category",,1,0,0,0
"access_ir_module_module_group_user","ir_module_module group_user","model_ir_module_module","group_system",1,1,1,1
diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py
index 3172caac1f4..1629ee1b9be 100644
--- a/openerp/tools/yaml_import.py
+++ b/openerp/tools/yaml_import.py
@@ -88,6 +88,9 @@ def is_ir_set(node):
def is_string(node):
return isinstance(node, basestring)
+def is_session(node):
+ return _is_yaml_mapping(node, yaml_tag.Session)
+
class RecordDictWrapper(dict):
"""
Used to pass a record as locals in eval:
@@ -324,7 +327,7 @@ class YamlInterpreter(object):
record_dict = self._create_record(model, fields, view_info, default=default)
_logger.debug("RECORD_DICT %s" % record_dict)
- id = self.pool.get('ir.model.data')._update(self.cr, SUPERUSER_ID, record.model, \
+ id = self.pool.get('ir.model.data')._update(self.cr, self.uid, record.model, \
self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode, context=context)
self.id_map[record.id] = int(id)
if config.get('import_partial'):
@@ -760,7 +763,7 @@ class YamlInterpreter(object):
keyword = 'client_action_relate'
value = 'ir.actions.act_window,%s' % id
replace = node.replace or True
- self.pool.get('ir.model.data').ir_set(self.cr, SUPERUSER_ID, 'action', keyword, \
+ self.pool.get('ir.model.data').ir_set(self.cr, self.uid, 'action', keyword, \
node.id, [node.src_model], value, replace=replace, noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id)
# TODO add remove ir.model.data
@@ -775,10 +778,16 @@ class YamlInterpreter(object):
self.pool.get(node.model).unlink(self.cr, self.uid, ids)
else:
self._log("Record not deleted.")
+
+ def process_session(self, node):
+ record, fields = node.items()[0]
+ uid = self.get_id(record.uid)
+ self.uid = uid
+ _logger.debug("RECORD_DICT %s" % uid)
def process_url(self, node):
self.validate_xml_id(node.id)
-
+
res = {'name': node.name, 'url': node.url, 'target': node.target}
id = self.pool.get('ir.model.data')._update(self.cr, SUPERUSER_ID, \
@@ -889,6 +898,8 @@ class YamlInterpreter(object):
self.process_act_window(node)
elif is_report(node):
self.process_report(node)
+ elif is_session(node):
+ self.process_session(node)
elif is_workflow(node):
if isinstance(node, types.DictionaryType):
self.process_workflow(node)
diff --git a/openerp/tools/yaml_tag.py b/openerp/tools/yaml_tag.py
index b22787ddf98..c4d5f251479 100644
--- a/openerp/tools/yaml_tag.py
+++ b/openerp/tools/yaml_tag.py
@@ -32,6 +32,13 @@ class Record(YamlTag):
def __str__(self):
return '!record {model: %s, id: %s}:' % (str(self.model,), str(self.id,))
+class Session(YamlTag):
+ def __init__(self, uid, **kwargs):
+ self.uid = uid
+ super(Session, self).__init__(**kwargs)
+ def __str__(self):
+ return '!session {uid: %s}:' % (str(self.uid,))
+
class Python(YamlTag):
def __init__(self, model, severity=logging.ERROR, name="", **kwargs):
self.model= model
@@ -113,6 +120,11 @@ def record_constructor(loader, node):
assert "id" in kwargs, "'id' argument is required for !record"
return Record(**kwargs)
+def session_constructor(loader, node):
+ kwargs = loader.construct_mapping(node)
+ assert "uid" in kwargs, "'uid' argument is required for !session"
+ return Session(**kwargs)
+
def python_constructor(loader, node):
kwargs = loader.construct_mapping(node)
return Python(**kwargs)
@@ -182,6 +194,7 @@ def add_constructors():
yaml.add_constructor(u"!eval", eval_constructor)
yaml.add_multi_constructor(u"!ref", ref_constructor)
yaml.add_constructor(u"!ir_set", ir_set_constructor)
+ yaml.add_constructor(u"!session", session_constructor)
add_constructors()
From 5ec04f670303a984c53b11bcdc0c0fed0bd51720 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 4 Feb 2013 15:04:25 +0530
Subject: [PATCH 002/256] [IMP]add yml file based on new session tag which test
yml as per session user
bzr revid: sgo@tinyerp.com-20130204093425-blivw1198zxhsh2u
---
addons/sale/__openerp__.py | 3 ++-
addons/sale/test/sale_order_demo.yml | 16 ----------------
2 files changed, 2 insertions(+), 17 deletions(-)
delete mode 100644 addons/sale/test/sale_order_demo.yml
diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py
index 8cc4919e3a1..6ba3a02bb00 100644
--- a/addons/sale/__openerp__.py
+++ b/addons/sale/__openerp__.py
@@ -80,7 +80,8 @@ The Dashboard for the Sales Manager will include
],
'demo': ['sale_demo.xml'],
'test': [
- 'test/sale_order_demo.yml',
+ 'test/sale_order_salesmanager_demo.yml',
+ 'test/sale_order_salesman_demo.yml',
'test/manual_order_policy.yml',
'test/cancel_order.yml',
'test/delete_order.yml',
diff --git a/addons/sale/test/sale_order_demo.yml b/addons/sale/test/sale_order_demo.yml
deleted file mode 100644
index 7e5a223e8b0..00000000000
--- a/addons/sale/test/sale_order_demo.yml
+++ /dev/null
@@ -1,16 +0,0 @@
--
- In order to test process of the Sale Order, I create sale order
--
- !record {model: sale.order, id: sale_order_test1}:
- partner_id: base.res_partner_2
- note: Invoice after delivery
- payment_term: account.account_payment_term
- order_line:
- - product_id: product.product_product_7
- product_uom_qty: 8
--
- I verify that the onchange was correctly triggered
--
- !assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
- - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- - order_line[0].price_unit == 1350.0
From 72043cfbc34586a937918ea2ac1a5f7ab177fb25 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 4 Feb 2013 18:02:12 +0530
Subject: [PATCH 003/256] [ADD]yml files
bzr revid: sgo@tinyerp.com-20130204123212-psghdi2mnp4r2lut
---
addons/sale/test/sale_order_salesman_demo.yml | 32 +++++++++++++++++++
.../test/sale_order_salesmanager_demo.yml | 28 ++++++++++++++++
2 files changed, 60 insertions(+)
create mode 100644 addons/sale/test/sale_order_salesman_demo.yml
create mode 100644 addons/sale/test/sale_order_salesmanager_demo.yml
diff --git a/addons/sale/test/sale_order_salesman_demo.yml b/addons/sale/test/sale_order_salesman_demo.yml
new file mode 100644
index 00000000000..4e297249322
--- /dev/null
+++ b/addons/sale/test/sale_order_salesman_demo.yml
@@ -0,0 +1,32 @@
+-
+ In order to test process of the Sale Order, I create sale order
+-
+ Create a user 'Salesman'
+-
+ !record {model: res.users, id: res_users_salesman}:
+ company_id: base.main_company
+ name: Salesman
+ login: su
+ password: su
+ groups_id:
+ - base.group_sale_salesman_all_leads
+-
+ Create a user 'Salesman'
+-
+ Now, check the data with salesman
+-
+ !session {uid: res_users_salesman}:
+-
+ !record {model: sale.order, id: sale_order_test2}:
+ partner_id: base.res_partner_3
+ note: Invoice after delivery
+ payment_term: account.account_payment_term
+ order_line:
+ - product_id: product.product_product_7
+ product_uom_qty: 7
+-
+ I verify that the onchange was correctly triggered
+-
+ !assert {model: sale.order, id: sale.sale_order_test2, string: The onchange function of product was not correctly triggered}:
+ - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
+ - order_line[0].price_unit == 1350.0
diff --git a/addons/sale/test/sale_order_salesmanager_demo.yml b/addons/sale/test/sale_order_salesmanager_demo.yml
new file mode 100644
index 00000000000..f4d7750029d
--- /dev/null
+++ b/addons/sale/test/sale_order_salesmanager_demo.yml
@@ -0,0 +1,28 @@
+-
+ In order to test process of the Sale Order, I create sale order
+-
+ !record {model: res.users, id: res_users_salesmanager}:
+ company_id: base.main_company
+ name: Sales manager
+ login: sm
+ password: sm
+ groups_id:
+ - base.group_sale_manager
+-
+ Now, check the data with sales Manager
+-
+ !session {uid: res_users_salesmanager}:
+-
+ !record {model: sale.order, id: sale_order_test1}:
+ partner_id: base.res_partner_2
+ note: Invoice after delivery
+ payment_term: account.account_payment_term
+ order_line:
+ - product_id: product.product_product_7
+ product_uom_qty: 8
+-
+ I verify that the onchange was correctly triggered
+-
+ !assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
+ - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
+ - order_line[0].price_unit == 1350.0
From 74a681e9dbafa5759ada82ec9262fe893ed99f9f Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 18 Feb 2013 11:40:14 +0530
Subject: [PATCH 004/256] [IMP]revert changes as per new spec
bzr revid: sgo@tinyerp.com-20130218061014-lg6uk108vajx43mo
---
openerp/tools/yaml_import.py | 13 +------------
openerp/tools/yaml_tag.py | 13 -------------
2 files changed, 1 insertion(+), 25 deletions(-)
diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py
index 1629ee1b9be..9a8b2a6cd8d 100644
--- a/openerp/tools/yaml_import.py
+++ b/openerp/tools/yaml_import.py
@@ -88,9 +88,6 @@ def is_ir_set(node):
def is_string(node):
return isinstance(node, basestring)
-def is_session(node):
- return _is_yaml_mapping(node, yaml_tag.Session)
-
class RecordDictWrapper(dict):
"""
Used to pass a record as locals in eval:
@@ -763,7 +760,7 @@ class YamlInterpreter(object):
keyword = 'client_action_relate'
value = 'ir.actions.act_window,%s' % id
replace = node.replace or True
- self.pool.get('ir.model.data').ir_set(self.cr, self.uid, 'action', keyword, \
+ self.pool.get('ir.model.data').ir_set(self.cr, SUPERUSER_ID, 'action', keyword, \
node.id, [node.src_model], value, replace=replace, noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id)
# TODO add remove ir.model.data
@@ -779,12 +776,6 @@ class YamlInterpreter(object):
else:
self._log("Record not deleted.")
- def process_session(self, node):
- record, fields = node.items()[0]
- uid = self.get_id(record.uid)
- self.uid = uid
- _logger.debug("RECORD_DICT %s" % uid)
-
def process_url(self, node):
self.validate_xml_id(node.id)
@@ -898,8 +889,6 @@ class YamlInterpreter(object):
self.process_act_window(node)
elif is_report(node):
self.process_report(node)
- elif is_session(node):
- self.process_session(node)
elif is_workflow(node):
if isinstance(node, types.DictionaryType):
self.process_workflow(node)
diff --git a/openerp/tools/yaml_tag.py b/openerp/tools/yaml_tag.py
index c4d5f251479..b22787ddf98 100644
--- a/openerp/tools/yaml_tag.py
+++ b/openerp/tools/yaml_tag.py
@@ -32,13 +32,6 @@ class Record(YamlTag):
def __str__(self):
return '!record {model: %s, id: %s}:' % (str(self.model,), str(self.id,))
-class Session(YamlTag):
- def __init__(self, uid, **kwargs):
- self.uid = uid
- super(Session, self).__init__(**kwargs)
- def __str__(self):
- return '!session {uid: %s}:' % (str(self.uid,))
-
class Python(YamlTag):
def __init__(self, model, severity=logging.ERROR, name="", **kwargs):
self.model= model
@@ -120,11 +113,6 @@ def record_constructor(loader, node):
assert "id" in kwargs, "'id' argument is required for !record"
return Record(**kwargs)
-def session_constructor(loader, node):
- kwargs = loader.construct_mapping(node)
- assert "uid" in kwargs, "'uid' argument is required for !session"
- return Session(**kwargs)
-
def python_constructor(loader, node):
kwargs = loader.construct_mapping(node)
return Python(**kwargs)
@@ -194,7 +182,6 @@ def add_constructors():
yaml.add_constructor(u"!eval", eval_constructor)
yaml.add_multi_constructor(u"!ref", ref_constructor)
yaml.add_constructor(u"!ir_set", ir_set_constructor)
- yaml.add_constructor(u"!session", session_constructor)
add_constructors()
From 3be3625587d11fbb44f2df3ca0c920d58831ad1b Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 18 Feb 2013 12:58:21 +0530
Subject: [PATCH 005/256] [IMP]improve code as per new spec
bzr revid: sgo@tinyerp.com-20130218072821-c1x1bwvv1gp77w9t
---
addons/sale/test/sale_order_salesman_demo.yml | 3 ++-
addons/sale/test/sale_order_salesmanager_demo.yml | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/addons/sale/test/sale_order_salesman_demo.yml b/addons/sale/test/sale_order_salesman_demo.yml
index 4e297249322..dbaf35c7a8f 100644
--- a/addons/sale/test/sale_order_salesman_demo.yml
+++ b/addons/sale/test/sale_order_salesman_demo.yml
@@ -15,7 +15,8 @@
-
Now, check the data with salesman
-
- !session {uid: res_users_salesman}:
+ !context
+ 'uid': 'res_users_salesman'
-
!record {model: sale.order, id: sale_order_test2}:
partner_id: base.res_partner_3
diff --git a/addons/sale/test/sale_order_salesmanager_demo.yml b/addons/sale/test/sale_order_salesmanager_demo.yml
index f4d7750029d..47bb9081a55 100644
--- a/addons/sale/test/sale_order_salesmanager_demo.yml
+++ b/addons/sale/test/sale_order_salesmanager_demo.yml
@@ -11,7 +11,8 @@
-
Now, check the data with sales Manager
-
- !session {uid: res_users_salesmanager}:
+ !context
+ 'uid': 'res_users_salesmanager'
-
!record {model: sale.order, id: sale_order_test1}:
partner_id: base.res_partner_2
From e983e49f209d4ac7283a6cc034cfbc9e27c3138d Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 18 Feb 2013 15:40:43 +0530
Subject: [PATCH 006/256] [IMP]improve code
bzr revid: sgo@tinyerp.com-20130218101043-7hy8m5goylr7fv2t
---
addons/sale/test/sale_order_salesman_demo.yml | 6 ++----
addons/sale/test/sale_order_salesmanager_demo.yml | 4 +++-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/addons/sale/test/sale_order_salesman_demo.yml b/addons/sale/test/sale_order_salesman_demo.yml
index dbaf35c7a8f..56c781d1451 100644
--- a/addons/sale/test/sale_order_salesman_demo.yml
+++ b/addons/sale/test/sale_order_salesman_demo.yml
@@ -1,7 +1,7 @@
-
In order to test process of the Sale Order, I create sale order
-
- Create a user 'Salesman'
+ Create a user as 'Salesman'
-
!record {model: res.users, id: res_users_salesman}:
company_id: base.main_company
@@ -10,13 +10,11 @@
password: su
groups_id:
- base.group_sale_salesman_all_leads
--
- Create a user 'Salesman'
-
Now, check the data with salesman
-
!context
- 'uid': 'res_users_salesman'
+ uid: 'res_users_salesman'
-
!record {model: sale.order, id: sale_order_test2}:
partner_id: base.res_partner_3
diff --git a/addons/sale/test/sale_order_salesmanager_demo.yml b/addons/sale/test/sale_order_salesmanager_demo.yml
index 47bb9081a55..f09e1327a75 100644
--- a/addons/sale/test/sale_order_salesmanager_demo.yml
+++ b/addons/sale/test/sale_order_salesmanager_demo.yml
@@ -1,5 +1,7 @@
-
In order to test process of the Sale Order, I create sale order
+-
+ Create a user as 'Salesmanager'
-
!record {model: res.users, id: res_users_salesmanager}:
company_id: base.main_company
@@ -12,7 +14,7 @@
Now, check the data with sales Manager
-
!context
- 'uid': 'res_users_salesmanager'
+ uid: 'res_users_salesmanager'
-
!record {model: sale.order, id: sale_order_test1}:
partner_id: base.res_partner_2
From 095af7d4932006e94ec30d06889ea6e86594c3b4 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 18 Feb 2013 17:02:38 +0530
Subject: [PATCH 007/256] [IMP]remove extra spaces
bzr revid: sgo@tinyerp.com-20130218113238-x6wf0vxjmv0etwft
---
openerp/tools/yaml_import.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py
index 9a8b2a6cd8d..46168f40bae 100644
--- a/openerp/tools/yaml_import.py
+++ b/openerp/tools/yaml_import.py
@@ -775,10 +775,9 @@ class YamlInterpreter(object):
self.pool.get(node.model).unlink(self.cr, self.uid, ids)
else:
self._log("Record not deleted.")
-
+
def process_url(self, node):
self.validate_xml_id(node.id)
-
res = {'name': node.name, 'url': node.url, 'target': node.target}
id = self.pool.get('ir.model.data')._update(self.cr, SUPERUSER_ID, \
From 83af08294e7ca5b0f788ea99822a19ba4fd0660f Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 19 Feb 2013 12:57:34 +0530
Subject: [PATCH 008/256] [IMP]improve yaml for cancel order
bzr revid: sgo@tinyerp.com-20130219072734-70093wqxyory3arz
---
addons/sale/__openerp__.py | 2 +-
..._order.yml => cancel_order_sale_manager.yml} | 17 ++++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
rename addons/sale/test/{cancel_order.yml => cancel_order_sale_manager.yml} (89%)
diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py
index 6ba3a02bb00..26014e8cadd 100644
--- a/addons/sale/__openerp__.py
+++ b/addons/sale/__openerp__.py
@@ -83,7 +83,7 @@ The Dashboard for the Sales Manager will include
'test/sale_order_salesmanager_demo.yml',
'test/sale_order_salesman_demo.yml',
'test/manual_order_policy.yml',
- 'test/cancel_order.yml',
+ 'test/cancel_order_sale_manager.yml',
'test/delete_order.yml',
'test/edi_sale_order.yml',
],
diff --git a/addons/sale/test/cancel_order.yml b/addons/sale/test/cancel_order_sale_manager.yml
similarity index 89%
rename from addons/sale/test/cancel_order.yml
rename to addons/sale/test/cancel_order_sale_manager.yml
index 12b28fcb7e8..78a3af6113b 100644
--- a/addons/sale/test/cancel_order.yml
+++ b/addons/sale/test/cancel_order_sale_manager.yml
@@ -3,7 +3,21 @@
I confirm order (with at least 2 lines)
-
!workflow {model: sale.order, action: order_confirm, ref: sale_order_8}
-
+-
+ Create a user as 'Salesmanager for cancel order'
+-
+ !record {model: res.users, id: res_users_can_so}:
+ company_id: base.main_company
+ name: Sales manager for cancel
+ login: smc
+ password: smc
+ groups_id:
+ - base.group_sale_manager
+-
+ Now, check the data with sales Manager for cancel order.
+-
+ !context
+ uid: 'res_users_can_so'
-
I check state of order in 'Sale Order'.
-
@@ -32,6 +46,7 @@
ctx = context.copy()
ctx.update({"active_model": 'sale.order', "active_ids": [ref("sale_order_8")], "active_id":ref("sale_order_8")})
pay_id = self.create(cr, uid, {'advance_payment_method': 'fixed', 'amount': 5})
+ print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.",uid
self.create_invoices(cr, uid, [pay_id], context=ctx)
invoice_ids = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_8")).invoice_ids
From 3e01083a1bb6bac3e2b46734cbd154a685283634 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 19 Feb 2013 14:23:28 +0530
Subject: [PATCH 009/256] [IMP]improve code
bzr revid: sgo@tinyerp.com-20130219085328-um1q8bs4iw773ivs
---
addons/sale/test/cancel_order_sale_manager.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/addons/sale/test/cancel_order_sale_manager.yml b/addons/sale/test/cancel_order_sale_manager.yml
index 78a3af6113b..770d1a5924d 100644
--- a/addons/sale/test/cancel_order_sale_manager.yml
+++ b/addons/sale/test/cancel_order_sale_manager.yml
@@ -46,7 +46,6 @@
ctx = context.copy()
ctx.update({"active_model": 'sale.order', "active_ids": [ref("sale_order_8")], "active_id":ref("sale_order_8")})
pay_id = self.create(cr, uid, {'advance_payment_method': 'fixed', 'amount': 5})
- print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.",uid
self.create_invoices(cr, uid, [pay_id], context=ctx)
invoice_ids = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_8")).invoice_ids
From d4a1cec260ffa9fb1c10430df6e8c38880ecaf6a Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 19 Feb 2013 18:49:21 +0530
Subject: [PATCH 010/256] [IMP]improve code and yml
bzr revid: sgo@tinyerp.com-20130219131921-nbfs273xujvriomu
---
addons/sale/__openerp__.py | 4 +-
addons/sale/test/create_sale_users.yml | 20 +++++++++
addons/sale/test/sale_order_demo.yml | 41 +++++++++++++++++++
.../test/sale_order_salesmanager_demo.yml | 31 --------------
4 files changed, 63 insertions(+), 33 deletions(-)
create mode 100644 addons/sale/test/create_sale_users.yml
create mode 100644 addons/sale/test/sale_order_demo.yml
delete mode 100644 addons/sale/test/sale_order_salesmanager_demo.yml
diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py
index 6ba3a02bb00..2bea86b41af 100644
--- a/addons/sale/__openerp__.py
+++ b/addons/sale/__openerp__.py
@@ -80,8 +80,8 @@ The Dashboard for the Sales Manager will include
],
'demo': ['sale_demo.xml'],
'test': [
- 'test/sale_order_salesmanager_demo.yml',
- 'test/sale_order_salesman_demo.yml',
+ 'test/create_sale_users.yml',
+ 'test/sale_order_demo.yml',
'test/manual_order_policy.yml',
'test/cancel_order.yml',
'test/delete_order.yml',
diff --git a/addons/sale/test/create_sale_users.yml b/addons/sale/test/create_sale_users.yml
new file mode 100644
index 00000000000..a61c4bd7c99
--- /dev/null
+++ b/addons/sale/test/create_sale_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Salesmanager'
+-
+ !record {model: res.users, id: res_users_salesmanager}:
+ company_id: base.main_company
+ name: Sales manager
+ login: sm
+ password: sm
+ groups_id:
+ - base.group_sale_manager
+-
+ Create a user as 'Salesman'
+-
+ !record {model: res.users, id: res_users_salesman}:
+ company_id: base.main_company
+ name: Salesman
+ login: su
+ password: su
+ groups_id:
+ - base.group_sale_salesman_all_leads
diff --git a/addons/sale/test/sale_order_demo.yml b/addons/sale/test/sale_order_demo.yml
new file mode 100644
index 00000000000..84aaa43a3cf
--- /dev/null
+++ b/addons/sale/test/sale_order_demo.yml
@@ -0,0 +1,41 @@
+-
+ Test the data with salesman,
+-
+ !context
+ uid: 'res_users_salesman'
+-
+ In order to test process of the Sale Order, I create sale order
+-
+ !record {model: sale.order, id: sale_order_test1}:
+ partner_id: base.res_partner_2
+ note: Invoice after delivery
+ payment_term: account.account_payment_term
+ order_line:
+ - product_id: product.product_product_7
+ product_uom_qty: 8
+-
+ I verify that the onchange was correctly triggered
+-
+ !assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
+ - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
+ - order_line[0].price_unit == 1350.0
+ - order_line[0].product_uom_qty == 8
+ - order_line[0].product_uom.id == ref('product.product_uom_unit')
+
+-
+ I create another sale order
+-
+ !record {model: sale.order, id: sale_order_test2}:
+ partner_id: base.res_partner_2
+ order_line:
+ - product_id: product.product_product_7
+ product_uom_qty: 16
+ product_uom: product.product_uom_dozen
+-
+ I verify that the onchange was correctly triggered
+-
+ !assert {model: sale.order, id: sale.sale_order_test2, string: The onchange function of product was not correctly triggered}:
+ - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
+ - order_line[0].price_unit == 1350.0 * 12
+ - order_line[0].product_uom.id == ref('product.product_uom_dozen')
+ - order_line[0].product_uom_qty == 16
\ No newline at end of file
diff --git a/addons/sale/test/sale_order_salesmanager_demo.yml b/addons/sale/test/sale_order_salesmanager_demo.yml
deleted file mode 100644
index f09e1327a75..00000000000
--- a/addons/sale/test/sale_order_salesmanager_demo.yml
+++ /dev/null
@@ -1,31 +0,0 @@
--
- In order to test process of the Sale Order, I create sale order
--
- Create a user as 'Salesmanager'
--
- !record {model: res.users, id: res_users_salesmanager}:
- company_id: base.main_company
- name: Sales manager
- login: sm
- password: sm
- groups_id:
- - base.group_sale_manager
--
- Now, check the data with sales Manager
--
- !context
- uid: 'res_users_salesmanager'
--
- !record {model: sale.order, id: sale_order_test1}:
- partner_id: base.res_partner_2
- note: Invoice after delivery
- payment_term: account.account_payment_term
- order_line:
- - product_id: product.product_product_7
- product_uom_qty: 8
--
- I verify that the onchange was correctly triggered
--
- !assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
- - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- - order_line[0].price_unit == 1350.0
From 33636c1dcfdd95e231d5e6b202775129d8e8ddd8 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 19 Feb 2013 18:57:12 +0530
Subject: [PATCH 011/256] [REM]remove file
bzr revid: sgo@tinyerp.com-20130219132712-hr0sns52uy0821th
---
addons/sale/test/sale_order_salesman_demo.yml | 31 -------------------
1 file changed, 31 deletions(-)
delete mode 100644 addons/sale/test/sale_order_salesman_demo.yml
diff --git a/addons/sale/test/sale_order_salesman_demo.yml b/addons/sale/test/sale_order_salesman_demo.yml
deleted file mode 100644
index 56c781d1451..00000000000
--- a/addons/sale/test/sale_order_salesman_demo.yml
+++ /dev/null
@@ -1,31 +0,0 @@
--
- In order to test process of the Sale Order, I create sale order
--
- Create a user as 'Salesman'
--
- !record {model: res.users, id: res_users_salesman}:
- company_id: base.main_company
- name: Salesman
- login: su
- password: su
- groups_id:
- - base.group_sale_salesman_all_leads
--
- Now, check the data with salesman
--
- !context
- uid: 'res_users_salesman'
--
- !record {model: sale.order, id: sale_order_test2}:
- partner_id: base.res_partner_3
- note: Invoice after delivery
- payment_term: account.account_payment_term
- order_line:
- - product_id: product.product_product_7
- product_uom_qty: 7
--
- I verify that the onchange was correctly triggered
--
- !assert {model: sale.order, id: sale.sale_order_test2, string: The onchange function of product was not correctly triggered}:
- - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- - order_line[0].price_unit == 1350.0
From fad27029faa105e9ca8d934d00680373d1487dc5 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Wed, 20 Feb 2013 10:27:34 +0530
Subject: [PATCH 012/256] [IMP] add yml file & improve code in crm
bzr revid: fka@tinyerp.com-20130220045734-auwnnd55vpimt3ep
---
addons/crm/__openerp__.py | 1 +
addons/crm/security/ir.model.access.csv | 1 +
.../crm/test/process/lead2opportunity2win.yml | 5 +++
.../lead2opportunity_assign_salesmen.yml | 45 ++++++++++---------
addons/crm/test/process/merge_opportunity.yml | 1 -
addons/crm/test/process/phonecalls.yml | 5 +++
addons/crm/test/ui/crm_access_group_users.yml | 20 +++++++++
addons/mail/security/ir.model.access.csv | 4 +-
8 files changed, 59 insertions(+), 23 deletions(-)
create mode 100644 addons/crm/test/ui/crm_access_group_users.yml
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index 0dd075d48e6..1d0ea32c0bc 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -103,6 +103,7 @@ Dashboard for CRM will include:
'crm_action_rule_demo.xml',
],
'test': [
+ 'test/ui/crm_access_group_users.yml',
'test/process/communication_with_customer.yml',
'test/process/lead2opportunity2win.yml',
'test/process/lead2opportunity_assign_salesmen.yml',
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 74394d3c5ce..0f2cf267b47 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -29,6 +29,7 @@ access_calendar_attendee_crm_manager,calendar.attendee.crm.manager,model_calenda
access_res_partner,res.partner.crm.user,base.model_res_partner,base.group_sale_salesman,1,1,1,0
access_res_partner_category,res.partner.category.crm.user,base.model_res_partner_category,base.group_sale_salesman,1,1,1,0
mail_mailgate_thread,mail.thread,mail.model_mail_thread,base.group_sale_salesman,1,1,1,1
+access_mail_message,mail.message,mail.model_mail_message,base.group_sale_salesman,1,1,1,0
access_crm_case_categ_manager,crm.case.categ manager,model_crm_case_categ,base.group_sale_manager,1,1,1,1
access_base_action_rule_manager,base.action.rule manager,base_action_rule.model_base_action_rule,base.group_sale_manager,1,1,1,1
access_crm_lead_report_user,crm.lead.report user,model_crm_lead_report,base.group_sale_salesman,1,1,1,1
diff --git a/addons/crm/test/process/lead2opportunity2win.yml b/addons/crm/test/process/lead2opportunity2win.yml
index 94e7149ef77..fc56b513588 100644
--- a/addons/crm/test/process/lead2opportunity2win.yml
+++ b/addons/crm/test/process/lead2opportunity2win.yml
@@ -1,3 +1,8 @@
+-
+ Now, check the data with Salesman.
+-
+ !context
+ uid: 'crm_res_users_salesman'
-
In order to test the conversion of a lead into a opportunity,
-
diff --git a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
index 0edd3b8edce..0d823dc87f4 100644
--- a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
+++ b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
@@ -1,5 +1,30 @@
-
During a lead to opp conversion, salesmen should be assigned to leads following the round-robin method. Start by creating 6 leads (1 to 6) and 4 salesmen (A to D).
+-
+ !record {model: res.users, id: test_res_user_01}:
+ name: 'Test user A'
+ login: 'tua'
+ new_password: 'tua'
+-
+ !record {model: res.users, id: test_res_user_02}:
+ name: 'Test user B'
+ login: 'tub'
+ new_password: 'tub'
+-
+ !record {model: res.users, id: test_res_user_03}:
+ name: 'Test user C'
+ login: 'tuc'
+ new_password: 'tuc'
+-
+ !record {model: res.users, id: test_res_user_04}:
+ name: 'Test user D'
+ login: 'tud'
+ new_password: 'tud'
+-
+ Now, check the data with Salesman.
+-
+ !context
+ uid: 'crm_res_users_salesman'
-
!record {model: crm.lead, id: test_crm_lead_01}:
type: 'lead'
@@ -36,26 +61,6 @@
name: 'Test lead 6'
partner_name: 'Agrolait SuperSeed SA'
stage_id: stage_lead1
--
- !record {model: res.users, id: test_res_user_01}:
- name: 'Test user A'
- login: 'tua'
- new_password: 'tua'
--
- !record {model: res.users, id: test_res_user_02}:
- name: 'Test user B'
- login: 'tub'
- new_password: 'tub'
--
- !record {model: res.users, id: test_res_user_03}:
- name: 'Test user C'
- login: 'tuc'
- new_password: 'tuc'
--
- !record {model: res.users, id: test_res_user_04}:
- name: 'Test user D'
- login: 'tud'
- new_password: 'tud'
-
I create a mass convert wizard and convert all the leads.
-
diff --git a/addons/crm/test/process/merge_opportunity.yml b/addons/crm/test/process/merge_opportunity.yml
index c50af5214e6..e9027e228d7 100644
--- a/addons/crm/test/process/merge_opportunity.yml
+++ b/addons/crm/test/process/merge_opportunity.yml
@@ -1,4 +1,3 @@
-
-
During a mixed merge (involving leads and opps), data should be handled a certain way following their type (m2o, m2m, text, ...) Start by creating two leads and an opp.
-
diff --git a/addons/crm/test/process/phonecalls.yml b/addons/crm/test/process/phonecalls.yml
index 51a933f3223..a71bfc05e03 100644
--- a/addons/crm/test/process/phonecalls.yml
+++ b/addons/crm/test/process/phonecalls.yml
@@ -1,3 +1,8 @@
+-
+ Now, check the data with Salesman.
+-
+ !context
+ uid: 'crm_res_users_salesman'
-
I schedule a phone call with a customer.
-
diff --git a/addons/crm/test/ui/crm_access_group_users.yml b/addons/crm/test/ui/crm_access_group_users.yml
new file mode 100644
index 00000000000..194c202ef15
--- /dev/null
+++ b/addons/crm/test/ui/crm_access_group_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Salesmanager'
+-
+ !record {model: res.users, id: crm_res_users_salesmanager}:
+ company_id: base.main_company
+ name: Sales manager
+ login: csm
+ password: csm
+ groups_id:
+ - base.group_sale_manager
+-
+ Create a user as 'Salesman'
+-
+ !record {model: res.users, id: crm_res_users_salesman}:
+ company_id: base.main_company
+ name: Salesman
+ login: csu
+ password: csu
+ groups_id:
+ - base.group_sale_salesman_all_leads
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index 1141a3f928d..8faa06b0aa5 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -7,12 +7,12 @@ access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,0,0
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
-access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0
+access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,1,0
access_mail_notification_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0
access_mail_notification_system,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1
access_mail_group_all,mail.group.all,model_mail_group,,1,0,0,0
access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
-access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
+access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,1,0
access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
From e07255568279284eebf4e337131ec7fb6af3984d Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Wed, 20 Feb 2013 11:33:26 +0530
Subject: [PATCH 013/256] [IMP]improve yml give them to appropriate access
rights
bzr revid: sgo@tinyerp.com-20130220060326-q22mdh5yob7erh7e
---
addons/sale/test/cancel_order.yml | 5 +++++
addons/sale/test/delete_order.yml | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/addons/sale/test/cancel_order.yml b/addons/sale/test/cancel_order.yml
index 12b28fcb7e8..48c6b91d2c4 100644
--- a/addons/sale/test/cancel_order.yml
+++ b/addons/sale/test/cancel_order.yml
@@ -1,3 +1,8 @@
+-
+ Salesman can also cancel order therfore test with that user which have salesman rights,
+-
+ !context
+ uid: 'res_users_salesman'
-
In order to test the cancel sale order.
I confirm order (with at least 2 lines)
diff --git a/addons/sale/test/delete_order.yml b/addons/sale/test/delete_order.yml
index abc68cb3af3..69c89020ad9 100644
--- a/addons/sale/test/delete_order.yml
+++ b/addons/sale/test/delete_order.yml
@@ -1,3 +1,8 @@
+-
+ Sales manager can only delete order therfore test with that user which have sales manager rights,
+-
+ !context
+ uid: 'res_users_salesmanager'
-
I try to delete In progress order and check Error Message.
-
From 0dd9f460dad5bc1ef93bcf28370c08341d01bfb4 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Wed, 20 Feb 2013 12:07:25 +0530
Subject: [PATCH 014/256] [IMP] improve yml
bzr revid: fka@tinyerp.com-20130220063725-dpa9tr2j57dor16c
---
addons/crm/security/ir.model.access.csv | 1 +
addons/crm/test/process/cancel_lead.yml | 19 +++++++++++--------
.../process/communication_with_customer.yml | 5 +++++
.../crm/test/process/lead2opportunity2win.yml | 2 +-
.../lead2opportunity_assign_salesmen.yml | 6 +++---
addons/crm/test/ui/crm_demo.yml | 12 ++++++++----
addons/crm/test/ui/delete_lead.yml | 7 ++++++-
addons/mail/security/ir.model.access.csv | 2 +-
8 files changed, 36 insertions(+), 18 deletions(-)
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 0f2cf267b47..cd460cf35d9 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -30,6 +30,7 @@ access_res_partner,res.partner.crm.user,base.model_res_partner,base.group_sale_s
access_res_partner_category,res.partner.category.crm.user,base.model_res_partner_category,base.group_sale_salesman,1,1,1,0
mail_mailgate_thread,mail.thread,mail.model_mail_thread,base.group_sale_salesman,1,1,1,1
access_mail_message,mail.message,mail.model_mail_message,base.group_sale_salesman,1,1,1,0
+mail_mail_message,mail.message,mail.model_mail_message,base.group_sale_manager,1,1,1,1
access_crm_case_categ_manager,crm.case.categ manager,model_crm_case_categ,base.group_sale_manager,1,1,1,1
access_base_action_rule_manager,base.action.rule manager,base_action_rule.model_base_action_rule,base.group_sale_manager,1,1,1,1
access_crm_lead_report_user,crm.lead.report user,model_crm_lead_report,base.group_sale_salesman,1,1,1,1
diff --git a/addons/crm/test/process/cancel_lead.yml b/addons/crm/test/process/cancel_lead.yml
index edaf844952a..ae2b6294c68 100644
--- a/addons/crm/test/process/cancel_lead.yml
+++ b/addons/crm/test/process/cancel_lead.yml
@@ -1,10 +1,13 @@
-
- I cancel unqualified lead.
+ Salesman cancel unqualified lead.
+-
+ !context
+ uid: 'crm_res_users_salesman'
-
!python {model: crm.lead}: |
self.case_cancel(cr, uid, [ref("crm_case_1")])
-
- I check cancelled lead.
+ Salesman check cancelled lead.
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref('crm_case_1'))
@@ -12,34 +15,34 @@
assert lead.state == 'cancel', "Opportunity is not in 'cancel' state."
assert lead.probability == 0.0, 'Opportunity is probably wrong and should be 0.0.'
-
- I reset cancelled lead into unqualified lead.
+ Salesman reset cancelled lead into unqualified lead.
-
!python {model: crm.lead}: |
self.case_reset(cr, uid, [ref("crm_case_1")])
-
- I check unqualified lead after reset.
+ Salesman check unqualified lead after reset.
-
!assert {model: crm.lead, id: crm.crm_case_1, string: Lead is in draft state}:
- state == "draft"
-
- I re-open the lead
+ Salesman re-open the lead
-
!python {model: crm.lead}: |
self.case_open(cr, uid, [ref("crm_case_1")])
-
- I check stage and state of the re-opened lead
+ Salesman check stage and state of the re-opened lead
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref('crm.crm_case_1'))
assert lead.stage_id.id == ref('crm.stage_lead2'), "Opportunity stage should be 'Qualification'."
assert lead.state == 'open', "Opportunity should be in 'open' state."
-
- I escalate the lead to parent team.
+ Salesman escalate the lead to parent team.
-
!python {model: crm.lead}: |
self.case_escalate(cr, uid, [ref("crm_case_1")])
-
- I check the lead is correctly escalated to the parent team.
+ Salesman check the lead is correctly escalated to the parent team.
-
!assert {model: crm.lead, id: crm.crm_case_1, string: Escalate lead to parent team}:
- section_id.name == "Support Department"
diff --git a/addons/crm/test/process/communication_with_customer.yml b/addons/crm/test/process/communication_with_customer.yml
index 221a4ab634f..4a36aeebd02 100644
--- a/addons/crm/test/process/communication_with_customer.yml
+++ b/addons/crm/test/process/communication_with_customer.yml
@@ -1,3 +1,8 @@
+-
+ Salesman communication with customer.
+-
+ !context
+ uid: 'crm_res_users_salesman'
-
Customer interested in our product, so he sends request by email to get more details.
-
diff --git a/addons/crm/test/process/lead2opportunity2win.yml b/addons/crm/test/process/lead2opportunity2win.yml
index fc56b513588..c69eb09ac31 100644
--- a/addons/crm/test/process/lead2opportunity2win.yml
+++ b/addons/crm/test/process/lead2opportunity2win.yml
@@ -1,5 +1,5 @@
-
- Now, check the data with Salesman.
+ Salesman convert the lead into opportunity.
-
!context
uid: 'crm_res_users_salesman'
diff --git a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
index 0d823dc87f4..9583c6034bf 100644
--- a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
+++ b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
@@ -1,5 +1,5 @@
-
- During a lead to opp conversion, salesmen should be assigned to leads following the round-robin method. Start by creating 6 leads (1 to 6) and 4 salesmen (A to D).
+ During a lead to opp conversion, salesmen should be assigned to leads following the round-robin method. Start by creating 4 salesmen (A to D) and 6 leads (1 to 6).
-
!record {model: res.users, id: test_res_user_01}:
name: 'Test user A'
@@ -21,7 +21,7 @@
login: 'tud'
new_password: 'tud'
-
- Now, check the data with Salesman.
+ Salesman creates lead.
-
!context
uid: 'crm_res_users_salesman'
@@ -62,7 +62,7 @@
partner_name: 'Agrolait SuperSeed SA'
stage_id: stage_lead1
-
- I create a mass convert wizard and convert all the leads.
+ Salesman create a mass convert wizard and convert all the leads.
-
!python {model: crm.lead2opportunity.partner.mass}: |
context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01")})
diff --git a/addons/crm/test/ui/crm_demo.yml b/addons/crm/test/ui/crm_demo.yml
index 9154ffdf513..85ed1cf7403 100644
--- a/addons/crm/test/ui/crm_demo.yml
+++ b/addons/crm/test/ui/crm_demo.yml
@@ -1,5 +1,9 @@
+
-
- I create a lead record to call a partner onchange, stage onchange and mailing opt-in onchange method.
+ Sales manager create a lead record to call a partner onchange, stage onchange and mailing opt-in onchange method.
+-
+ !context
+ uid: 'crm_res_users_salesmanager'
-
!record {model: crm.lead, id: crm_case_25}:
name: 'Need more info about your pc2'
@@ -8,7 +12,7 @@
stage_id: crm.stage_lead1
state: draft
-
- I create a lead record to call a mailing opt-out onchange method.
+ Sales manager create a lead record to call a mailing opt-out onchange method.
-
!record {model: crm.lead, id: crm_case_18}:
name: 'Need 20 Days of Consultancy'
@@ -16,13 +20,13 @@
state: draft
opt_out: True
-
- I create a phonecall record to call a partner onchange method.
+ Sales manager create a phonecall record to call a partner onchange method.
-
!record {model: crm.phonecall, id: crm_phonecall_5}:
name: 'Bad time'
partner_id: base.res_partner_5
-
- I set the next stage to "New" for the lead.
+ Sales manager set the next stage to "New" for the lead.
-
!python {model: crm.lead}: |
self.stage_next(cr, uid, [ref("crm_case_4")], context={'stage_type': 'lead'})
diff --git a/addons/crm/test/ui/delete_lead.yml b/addons/crm/test/ui/delete_lead.yml
index 49713039f22..f68061ba6cd 100644
--- a/addons/crm/test/ui/delete_lead.yml
+++ b/addons/crm/test/ui/delete_lead.yml
@@ -1,5 +1,10 @@
-
- I Unlink the Lead.
+ Sales manager can delete lead.
+-
+ !context
+ uid: 'crm_res_users_salesmanager'
+-
+ Sales manager Unlink the Lead.
-
!python {model: crm.lead}: |
self.unlink(cr, uid, [ref("crm_case_4")])
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index 8faa06b0aa5..81b2356938b 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -1,7 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_all,mail.message.all,model_mail_message,,1,0,0,0
access_mail_message_user,mail.message.user,model_mail_message,base.group_user,1,1,1,1
-access_mail_mail_all,mail.mail.all,model_mail_mail,,1,0,0,0
+access_mail_mail_all,mail.mail.all,model_mail_mail,,1,0,1,0
access_mail_mail_user,mail.mail.user,model_mail_mail,base.group_user,1,1,1,0
access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
From 99cb34cee9d978e883e2922004230a094a9037a4 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Wed, 20 Feb 2013 13:02:36 +0530
Subject: [PATCH 015/256] [IMP] add access rights in merge_opportunity.yml
bzr revid: fka@tinyerp.com-20130220073236-7v3i73ui0zua4uo9
---
addons/crm/test/process/merge_opportunity.yml | 5 ++++-
addons/crm/test/process/phonecalls.yml | 2 +-
addons/crm/test/ui/crm_access_group_users.yml | 2 +-
addons/crm/test/ui/delete_lead.yml | 4 +---
4 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/addons/crm/test/process/merge_opportunity.yml b/addons/crm/test/process/merge_opportunity.yml
index e9027e228d7..a85372bdf7b 100644
--- a/addons/crm/test/process/merge_opportunity.yml
+++ b/addons/crm/test/process/merge_opportunity.yml
@@ -1,5 +1,8 @@
-
- During a mixed merge (involving leads and opps), data should be handled a certain way following their type (m2o, m2m, text, ...) Start by creating two leads and an opp.
+ During a mixed merge (involving leads and opps), data should be handled a certain way following their type (m2o, m2m, text, ...) Start by creating two leads and an opp and giving the rights of Sales manager.
+-
+ !context
+ uid: 'crm_res_users_salesmanager'
-
!record {model: crm.lead, id: test_crm_lead_01}:
type: 'lead'
diff --git a/addons/crm/test/process/phonecalls.yml b/addons/crm/test/process/phonecalls.yml
index a71bfc05e03..63df8ac89c2 100644
--- a/addons/crm/test/process/phonecalls.yml
+++ b/addons/crm/test/process/phonecalls.yml
@@ -1,5 +1,5 @@
-
- Now, check the data with Salesman.
+ Salesman check the phone calls data.
-
!context
uid: 'crm_res_users_salesman'
diff --git a/addons/crm/test/ui/crm_access_group_users.yml b/addons/crm/test/ui/crm_access_group_users.yml
index 194c202ef15..cc5be01ee85 100644
--- a/addons/crm/test/ui/crm_access_group_users.yml
+++ b/addons/crm/test/ui/crm_access_group_users.yml
@@ -1,5 +1,5 @@
-
- Create a user as 'Salesmanager'
+ Create a user as 'Salesmanager'
-
!record {model: res.users, id: crm_res_users_salesmanager}:
company_id: base.main_company
diff --git a/addons/crm/test/ui/delete_lead.yml b/addons/crm/test/ui/delete_lead.yml
index f68061ba6cd..10d2de82e7e 100644
--- a/addons/crm/test/ui/delete_lead.yml
+++ b/addons/crm/test/ui/delete_lead.yml
@@ -1,10 +1,8 @@
-
- Sales manager can delete lead.
+ Sales manager Unlink the Lead.
-
!context
uid: 'crm_res_users_salesmanager'
--
- Sales manager Unlink the Lead.
-
!python {model: crm.lead}: |
self.unlink(cr, uid, [ref("crm_case_4")])
From 49ef7d89a8b525acf59fd6f69d9131ec68e2d147 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Wed, 20 Feb 2013 15:02:27 +0530
Subject: [PATCH 016/256] [IMP] add yml file & add access rights in event
bzr revid: fka@tinyerp.com-20130220093227-uf6oom9fginl2jdc
---
addons/event/__openerp__.py | 2 +-
addons/event/security/ir.model.access.csv | 2 ++
.../event/test/process/event_draft2done.yml | 5 +++++
addons/event/test/ui/event_users.yml | 20 +++++++++++++++++++
4 files changed, 28 insertions(+), 1 deletion(-)
create mode 100644 addons/event/test/ui/event_users.yml
diff --git a/addons/event/__openerp__.py b/addons/event/__openerp__.py
index 54b77c82f69..e8ecb09bf03 100644
--- a/addons/event/__openerp__.py
+++ b/addons/event/__openerp__.py
@@ -52,7 +52,7 @@ Key Features
'email_template.xml',
],
'demo': ['event_demo.xml'],
- 'test': ['test/process/event_draft2done.yml'],
+ 'test': ['test/ui/event_users.yml','test/process/event_draft2done.yml'],
'css': ['static/src/css/event.css'],
'installable': True,
'application': True,
diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv
index b6cdc4c4ec6..0801b8306f0 100644
--- a/addons/event/security/ir.model.access.csv
+++ b/addons/event/security/ir.model.access.csv
@@ -6,3 +6,5 @@ access_event_registration,event.registration,model_event_registration,event.grou
access_report_event_registration,report.event.registration,model_report_event_registration,event.group_event_user,1,1,1,1
access_event_event_portal,event.event,model_event_event,,1,0,0,0
access_event_registration_portal,event.registration,model_event_registration,,1,0,0,0
+access_event_partner,res.partner,model_res_partner,event.group_event_user,1,1,1,0
+access_mail_message,mail.message,mail.model_mail_message,event.group_event_user,1,1,1,0
diff --git a/addons/event/test/process/event_draft2done.yml b/addons/event/test/process/event_draft2done.yml
index eb40340befd..e485cb5af6c 100644
--- a/addons/event/test/process/event_draft2done.yml
+++ b/addons/event/test/process/event_draft2done.yml
@@ -1,3 +1,8 @@
+-
+ give the access rights of Event user to organize an event and also do registration.
+-
+ !context
+ uid: 'res_users_eventuser'
-
I want to organize an event,
into this conference I should create two registration.
diff --git a/addons/event/test/ui/event_users.yml b/addons/event/test/ui/event_users.yml
new file mode 100644
index 00000000000..34564d6c433
--- /dev/null
+++ b/addons/event/test/ui/event_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Event manager'
+-
+ !record {model: res.users, id: res_users_eventmanager}:
+ company_id: base.main_company
+ name: Event manager
+ login: em
+ password: em
+ groups_id:
+ - event.group_event_manager
+-
+ Create a user as 'Event user'
+-
+ !record {model: res.users, id: res_users_eventuser}:
+ company_id: base.main_company
+ name: User
+ login: eu
+ password: eu
+ groups_id:
+ - event.group_event_user
\ No newline at end of file
From 96ec54e330a394a9aa0b92faad7280ec97512f98 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Wed, 20 Feb 2013 18:45:24 +0530
Subject: [PATCH 017/256] [IMP] add yml file in purchase
bzr revid: fka@tinyerp.com-20130220131524-joc0qyy5dfdc1o6j
---
addons/purchase/__openerp__.py | 3 ++-
addons/purchase/purchase_order_demo.yml | 5 +++++
addons/purchase/test/process/cancel_order.yml | 5 +++++
.../test/process/generate_invoice_from_reception.yml | 5 +++++
addons/purchase/test/process/merge_order.yml | 5 +++++
addons/purchase/test/ui/delete_order.yml | 5 +++++
6 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/addons/purchase/__openerp__.py b/addons/purchase/__openerp__.py
index a7c66344f4a..3f3acdf9f0f 100644
--- a/addons/purchase/__openerp__.py
+++ b/addons/purchase/__openerp__.py
@@ -67,6 +67,8 @@ Dashboard / Reports for Purchase Management will include:
'res_config_view.xml',
],
'test': [
+ 'test/ui/purchase_users.yml',
+ 'purchase_order_demo.yml',
'test/process/cancel_order.yml',
'test/process/rfq2order2done.yml',
'test/process/generate_invoice_from_reception.yml',
@@ -79,7 +81,6 @@ Dashboard / Reports for Purchase Management will include:
'test/ui/delete_order.yml',
],
'demo': [
- 'purchase_order_demo.yml',
'purchase_demo.xml',
],
'installable': True,
diff --git a/addons/purchase/purchase_order_demo.yml b/addons/purchase/purchase_order_demo.yml
index 9c83ff84406..fb49bfc1248 100644
--- a/addons/purchase/purchase_order_demo.yml
+++ b/addons/purchase/purchase_order_demo.yml
@@ -1,3 +1,8 @@
+-
+ Give access rights of Purchase user to create purchase order
+-
+ !context
+ uid: 'res_users_purchase_user'
-
!record {model: purchase.order, id: purchase_order_1}:
partner_id: base.res_partner_1
diff --git a/addons/purchase/test/process/cancel_order.yml b/addons/purchase/test/process/cancel_order.yml
index 80fd7b5ff57..d3695cba6c8 100644
--- a/addons/purchase/test/process/cancel_order.yml
+++ b/addons/purchase/test/process/cancel_order.yml
@@ -1,3 +1,8 @@
+-
+ Purchase user can also cancel order therfore test with that user which have Purchase user rights.
+-
+ !context
+ uid: 'res_users_purchase_user'
-
In order to test the cancel flow, I start it from canceling confirmed purchase order.
-
diff --git a/addons/purchase/test/process/generate_invoice_from_reception.yml b/addons/purchase/test/process/generate_invoice_from_reception.yml
index 6390413b418..565f52f258f 100644
--- a/addons/purchase/test/process/generate_invoice_from_reception.yml
+++ b/addons/purchase/test/process/generate_invoice_from_reception.yml
@@ -1,3 +1,8 @@
+-
+ Purchase user can create an invoice for order on receptions therfore test with that user which have Purchase user rights.
+-
+ !context
+ uid: 'res_users_purchase_user'
-
I confirm another order where invoice control is 'Bases on incoming shipments'.
-
diff --git a/addons/purchase/test/process/merge_order.yml b/addons/purchase/test/process/merge_order.yml
index fb39bed310c..fdc7f0092e7 100644
--- a/addons/purchase/test/process/merge_order.yml
+++ b/addons/purchase/test/process/merge_order.yml
@@ -1,3 +1,8 @@
+-
+ Give access rights of Purchase user to merge two RFQ.
+-
+ !context
+ uid: 'res_users_purchase_user'
-
In order to merge RFQ, I merge two RFQ which has same supplier and check new merged order.
-
diff --git a/addons/purchase/test/ui/delete_order.yml b/addons/purchase/test/ui/delete_order.yml
index f8795007622..6f9ba54e89d 100644
--- a/addons/purchase/test/ui/delete_order.yml
+++ b/addons/purchase/test/ui/delete_order.yml
@@ -1,3 +1,8 @@
+-
+ Give access rights of Purchase user to delete purchase order
+-
+ !context
+ uid: 'res_users_purchase_user'
-
In order to test to delete process on purchase order.
-
From 5252d9ac9cdf02f6a405bbe041cb54e62d7d4428 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 21 Feb 2013 10:44:59 +0530
Subject: [PATCH 018/256] [IMP] add yml file in purchase_reqisition
bzr revid: fka@tinyerp.com-20130221051459-v20ilhc128ipw6yi
---
addons/purchase/test/ui/purchase_users.yml | 20 +++++++++++++++++++
addons/purchase_requisition/__openerp__.py | 1 +
.../test/cancel_purchase_requisition.yml | 5 +++++
.../test/purchase_reqisition_users.yml | 20 +++++++++++++++++++
.../test/purchase_requisition.yml | 5 +++++
.../test/purchase_requisition_demo.yml | 5 +++++
6 files changed, 56 insertions(+)
create mode 100644 addons/purchase/test/ui/purchase_users.yml
create mode 100644 addons/purchase_requisition/test/purchase_reqisition_users.yml
diff --git a/addons/purchase/test/ui/purchase_users.yml b/addons/purchase/test/ui/purchase_users.yml
new file mode 100644
index 00000000000..e36d8140d28
--- /dev/null
+++ b/addons/purchase/test/ui/purchase_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Purchase manager'
+-
+ !record {model: res.users, id: res_users_purchase_manager}:
+ company_id: base.main_company
+ name: Purchase Manager
+ login: pm
+ password: pm
+ groups_id:
+ - purchase.group_purchase_manager
+-
+ Create a user as 'Purchase user'
+-
+ !record {model: res.users, id: res_users_purchase_user}:
+ company_id: base.main_company
+ name: Purchase User
+ login: pu
+ password: pu
+ groups_id:
+ - purchase.group_purchase_user
\ No newline at end of file
diff --git a/addons/purchase_requisition/__openerp__.py b/addons/purchase_requisition/__openerp__.py
index 2ba0ad95e09..d2a94365318 100644
--- a/addons/purchase_requisition/__openerp__.py
+++ b/addons/purchase_requisition/__openerp__.py
@@ -43,6 +43,7 @@ keep track and order all your purchase orders.
],
'auto_install': False,
'test': [
+ 'test/purchase_reqisition_users.yml',
'test/purchase_requisition_demo.yml',
'test/purchase_requisition.yml',
'test/cancel_purchase_requisition.yml',
diff --git a/addons/purchase_requisition/test/cancel_purchase_requisition.yml b/addons/purchase_requisition/test/cancel_purchase_requisition.yml
index 389c328a5cf..a865cb0548a 100644
--- a/addons/purchase_requisition/test/cancel_purchase_requisition.yml
+++ b/addons/purchase_requisition/test/cancel_purchase_requisition.yml
@@ -1,3 +1,8 @@
+-
+ Give access rights of Purchase Requisition User to cancelled requisition
+-
+ !context
+ uid: 'res_users_purchase_requisition_user'
-
I cancel requisition.
-
diff --git a/addons/purchase_requisition/test/purchase_reqisition_users.yml b/addons/purchase_requisition/test/purchase_reqisition_users.yml
new file mode 100644
index 00000000000..aa07e8a53f1
--- /dev/null
+++ b/addons/purchase_requisition/test/purchase_reqisition_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Purchase Reqisition Manager'
+-
+ !record {model: res.users, id: res_users_purchase_requisition_manager}:
+ company_id: base.main_company
+ name: Purchase Reqisition Manager
+ login: prm
+ password: prm
+ groups_id:
+ - purchase_reqisition.group_purchase_requisition_manager
+-
+ Create a user as 'Purchase Reqisition User'
+-
+ !record {model: res.users, id: res_users_purchase_requisition_user}:
+ company_id: base.main_company
+ name: Purchase Reqisition User
+ login: pru
+ password: pru
+ groups_id:
+ - purchase_reqisition.group_purchase_requisition_user
\ No newline at end of file
diff --git a/addons/purchase_requisition/test/purchase_requisition.yml b/addons/purchase_requisition/test/purchase_requisition.yml
index 8fd36e07d71..5477f46e18b 100644
--- a/addons/purchase_requisition/test/purchase_requisition.yml
+++ b/addons/purchase_requisition/test/purchase_requisition.yml
@@ -1,3 +1,8 @@
+-
+ Give access rights of Purchase Requisition User
+-
+ !context
+ uid: 'res_users_purchase_requisition_user'
-
I create the procurement order and run that procurement.
-
diff --git a/addons/purchase_requisition/test/purchase_requisition_demo.yml b/addons/purchase_requisition/test/purchase_requisition_demo.yml
index 5e441f26ea3..8265e6c72da 100755
--- a/addons/purchase_requisition/test/purchase_requisition_demo.yml
+++ b/addons/purchase_requisition/test/purchase_requisition_demo.yml
@@ -1,3 +1,8 @@
+-
+ Give access rights of Purchase Requisition User to create requisition
+-
+ !context
+ uid: 'res_users_purchase_requisition_user'
-
In order to test process of the purchase requisition ,I create requisition
-
From 56cb7db019b771014a0a62a5b8824abe241022b4 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Thu, 21 Feb 2013 10:59:58 +0530
Subject: [PATCH 019/256] [IMP]improve yml for account
bzr revid: sgo@tinyerp.com-20130221052958-metvr090gsubdc5u
---
addons/account/__openerp__.py | 1 +
addons/account/security/account_security.xml | 2 +-
.../account/test/account_customer_invoice.yml | 5 +++++
addons/account/test/account_invoice_state.yml | 5 +++++
addons/account/test/account_test_users.yml | 20 +++++++++++++++++++
.../test/account_validate_account_move.yml | 5 +++++
6 files changed, 37 insertions(+), 1 deletion(-)
create mode 100644 addons/account/test/account_test_users.yml
diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py
index 43aaa2f58c1..d72711424f2 100644
--- a/addons/account/__openerp__.py
+++ b/addons/account/__openerp__.py
@@ -146,6 +146,7 @@ for a particular financial year and for preparation of vouchers there is a modul
'account_unit_test.xml',
],
'test': [
+ 'test/account_test_users.yml',
'test/account_customer_invoice.yml',
'test/account_supplier_invoice.yml',
'test/account_change_currency.yml',
diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml
index 9a2383de45b..c86e1cf5eb6 100644
--- a/addons/account/security/account_security.xml
+++ b/addons/account/security/account_security.xml
@@ -12,7 +12,7 @@
Accountant
-
+
diff --git a/addons/account/test/account_customer_invoice.yml b/addons/account/test/account_customer_invoice.yml
index b2d87e753de..866e8921220 100644
--- a/addons/account/test/account_customer_invoice.yml
+++ b/addons/account/test/account_customer_invoice.yml
@@ -11,6 +11,11 @@
footer: True
bank: base.res_bank_1
bank_name: Reserve
+-
+ Test with that user which have rights to make Invoicing and payment and who is accountant.
+-
+ !context
+ uid: 'res_users_account_user'
-
I create a customer invoice
-
diff --git a/addons/account/test/account_invoice_state.yml b/addons/account/test/account_invoice_state.yml
index 7b995150af0..34ac53e7b85 100644
--- a/addons/account/test/account_invoice_state.yml
+++ b/addons/account/test/account_invoice_state.yml
@@ -1,3 +1,8 @@
+-
+ Test with that user which have rights to make Invoicing.
+-
+ !context
+ uid: 'res_users_account_user'
-
In order to test Confirm Draft Invoice wizard I create an invoice and confirm it with this wizard
-
diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml
new file mode 100644
index 00000000000..55e57968f50
--- /dev/null
+++ b/addons/account/test/account_test_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Accountant'
+-
+ !record {model: res.users, id: res_users_account_user}:
+ company_id: base.main_company
+ name: Accountant
+ login: acc
+ password: acc
+ groups_id:
+ - account.group_account_user
+-
+ Create a user as 'Financial Manager'
+-
+ !record {model: res.users, id: res_users_account_manager}:
+ company_id: base.main_company
+ name: Financial Manager
+ login: fm
+ password: fm
+ groups_id:
+ - account.group_account_manager
\ No newline at end of file
diff --git a/addons/account/test/account_validate_account_move.yml b/addons/account/test/account_validate_account_move.yml
index d47ee564019..a99ae3b4d43 100644
--- a/addons/account/test/account_validate_account_move.yml
+++ b/addons/account/test/account_validate_account_move.yml
@@ -1,3 +1,8 @@
+-
+ Test validate account move with user who is accountant which have its rights.'
+-
+ !context
+ uid: 'res_users_account_user'
-
In order to test the account move lines in OpenERP, I create account move
-
From c7a417ea03e80da6f8895f6514d00d17362450e0 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 21 Feb 2013 11:47:37 +0530
Subject: [PATCH 020/256] [IMP] improve code
bzr revid: fka@tinyerp.com-20130221061737-d7vyr9lpwnas1qay
---
addons/purchase/__openerp__.py | 4 ++--
.../purchase_requisition/security/purchase_tender.xml | 1 +
.../test/purchase_reqisition_users.yml | 4 ++--
.../purchase_requisition/test/purchase_requisition.yml | 10 +++++-----
4 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/addons/purchase/__openerp__.py b/addons/purchase/__openerp__.py
index 3f3acdf9f0f..b75c84e332b 100644
--- a/addons/purchase/__openerp__.py
+++ b/addons/purchase/__openerp__.py
@@ -67,8 +67,6 @@ Dashboard / Reports for Purchase Management will include:
'res_config_view.xml',
],
'test': [
- 'test/ui/purchase_users.yml',
- 'purchase_order_demo.yml',
'test/process/cancel_order.yml',
'test/process/rfq2order2done.yml',
'test/process/generate_invoice_from_reception.yml',
@@ -81,6 +79,8 @@ Dashboard / Reports for Purchase Management will include:
'test/ui/delete_order.yml',
],
'demo': [
+ 'test/ui/purchase_users.yml',
+ 'purchase_order_demo.yml',
'purchase_demo.xml',
],
'installable': True,
diff --git a/addons/purchase_requisition/security/purchase_tender.xml b/addons/purchase_requisition/security/purchase_tender.xml
index e60665e202b..f365643a8fe 100644
--- a/addons/purchase_requisition/security/purchase_tender.xml
+++ b/addons/purchase_requisition/security/purchase_tender.xml
@@ -10,6 +10,7 @@
User
+
diff --git a/addons/purchase_requisition/test/purchase_reqisition_users.yml b/addons/purchase_requisition/test/purchase_reqisition_users.yml
index aa07e8a53f1..abf62286998 100644
--- a/addons/purchase_requisition/test/purchase_reqisition_users.yml
+++ b/addons/purchase_requisition/test/purchase_reqisition_users.yml
@@ -7,7 +7,7 @@
login: prm
password: prm
groups_id:
- - purchase_reqisition.group_purchase_requisition_manager
+ - purchase_requisition.group_purchase_requisition_manager
-
Create a user as 'Purchase Reqisition User'
-
@@ -17,4 +17,4 @@
login: pru
password: pru
groups_id:
- - purchase_reqisition.group_purchase_requisition_user
\ No newline at end of file
+ - purchase_requisition.group_purchase_requisition_user
\ No newline at end of file
diff --git a/addons/purchase_requisition/test/purchase_requisition.yml b/addons/purchase_requisition/test/purchase_requisition.yml
index 5477f46e18b..648425829cb 100644
--- a/addons/purchase_requisition/test/purchase_requisition.yml
+++ b/addons/purchase_requisition/test/purchase_requisition.yml
@@ -1,8 +1,3 @@
--
- Give access rights of Purchase Requisition User
--
- !context
- uid: 'res_users_purchase_requisition_user'
-
I create the procurement order and run that procurement.
-
@@ -35,6 +30,11 @@
assert line.product_id.id == procurement.product_id.id, "Product is not correspond."
assert line.product_uom_id.id == procurement.product_uom.id, "UOM is not correspond."
assert line.product_qty == procurement.product_qty, "Quantity is not correspond."
+-
+ Give access rights of Purchase Requisition User to open requisition
+-
+ !context
+ uid: 'res_users_purchase_requisition_user'
-
I open another requisition.
-
From 12701f01a66944f37d21616ad24ffbcbb48455f6 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Thu, 21 Feb 2013 12:19:44 +0530
Subject: [PATCH 021/256] [IMP]add manager in supplier invoice yml
bzr revid: sgo@tinyerp.com-20130221064944-zhgumhtnum6z21jz
---
addons/account/test/account_supplier_invoice.yml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/addons/account/test/account_supplier_invoice.yml b/addons/account/test/account_supplier_invoice.yml
index 02c3d367050..e1528abd980 100644
--- a/addons/account/test/account_supplier_invoice.yml
+++ b/addons/account/test/account_supplier_invoice.yml
@@ -1,3 +1,8 @@
+-
+ Test with that Finance manager who can only create supplier invoice.
+-
+ !context
+ uid: 'res_users_account_manager'
-
In order to test account invoice I create a new supplier invoice
-
From 794926267e07eb14e73ce3b9845e61e0910cd6d1 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 21 Feb 2013 12:31:28 +0530
Subject: [PATCH 022/256] [IMP] add hr_users.yml in hr
bzr revid: fka@tinyerp.com-20130221070128-bycahqtp3sqjm06d
---
addons/hr/__openerp__.py | 1 +
addons/hr/test/hr_demo.yml | 5 ++++
addons/hr/test/hr_users.yml | 30 +++++++++++++++++++++++
addons/hr/test/open2recruit2close_job.yml | 5 ++++
4 files changed, 41 insertions(+)
create mode 100644 addons/hr/test/hr_users.yml
diff --git a/addons/hr/__openerp__.py b/addons/hr/__openerp__.py
index b4b8e982845..001933ad2bf 100644
--- a/addons/hr/__openerp__.py
+++ b/addons/hr/__openerp__.py
@@ -62,6 +62,7 @@ You can manage:
],
'demo': ['hr_demo.xml'],
'test': [
+ 'test/hr_users.yml',
'test/open2recruit2close_job.yml',
'test/hr_demo.yml',
],
diff --git a/addons/hr/test/hr_demo.yml b/addons/hr/test/hr_demo.yml
index 7c184a25c52..c9de2e4ff21 100644
--- a/addons/hr/test/hr_demo.yml
+++ b/addons/hr/test/hr_demo.yml
@@ -1,3 +1,8 @@
+-
+ give the access rights of Hr Officer to create employee.
+-
+ !context
+ uid: 'res_users_hr_officer'
-
!record {model: hr.job, id: job_developer, view: False}:
no_of_employee: 0.0
diff --git a/addons/hr/test/hr_users.yml b/addons/hr/test/hr_users.yml
new file mode 100644
index 00000000000..3415b20efbd
--- /dev/null
+++ b/addons/hr/test/hr_users.yml
@@ -0,0 +1,30 @@
+-
+ Create a user as 'HR Manager'
+-
+ !record {model: res.users, id: res_users_hr_manager}:
+ company_id: base.main_company
+ name: HR manager
+ login: hrm
+ password: hrm
+ groups_id:
+ - base.group_hr_manager
+-
+ Create a user as 'HR Officer'
+-
+ !record {model: res.users, id: res_users_hr_officer}:
+ company_id: base.main_company
+ name: HR Officer
+ login: hro
+ password: hro
+ groups_id:
+ - base.group_hr_user
+-
+ Create a user as 'Employee'
+-
+ !record {model: res.users, id: res_users_employee}:
+ company_id: base.main_company
+ name: Employee
+ login: emp
+ password: emp
+ groups_id:
+ - base.group_user
\ No newline at end of file
diff --git a/addons/hr/test/open2recruit2close_job.yml b/addons/hr/test/open2recruit2close_job.yml
index 5a01b55439b..66fdfa393c8 100644
--- a/addons/hr/test/open2recruit2close_job.yml
+++ b/addons/hr/test/open2recruit2close_job.yml
@@ -1,3 +1,8 @@
+-
+ give the access rights of Hr Officer to the user to test the process of Human Resource Management.
+-
+ !context
+ uid: 'res_users_hr_officer'
-
In order to test the process of Human Resource Management, I open Job Postion for "Developer".
-
From d9d8abd8b0960c26a63b2cce783fbde03a5dbca6 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 21 Feb 2013 14:33:00 +0530
Subject: [PATCH 023/256] [IMP] add access rights in hr_attendance
bzr revid: fka@tinyerp.com-20130221090300-is57aytddja1m4f5
---
addons/hr_attendance/test/attendance_process.yml | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/addons/hr_attendance/test/attendance_process.yml b/addons/hr_attendance/test/attendance_process.yml
index 7902bf11d9d..9f197341b55 100644
--- a/addons/hr_attendance/test/attendance_process.yml
+++ b/addons/hr_attendance/test/attendance_process.yml
@@ -1,3 +1,18 @@
+-
+ Create a user as 'HR Attendance Officer'
+-
+ !record {model: res.users, id: res_users_attendance_officer}:
+ company_id: base.main_company
+ name: HR Officer
+ login: ao
+ password: ao
+ groups_id:
+ - base.group_hr_user
+-
+ give the access rights of Hr Officer to test attendance process.
+-
+ !context
+ uid: 'res_users_attendance_officer'
-
In order to test attendance process in OpenERP, I entry of SignIn of employee.
-
From 8d7669d0ade7ccd7e6cef7b0630bac6cb6dbf729 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 21 Feb 2013 16:09:22 +0530
Subject: [PATCH 024/256] [IMP] add access rights in hr_recruitment
bzr revid: fka@tinyerp.com-20130221103922-tczeru30rer8ae6e
---
.../security/hr_recruitment_security.xml | 3 +++
.../hr_recruitment/test/recruitment_process.yml | 15 ++++++++++++++-
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/addons/hr_recruitment/security/hr_recruitment_security.xml b/addons/hr_recruitment/security/hr_recruitment_security.xml
index 570b0f036cd..a85643b62ec 100644
--- a/addons/hr_recruitment/security/hr_recruitment_security.xml
+++ b/addons/hr_recruitment/security/hr_recruitment_security.xml
@@ -9,6 +9,9 @@
['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
+
diff --git a/addons/hr_recruitment/test/recruitment_process.yml b/addons/hr_recruitment/test/recruitment_process.yml
index f2d4e4736fa..0359f253dd9 100644
--- a/addons/hr_recruitment/test/recruitment_process.yml
+++ b/addons/hr_recruitment/test/recruitment_process.yml
@@ -1,5 +1,18 @@
-
- In Order to test process of Recruitment,
+ Create a user as 'HR Recruitment Officer'
+-
+ !record {model: res.users, id: res_users_hr_officer}:
+ company_id: base.main_company
+ name: HR Officer
+ login: hrro
+ password: hrro
+ groups_id:
+ - base.group_hr_user
+-
+ In Order to test process of Recruitment so giving HR officer's rights,
+-
+ !context
+ uid: 'res_users_hr_officer'
-
An applicant is interested in the job position. So he sends a resume by email.
-
From fa94d5e112bb7dcce1d53fb80f6af3a25838e5d3 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Thu, 21 Feb 2013 16:47:28 +0530
Subject: [PATCH 025/256] [IMP]improve yml for account_voucher
bzr revid: sgo@tinyerp.com-20130221111728-jlvv0lf5w258ujbz
---
addons/account_voucher/__openerp__.py | 1 +
.../account_voucher/test/account_voucher.yml | 6 ++++++
.../test/account_voucher_users.yml | 20 +++++++++++++++++++
addons/account_voucher/test/case_eur_usd.yml | 5 +++++
addons/account_voucher/test/sales_payment.yml | 5 +++++
addons/account_voucher/test/sales_receipt.yml | 5 +++++
6 files changed, 42 insertions(+)
create mode 100644 addons/account_voucher/test/account_voucher_users.yml
diff --git a/addons/account_voucher/__openerp__.py b/addons/account_voucher/__openerp__.py
index 345c3378aa0..a6cbc9a3e40 100644
--- a/addons/account_voucher/__openerp__.py
+++ b/addons/account_voucher/__openerp__.py
@@ -61,6 +61,7 @@ This module manages:
'account_voucher_data.xml',
],
'test' : [
+ 'test/account_voucher_users.yml',
'test/case5_suppl_usd_usd.yml',
'test/account_voucher.yml',
'test/sales_receipt.yml',
diff --git a/addons/account_voucher/test/account_voucher.yml b/addons/account_voucher/test/account_voucher.yml
index c9270dc695c..e2df6b8597f 100644
--- a/addons/account_voucher/test/account_voucher.yml
+++ b/addons/account_voucher/test/account_voucher.yml
@@ -1,3 +1,9 @@
+-
+ I check the voucher module with user who is accountant.
+-
+ !context
+ uid: 'res_users_account_voucher_user'
+
-
In order to check account voucher module in OpenERP I create a customer voucher
-
diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml
new file mode 100644
index 00000000000..e8a4fa3e612
--- /dev/null
+++ b/addons/account_voucher/test/account_voucher_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Accountant for voucher'
+-
+ !record {model: res.users, id: res_users_account_voucher_user}:
+ company_id: base.main_company
+ name: Voucher Accountant
+ login: vacc
+ password: acc
+ groups_id:
+ - account.group_account_user
+-
+ Create a user as 'Financial Manager for account voucher'
+-
+ !record {model: res.users, id: res_users_account_voucher_manager}:
+ company_id: base.main_company
+ name: Financial Manager for voucher
+ login: fmv
+ password: fmv
+ groups_id:
+ - account.group_account_manager
\ No newline at end of file
diff --git a/addons/account_voucher/test/case_eur_usd.yml b/addons/account_voucher/test/case_eur_usd.yml
index 94f0c718980..23a35d8ad24 100644
--- a/addons/account_voucher/test/case_eur_usd.yml
+++ b/addons/account_voucher/test/case_eur_usd.yml
@@ -1,4 +1,9 @@
##YAML test on the account_voucher as depicted in this bug report: https://bugs.launchpad.net/openobject-addons/+bug/954155
+-
+ Only manager can create and take decision about bank and currency there I checkd this test with user who is finance manager.
+-
+ !context
+ uid: 'res_users_account_voucher_manager'
-
In order to check the payment with multi-currency in OpenERP,
I create an invoice in EUR and make payment in USD based on the currency rating.
diff --git a/addons/account_voucher/test/sales_payment.yml b/addons/account_voucher/test/sales_payment.yml
index 37264de114a..005c5cc79c3 100644
--- a/addons/account_voucher/test/sales_payment.yml
+++ b/addons/account_voucher/test/sales_payment.yml
@@ -1,3 +1,8 @@
+-
+ I test sales payment with user who is accountant.
+-
+ !context
+ uid: 'res_users_account_voucher_user'
-
Create an invoice for the partner Seagate with amount 450.0
-
diff --git a/addons/account_voucher/test/sales_receipt.yml b/addons/account_voucher/test/sales_receipt.yml
index dc5602753fd..c637cfd8835 100644
--- a/addons/account_voucher/test/sales_receipt.yml
+++ b/addons/account_voucher/test/sales_receipt.yml
@@ -1,3 +1,8 @@
+-
+ Accountant can also be created receipt and validate it there for I checked it with that user who is accountant.
+-
+ !context
+ uid: 'res_users_account_voucher_user'
-
Creating a Voucher Receipt for partner Seagate with amount 30000.0
-
From edc4077c54ba215228036a4316706e07a094b5df Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Fri, 22 Feb 2013 12:57:57 +0530
Subject: [PATCH 026/256] [IMP] remove access rights in mail,crm,event
bzr revid: fka@tinyerp.com-20130222072757-0fain23pzyb8dzk9
---
addons/crm/security/crm_security.xml | 1 +
addons/crm/security/ir.model.access.csv | 2 --
addons/event/security/event_security.xml | 1 +
addons/event/security/ir.model.access.csv | 2 --
addons/mail/security/ir.model.access.csv | 6 +++---
5 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/addons/crm/security/crm_security.xml b/addons/crm/security/crm_security.xml
index 2c34252209c..97bc220cf04 100644
--- a/addons/crm/security/crm_security.xml
+++ b/addons/crm/security/crm_security.xml
@@ -5,6 +5,7 @@
User: Own Leads Only
+ the user will have access to his own data in the sales application.
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index cd460cf35d9..74394d3c5ce 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -29,8 +29,6 @@ access_calendar_attendee_crm_manager,calendar.attendee.crm.manager,model_calenda
access_res_partner,res.partner.crm.user,base.model_res_partner,base.group_sale_salesman,1,1,1,0
access_res_partner_category,res.partner.category.crm.user,base.model_res_partner_category,base.group_sale_salesman,1,1,1,0
mail_mailgate_thread,mail.thread,mail.model_mail_thread,base.group_sale_salesman,1,1,1,1
-access_mail_message,mail.message,mail.model_mail_message,base.group_sale_salesman,1,1,1,0
-mail_mail_message,mail.message,mail.model_mail_message,base.group_sale_manager,1,1,1,1
access_crm_case_categ_manager,crm.case.categ manager,model_crm_case_categ,base.group_sale_manager,1,1,1,1
access_base_action_rule_manager,base.action.rule manager,base_action_rule.model_base_action_rule,base.group_sale_manager,1,1,1,1
access_crm_lead_report_user,crm.lead.report user,model_crm_lead_report,base.group_sale_salesman,1,1,1,1
diff --git a/addons/event/security/event_security.xml b/addons/event/security/event_security.xml
index 83039ca4686..470365dd5f0 100644
--- a/addons/event/security/event_security.xml
+++ b/addons/event/security/event_security.xml
@@ -10,6 +10,7 @@
User
+
diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv
index 0801b8306f0..b6cdc4c4ec6 100644
--- a/addons/event/security/ir.model.access.csv
+++ b/addons/event/security/ir.model.access.csv
@@ -6,5 +6,3 @@ access_event_registration,event.registration,model_event_registration,event.grou
access_report_event_registration,report.event.registration,model_report_event_registration,event.group_event_user,1,1,1,1
access_event_event_portal,event.event,model_event_event,,1,0,0,0
access_event_registration_portal,event.registration,model_event_registration,,1,0,0,0
-access_event_partner,res.partner,model_res_partner,event.group_event_user,1,1,1,0
-access_mail_message,mail.message,mail.model_mail_message,event.group_event_user,1,1,1,0
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index 81b2356938b..1141a3f928d 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -1,18 +1,18 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_all,mail.message.all,model_mail_message,,1,0,0,0
access_mail_message_user,mail.message.user,model_mail_message,base.group_user,1,1,1,1
-access_mail_mail_all,mail.mail.all,model_mail_mail,,1,0,1,0
+access_mail_mail_all,mail.mail.all,model_mail_mail,,1,0,0,0
access_mail_mail_user,mail.mail.user,model_mail_mail,base.group_user,1,1,1,0
access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,0,0
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
-access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,1,0
+access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0
access_mail_notification_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0
access_mail_notification_system,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1
access_mail_group_all,mail.group.all,model_mail_group,,1,0,0,0
access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
-access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,1,0
+access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
From bf814eb41456d692cb166581dcbda13980ecadd6 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Fri, 22 Feb 2013 15:03:15 +0530
Subject: [PATCH 027/256] [IMP] improve yml string
bzr revid: fka@tinyerp.com-20130222093315-efl5r3wjwn7wu2nd
---
addons/crm/test/process/cancel_lead.yml | 2 +-
addons/crm/test/process/communication_with_customer.yml | 2 +-
addons/crm/test/process/lead2opportunity2win.yml | 2 +-
addons/crm/test/process/lead2opportunity_assign_salesmen.yml | 2 +-
addons/crm/test/process/phonecalls.yml | 2 +-
addons/crm/test/ui/delete_lead.yml | 2 +-
addons/event/test/process/event_draft2done.yml | 2 +-
addons/hr/test/hr_demo.yml | 2 +-
addons/hr/test/open2recruit2close_job.yml | 2 +-
addons/hr_attendance/test/attendance_process.yml | 2 +-
10 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/addons/crm/test/process/cancel_lead.yml b/addons/crm/test/process/cancel_lead.yml
index ae2b6294c68..bbd17dc2bb0 100644
--- a/addons/crm/test/process/cancel_lead.yml
+++ b/addons/crm/test/process/cancel_lead.yml
@@ -1,5 +1,5 @@
-
- Salesman cancel unqualified lead.
+ Salesman can also cancelled unqualified lead and re-open lead so giving access rights of salesman.
-
!context
uid: 'crm_res_users_salesman'
diff --git a/addons/crm/test/process/communication_with_customer.yml b/addons/crm/test/process/communication_with_customer.yml
index 4a36aeebd02..8cce69dada9 100644
--- a/addons/crm/test/process/communication_with_customer.yml
+++ b/addons/crm/test/process/communication_with_customer.yml
@@ -1,5 +1,5 @@
-
- Salesman communication with customer.
+ Give the access rights of Salesman to communicat with customer.
-
!context
uid: 'crm_res_users_salesman'
diff --git a/addons/crm/test/process/lead2opportunity2win.yml b/addons/crm/test/process/lead2opportunity2win.yml
index c69eb09ac31..1e42fba0e9c 100644
--- a/addons/crm/test/process/lead2opportunity2win.yml
+++ b/addons/crm/test/process/lead2opportunity2win.yml
@@ -1,5 +1,5 @@
-
- Salesman convert the lead into opportunity.
+ Giving access rights of salesman to convert the lead into opportunity.
-
!context
uid: 'crm_res_users_salesman'
diff --git a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
index 9583c6034bf..5a750c94b72 100644
--- a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
+++ b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml
@@ -21,7 +21,7 @@
login: 'tud'
new_password: 'tud'
-
- Salesman creates lead.
+ Salesman also creates lead so giving access rights of salesman.
-
!context
uid: 'crm_res_users_salesman'
diff --git a/addons/crm/test/process/phonecalls.yml b/addons/crm/test/process/phonecalls.yml
index 63df8ac89c2..e92ae999a87 100644
--- a/addons/crm/test/process/phonecalls.yml
+++ b/addons/crm/test/process/phonecalls.yml
@@ -1,5 +1,5 @@
-
- Salesman check the phone calls data.
+ Salesman check the phone calls data so test with the access rights of salesman.
-
!context
uid: 'crm_res_users_salesman'
diff --git a/addons/crm/test/ui/delete_lead.yml b/addons/crm/test/ui/delete_lead.yml
index 10d2de82e7e..be545e53773 100644
--- a/addons/crm/test/ui/delete_lead.yml
+++ b/addons/crm/test/ui/delete_lead.yml
@@ -1,5 +1,5 @@
-
- Sales manager Unlink the Lead.
+ Only Sales manager Unlink the Lead so test with Manager's access rights'.
-
!context
uid: 'crm_res_users_salesmanager'
diff --git a/addons/event/test/process/event_draft2done.yml b/addons/event/test/process/event_draft2done.yml
index e485cb5af6c..2ad762682e5 100644
--- a/addons/event/test/process/event_draft2done.yml
+++ b/addons/event/test/process/event_draft2done.yml
@@ -1,5 +1,5 @@
-
- give the access rights of Event user to organize an event and also do registration.
+ Give the access rights of Event user to organize an event and also do registration.
-
!context
uid: 'res_users_eventuser'
diff --git a/addons/hr/test/hr_demo.yml b/addons/hr/test/hr_demo.yml
index c9de2e4ff21..db0d0037c4a 100644
--- a/addons/hr/test/hr_demo.yml
+++ b/addons/hr/test/hr_demo.yml
@@ -1,5 +1,5 @@
-
- give the access rights of Hr Officer to create employee.
+ Give the access rights of Hr Officer to create employee.
-
!context
uid: 'res_users_hr_officer'
diff --git a/addons/hr/test/open2recruit2close_job.yml b/addons/hr/test/open2recruit2close_job.yml
index 66fdfa393c8..5519635d2fc 100644
--- a/addons/hr/test/open2recruit2close_job.yml
+++ b/addons/hr/test/open2recruit2close_job.yml
@@ -1,5 +1,5 @@
-
- give the access rights of Hr Officer to the user to test the process of Human Resource Management.
+ Give the access rights of Hr Officer to the user to test the process of Human Resource Management.
-
!context
uid: 'res_users_hr_officer'
diff --git a/addons/hr_attendance/test/attendance_process.yml b/addons/hr_attendance/test/attendance_process.yml
index 9f197341b55..8348757b63e 100644
--- a/addons/hr_attendance/test/attendance_process.yml
+++ b/addons/hr_attendance/test/attendance_process.yml
@@ -9,7 +9,7 @@
groups_id:
- base.group_hr_user
-
- give the access rights of Hr Officer to test attendance process.
+ Give the access rights of Hr Officer to test attendance process.
-
!context
uid: 'res_users_attendance_officer'
From ca1bf8f8f71a9b88af89c8903199e9f4bd1356d1 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Fri, 22 Feb 2013 18:59:40 +0530
Subject: [PATCH 028/256] [IMP] add access rights of ir_property in crm
bzr revid: fka@tinyerp.com-20130222132940-2r0kuv66ul3k3pdu
---
addons/crm/security/ir.model.access.csv | 1 +
1 file changed, 1 insertion(+)
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 74394d3c5ce..3aada0d10be 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -37,3 +37,4 @@ access_crm_lead_partner_manager,crm.lead.partner.manager,model_crm_lead,base.gro
access_crm_phonecall_partner_manager,crm.phonecall.partner.manager,model_crm_phonecall,base.group_partner_manager,1,1,1,1
access_crm_payment_mode_user,crm.payment.mode,model_crm_payment_mode,base.group_sale_salesman,1,0,0,0
access_crm_payment_mode,crm.payment.mode,model_crm_payment_mode,base.group_sale_manager,1,1,1,1
+access_ir_property_salesman,ir_property_salesman,base.model_ir_property,base.group_sale_salesman,1,1,1,0
From ffc81649c6c1948ac2f812cbe3b5c9175b160cd2 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 26 Feb 2013 15:20:17 +0530
Subject: [PATCH 029/256] [IMP]improve code
bzr revid: sgo@tinyerp.com-20130226095017-f7bbnnqngszfq95r
---
addons/account/security/account_security.xml | 2 +-
addons/account/security/ir.model.access.csv | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml
index c86e1cf5eb6..9a2383de45b 100644
--- a/addons/account/security/account_security.xml
+++ b/addons/account/security/account_security.xml
@@ -12,7 +12,7 @@
Accountant
-
+
diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv
index d1f0bbab6b5..d164b8026ce 100644
--- a/addons/account/security/ir.model.access.csv
+++ b/addons/account/security/ir.model.access.csv
@@ -71,6 +71,7 @@ access_report_account_type_sales,report.account_type.sales,model_report_account_
access_report_account_sales,report.account.sales,model_report_account_sales,account.group_account_manager,1,1,1,1
access_account_invoice_report,account.invoice.report,model_account_invoice_report,account.group_account_manager,1,1,1,1
access_res_partner_group_account_manager,res_partner group_account_manager,model_res_partner,account.group_account_manager,1,0,0,0
+access_res_partner_group_account_user,res_partner group_account_user,model_res_partner,account.group_account_user,1,1,1,0
access_account_invoice_accountant,account.invoice accountant,model_account_invoice,account.group_account_user,1,0,0,0
access_account_tax_code_accountant,account.tax.code accountant,model_account_tax_code,account.group_account_user,1,1,1,1
access_account_move_line_manager,account.move.line manager,model_account_move_line,account.group_account_manager,1,0,0,0
From 345845415d0e3a507cac078c4c5b887d7ab19213 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 28 Feb 2013 10:30:26 +0530
Subject: [PATCH 030/256] [IMP] change all data of stock yml
bzr revid: fka@tinyerp.com-20130228050026-5yaf2ftrps77jlff
---
addons/stock/__openerp__.py | 8 +-
addons/stock/product.py | 5 +-
addons/stock/stock.py | 1 -
addons/stock/stock_demo.yml | 118 ++++++++++++++--------------
addons/stock/test/opening_stock.yml | 62 +++++++--------
addons/stock/test/shipment.yml | 103 ++++++++++++------------
6 files changed, 146 insertions(+), 151 deletions(-)
diff --git a/addons/stock/__openerp__.py b/addons/stock/__openerp__.py
index b3d7e30f1ce..e1c96ba0c25 100644
--- a/addons/stock/__openerp__.py
+++ b/addons/stock/__openerp__.py
@@ -59,7 +59,6 @@ Dashboard / Reports for Warehouse Management will include:
'sequence': 16,
'demo': [
'stock_demo.xml',
-# 'stock_demo.yml',
],
'data': [
'security/stock_security.xml',
@@ -91,9 +90,10 @@ Dashboard / Reports for Warehouse Management will include:
'res_config_view.xml',
],
'test': [
-# 'test/opening_stock.yml',
-# 'test/shipment.yml',
-# 'test/stock_report.yml',
+ 'stock_demo.yml',
+ 'test/opening_stock.yml',
+ 'test/shipment.yml',
+ #'test/stock_report.yml',
],
'installable': True,
'application': True,
diff --git a/addons/stock/product.py b/addons/stock/product.py
index 1d56ce64988..b00f8615667 100644
--- a/addons/stock/product.py
+++ b/addons/stock/product.py
@@ -55,15 +55,14 @@ class product_product(osv.osv):
if context is None:
context = {}
product_obj = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
-
stock_input_acc = product_obj.property_stock_account_input and product_obj.property_stock_account_input.id or False
if not stock_input_acc:
stock_input_acc = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id or False
-
+
stock_output_acc = product_obj.property_stock_account_output and product_obj.property_stock_account_output.id or False
if not stock_output_acc:
stock_output_acc = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id or False
-
+
journal_id = product_obj.categ_id.property_stock_journal and product_obj.categ_id.property_stock_journal.id or False
account_valuation = product_obj.categ_id.property_stock_valuation_account_id and product_obj.categ_id.property_stock_valuation_account_id.id or False
return {
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 8e4611dc5d0..ed2b36bd1f5 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -2233,7 +2233,6 @@ class stock_move(osv.osv):
acc_valuation = accounts.get('property_stock_valuation_account_id', False)
journal_id = accounts['stock_journal']
-
if acc_dest == acc_valuation:
raise osv.except_osv(_('Error!'), _('Cannot create Journal Entry, Output Account of this product and Valuation account on category of this product are same.'))
diff --git a/addons/stock/stock_demo.yml b/addons/stock/stock_demo.yml
index 99565f0c08c..8e9ea4977b8 100644
--- a/addons/stock/stock_demo.yml
+++ b/addons/stock/stock_demo.yml
@@ -1,99 +1,97 @@
-
- !record {model: stock.location, id: location_refrigerator}:
- name: Refrigerator
+ !record {model: stock.location, id: location_monitor}:
+ name: chicago shop
usage: internal
-
- !record {model: stock.location, id: location_delivery_counter}:
- name: Delivery Counter
+ !record {model: stock.location, id: stock_location_output}:
+ name: Output
usage: internal
-
- !record {model: stock.location, id: location_refrigerator_small}:
- name: Small Refrigerator
+ !record {model: stock.location, id: location_monitor_small}:
+ name: Small chicago shop
usage: internal
- location_id: location_refrigerator
+ location_id: location_monitor
-
!record {model: stock.location, id: location_opening}:
name: opening
usage: inventory
-
- !record {model: stock.location, id: location_convenience_shop}:
- name: Convenient Store
+ !record {model: stock.location, id: stock_location_3}:
+ name: IT Suppliers
usage: supplier
-
- !record {model: stock.warehouse, id: warehouse_icecream}:
- name: Ice Cream Shop
- lot_input_id: location_refrigerator
- lot_stock_id: location_refrigerator
- lot_output_id: location_delivery_counter
+ !record {model: stock.warehouse, id: stock_warehouse_shop0}:
+ name: Chicago Warehouse
+ lot_input_id: location_monitor
+ lot_stock_id: location_monitor
+ lot_output_id: stock_location_output
-
- !record {model: product.product, id: product_icecream}:
- default_code: 001
- name: Ice Cream
- type: product
- categ_id: product.product_category_1
- list_price: 100.0
- standard_price: 70.0
- uom_id: product.product_uom_kgm
- uom_po_id: product.product_uom_kgm
- procure_method: make_to_stock
+ !record {model: product.product, id: product_product_6}:
+ default_code: LCD15
+ name: 15” LCD Monitor
+ type: consu
+ categ_id: product.product_category_8
+ list_price: 1200.0
+ standard_price: 800.0
+ uom_id: product.product_uom_unit
+ uom_po_id: product.product_uom_unit
property_stock_inventory: location_opening
valuation: real_time
cost_method: average
- property_stock_account_input: account.o_expense
- property_stock_account_output: account.o_income
- description: Ice cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
-
+ property_stock_account_input: account.conf_o_expense
+ property_stock_account_output: account.conf_o_income
-
- !record {model: stock.production.lot, id: lot_icecream_0}:
- name: Lot0 for Ice cream
- product_id: product_icecream
+ !record {model: stock.production.lot, id: lot_monitor_0}:
+ name: Lot0 for LCD Monitor
+ product_id: product_product_6
-
- !record {model: stock.production.lot, id: lot_icecream_1}:
- name: Lot1 for Ice cream
- product_id: product_icecream
+ !record {model: stock.production.lot, id: lot_monitor_1}:
+ name: Lot1 for LCD Monitor
+ product_id: product_product_6
-
- !record {model: stock.inventory, id: stock_inventory_icecream}:
- name: Inventory for icecream
+ !record {model: stock.inventory, id: stock_inventory_0}:
+ name: Starting Inventory
+ state: draft
-
- !record {model: stock.inventory.line, id: stock_inventory_line_icecream_lot0}:
- product_id: product_icecream
- product_uom: product.product_uom_kgm
- inventory_id: stock_inventory_icecream
+ !record {model: stock.inventory.line, id: stock_inventory_line_3}:
+ product_id: product_product_6
+ product_uom: product.product_uom_unit
+ inventory_id: stock_inventory_0
product_qty: 50.0
- prod_lot_id: lot_icecream_0
- location_id: location_refrigerator
+ prod_lot_id: lot_monitor_0
+ location_id: location_monitor
-
- !record {model: stock.inventory.line, id: stock_inventory_line_icecream_lot1}:
- product_id: product_icecream
- product_uom: product.product_uom_kgm
- inventory_id: stock_inventory_icecream
+ !record {model: stock.inventory.line, id: stock_inventory_line_monitor}:
+ product_id: product_product_6
+ product_uom: product.product_uom_unit
+ inventory_id: stock_inventory_0
product_qty: 40.0
- prod_lot_id: lot_icecream_1
- location_id: location_refrigerator
+ prod_lot_id: lot_monitor_1
+ location_id: location_monitor
-
!record {model: stock.picking, id: outgoing_shipment}:
type: out
- location_dest_id: location_delivery_counter
+ location_dest_id: stock_location_output
-
- !record {model: stock.move, id: outgoing_shipment_icecream}:
+ !record {model: stock.move, id: outgoing_shipment_monitor}:
picking_id: outgoing_shipment
- product_id: product_icecream
- product_uom: product.product_uom_kgm
+ product_id: product_product_6
+ product_uom: product.product_uom_unit
product_qty: 130.0
- location_id: location_refrigerator
- location_dest_id: location_delivery_counter
+ location_id: location_monitor
+ location_dest_id: stock_location_output
-
!record {model: stock.picking, id: incomming_shipment}:
type: in
invoice_state: 2binvoiced
partner_id: base.res_partner_address_9
- location_dest_id: location_refrigerator
+ location_dest_id: location_monitor
-
- !record {model: stock.move, id: incomming_shipment_icecream}:
+ !record {model: stock.move, id: incomming_shipment_monitor}:
picking_id: incomming_shipment
- product_id: product_icecream
- product_uom: product.product_uom_kgm
+ product_id: product_product_6
+ product_uom: product.product_uom_unit
product_qty: 50.0
- location_id: location_convenience_shop
- location_dest_id: location_refrigerator
+ location_id: stock_location_3
+ location_dest_id: location_monitor
diff --git a/addons/stock/test/opening_stock.yml b/addons/stock/test/opening_stock.yml
index 2e3eaeadc74..41b906e560c 100644
--- a/addons/stock/test/opening_stock.yml
+++ b/addons/stock/test/opening_stock.yml
@@ -1,42 +1,42 @@
-
- I update the price of the Ice-cream.
+ I update the price of the 15” LCD Monitor.
-
!python {model: stock.change.standard.price}: |
- context.update({'active_model':'product.product', 'active_id': ref('product_icecream'), 'active_ids':[ref('product_icecream')]})
+ context.update({'active_model':'product.product', 'active_id': ref('product_product_6'), 'active_ids':[ref('product_product_6')]})
-
!record {model: stock.change.standard.price, id: change_price}:
- new_price: 120
+ new_price: 1500
-
!python {model: stock.change.standard.price}: |
self.change_price(cr, uid, [ref('change_price')], context=context)
-
- I check price of Ice-cream after update price.
+ I check price of 15” LCD Monitor after update price.
-
!python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_icecream'), context=context)
- assert product.standard_price == 120, "Price is not updated."
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
+ assert product.standard_price == 1500, "Price is not updated."
-
- I update the current stock of the Ice-cream with 10 kgm in Small Refrigerator in lot0.
+ I update the current stock of the 15” LCD Monitor with 10 unit in stock location shop1 in lot0.
-
!record {model: stock.change.product.qty, id: change_qty}:
- location_id: location_refrigerator_small
+ location_id: location_monitor_small
new_quantity: 10
- product_id: product_icecream
- prodlot_id: lot_icecream_1
+ product_id: product_product_6
+ prodlot_id: lot_monitor_1
-
!python {model: stock.change.product.qty}: |
self.change_product_qty(cr, uid, [ref('change_qty')], context=context)
-
- I check available stock of Ice-cream after update stock.
+ I check available stock of 15” LCD Monitor after update stock.
-
!python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_icecream'), context=context)
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.qty_available == 10, "Stock is not updated."
-
I merge inventory.
-
!python {model: stock.inventory.merge }: |
- context.update({'active_model': 'stock.inventory', 'active_id': ref('stock_inventory_icecream'), 'active_ids': [ref('stock_inventory_icecream')]})
+ context.update({'active_model': 'stock.inventory', 'active_id': ref('stock_inventory_0'), 'active_ids': [ref('stock_inventory_0')]})
-
!record {model: stock.inventory.merge, id: merge_inventory}:
-
@@ -46,22 +46,22 @@
I cancel inventory.
-
!python {model: stock.inventory}: |
- self.action_cancel_inventory(cr, uid, [ref('stock_inventory_icecream')])
+ self.action_cancel_inventory(cr, uid, [ref('stock_inventory_0')])
-
I reset to draft inventory.
-
!python {model: stock.inventory}: |
- self.action_cancel_draft(cr, uid, [ref('stock_inventory_icecream')])
+ self.action_cancel_draft(cr, uid, [ref('stock_inventory_0')])
-
- I confirm physical inventory of Ice-cream which are came in different lots.
+ I confirm physical inventory of 15” LCD Monitor which are came in different lots.
-
!python {model: stock.inventory}: |
- self.action_confirm(cr, uid, [ref('stock_inventory_icecream')], context=context)
+ self.action_confirm(cr, uid, [ref('stock_inventory_0')], context=context)
-
I check move details after confirmed physical inventory.
-
!python {model: stock.inventory}: |
- inventory = self.browse(cr, uid, ref('stock_inventory_icecream'), context=context)
+ inventory = self.browse(cr, uid, ref('stock_inventory_0'), context=context)
assert len(inventory.move_ids) == len(inventory.inventory_line_id), "moves are not correspond."
for move_line in inventory.move_ids:
for line in inventory.inventory_line_id:
@@ -77,15 +77,15 @@
I split inventory line.
-
!python {model: stock.inventory.line.split}: |
- context.update({'active_model': 'stock.inventory.line', 'active_id': ref('stock_inventory_line_icecream_lot0'), 'active_ids': [ref('stock_inventory_line_icecream_lot0')]})
+ context.update({'active_model': 'stock.inventory.line', 'active_id': ref('stock_inventory_line_3'), 'active_ids': [ref('stock_inventory_line_3')]})
-
!record {model: stock.inventory.line.split, id: split_inventory_lot0}:
use_exist: True
line_exist_ids:
- quantity: 6
- prodlot_id: lot_icecream_0
+ prodlot_id: lot_monitor_0
- quantity: 4
- prodlot_id: lot_icecream_0
+ prodlot_id: lot_monitor_0
-
!python {model: stock.inventory.line.split }: |
self.split_lot(cr, uid, [ref('split_inventory_lot0')], context=context)
@@ -93,39 +93,39 @@
I fill inventory line.
-
!python {model: stock.fill.inventory}: |
- context.update({'active_model': 'stock.inventory', 'active_id': ref('stock_inventory_icecream'), 'active_ids': [ref('stock_inventory_icecream')]})
+ context.update({'active_model': 'stock.inventory', 'active_id': ref('stock_inventory_0'), 'active_ids': [ref('stock_inventory_0')]})
-
!record {model: stock.fill.inventory, id: fill_inventory}:
- location_id: location_refrigerator
+ location_id: location_monitor
recursive: True
-
!python {model: stock.fill.inventory }: |
self.fill_inventory(cr, uid, [ref('fill_inventory')], context=context)
-
- Now I check vitual stock of Ice-cream after confirmed physical inventory.
+ Now I check vitual stock of 15” LCD Monitor after confirmed physical inventory.
-
!python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_icecream'), context=context)
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.virtual_available == 100, "Vitual stock is not updated."
-
- I close physical inventory of Ice-cream.
+ I close physical inventory of 15” LCD Monitor.
-
!python {model: stock.inventory}: |
- self.action_done(cr, uid, [ref('stock_inventory_icecream')], context=context)
+ self.action_done(cr, uid, [ref('stock_inventory_0')], context=context)
-
- I check closed move and real stock of Ice-cream after closed physical inventory.
+ I check closed move and real stock of 15” LCD Monitor after closed physical inventory.
-
!python {model: stock.inventory}: |
- inventory = self.browse(cr, uid, ref('stock_inventory_icecream'), context=context)
+ inventory = self.browse(cr, uid, ref('stock_inventory_0'), context=context)
assert inventory.state == 'done', "inventory is not closed."
for move_line in inventory.move_ids:
assert move_line.state == 'done', "Move is not closed."
- product = self.pool.get('product.product').browse(cr, uid, ref('product_icecream'), context=context)
+ product = self.pool.get('product.product').browse(cr, uid, ref('product_product_6'), context=context)
product.qty_available == 100, "Real stock is not updated."
-
I check stock in lot.
-
!python {model: stock.production.lot}: |
- lot = self.browse(cr, uid, ref('lot_icecream_0'), context=context)
+ lot = self.browse(cr, uid, ref('lot_monitor_0'), context=context)
assert lot.stock_available == 50, "Stock in lot is not correspond."
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index b30feccdacf..27d5c44d199 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -1,5 +1,5 @@
-
- I confirm outgoing shipment of 130 kgm Ice-cream.
+ I confirm outgoing shipment of 130 unit 15” LCD Monitor.
-
!workflow {model: stock.picking, action: button_confirm, ref: outgoing_shipment}
-
@@ -12,18 +12,18 @@
assert move_line.state == "confirmed", "Move should be confirmed."
-
- Now I check vitual stock of Ice-cream after confirmed outgoing shipment.
+ Now I check vitual stock of 15” LCD Monitor after confirmed outgoing shipment.
-
!python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_icecream'), context=context)
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
product.virtual_available == -30, "Vitual stock is not updated."
-
- I confirm incomming shipment of 50 kgm Ice-cream.
+ I confirm incomming shipment of 50 unit 15” LCD Monitor.
-
!workflow {model: stock.picking, action: button_confirm, ref: incomming_shipment}
-
- I receive 40kgm Ice-cream so I make backorder of incomming shipment for 40 kgm.
+ I receive 40 unit 15” LCD Monitor so I make backorder of incomming shipment for 40 unit.
-
!python {model: stock.partial.picking}: |
context.update({'active_model': 'stock.picking', 'active_id': ref('incomming_shipment'), 'active_ids': [ref('incomming_shipment')]})
@@ -31,11 +31,11 @@
!record {model: stock.partial.picking, id: partial_incomming}:
move_ids:
- quantity: 40
- product_id: product_icecream
- product_uom: product.product_uom_kgm
- move_id: incomming_shipment_icecream
- location_id: location_convenience_shop
- location_dest_id: location_refrigerator
+ product_id: product_product_6
+ product_uom: product.product_uom_unit
+ move_id: incomming_shipment_monitor
+ location_id: stock_location_3
+ location_dest_id: location_monitor
-
!python {model: stock.partial.picking }: |
self.do_partial(cr, uid, [ref('partial_incomming')], context=context)
@@ -51,16 +51,16 @@
assert move_line.product_qty == 40, "Qty in backorder does not correspond."
assert move_line.state == 'done', "Move line of backorder should be closed."
-
- I receive another 10kgm Ice-cream.
+ I receive another 10 unit 15” LCD Monitor.
-
!record {model: stock.partial.picking, id: partial_incomming}:
move_ids:
- quantity: 10
- product_id: product_icecream
- product_uom: product.product_uom_kgm
- move_id: incomming_shipment_icecream
- location_id: location_convenience_shop
- location_dest_id: location_refrigerator
+ product_id: product_product_6
+ product_uom: product.product_uom_unit
+ move_id: incomming_shipment_monitor
+ location_id: stock_location_3
+ location_dest_id: location_monitor
-
!python {model: stock.partial.picking }: |
self.do_partial(cr, uid, [ref('partial_incomming')], context=context)
@@ -77,7 +77,7 @@
assert move_line.state == 'done', "Move line should be closed."
-
- I return last incomming shipment for 10 kgm Ice-cream.
+ I return last incomming shipment for 10 unit 15” LCD Monitor.
-
!record {model: stock.return.picking, id: return_incomming}:
invoice_state: none
@@ -115,11 +115,11 @@
I check available stock after received incomming shipping.
-
!python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_icecream'), context=context)
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.qty_available == 140, "Stock does not correspond."
assert product.virtual_available == 0, "Vitual stock does not correspond."
-
- I split incomming shipment into lots. each lot contain 10 kgm Ice-cream.
+ I split incomming shipment into lots. each lot contain 10 unit 15” LCD Monitor.
-
!python {model: stock.picking}: |
shipment = self.browse(cr, uid, ref("incomming_shipment"))
@@ -147,7 +147,7 @@
lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incomming'), context=context)
lot_ids = self.pool.get('stock.production.lot').search(cr, uid, [('name','in',[x.name for x in lot.line_ids])])
assert len(lot_ids) == 4, 'lots of incomming shipment are not correspond.'
- move_ids = self.search(cr, uid, [('location_dest_id','=',ref('location_refrigerator')),('prodlot_id','in',lot_ids)])
+ move_ids = self.search(cr, uid, [('location_dest_id','=',ref('location_monitor')),('prodlot_id','in',lot_ids)])
assert len(move_ids) == 4, 'move lines are not correspond per prodcution lot after splited.'
for move in self.browse(cr, uid, move_ids, context=context):
assert move.prodlot_id.name in ['incoming_lot0', 'incoming_lot1', 'incoming_lot2', 'incoming_lot3'], "lot does not correspond."
@@ -171,39 +171,39 @@
assert account_move_line.credit == 0.0, "Credit amount does not correspond."
assert account_move_line.debit == 800.0, "Debit amount does not correspond."
-
- I consume 1 kgm ice-cream from each incoming lots into internal production.
+ I consume 1 unit 15” LCD Monitor from each incoming lots into internal production.
-
!record {model: stock.move.consume, id: consume_lot_incomming}:
product_qty: 1
- location_id: location_refrigerator
+ location_id: location_monitor
-
!python {model: stock.move.consume}: |
self.do_move_consume(cr, uid, [ref('consume_lot_incomming')], context=context)
-
- I scrap 10 gm ice-cream from each incoming lots into scrap location.
+ I scrap 1 unit 15” LCD Monitor from each incoming lots into scrap location.
-
!record {model: stock.move.scrap, id: scrap_lot_incomming}:
- product_qty: 0.010
+ product_qty: 1
-
!python {model: stock.move.scrap}: |
self.move_scrap(cr, uid, [ref('scrap_lot_incomming')], context=context)
-
- I check stock in scrap location and refrigerator location.
+ I check stock in scrap location and stock location shop0.
-
!python {model: stock.location}: |
- ctx = {'product_id': ref('product_icecream')}
- refrigerator_location = self.pool.get('stock.location').browse(cr, uid, ref('location_refrigerator'), context=ctx)
- assert refrigerator_location.stock_real == 135.96, 'stock does not correspond in refrigerator location.'
+ ctx = {'product_id': ref('product_product_6')}
+ refrigerator_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
+ assert refrigerator_location.stock_real == 36.0, 'stock does not correspond in stock location shop0.'
scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
- assert scrapped_location.stock_real == 0.010*4, 'scraped stock does not correspond in scrap location.'
+ assert scrapped_location.stock_real == 1*4, 'scraped stock does not correspond in scrap location.'
-
I check availabile stock after consumed and scraped.
-
!python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_icecream'), context=context)
- assert product.qty_available == 135.96, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -4.04, "Vitual stock does not correspond."
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
+ assert product.qty_available == 136.0, "Stock does not correspond."
+ assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
-
I trace all incoming lots.
-
@@ -212,40 +212,39 @@
lot_ids = self.search(cr, uid, [('name', 'in', [x.name for x in lot.line_ids])])
self.action_traceability(cr, uid, lot_ids, context=context)
-
- I check outgoing shipment after stock availablity in refrigerator.
+ I check outgoing shipment after stock availablity in Chicago shop.
-
!python {model: stock.picking}: |
shipment = self.browse(cr, uid, ref("outgoing_shipment"), context=context)
- self.pool.get('stock.move').action_assign(cr, uid, [x.id for x in shipment.move_lines]) #TOFIX: assignment of move lines should be call before testing assigment otherwise picking never gone in assign state
- #TOFIX: shipment should be assigned if stock available
- #assert shipment.state == "assigned", "Shipment should be assigned."
- #for move_line in shipment.move_lines:
- # assert move_line.state == "assigned", "Move should be assigned."
+ self.pool.get('stock.move').action_assign(cr, uid, [x.id for x in shipment.move_lines])
+ assert shipment.state == "assigned", "Shipment should be assigned."
+ for move_line in shipment.move_lines:
+ assert move_line.state == "assigned", "Move should be assigned."
self.force_assign(cr, uid, [shipment.id])
-
- I deliver 5kgm Ice-cream to customer so I make partial deliver
+ I deliver 5 unit 15” LCD Monitor to customer so I make partial deliver
-
!python {model: stock.partial.move}: |
- context.update({'active_model': 'stock.move', 'active_id': ref('outgoing_shipment_icecream'), 'active_ids': [ref('outgoing_shipment_icecream')]})
+ context.update({'active_model': 'stock.move', 'active_id': ref('outgoing_shipment_monitor'), 'active_ids': [ref('outgoing_shipment_monitor')]})
-
- !record {model: stock.partial.move, id: partial_outgoing_icecream}:
+ !record {model: stock.partial.move, id: partial_outgoing_monitor}:
move_ids:
- quantity: 5
- product_id: product_icecream
- product_uom: product.product_uom_kgm
- move_id: outgoing_shipment_icecream
- location_id: location_refrigerator
- location_dest_id: location_delivery_counter
+ product_id: product_product_6
+ product_uom: product.product_uom_unit
+ move_id: outgoing_shipment_monitor
+ location_id: location_monitor
+ location_dest_id: stock_location_output
-
!python {model: stock.partial.move }: |
- self.do_partial(cr, uid, [ref('partial_outgoing_icecream')], context=context)
+ self.do_partial(cr, uid, [ref('partial_outgoing_monitor')], context=context)
-
- I packing outgoing shipment into box per 10kgm with unique tracking lot.
+ I packing outgoing shipment into box per 10 unit with unique tracking lot.
-
!python {model: stock.move}: |
stock_split = self.pool.get('stock.split.into')
- move = self.browse(cr, uid, ref('outgoing_shipment_icecream'), context=context)
+ move = self.browse(cr, uid, ref('outgoing_shipment_monitor'), context=context)
context.update({'active_model': 'stock.move', 'active_id': move.id, 'active_ids': [move.id]})
total_qty = move.product_qty
split_qty = 10
@@ -277,6 +276,6 @@
I check availaible stock after deliver.
-
!python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_icecream'), context=context)
- assert round(product.qty_available, 2) == 5.96, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -4.04, "Vitual stock does not correspond."
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
+ assert round(product.qty_available, 2) == 6, "Stock does not correspond."
+ assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
From 4de42a4254bb6abc30321d183c8609e04dc18009 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 28 Feb 2013 12:01:46 +0530
Subject: [PATCH 031/256] [IMP] improve stock yml
bzr revid: fka@tinyerp.com-20130228063146-kfy538aq3p1hh029
---
addons/stock/stock_demo.yml | 4 ++--
addons/stock/test/shipment.yml | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/addons/stock/stock_demo.yml b/addons/stock/stock_demo.yml
index 8e9ea4977b8..b79fd68d509 100644
--- a/addons/stock/stock_demo.yml
+++ b/addons/stock/stock_demo.yml
@@ -38,8 +38,8 @@
property_stock_inventory: location_opening
valuation: real_time
cost_method: average
- property_stock_account_input: account.conf_o_expense
- property_stock_account_output: account.conf_o_income
+ property_stock_account_input: account.conf_o_income
+ property_stock_account_output: account.conf_o_expense
-
!record {model: stock.production.lot, id: lot_monitor_0}:
name: Lot0 for LCD Monitor
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index 27d5c44d199..4622e0072bc 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -165,11 +165,11 @@
for account_move_line in account_move.line_id:
for stock_move in incomming_shipment.move_lines:
if account_move_line.account_id.id == stock_move.product_id.property_stock_account_input.id:
- assert account_move_line.credit == 800.0, "Credit amount does not correspond."
+ assert account_move_line.credit == 10000.0, "Credit amount does not correspond."
assert account_move_line.debit == 0.0, "Debit amount does not correspond."
else:
assert account_move_line.credit == 0.0, "Credit amount does not correspond."
- assert account_move_line.debit == 800.0, "Debit amount does not correspond."
+ assert account_move_line.debit == 10000.0, "Debit amount does not correspond."
-
I consume 1 unit 15” LCD Monitor from each incoming lots into internal production.
-
@@ -193,7 +193,7 @@
!python {model: stock.location}: |
ctx = {'product_id': ref('product_product_6')}
refrigerator_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
- assert refrigerator_location.stock_real == 36.0, 'stock does not correspond in stock location shop0.'
+ assert refrigerator_location.stock_real == 136.0, 'stock does not correspond in stock location shop0.'
scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
assert scrapped_location.stock_real == 1*4, 'scraped stock does not correspond in scrap location.'
From 506b7c9c64f5e0022aa15aeab05d18ca6b766fed Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 28 Feb 2013 12:13:38 +0530
Subject: [PATCH 032/256] [IMP] change data of stock_report.yml
bzr revid: fka@tinyerp.com-20130228064338-r93gtraqgrfc76mc
---
addons/stock/__openerp__.py | 2 +-
addons/stock/test/shipment.yml | 4 ++--
addons/stock/test/stock_report.yml | 8 ++++----
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/addons/stock/__openerp__.py b/addons/stock/__openerp__.py
index e1c96ba0c25..323fac889be 100644
--- a/addons/stock/__openerp__.py
+++ b/addons/stock/__openerp__.py
@@ -93,7 +93,7 @@ Dashboard / Reports for Warehouse Management will include:
'stock_demo.yml',
'test/opening_stock.yml',
'test/shipment.yml',
- #'test/stock_report.yml',
+ 'test/stock_report.yml',
],
'installable': True,
'application': True,
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index 4622e0072bc..96273a081b7 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -192,8 +192,8 @@
-
!python {model: stock.location}: |
ctx = {'product_id': ref('product_product_6')}
- refrigerator_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
- assert refrigerator_location.stock_real == 136.0, 'stock does not correspond in stock location shop0.'
+ monitor_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
+ assert monitor_location.stock_real == 136.0, 'stock does not correspond in stock location shop0.'
scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
assert scrapped_location.stock_real == 1*4, 'scraped stock does not correspond in scrap location.'
diff --git a/addons/stock/test/stock_report.yml b/addons/stock/test/stock_report.yml
index b1590a4d819..b16ade8a085 100644
--- a/addons/stock/test/stock_report.yml
+++ b/addons/stock/test/stock_report.yml
@@ -4,7 +4,7 @@
!python {model: stock.location}: |
import os
from openerp import netsvc, tools
- (data, format) = netsvc.LocalService('report.lot.stock.overview').create(cr, uid, [ref('location_refrigerator')], {}, {})
+ (data, format) = netsvc.LocalService('report.lot.stock.overview').create(cr, uid, [ref('location_monitor')], {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'stock-overview'+format), 'wb+').write(data)
-
@@ -13,7 +13,7 @@
!python {model: stock.location}: |
import os
from openerp import netsvc, tools
- (data, format) = netsvc.LocalService('report.lot.stock.overview_all').create(cr, uid, [ref('location_refrigerator')], {}, {})
+ (data, format) = netsvc.LocalService('report.lot.stock.overview_all').create(cr, uid, [ref('location_monitor')], {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'stock-overviewall'+format), 'wb+').write(data)
-
@@ -22,7 +22,7 @@
!python {model: stock.inventory}: |
import os
from openerp import netsvc, tools
- (data, format) = netsvc.LocalService('report.stock.inventory.move').create(cr, uid, [ref('stock_inventory_icecream')], {}, {})
+ (data, format) = netsvc.LocalService('report.stock.inventory.move').create(cr, uid, [ref('stock_inventory_0')], {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'stock-stock_inventory_move.'+format), 'wb+').write(data)
-
@@ -40,7 +40,7 @@
!python {model: product.product}: |
import os
from openerp import netsvc, tools
- (data, format) = netsvc.LocalService('report.stock.product.history').create(cr, uid, [ref('product_icecream')], {}, {})
+ (data, format) = netsvc.LocalService('report.stock.product.history').create(cr, uid, [ref('product_product_6')], {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'stock-product_stock_report.'+format), 'wb+').write(data)
From 721daed4c6bdd35ae44476ad9ae0c3edbb823e89 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 28 Feb 2013 14:35:06 +0530
Subject: [PATCH 033/256] [IMP] add stock_users.yml file in stock for test
access rights
bzr revid: fka@tinyerp.com-20130228090506-esh7d6nycoqqymay
---
addons/stock/__openerp__.py | 1 +
addons/stock/stock_demo.yml | 10 ++++++++++
addons/stock/test/stock_users.yml | 20 ++++++++++++++++++++
3 files changed, 31 insertions(+)
create mode 100644 addons/stock/test/stock_users.yml
diff --git a/addons/stock/__openerp__.py b/addons/stock/__openerp__.py
index 323fac889be..dd2a996f6dd 100644
--- a/addons/stock/__openerp__.py
+++ b/addons/stock/__openerp__.py
@@ -90,6 +90,7 @@ Dashboard / Reports for Warehouse Management will include:
'res_config_view.xml',
],
'test': [
+ 'test/stock_users.yml',
'stock_demo.yml',
'test/opening_stock.yml',
'test/shipment.yml',
diff --git a/addons/stock/stock_demo.yml b/addons/stock/stock_demo.yml
index b79fd68d509..64be92f8d06 100644
--- a/addons/stock/stock_demo.yml
+++ b/addons/stock/stock_demo.yml
@@ -1,3 +1,8 @@
+-
+ Only stock manager can create location,warehouse and product, so let's check data with giving the access rights of manager
+-
+ !context
+ uid: 'res_users_stock_manager'
-
!record {model: stock.location, id: location_monitor}:
name: chicago shop
@@ -40,6 +45,11 @@
cost_method: average
property_stock_account_input: account.conf_o_income
property_stock_account_output: account.conf_o_expense
+-
+ Stock user can handled production lot,inventory and picking, so let's check data with giving the access rights of user
+-
+ !context
+ uid: 'res_users_stock_user'
-
!record {model: stock.production.lot, id: lot_monitor_0}:
name: Lot0 for LCD Monitor
diff --git a/addons/stock/test/stock_users.yml b/addons/stock/test/stock_users.yml
new file mode 100644
index 00000000000..82d48197b0d
--- /dev/null
+++ b/addons/stock/test/stock_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Stock Manager'
+-
+ !record {model: res.users, id: res_users_stock_manager}:
+ company_id: base.main_company
+ name: Stock Manager
+ login: sam
+ password: sam
+ groups_id:
+ - stock.group_stock_manager
+-
+ Create a user as 'Stock User'
+-
+ !record {model: res.users, id: res_users_stock_user}:
+ company_id: base.main_company
+ name: Stock User
+ login: sau
+ password: sau
+ groups_id:
+ - stock.group_stock_user
From 8009999e2981260e73fd11e11bacfa8a1fd84e2f Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 28 Feb 2013 18:58:36 +0530
Subject: [PATCH 034/256] [IMP] give access rights to the user in stock ymls
bzr revid: fka@tinyerp.com-20130228132836-e2f7rknoodpqix0p
---
addons/stock/security/stock_security.xml | 2 +-
addons/stock/test/opening_stock.yml | 30 ++++++++++++++++++++++++
addons/stock/test/stock_report.yml | 5 ++++
3 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/addons/stock/security/stock_security.xml b/addons/stock/security/stock_security.xml
index 10ae6a16d59..e51f20c67c9 100644
--- a/addons/stock/security/stock_security.xml
+++ b/addons/stock/security/stock_security.xml
@@ -10,7 +10,7 @@
Manager
-
+
diff --git a/addons/stock/test/opening_stock.yml b/addons/stock/test/opening_stock.yml
index 41b906e560c..9efcfd3af99 100644
--- a/addons/stock/test/opening_stock.yml
+++ b/addons/stock/test/opening_stock.yml
@@ -1,3 +1,8 @@
+-
+ Only stock manager can change the price and update stock of products, so let's check data with giving the access rights of manager
+-
+ !context
+ uid: 'res_users_stock_manager'
-
I update the price of the 15” LCD Monitor.
-
@@ -32,6 +37,11 @@
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.qty_available == 10, "Stock is not updated."
+-
+ Stock user can merge inventory, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_stock_user'
-
I merge inventory.
-
@@ -42,11 +52,21 @@
-
!python {model: stock.inventory.merge }: |
self.do_merge(cr, uid, [ref('merge_inventory')], context=context)
+-
+ Only stock manager cancelled inventory, so let's check data with giving the access rights of manager
+-
+ !context
+ uid: 'res_users_stock_manager'
-
I cancel inventory.
-
!python {model: stock.inventory}: |
self.action_cancel_inventory(cr, uid, [ref('stock_inventory_0')])
+-
+ stock user can reset inventory, so let's check data with giving the access rights of user
+-
+ !context
+ uid: 'res_users_stock_user'
-
I reset to draft inventory.
-
@@ -108,11 +128,21 @@
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.virtual_available == 100, "Vitual stock is not updated."
+-
+ Only stock manager can close physical inventory, so let's check data with giving the access rights of manager
+-
+ !context
+ uid: 'res_users_stock_manager'
-
I close physical inventory of 15” LCD Monitor.
-
!python {model: stock.inventory}: |
self.action_done(cr, uid, [ref('stock_inventory_0')], context=context)
+-
+ Stock user can check closed move and real stock, so let's check data with giving the access rights of user
+-
+ !context
+ uid: 'res_users_stock_user'
-
I check closed move and real stock of 15” LCD Monitor after closed physical inventory.
-
diff --git a/addons/stock/test/stock_report.yml b/addons/stock/test/stock_report.yml
index b16ade8a085..f1a31f23d93 100644
--- a/addons/stock/test/stock_report.yml
+++ b/addons/stock/test/stock_report.yml
@@ -1,3 +1,8 @@
+-
+ Stock user can print all reports related to stock, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_stock_user'
-
I print a stock overview report of location.
-
From 8eb3f28c28d54a7cca27ac274f78054db9185991 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Fri, 1 Mar 2013 11:12:53 +0530
Subject: [PATCH 035/256] [IMP] remove space
bzr revid: fka@tinyerp.com-20130301054253-qbep31elzopq6xa4
---
addons/stock/product.py | 5 +++--
addons/stock/stock.py | 1 +
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/addons/stock/product.py b/addons/stock/product.py
index b00f8615667..254eb53d255 100644
--- a/addons/stock/product.py
+++ b/addons/stock/product.py
@@ -55,14 +55,15 @@ class product_product(osv.osv):
if context is None:
context = {}
product_obj = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+
stock_input_acc = product_obj.property_stock_account_input and product_obj.property_stock_account_input.id or False
if not stock_input_acc:
stock_input_acc = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id or False
-
+
stock_output_acc = product_obj.property_stock_account_output and product_obj.property_stock_account_output.id or False
if not stock_output_acc:
stock_output_acc = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id or False
-
+
journal_id = product_obj.categ_id.property_stock_journal and product_obj.categ_id.property_stock_journal.id or False
account_valuation = product_obj.categ_id.property_stock_valuation_account_id and product_obj.categ_id.property_stock_valuation_account_id.id or False
return {
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index ed2b36bd1f5..8e4611dc5d0 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -2233,6 +2233,7 @@ class stock_move(osv.osv):
acc_valuation = accounts.get('property_stock_valuation_account_id', False)
journal_id = accounts['stock_journal']
+
if acc_dest == acc_valuation:
raise osv.except_osv(_('Error!'), _('Cannot create Journal Entry, Output Account of this product and Valuation account on category of this product are same.'))
From b6cbd45d01274615bdb1ee17a831dbddc4fdc091 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Fri, 1 Mar 2013 11:17:45 +0530
Subject: [PATCH 036/256] [IMP] remove space
bzr revid: fka@tinyerp.com-20130301054745-52t0n0mh8b3xo4qt
---
addons/stock/product.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/addons/stock/product.py b/addons/stock/product.py
index 254eb53d255..1d56ce64988 100644
--- a/addons/stock/product.py
+++ b/addons/stock/product.py
@@ -55,15 +55,15 @@ class product_product(osv.osv):
if context is None:
context = {}
product_obj = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
-
+
stock_input_acc = product_obj.property_stock_account_input and product_obj.property_stock_account_input.id or False
if not stock_input_acc:
stock_input_acc = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id or False
-
+
stock_output_acc = product_obj.property_stock_account_output and product_obj.property_stock_account_output.id or False
if not stock_output_acc:
stock_output_acc = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id or False
-
+
journal_id = product_obj.categ_id.property_stock_journal and product_obj.categ_id.property_stock_journal.id or False
account_valuation = product_obj.categ_id.property_stock_valuation_account_id and product_obj.categ_id.property_stock_valuation_account_id.id or False
return {
From ab83bc5d851b6339806c56804adb0eee151816a0 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Fri, 1 Mar 2013 14:58:05 +0530
Subject: [PATCH 037/256] [IMP] add yml of mrp_users and give access rights
bzr revid: fka@tinyerp.com-20130301092805-56z3lzm8obeon6re
---
addons/mrp/__openerp__.py | 7 ++++---
addons/mrp/security/ir.model.access.csv | 1 +
addons/mrp/security/mrp_security.xml | 1 +
addons/mrp/test/cancel_order.yml | 5 +++++
addons/mrp/test/mrp_users.yml | 20 ++++++++++++++++++++
addons/mrp/test/order_demo.yml | 5 +++++
addons/mrp/test/order_process.yml | 5 +++++
7 files changed, 41 insertions(+), 3 deletions(-)
create mode 100644 addons/mrp/test/mrp_users.yml
diff --git a/addons/mrp/__openerp__.py b/addons/mrp/__openerp__.py
index 6e025ae2d90..5f5ded337a7 100644
--- a/addons/mrp/__openerp__.py
+++ b/addons/mrp/__openerp__.py
@@ -77,9 +77,10 @@ Dashboard / Reports for MRP will include:
#TODO: This yml tests are needed to be completely reviewed again because the product wood panel is removed in product demo as it does not suit for new demo context of computer and consultant company
# so the ymls are too complex to change at this stage
'test': [
-# 'test/order_demo.yml',
-# 'test/order_process.yml',
-# 'test/cancel_order.yml',
+ 'test/mrp_users.yml',
+ 'test/order_demo.yml',
+ 'test/order_process.yml',
+ 'test/cancel_order.yml',
],
'installable': True,
'application': True,
diff --git a/addons/mrp/security/ir.model.access.csv b/addons/mrp/security/ir.model.access.csv
index 7a94b1a660c..2d0a478c33d 100644
--- a/addons/mrp/security/ir.model.access.csv
+++ b/addons/mrp/security/ir.model.access.csv
@@ -1,4 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_account_analytic_line_user,account.analytic.line,account.model_account_analytic_line,group_mrp_user,1,1,1,0
access_mrp_workcenter,mrp.workcenter,model_mrp_workcenter,mrp.group_mrp_user,1,0,0,0
access_mrp_routing,mrp.routing,model_mrp_routing,mrp.group_mrp_user,1,0,0,0
access_mrp_routing_workcenter,mrp.routing.workcenter,model_mrp_routing_workcenter,mrp.group_mrp_user,1,0,0,0
diff --git a/addons/mrp/security/mrp_security.xml b/addons/mrp/security/mrp_security.xml
index 35aae42b53d..9e5dc919720 100644
--- a/addons/mrp/security/mrp_security.xml
+++ b/addons/mrp/security/mrp_security.xml
@@ -4,6 +4,7 @@
User
+
diff --git a/addons/mrp/test/cancel_order.yml b/addons/mrp/test/cancel_order.yml
index 9e197156b15..2f721b6c653 100644
--- a/addons/mrp/test/cancel_order.yml
+++ b/addons/mrp/test/cancel_order.yml
@@ -1,3 +1,8 @@
+-
+ MRP user can cancelled Production Order, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_mrp_user'
-
I first confirm order for PC Assemble SC349.
-
diff --git a/addons/mrp/test/mrp_users.yml b/addons/mrp/test/mrp_users.yml
new file mode 100644
index 00000000000..d18cc51e8e6
--- /dev/null
+++ b/addons/mrp/test/mrp_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'MRP Manager'
+-
+ !record {model: res.users, id: res_users_mrp_manager}:
+ company_id: base.main_company
+ name: MRP Manager
+ login: mam
+ password: mam
+ groups_id:
+ - mrp.group_mrp_manager
+-
+ Create a user as 'MRP User'
+-
+ !record {model: res.users, id: res_users_mrp_user}:
+ company_id: base.main_company
+ name: MRP User
+ login: mau
+ password: mau
+ groups_id:
+ - mrp.group_mrp_user
diff --git a/addons/mrp/test/order_demo.yml b/addons/mrp/test/order_demo.yml
index ee21fee96df..0e4914390fb 100644
--- a/addons/mrp/test/order_demo.yml
+++ b/addons/mrp/test/order_demo.yml
@@ -1,3 +1,8 @@
+-
+ MRP user can create Production Order, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_mrp_user'
-
I create Production Order of PC Assemble SC349 to produce 5.0 Unit.
-
diff --git a/addons/mrp/test/order_process.yml b/addons/mrp/test/order_process.yml
index 89331d5c255..8aa408483e9 100644
--- a/addons/mrp/test/order_process.yml
+++ b/addons/mrp/test/order_process.yml
@@ -1,3 +1,8 @@
+-
+ MRP user can doing all process related to Production Order, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_mrp_user'
-
I compute the production order.
-
From b98082337bae437ac615465e40226a4f42838575 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 12 Mar 2013 14:09:55 +0530
Subject: [PATCH 038/256] [IMP]Improve yml and make work with purchase user
bzr revid: sgo@tinyerp.com-20130312083955-la478tbdpfqtv1wr
---
addons/purchase/test/process/invoice_on_poline.yml | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/addons/purchase/test/process/invoice_on_poline.yml b/addons/purchase/test/process/invoice_on_poline.yml
index 8a391b13ebb..d31b371f009 100644
--- a/addons/purchase/test/process/invoice_on_poline.yml
+++ b/addons/purchase/test/process/invoice_on_poline.yml
@@ -1,3 +1,8 @@
+-
+ Purchase User confirm the order and create invoice based on purchase order line.
+-
+ !context
+ uid: 'res_users_purchase_user'
-
I confirm purchase order which has invoicing control method "Based on Purchase Order Lines".
-
@@ -17,10 +22,4 @@
!python {model: purchase.order}: |
purchase_order = self.browse(cr, uid, ref("purchase_order_6"))
for purchase_line in purchase_order.order_line:
- assert len(purchase_order.invoice_ids) == 1, "Invoice should be generated."
--
- I set the default invoicing control method "Based on Purchase Order Lines".
--
- !python {model: purchase.config.settings}: |
- new_id = self.create(cr, uid, {'default_invoice_method': 'manual'})
- self.execute(cr, uid, [new_id])
+ assert len(purchase_order.invoice_ids) == 1, "Invoice should be generated."
\ No newline at end of file
From 15f6bea1e9eacbe97b90b38337105a1c4219d757 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 12 Mar 2013 16:57:00 +0530
Subject: [PATCH 039/256] [IMP]improve yml for project and add access rights as
needed
bzr revid: sgo@tinyerp.com-20130312112700-zamjzcjp5d9nv5n0
---
addons/project/__openerp__.py | 1 +
addons/project/security/ir.model.access.csv | 1 +
addons/project/security/project_security.xml | 1 +
addons/project/test/project_demo.yml | 23 +++++++++----
addons/project/test/project_process.yml | 5 +++
addons/project/test/project_users.yml | 20 +++++++++++
addons/project/test/task_process.yml | 35 +++++++++++---------
7 files changed, 65 insertions(+), 21 deletions(-)
create mode 100644 addons/project/test/project_users.yml
diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py
index c0a516cf555..94761835656 100644
--- a/addons/project/__openerp__.py
+++ b/addons/project/__openerp__.py
@@ -79,6 +79,7 @@ Dashboard / Reports for Project Management will include:
],
'demo': ['project_demo.xml'],
'test': [
+ 'test/project_users.yml',
'test/project_demo.yml',
'test/project_process.yml',
'test/task_process.yml',
diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv
index a76faa0fcce..1428f445e43 100644
--- a/addons/project/security/ir.model.access.csv
+++ b/addons/project/security/ir.model.access.csv
@@ -19,6 +19,7 @@ access_project_task_history,project.task.history project,project.model_project_t
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0
access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0
+access_resource_calendar_leaves,project.resource_calendar_leaves manager,resource.model_resource_calendar_leaves,project.group_project_manager,1,0,0,0
access_project_category,project.project_category,model_project_category,,1,0,0,0
access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1
diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml
index f1d8c7e760f..f17c781d702 100644
--- a/addons/project/security/project_security.xml
+++ b/addons/project/security/project_security.xml
@@ -4,6 +4,7 @@
User
+
diff --git a/addons/project/test/project_demo.yml b/addons/project/test/project_demo.yml
index 5f6acbb913e..21d0af324ff 100644
--- a/addons/project/test/project_demo.yml
+++ b/addons/project/test/project_demo.yml
@@ -1,12 +1,23 @@
+-
+ I Update project and create the new task with project manager
+-
+ !context
+ uid: 'res_users_project_manager'
+-
+ Change partner for The Jackson Group's Project
-
!record {model: project.project, id: project_project_1, view: False}:
partner_id: base.res_partner_2
-
- !record {model: project.task, id: project_task_1, view: False}:
+ I create task for The Jackson Group's Project project user can create task.
+-
+ !context
+ uid: 'res_users_project_user'
+-
+ !record {model: project.task, id: project_task_for_project1}:
+ name: Worked on new specification
+ user_id: res_users_project_user
remaining_hours: 10.00
--
- !record {model: project.task, id: project_task_1, view: False}:
planned_hours: 10.00
--
- !record {model: project.task, id: project_task_1, view: False}:
- project_id: project_project_1
\ No newline at end of file
+ project_id: project_project_1
+ stage_id: project_tt_specification
\ No newline at end of file
diff --git a/addons/project/test/project_process.yml b/addons/project/test/project_process.yml
index c2a5c072e9d..9ef488a07b4 100644
--- a/addons/project/test/project_process.yml
+++ b/addons/project/test/project_process.yml
@@ -1,3 +1,8 @@
+-
+ Test the whole project process with project manager.
+-
+ !context
+ uid: 'res_users_project_manager'
-
In order to Test Process of Project Management,
-
diff --git a/addons/project/test/project_users.yml b/addons/project/test/project_users.yml
new file mode 100644
index 00000000000..b23ab0fc7f6
--- /dev/null
+++ b/addons/project/test/project_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Project manager'
+-
+ !record {model: res.users, id: res_users_project_manager}:
+ company_id: base.main_company
+ name: Project Manager
+ login: prm
+ password: prm
+ groups_id:
+ - project.group_project_manager
+-
+ Create a user as 'Project user'
+-
+ !record {model: res.users, id: res_users_project_user}:
+ company_id: base.main_company
+ name: Project User
+ login: pru
+ password: pru
+ groups_id:
+ - project.group_project_user
\ No newline at end of file
diff --git a/addons/project/test/task_process.yml b/addons/project/test/task_process.yml
index e37b2116714..664f0b1563e 100644
--- a/addons/project/test/task_process.yml
+++ b/addons/project/test/task_process.yml
@@ -1,46 +1,51 @@
+-
+ Test the whole task process with project user.
+-
+ !context
+ uid: 'res_users_project_user'
-
I put task in pending due to specification is not clear.
-
!python {model: project.task}: |
- self.do_pending(cr, uid, [ref("project_task_1")])
- context.update({"active_id": ref("project_task_1")})
+ self.do_pending(cr, uid, [ref("project_task_for_project1")])
+ context.update({"active_id": ref("project_task_for_project1")})
-
I check state of task after put in pending.
-
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be in pending state}:
+ !assert {model: project.task, id: project_task_for_project1, severity: error, string: task should be in pending state}:
- state == "pending"
-
!record {model: project.task.delegate, id: delegate_id}:
- user_id: base.user_demo
+ user_id: res_users_project_user
planned_hours: 12.0
planned_hours_me: 2.0
-
Now I delegate task to team member.
-
!python {model: project.task.delegate}: |
- self.delegate(cr, uid, [ref("delegate_id")], {"active_id": ref("project_task_1")})
+ self.delegate(cr, uid, [ref("delegate_id")], {"active_id": ref("project_task_for_project1")})
-
I check delegated task details.
-
!python {model: project.task}: |
- task = self.browse(cr, uid, ref("project_task_1"), context=context)
+ task = self.browse(cr, uid, ref("project_task_for_project1"), context=context)
assert task.planned_hours == 2.0, "Planning hours is not correct after delegated."
assert task.state == "pending", "Task should be in Pending after delegated."
-
I re-open the task.
-
!python {model: project.task}: |
- self.do_reopen(cr, uid, [ref("project_task_1")])
+ self.do_reopen(cr, uid, [ref("project_task_for_project1")])
-
I check reopened task details.
-
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be open.}:
+ !assert {model: project.task, id: project_task_for_project1, severity: error, string: task should be open.}:
- state == "open"
-
I change the stage of task to next stage.
-
!python {model: project.task}: |
- self.stage_next(cr, uid, [ref("project_task_1")])
+ self.stage_next(cr, uid, [ref("project_task_for_project1")])
-
!record {model: project.task.reevaluate, id: reevaluate_id}:
remaining_hours : 120
@@ -48,29 +53,29 @@
I reevaluate task with remaining hours.
-
!python {model: project.task.reevaluate}: |
- self.compute_hours(cr, uid, [ref("reevaluate_id")], {"active_id": ref("project_task_1")})
+ self.compute_hours(cr, uid, [ref("reevaluate_id")], {"active_id": ref("project_task_for_project1")})
-
I check remaining hours after reevaluated task.
-
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be reevaluated}:
+ !assert {model: project.task, id: project_task_for_project1, severity: error, string: task should be reevaluated}:
- remaining_hours == 120.0
-
I close the task.
-
!python {model: project.task}: |
- self.action_close(cr, uid, [ref("project_task_1")])
+ self.action_close(cr, uid, [ref("project_task_for_project1")])
-
I check state after closed.
-
- !assert {model: project.task, id: project_task_1, severity: error, string: task is in open state}:
+ !assert {model: project.task, id: project_task_for_project1, severity: error, string: task is in open state}:
- state == "done"
-
I change the stage of task to previous stage.
-
!python {model: project.task}: |
- self.stage_previous(cr, uid, [ref("project_task_1")])
+ self.stage_previous(cr, uid, [ref("project_task_for_project1")])
-
I cancel Task.
-
!python {model: project.task}: |
- self.do_cancel(cr, uid, [ref("project_task_2")])
+ self.do_cancel(cr, uid, [ref("project_task_for_project1")])
From 6bc66196127703dfc15245872fa99ee23dc91946 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 14 Mar 2013 15:11:01 +0530
Subject: [PATCH 040/256] [IMP] give access rights in project issue
bzr revid: fka@tinyerp.com-20130314094101-3n3mkiw1msm0vyrc
---
addons/project_issue/__openerp__.py | 1 +
addons/project_issue/test/cancel_issue.yml | 5 +++++
addons/project_issue/test/issue_demo.yml | 5 +++++
addons/project_issue/test/issue_process.yml | 15 ++++++++++++++
addons/project_issue/test/issue_users.yml | 20 +++++++++++++++++++
addons/project_issue/test/subscribe_issue.yml | 7 ++++++-
6 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 addons/project_issue/test/issue_users.yml
diff --git a/addons/project_issue/__openerp__.py b/addons/project_issue/__openerp__.py
index 2c5f311c044..553268ca224 100644
--- a/addons/project_issue/__openerp__.py
+++ b/addons/project_issue/__openerp__.py
@@ -53,6 +53,7 @@ It allows the manager to quickly check the issues, assign them and decide on the
],
'demo': ['project_issue_demo.xml'],
'test': [
+ 'test/issue_users.yml',
'test/subscribe_issue.yml',
'test/issue_process.yml',
'test/cancel_issue.yml',
diff --git a/addons/project_issue/test/cancel_issue.yml b/addons/project_issue/test/cancel_issue.yml
index 76ffa212638..b69f95675c6 100644
--- a/addons/project_issue/test/cancel_issue.yml
+++ b/addons/project_issue/test/cancel_issue.yml
@@ -1,3 +1,8 @@
+-
+ Project user can cancelled issue, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_project_issue_user'
-
In order to test process of issue tracking in OpenERP, I cancel the unqualified Issue.
-
diff --git a/addons/project_issue/test/issue_demo.yml b/addons/project_issue/test/issue_demo.yml
index 4e3790361e5..7040b631f46 100644
--- a/addons/project_issue/test/issue_demo.yml
+++ b/addons/project_issue/test/issue_demo.yml
@@ -1,3 +1,8 @@
+-
+ Test the whole create project issue with project user.
+-
+ !context
+ uid: 'res_users_project_issue_user'
-
!record {model: project.issue, id: project_task_1, view: False}:
task_id: 'project.project_task_17'
diff --git a/addons/project_issue/test/issue_process.yml b/addons/project_issue/test/issue_process.yml
index afb68a9b6c2..b5fe763fb43 100644
--- a/addons/project_issue/test/issue_process.yml
+++ b/addons/project_issue/test/issue_process.yml
@@ -1,3 +1,8 @@
+-
+ Project user can subscribe issue, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_project_issue_user'
-
In order to test process of issue tracking in OpenERP, I Open the Issue.
-
@@ -40,11 +45,21 @@
-
!assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in open state}:
- state == 'open'
+-
+ Only project manager can create Task for Issue, so let's check data with giving the access rights of manager.
+-
+ !context
+ uid: 'res_users_project_issue_manager'
-
I create Task for Issue.
-
!python {model: project.issue}: |
self.convert_issue_task(cr, uid, [ref("crm_case_buginaccountsmodule0")])
+-
+ After resolving Issue Project user can close issue, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_project_issue_user'
-
I close Issue after resolving it
-
diff --git a/addons/project_issue/test/issue_users.yml b/addons/project_issue/test/issue_users.yml
new file mode 100644
index 00000000000..3493ac58ffc
--- /dev/null
+++ b/addons/project_issue/test/issue_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'Project manager'
+-
+ !record {model: res.users, id: res_users_project_issue_manager}:
+ company_id: base.main_company
+ name: Project Manager
+ login: prim
+ password: prim
+ groups_id:
+ - project.group_project_manager
+-
+ Create a user as 'Project user'
+-
+ !record {model: res.users, id: res_users_project_issue_user}:
+ company_id: base.main_company
+ name: Project User
+ login: priu
+ password: priu
+ groups_id:
+ - project.group_project_user
\ No newline at end of file
diff --git a/addons/project_issue/test/subscribe_issue.yml b/addons/project_issue/test/subscribe_issue.yml
index 9f16dc80b97..e07c8704a14 100644
--- a/addons/project_issue/test/subscribe_issue.yml
+++ b/addons/project_issue/test/subscribe_issue.yml
@@ -1,5 +1,10 @@
-
- In Order to test process of Issue in OpenERP, Custmer send the issue by email.
+ Project user can subscribe issue, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_project_issue_user'
+-
+ In Order to test process of Issue in OpenERP, Customer send the issue by email.
-
!python {model: mail.thread}: |
from openerp import addons
From 6f4588cc62f212ba1f0040f83ab21b0546c62989 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 14 Mar 2013 15:44:49 +0530
Subject: [PATCH 041/256] [IMP] give access rights in yml of mrp operations
bzr revid: fka@tinyerp.com-20130314101449-a51uxlekaod33s8f
---
addons/mrp_operations/__openerp__.py | 2 +-
.../test/workcenter_operations.yml | 19 ++++++++++++++++---
2 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/addons/mrp_operations/__openerp__.py b/addons/mrp_operations/__openerp__.py
index 7fedb426edd..12d86e0b532 100644
--- a/addons/mrp_operations/__openerp__.py
+++ b/addons/mrp_operations/__openerp__.py
@@ -70,7 +70,7 @@ So, that we can compare the theoretic delay and real delay.
'mrp_operations_demo.yml'
],
'test': [
-# 'test/workcenter_operations.yml',
+ 'test/workcenter_operations.yml',
],
'installable': True,
'auto_install': False,
diff --git a/addons/mrp_operations/test/workcenter_operations.yml b/addons/mrp_operations/test/workcenter_operations.yml
index 4754bb463ff..3bcdc77ecab 100644
--- a/addons/mrp_operations/test/workcenter_operations.yml
+++ b/addons/mrp_operations/test/workcenter_operations.yml
@@ -1,6 +1,19 @@
+-
+ Create a user as 'MRP User'
+-
+ !record {model: res.users, id: res_mrp_operation_user}:
+ company_id: base.main_company
+ name: MRP User
+ login: maou
+ password: maou
+ groups_id:
+ - mrp.group_mrp_user
-
In order to test mrp_operations with OpenERP, I refer created production order of PC Assemble SC349
- with routing - Manual Component's Assembly to test complete production process with respect of workcenter.
+ with routing - Manual Component's Assembly to test complete production process with respect of workcenter with giving access rights of MRP User.
+-
+ !context
+ uid: 'res_mrp_operation_user'
-
I compute the production order.
-
@@ -92,7 +105,7 @@
I print a Barcode Report of Operation line.
-
!python {model: mrp_operations.operation.code}: |
- import netsvc, tools, os
+ from openerp import netsvc, tools
(data, format) = netsvc.LocalService('report.mrp.code.barcode').create(cr, uid, [ref('mrp_operations.mrp_op_1'),ref('mrp_operations.mrp_op_2'),ref('mrp_operations.mrp_op_3'),ref('mrp_operations.mrp_op_4'),ref('mrp_operations.mrp_op_5')], {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'mrp_operations-barcode_report.'+format), 'wb+').write(data)
@@ -101,7 +114,7 @@
I print Workcenter's Barcode Report.
-
!python {model: mrp.workcenter}: |
- import netsvc, tools, os
+ from openerp import netsvc, tools
(data, format) = netsvc.LocalService('report.mrp.wc.barcode').create(cr, uid, [ref('mrp.mrp_workcenter_0'),ref('mrp.mrp_workcenter_1')], {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'mrp_operations-workcenter_barcode_report.'+format), 'wb+').write(data)
From d3905268bc08f08db34669b8e90045fc2fb6f8f6 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 14 Mar 2013 16:24:11 +0530
Subject: [PATCH 042/256] [IMP] give access rights in yml of mrp repair
bzr revid: fka@tinyerp.com-20130314105411-cgqik9a6grk0pqjl
---
addons/mrp_repair/__openerp__.py | 3 ++-
addons/mrp_repair/test/mrp_repair_users.yml | 20 +++++++++++++++++++
.../test/test_mrp_repair_afterinv.yml | 5 ++++-
.../mrp_repair/test/test_mrp_repair_b4inv.yml | 5 ++++-
.../test/test_mrp_repair_cancel.yml | 5 ++++-
.../test/test_mrp_repair_noneinv.yml | 5 ++++-
6 files changed, 38 insertions(+), 5 deletions(-)
create mode 100644 addons/mrp_repair/test/mrp_repair_users.yml
diff --git a/addons/mrp_repair/__openerp__.py b/addons/mrp_repair/__openerp__.py
index 26480e975ce..a274cbc3ef4 100644
--- a/addons/mrp_repair/__openerp__.py
+++ b/addons/mrp_repair/__openerp__.py
@@ -52,7 +52,8 @@ The following topics should be covered by this module:
'mrp_repair_report.xml',
],
'demo': ['mrp_repair_demo.yml'],
- 'test': ['test/test_mrp_repair_noneinv.yml',
+ 'test': ['test/mrp_repair_users.yml',
+ 'test/test_mrp_repair_noneinv.yml',
'test/test_mrp_repair_b4inv.yml',
'test/test_mrp_repair_afterinv.yml',
'test/test_mrp_repair_cancel.yml',
diff --git a/addons/mrp_repair/test/mrp_repair_users.yml b/addons/mrp_repair/test/mrp_repair_users.yml
new file mode 100644
index 00000000000..dee61653209
--- /dev/null
+++ b/addons/mrp_repair/test/mrp_repair_users.yml
@@ -0,0 +1,20 @@
+-
+ Create a user as 'MRP Repair Manager'
+-
+ !record {model: res.users, id: res_mrp_repair_manager}:
+ company_id: base.main_company
+ name: MRP Manager
+ login: marm
+ password: marm
+ groups_id:
+ - mrp.group_mrp_manager
+-
+ Create a user as 'MRP Repair User'
+-
+ !record {model: res.users, id: res_mrp_repair_user}:
+ company_id: base.main_company
+ name: MRP User
+ login: maru
+ password: maru
+ groups_id:
+ - mrp.group_mrp_user
\ No newline at end of file
diff --git a/addons/mrp_repair/test/test_mrp_repair_afterinv.yml b/addons/mrp_repair/test/test_mrp_repair_afterinv.yml
index 5ceb3f9b02b..583518ee7de 100644
--- a/addons/mrp_repair/test/test_mrp_repair_afterinv.yml
+++ b/addons/mrp_repair/test/test_mrp_repair_afterinv.yml
@@ -1,5 +1,8 @@
-
- In order to test Invoice Method 'After Repair'.
+ In order to test Invoice Method 'After Repair' with giving the access rights of mrp user.
+-
+ !context
+ uid: 'res_mrp_repair_user'
-
I confirm Repair order taking Invoice Method 'After Repair'.
-
diff --git a/addons/mrp_repair/test/test_mrp_repair_b4inv.yml b/addons/mrp_repair/test/test_mrp_repair_b4inv.yml
index 47e301cd0bd..0b31c3970de 100644
--- a/addons/mrp_repair/test/test_mrp_repair_b4inv.yml
+++ b/addons/mrp_repair/test/test_mrp_repair_b4inv.yml
@@ -1,5 +1,8 @@
-
- Now I test for Invoice Method 'Before Repair'.
+ Now I test for Invoice Method 'Before Repair' with giving the access rights of mrp user.
+-
+ !context
+ uid: 'res_mrp_repair_user'
-
I confirm Repair order for Invoice Method 'Before Repair'.
-
diff --git a/addons/mrp_repair/test/test_mrp_repair_cancel.yml b/addons/mrp_repair/test/test_mrp_repair_cancel.yml
index ee0e9c379e7..83a8b901fd3 100644
--- a/addons/mrp_repair/test/test_mrp_repair_cancel.yml
+++ b/addons/mrp_repair/test/test_mrp_repair_cancel.yml
@@ -1,6 +1,9 @@
-
In order to test the cancel flow of mrp_repair module,
- I start by creating new copy Repair order for "PC Assemble SC234" product.
+ I start by creating new copy Repair order for "PC Assemble SC234" product with giving access rights of mrp user.
+-
+ !context
+ uid: 'res_mrp_repair_user'
-
!python {model: mrp.repair}: |
copy_id = self.copy(cr, uid, ref("mrp_repair_rmrp1"))
diff --git a/addons/mrp_repair/test/test_mrp_repair_noneinv.yml b/addons/mrp_repair/test/test_mrp_repair_noneinv.yml
index 4334af01958..1e411e45605 100644
--- a/addons/mrp_repair/test/test_mrp_repair_noneinv.yml
+++ b/addons/mrp_repair/test/test_mrp_repair_noneinv.yml
@@ -1,5 +1,8 @@
-
- In order to test "mrp_repair" module, I start with confirm state, and start repair.
+ In order to test "mrp_repair" module, I start with confirm state, and start repair with giving the access rights of mrp user.
+-
+ !context
+ uid: 'res_mrp_repair_user'
-
I confirm Repair order for Invoice Method 'No Invoice'.
-
From 541a46fb877043ccb0f65768178495e8a30900c9 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 14 Mar 2013 16:50:18 +0530
Subject: [PATCH 043/256] [IMP] give access rights in yml of hr_timesheet
bzr revid: fka@tinyerp.com-20130314112018-oy0m2lhwc0euq3nh
---
addons/hr_timesheet/__openerp__.py | 1 +
.../hr_timesheet/test/hr_timesheet_demo.yml | 5 ++++
.../hr_timesheet/test/hr_timesheet_users.yml | 30 +++++++++++++++++++
.../hr_timesheet/test/test_hr_timesheet.yml | 10 +++++++
4 files changed, 46 insertions(+)
create mode 100644 addons/hr_timesheet/test/hr_timesheet_users.yml
diff --git a/addons/hr_timesheet/__openerp__.py b/addons/hr_timesheet/__openerp__.py
index bff25e8e55d..3c026ac2d07 100644
--- a/addons/hr_timesheet/__openerp__.py
+++ b/addons/hr_timesheet/__openerp__.py
@@ -57,6 +57,7 @@ up a management by affair.
],
'demo': ['hr_timesheet_demo.xml'],
'test': [
+ 'test/hr_timesheet_users.yml',
'test/test_hr_timesheet.yml',
'test/hr_timesheet_report.yml',
'test/hr_timesheet_demo.yml',
diff --git a/addons/hr_timesheet/test/hr_timesheet_demo.yml b/addons/hr_timesheet/test/hr_timesheet_demo.yml
index a868be2d710..723e3a1a08c 100644
--- a/addons/hr_timesheet/test/hr_timesheet_demo.yml
+++ b/addons/hr_timesheet/test/hr_timesheet_demo.yml
@@ -1,3 +1,8 @@
+-
+ Give the access rights of Hr Officer to create employee.
+-
+ !context
+ uid: 'res_hr_timesheet_officer'
-
!record {model: hr.analytic.timesheet, id: working_hours_coding, view: False}:
user_id: base.user_demo
diff --git a/addons/hr_timesheet/test/hr_timesheet_users.yml b/addons/hr_timesheet/test/hr_timesheet_users.yml
new file mode 100644
index 00000000000..428c1b7c40f
--- /dev/null
+++ b/addons/hr_timesheet/test/hr_timesheet_users.yml
@@ -0,0 +1,30 @@
+-
+ Create a user as 'HR Manager'
+-
+ !record {model: res.users, id: res_hr_timesheet_manager}:
+ company_id: base.main_company
+ name: HR manager
+ login: hrtm
+ password: hrtm
+ groups_id:
+ - base.group_hr_manager
+-
+ Create a user as 'HR Officer'
+-
+ !record {model: res.users, id: res_hr_timesheet_officer}:
+ company_id: base.main_company
+ name: HR Officer
+ login: hrto
+ password: hrto
+ groups_id:
+ - base.group_hr_user
+-
+ Create a user as 'Employee'
+-
+ !record {model: res.users, id: res_hr_timesheet_employee}:
+ company_id: base.main_company
+ name: Employee
+ login: empt
+ password: empt
+ groups_id:
+ - base.group_user
\ No newline at end of file
diff --git a/addons/hr_timesheet/test/test_hr_timesheet.yml b/addons/hr_timesheet/test/test_hr_timesheet.yml
index 59d688f183b..b9010ce5e53 100644
--- a/addons/hr_timesheet/test/test_hr_timesheet.yml
+++ b/addons/hr_timesheet/test/test_hr_timesheet.yml
@@ -1,6 +1,11 @@
-
In order to test hr_timesheet Module in OpenERP, I make "Sign In/Sign Out for Project" to encode and
track time spent on the different projects.
+-
+ Give the access rights of Hr Officer to create employee.
+-
+ !context
+ uid: 'res_hr_timesheet_officer'
-
I create employee "Quentin Paolino" as "User".
-
@@ -9,6 +14,11 @@
name: Quentin Paolino
parent_id: 'hr.employee_al'
user_id: 'base.user_demo'
+-
+ Give the access rights of Employee to Sign In/Sign Out in Project.
+-
+ !context
+ uid: 'res_hr_timesheet_employee'
-
On "Sign In/Sign Out by Project" wizard i click on "Sign In/Sign Out" button of this wizard.
-
From 5a3acd71ed1aba2817189d4e8ffef72099c1148b Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Thu, 14 Mar 2013 19:07:50 +0530
Subject: [PATCH 044/256] [IMP] give access rights in yml of sale_stock
bzr revid: fka@tinyerp.com-20130314133750-r034ygbcna6l49m6
---
addons/sale_stock/__openerp__.py | 3 +-
.../test/cancel_order_sale_stock.yml | 17 +++++++-
.../sale_stock/test/picking_order_policy.yml | 25 ++++++++++-
.../sale_stock/test/prepaid_order_policy.yml | 5 ++-
.../sale_stock/test/sale_order_onchange.yml | 5 +++
addons/sale_stock/test/sale_stock_users.yml | 41 +++++++++++++++++++
6 files changed, 91 insertions(+), 5 deletions(-)
create mode 100644 addons/sale_stock/test/sale_stock_users.yml
diff --git a/addons/sale_stock/__openerp__.py b/addons/sale_stock/__openerp__.py
index a849df295d4..a1941524868 100644
--- a/addons/sale_stock/__openerp__.py
+++ b/addons/sale_stock/__openerp__.py
@@ -59,7 +59,8 @@ You can choose flexible invoicing methods:
],
'data': ['sale_stock_data.xml'],
'demo_xml': ['sale_stock_demo.xml'],
- 'test': ['test/cancel_order_sale_stock.yml',
+ 'test': ['test/sale_stock_users.yml',
+ 'test/cancel_order_sale_stock.yml',
'test/picking_order_policy.yml',
'test/prepaid_order_policy.yml',
'test/sale_order_onchange.yml',
diff --git a/addons/sale_stock/test/cancel_order_sale_stock.yml b/addons/sale_stock/test/cancel_order_sale_stock.yml
index 9081cac9fa3..397bc0e49fe 100644
--- a/addons/sale_stock/test/cancel_order_sale_stock.yml
+++ b/addons/sale_stock/test/cancel_order_sale_stock.yml
@@ -1,6 +1,9 @@
-
- In order to test the cancel sale order.
+ In order to test the cancel sale order with that user which have salesman rights.
First I confirm order.
+-
+ !context
+ uid: 'res_sale_stock_salesman'
-
!workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_8}
-
@@ -8,7 +11,7 @@
-
!python {model: stock.picking}: |
delivery_orders = self.search(cr, uid, [('sale_id','=',ref("sale.sale_order_8"))])
- first_picking = self.browse(cr, uid, delivery_orders[0], context=context)
+ first_picking = self.browse(cr, uid, delivery_orders[0], context=None)
if first_picking.force_assign(cr, uid, first_picking):
first_move = first_picking.move_lines[0]
values = {'move%s'%(first_move.id): {'product_qty': 2, 'product_uom':ref('product.product_uom_unit')}}
@@ -20,12 +23,22 @@
delivery_orders = self.search(cr, uid, [('sale_id','=',ref("sale.sale_order_8"))])
last_delivery_order_id = delivery_orders[0]
self.pool.get('stock.picking').signal_button_cancel(cr, uid, [last_delivery_order_id])
+-
+ Only Stock User can change data related warehouse therfore test with that user which have stcok user rights,
+-
+ !context
+ uid: 'res_stock_user'
-
I run the scheduler.
-
!python {model: procurement.order}: |
self.run_scheduler(cr, uid)
+-
+ Salesman can also check order therfore test with that user which have salesman rights,
+-
+ !context
+ uid: 'res_sale_stock_salesman'
-
I check order status in "Ship Exception".
-
diff --git a/addons/sale_stock/test/picking_order_policy.yml b/addons/sale_stock/test/picking_order_policy.yml
index f761dfb65db..003bfbc1f30 100644
--- a/addons/sale_stock/test/picking_order_policy.yml
+++ b/addons/sale_stock/test/picking_order_policy.yml
@@ -1,5 +1,8 @@
-
- In order to test process of the Sale Order,
+ In order to test process of the Sale Order with access rights of saleman,
+-
+ !context
+ uid: 'res_sale_stock_salesman'
-
First I check the total amount of the Quotation before Approved.
-
@@ -33,11 +36,21 @@
assert procurement.product_qty == order_line.product_uom_qty, "Qty is not correspond."
assert procurement.product_uom.id == order_line.product_uom.id, "UOM is not correspond."
assert procurement.procure_method == order_line.type, "Procurement method is not correspond."
+-
+ Only Stock User can change data related warehouse therfore test with that user which have stcok user rights,
+-
+ !context
+ uid: 'res_stock_user'
-
I run the scheduler.
-
!python {model: procurement.order}: |
self.run_scheduler(cr, uid)
+-
+ Salesman can also check order therfore test with that user which have salesman rights,
+-
+ !context
+ uid: 'res_sale_stock_salesman'
-
I check the details of delivery order after confirmed quotation.
-
@@ -128,6 +141,11 @@
assert inv_line.price_unit == so_line.price_unit , "Price Unit is not correspond."
assert inv_line.quantity == (so_line.product_uos and so_line.product_uos_qty) or so_line.product_uom_qty , "Product qty is not correspond."
assert inv_line.price_subtotal == so_line.price_subtotal, "Price sub total is not correspond."
+-
+ Only Stock manager can open the Invoice therfore test with that user which have stock manager rights,
+-
+ !context
+ uid: 'res_stock_manager'
-
I open the Invoice.
-
@@ -149,6 +167,11 @@
journal_ids[0], ref('account.cash'),
ref('account.period_8'), journal_ids[0],
name='test')
+-
+ To test process of the Sale Order with access rights of saleman,
+-
+ !context
+ uid: 'res_sale_stock_salesman'
-
I check the order after paid invoice.
-
diff --git a/addons/sale_stock/test/prepaid_order_policy.yml b/addons/sale_stock/test/prepaid_order_policy.yml
index e35a9a82cf9..0e0345868d0 100644
--- a/addons/sale_stock/test/prepaid_order_policy.yml
+++ b/addons/sale_stock/test/prepaid_order_policy.yml
@@ -1,5 +1,8 @@
-
- Now I confirm the Quotation with "Pay before delivery" policy.
+ Now I confirm the Quotation with "Pay before delivery" policy with access rights of salesman.
+-
+ !context
+ uid: 'res_sale_stock_salesman'
-
!workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_4}
-
diff --git a/addons/sale_stock/test/sale_order_onchange.yml b/addons/sale_stock/test/sale_order_onchange.yml
index 1026e5627c3..d37dd74e1ef 100644
--- a/addons/sale_stock/test/sale_order_onchange.yml
+++ b/addons/sale_stock/test/sale_order_onchange.yml
@@ -1,3 +1,8 @@
+-
+ In sale order to test process of onchange of Sale Order with access rights of salemanager,
+-
+ !context
+ uid: 'res_sale_stock_salesmanager'
-
In order to test the onchange of the Sale Order, I create a product
-
diff --git a/addons/sale_stock/test/sale_stock_users.yml b/addons/sale_stock/test/sale_stock_users.yml
new file mode 100644
index 00000000000..f9bd1cf1727
--- /dev/null
+++ b/addons/sale_stock/test/sale_stock_users.yml
@@ -0,0 +1,41 @@
+-
+ Create a user as 'Salesmanager'
+-
+ !record {model: res.users, id: res_sale_stock_salesmanager}:
+ company_id: base.main_company
+ name: Sales manager
+ login: ssm
+ password: ssm
+ groups_id:
+ - base.group_sale_manager
+-
+ Create a user as 'Salesman'
+-
+ !record {model: res.users, id: res_sale_stock_salesman}:
+ company_id: base.main_company
+ name: Salesman
+ login: ssu
+ password: ssu
+ groups_id:
+ - base.group_sale_salesman_all_leads
+-
+ Create a user as 'Stock User'
+-
+ !record {model: res.users, id: res_stock_user}:
+ company_id: base.main_company
+ name: Stock User
+ login: sau
+ password: sau
+ groups_id:
+ - stock.group_stock_user
+-
+ Create a user as 'Stock Manager'
+-
+ !record {model: res.users, id: res_stock_manager}:
+ company_id: base.main_company
+ name: Stock Manager
+ login: sam
+ password: sam
+ email: admin@portal.example.com
+ groups_id:
+ - stock.group_stock_manager
\ No newline at end of file
From 093f0f771abf94e4ec5896cf6d9b9a9572a162a9 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Fri, 15 Mar 2013 10:57:29 +0530
Subject: [PATCH 045/256] [IMP] improve yml of sale_stock
bzr revid: fka@tinyerp.com-20130315052729-31z589lryen4wcy0
---
addons/sale_stock/test/sale_order_onchange.yml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/addons/sale_stock/test/sale_order_onchange.yml b/addons/sale_stock/test/sale_order_onchange.yml
index d37dd74e1ef..f21f88c9eb7 100644
--- a/addons/sale_stock/test/sale_order_onchange.yml
+++ b/addons/sale_stock/test/sale_order_onchange.yml
@@ -1,5 +1,5 @@
-
- In sale order to test process of onchange of Sale Order with access rights of salemanager,
+ Only sales manager Creates product so let's check with access rights of salemanager.
-
!context
uid: 'res_sale_stock_salesmanager'
@@ -10,6 +10,11 @@
name: 'Devil Worship Book'
list_price: 66.6
procure_method: 'make_to_order'
+-
+ In sale order to test process of onchange of Sale Order with access rights of saleman.
+-
+ !context
+ uid: 'res_sale_stock_salesman'
-
Now i create a sale order that uses my new product
-
From 2496811ec42cc6b06fa9293ff97123c85e593433 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Mon, 18 Mar 2013 11:24:12 +0530
Subject: [PATCH 046/256] [IMP] improve yml strings
bzr revid: fka@tinyerp.com-20130318055412-se3c890mlckxd8ty
---
addons/sale/test/cancel_order.yml | 2 +-
addons/sale/test/delete_order.yml | 2 +-
addons/sale_stock/test/cancel_order_sale_stock.yml | 6 +++---
addons/sale_stock/test/picking_order_policy.yml | 6 +++---
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/addons/sale/test/cancel_order.yml b/addons/sale/test/cancel_order.yml
index 48c6b91d2c4..a2c62e16031 100644
--- a/addons/sale/test/cancel_order.yml
+++ b/addons/sale/test/cancel_order.yml
@@ -1,5 +1,5 @@
-
- Salesman can also cancel order therfore test with that user which have salesman rights,
+ Salesman can also cancel order therefore test with that user which have salesman rights,
-
!context
uid: 'res_users_salesman'
diff --git a/addons/sale/test/delete_order.yml b/addons/sale/test/delete_order.yml
index 69c89020ad9..9d8db337d5b 100644
--- a/addons/sale/test/delete_order.yml
+++ b/addons/sale/test/delete_order.yml
@@ -1,5 +1,5 @@
-
- Sales manager can only delete order therfore test with that user which have sales manager rights,
+ Sales manager can only delete order therefore test with that user which have sales manager rights,
-
!context
uid: 'res_users_salesmanager'
diff --git a/addons/sale_stock/test/cancel_order_sale_stock.yml b/addons/sale_stock/test/cancel_order_sale_stock.yml
index 397bc0e49fe..1109124705c 100644
--- a/addons/sale_stock/test/cancel_order_sale_stock.yml
+++ b/addons/sale_stock/test/cancel_order_sale_stock.yml
@@ -11,7 +11,7 @@
-
!python {model: stock.picking}: |
delivery_orders = self.search(cr, uid, [('sale_id','=',ref("sale.sale_order_8"))])
- first_picking = self.browse(cr, uid, delivery_orders[0], context=None)
+ first_picking = self.browse(cr, uid, delivery_orders[0])
if first_picking.force_assign(cr, uid, first_picking):
first_move = first_picking.move_lines[0]
values = {'move%s'%(first_move.id): {'product_qty': 2, 'product_uom':ref('product.product_uom_unit')}}
@@ -24,7 +24,7 @@
last_delivery_order_id = delivery_orders[0]
self.pool.get('stock.picking').signal_button_cancel(cr, uid, [last_delivery_order_id])
-
- Only Stock User can change data related warehouse therfore test with that user which have stcok user rights,
+ Only Stock User can change data related warehouse therefore test with that user which have stcok user rights,
-
!context
uid: 'res_stock_user'
@@ -35,7 +35,7 @@
self.run_scheduler(cr, uid)
-
- Salesman can also check order therfore test with that user which have salesman rights,
+ Salesman can also check order therefore test with that user which have salesman rights,
-
!context
uid: 'res_sale_stock_salesman'
diff --git a/addons/sale_stock/test/picking_order_policy.yml b/addons/sale_stock/test/picking_order_policy.yml
index 003bfbc1f30..50d79058764 100644
--- a/addons/sale_stock/test/picking_order_policy.yml
+++ b/addons/sale_stock/test/picking_order_policy.yml
@@ -37,7 +37,7 @@
assert procurement.product_uom.id == order_line.product_uom.id, "UOM is not correspond."
assert procurement.procure_method == order_line.type, "Procurement method is not correspond."
-
- Only Stock User can change data related warehouse therfore test with that user which have stcok user rights,
+ Only Stock User can change data related warehouse therefore test with that user which have stcok user rights,
-
!context
uid: 'res_stock_user'
@@ -47,7 +47,7 @@
!python {model: procurement.order}: |
self.run_scheduler(cr, uid)
-
- Salesman can also check order therfore test with that user which have salesman rights,
+ Salesman can also check order therefore test with that user which have salesman rights,
-
!context
uid: 'res_sale_stock_salesman'
@@ -142,7 +142,7 @@
assert inv_line.quantity == (so_line.product_uos and so_line.product_uos_qty) or so_line.product_uom_qty , "Product qty is not correspond."
assert inv_line.price_subtotal == so_line.price_subtotal, "Price sub total is not correspond."
-
- Only Stock manager can open the Invoice therfore test with that user which have stock manager rights,
+ Only Stock manager can open the Invoice therefore test with that user which have stock manager rights,
-
!context
uid: 'res_stock_manager'
From f748568f3c32f7c48c98518119bc19de056df36f Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 18 Mar 2013 17:19:12 +0530
Subject: [PATCH 047/256] [IMP]add access rights for create, read and write to
acccount.journal and res.partner.bank
bzr revid: sgo@tinyerp.com-20130318114912-klopr2dtalsgbp4g
---
addons/account/security/ir.model.access.csv | 2 ++
addons/account/test/account_customer_invoice.yml | 10 +++++-----
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv
index d164b8026ce..d6ba955aabc 100644
--- a/addons/account/security/ir.model.access.csv
+++ b/addons/account/security/ir.model.access.csv
@@ -37,6 +37,7 @@ access_account_payment_term_manager,account.payment.term,model_account_payment_t
access_account_payment_term_line_manager,account.payment.term.line,model_account_payment_term_line,account.group_account_manager,1,1,1,1
access_account_tax_manager,account.tax,model_account_tax,account.group_account_manager,1,1,1,1
access_account_journal_manager,account.journal,model_account_journal,account.group_account_manager,1,1,1,1
+access_account_journal_user,account.journal,model_account_journal,account.group_account_user,1,1,1,0
access_account_journal_invoice,account.journal invoice,model_account_journal,account.group_account_invoice,1,0,0,0
access_account_period_manager,account.period,model_account_period,account.group_account_manager,1,1,1,1
access_account_period_invoice,account.period invoice,model_account_period,account.group_account_invoice,1,0,0,0
@@ -99,3 +100,4 @@ access_account_sequence_fiscal_year_sale_manager,account.sequence.fiscalyear.sal
access_account_treasury_report_manager,account.treasury.report.manager,model_account_treasury_report,account.group_account_manager,1,0,0,0
access_account_financial_report,account.financial.report,model_account_financial_report,account.group_account_user,1,1,1,1
access_account_financial_report_invoice,account.financial.report invoice,model_account_financial_report,account.group_account_invoice,1,0,0,0
+access_res_partner_bank,res.partner.bank,model_res_partner_bank,account.group_account_user,1,1,1,0
diff --git a/addons/account/test/account_customer_invoice.yml b/addons/account/test/account_customer_invoice.yml
index 866e8921220..a41ca9bbfe4 100644
--- a/addons/account/test/account_customer_invoice.yml
+++ b/addons/account/test/account_customer_invoice.yml
@@ -1,3 +1,8 @@
+-
+ Test with that user which have rights to make Invoicing and payment and who is accountant.
+-
+ !context
+ uid: 'res_users_account_user'
-
In order to test account invoice I create a new customer invoice
-
@@ -11,11 +16,6 @@
footer: True
bank: base.res_bank_1
bank_name: Reserve
--
- Test with that user which have rights to make Invoicing and payment and who is accountant.
--
- !context
- uid: 'res_users_account_user'
-
I create a customer invoice
-
From 1c52635020820ebe927d400c26825d350da9262d Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Tue, 19 Mar 2013 14:28:54 +0530
Subject: [PATCH 048/256] [IMP] add user group in context
bzr revid: fka@tinyerp.com-20130319085854-norcgk7smiz37fs8
---
addons/crm/test/ui/crm_access_group_users.yml | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/addons/crm/test/ui/crm_access_group_users.yml b/addons/crm/test/ui/crm_access_group_users.yml
index cc5be01ee85..407eb8007e5 100644
--- a/addons/crm/test/ui/crm_access_group_users.yml
+++ b/addons/crm/test/ui/crm_access_group_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Salesmanager'
+-
+ !context
+ default_groups_ref: ['base.group_sale_manager']
-
!record {model: res.users, id: crm_res_users_salesmanager}:
company_id: base.main_company
name: Sales manager
login: csm
password: csm
- groups_id:
- - base.group_sale_manager
-
Create a user as 'Salesman'
+-
+ !context
+ default_groups_ref: ['base.group_sale_salesman_all_leads']
-
!record {model: res.users, id: crm_res_users_salesman}:
company_id: base.main_company
name: Salesman
login: csu
password: csu
- groups_id:
- - base.group_sale_salesman_all_leads
From b358bcd430399d9f0801903434ede39879f23cba Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Tue, 19 Mar 2013 14:44:40 +0530
Subject: [PATCH 049/256] [IMP] add user group in context of project &
project_issue yml files
bzr revid: fka@tinyerp.com-20130319091440-i6xrkrxzdskeayvs
---
addons/project/test/project_users.yml | 10 ++++++----
addons/project_issue/test/issue_users.yml | 10 ++++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/addons/project/test/project_users.yml b/addons/project/test/project_users.yml
index b23ab0fc7f6..ad8503d5f76 100644
--- a/addons/project/test/project_users.yml
+++ b/addons/project/test/project_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Project manager'
+-
+ !context
+ default_groups_ref: ['project.group_project_manager']
-
!record {model: res.users, id: res_users_project_manager}:
company_id: base.main_company
name: Project Manager
login: prm
password: prm
- groups_id:
- - project.group_project_manager
-
Create a user as 'Project user'
+-
+ !context
+ default_groups_ref: ['project.group_project_user']
-
!record {model: res.users, id: res_users_project_user}:
company_id: base.main_company
name: Project User
login: pru
password: pru
- groups_id:
- - project.group_project_user
\ No newline at end of file
diff --git a/addons/project_issue/test/issue_users.yml b/addons/project_issue/test/issue_users.yml
index 3493ac58ffc..04789eb6334 100644
--- a/addons/project_issue/test/issue_users.yml
+++ b/addons/project_issue/test/issue_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Project manager'
+-
+ !context
+ default_groups_ref: ['project.group_project_manager']
-
!record {model: res.users, id: res_users_project_issue_manager}:
company_id: base.main_company
name: Project Manager
login: prim
password: prim
- groups_id:
- - project.group_project_manager
-
Create a user as 'Project user'
+-
+ !context
+ default_groups_ref: ['project.group_project_user']
-
!record {model: res.users, id: res_users_project_issue_user}:
company_id: base.main_company
name: Project User
login: priu
password: priu
- groups_id:
- - project.group_project_user
\ No newline at end of file
From 4d2f294a1287443f242f2fd3ada2e89014e7ceff Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Tue, 19 Mar 2013 15:03:08 +0530
Subject: [PATCH 050/256] [IMP] add user group in context of hr &
hr_recruitment yml files
bzr revid: fka@tinyerp.com-20130319093308-mjopwflkzi8tasf4
---
addons/hr/test/hr_users.yml | 15 +++++++++------
.../hr_recruitment/test/recruitment_process.yml | 5 +++--
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/addons/hr/test/hr_users.yml b/addons/hr/test/hr_users.yml
index 3415b20efbd..265482ca457 100644
--- a/addons/hr/test/hr_users.yml
+++ b/addons/hr/test/hr_users.yml
@@ -1,30 +1,33 @@
-
Create a user as 'HR Manager'
+-
+ !context
+ default_groups_ref: [base.group_hr_manager]
-
!record {model: res.users, id: res_users_hr_manager}:
company_id: base.main_company
name: HR manager
login: hrm
password: hrm
- groups_id:
- - base.group_hr_manager
-
Create a user as 'HR Officer'
+-
+ !context
+ default_groups_ref: [base.group_hr_user]
-
!record {model: res.users, id: res_users_hr_officer}:
company_id: base.main_company
name: HR Officer
login: hro
password: hro
- groups_id:
- - base.group_hr_user
-
Create a user as 'Employee'
+-
+ !context
+ default_groups_ref: [base.group_user]
-
!record {model: res.users, id: res_users_employee}:
company_id: base.main_company
name: Employee
login: emp
password: emp
- groups_id:
- - base.group_user
\ No newline at end of file
diff --git a/addons/hr_recruitment/test/recruitment_process.yml b/addons/hr_recruitment/test/recruitment_process.yml
index 0359f253dd9..c284aeb2181 100644
--- a/addons/hr_recruitment/test/recruitment_process.yml
+++ b/addons/hr_recruitment/test/recruitment_process.yml
@@ -1,13 +1,14 @@
-
Create a user as 'HR Recruitment Officer'
+-
+ !context
+ default_groups_ref: [base.group_hr_user]
-
!record {model: res.users, id: res_users_hr_officer}:
company_id: base.main_company
name: HR Officer
login: hrro
password: hrro
- groups_id:
- - base.group_hr_user
-
In Order to test process of Recruitment so giving HR officer's rights,
-
From 8b123b8d744633693cdf8ce3d3e8b72a0a1cde4d Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Tue, 19 Mar 2013 15:32:52 +0530
Subject: [PATCH 051/256] [IMP] add user group in context of event &
hr_timesheet yml files
bzr revid: fka@tinyerp.com-20130319100252-74kzr5qc41gj6d2t
---
addons/event/test/ui/event_users.yml | 10 ++++++----
addons/hr_attendance/test/attendance_process.yml | 5 +++--
addons/hr_timesheet/test/hr_timesheet_users.yml | 15 +++++++++------
3 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/addons/event/test/ui/event_users.yml b/addons/event/test/ui/event_users.yml
index 34564d6c433..e08464bdfd8 100644
--- a/addons/event/test/ui/event_users.yml
+++ b/addons/event/test/ui/event_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Event manager'
+-
+ !context
+ default_groups_ref: [event.group_event_manager]
-
!record {model: res.users, id: res_users_eventmanager}:
company_id: base.main_company
name: Event manager
login: em
password: em
- groups_id:
- - event.group_event_manager
-
Create a user as 'Event user'
+-
+ !context
+ default_groups_ref: [event.group_event_user]
-
!record {model: res.users, id: res_users_eventuser}:
company_id: base.main_company
name: User
login: eu
password: eu
- groups_id:
- - event.group_event_user
\ No newline at end of file
diff --git a/addons/hr_attendance/test/attendance_process.yml b/addons/hr_attendance/test/attendance_process.yml
index 8348757b63e..383cf209bd4 100644
--- a/addons/hr_attendance/test/attendance_process.yml
+++ b/addons/hr_attendance/test/attendance_process.yml
@@ -1,13 +1,14 @@
-
Create a user as 'HR Attendance Officer'
+-
+ !context
+ default_groups_ref: [base.group_hr_user]
-
!record {model: res.users, id: res_users_attendance_officer}:
company_id: base.main_company
name: HR Officer
login: ao
password: ao
- groups_id:
- - base.group_hr_user
-
Give the access rights of Hr Officer to test attendance process.
-
diff --git a/addons/hr_timesheet/test/hr_timesheet_users.yml b/addons/hr_timesheet/test/hr_timesheet_users.yml
index 428c1b7c40f..4a557bff44f 100644
--- a/addons/hr_timesheet/test/hr_timesheet_users.yml
+++ b/addons/hr_timesheet/test/hr_timesheet_users.yml
@@ -1,30 +1,33 @@
-
Create a user as 'HR Manager'
+-
+ !context
+ default_groups_ref: [base.group_hr_manager]
-
!record {model: res.users, id: res_hr_timesheet_manager}:
company_id: base.main_company
name: HR manager
login: hrtm
password: hrtm
- groups_id:
- - base.group_hr_manager
-
Create a user as 'HR Officer'
+-
+ !context
+ default_groups_ref: [base.group_hr_user]
-
!record {model: res.users, id: res_hr_timesheet_officer}:
company_id: base.main_company
name: HR Officer
login: hrto
password: hrto
- groups_id:
- - base.group_hr_user
-
Create a user as 'Employee'
+-
+ !context
+ default_groups_ref: [base.group_user]
-
!record {model: res.users, id: res_hr_timesheet_employee}:
company_id: base.main_company
name: Employee
login: empt
password: empt
- groups_id:
- - base.group_user
\ No newline at end of file
From eff57d769b8e0792e937353525d291d4f7db53da Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 19 Mar 2013 16:24:35 +0530
Subject: [PATCH 052/256] [IMP]add user access rights as per new improvement
bzr revid: sgo@tinyerp.com-20130319105435-es76c9arqghz2rys
---
addons/account/test/account_test_users.yml | 10 ++++++----
addons/account_voucher/test/account_voucher_users.yml | 10 ++++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml
index 55e57968f50..7f4ccf1ed09 100644
--- a/addons/account/test/account_test_users.yml
+++ b/addons/account/test/account_test_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Accountant'
+-
+ !context
+ default_groups_ref: [account.group_account_user]
-
!record {model: res.users, id: res_users_account_user}:
company_id: base.main_company
name: Accountant
login: acc
password: acc
- groups_id:
- - account.group_account_user
-
Create a user as 'Financial Manager'
+-
+ !context
+ default_groups_ref: [account.group_account_manager]
-
!record {model: res.users, id: res_users_account_manager}:
company_id: base.main_company
name: Financial Manager
login: fm
password: fm
- groups_id:
- - account.group_account_manager
\ No newline at end of file
diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml
index e8a4fa3e612..c1ecb7f59f2 100644
--- a/addons/account_voucher/test/account_voucher_users.yml
+++ b/addons/account_voucher/test/account_voucher_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Accountant for voucher'
+-
+ !context
+ default_groups_ref: [account.group_account_user]
-
!record {model: res.users, id: res_users_account_voucher_user}:
company_id: base.main_company
name: Voucher Accountant
login: vacc
password: acc
- groups_id:
- - account.group_account_user
-
Create a user as 'Financial Manager for account voucher'
+-
+ !context
+ default_groups_ref: [account.group_account_manager]
-
!record {model: res.users, id: res_users_account_voucher_manager}:
company_id: base.main_company
name: Financial Manager for voucher
login: fmv
password: fmv
- groups_id:
- - account.group_account_manager
\ No newline at end of file
From 5823dc85635d5817d8cb244a9b496d73ff64b8ac Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Tue, 19 Mar 2013 16:56:21 +0530
Subject: [PATCH 053/256] [IMP] add user group in context in all yml files
bzr revid: fka@tinyerp.com-20130319112621-ohy33bgzwmtmw8kl
---
addons/mrp/test/mrp_users.yml | 10 ++++++----
.../test/workcenter_operations.yml | 5 +++--
addons/mrp_repair/test/mrp_repair_users.yml | 10 ++++++----
addons/purchase/test/ui/purchase_users.yml | 10 ++++++----
.../test/purchase_reqisition_users.yml | 10 ++++++----
addons/sale/test/create_sale_users.yml | 10 ++++++----
addons/sale_stock/test/sale_stock_users.yml | 20 +++++++++++--------
addons/stock/test/stock_users.yml | 10 ++++++----
8 files changed, 51 insertions(+), 34 deletions(-)
diff --git a/addons/mrp/test/mrp_users.yml b/addons/mrp/test/mrp_users.yml
index d18cc51e8e6..ccbb37e133c 100644
--- a/addons/mrp/test/mrp_users.yml
+++ b/addons/mrp/test/mrp_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'MRP Manager'
+-
+ !context
+ default_groups_ref: [mrp.group_mrp_manager]
-
!record {model: res.users, id: res_users_mrp_manager}:
company_id: base.main_company
name: MRP Manager
login: mam
password: mam
- groups_id:
- - mrp.group_mrp_manager
-
Create a user as 'MRP User'
+-
+ !context
+ default_groups_ref: [mrp.group_mrp_user]
-
!record {model: res.users, id: res_users_mrp_user}:
company_id: base.main_company
name: MRP User
login: mau
password: mau
- groups_id:
- - mrp.group_mrp_user
diff --git a/addons/mrp_operations/test/workcenter_operations.yml b/addons/mrp_operations/test/workcenter_operations.yml
index 3bcdc77ecab..14eab36e345 100644
--- a/addons/mrp_operations/test/workcenter_operations.yml
+++ b/addons/mrp_operations/test/workcenter_operations.yml
@@ -1,13 +1,14 @@
-
Create a user as 'MRP User'
+-
+ !context
+ default_groups_ref: [mrp.group_mrp_user]
-
!record {model: res.users, id: res_mrp_operation_user}:
company_id: base.main_company
name: MRP User
login: maou
password: maou
- groups_id:
- - mrp.group_mrp_user
-
In order to test mrp_operations with OpenERP, I refer created production order of PC Assemble SC349
with routing - Manual Component's Assembly to test complete production process with respect of workcenter with giving access rights of MRP User.
diff --git a/addons/mrp_repair/test/mrp_repair_users.yml b/addons/mrp_repair/test/mrp_repair_users.yml
index dee61653209..986320ed941 100644
--- a/addons/mrp_repair/test/mrp_repair_users.yml
+++ b/addons/mrp_repair/test/mrp_repair_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'MRP Repair Manager'
+-
+ !context
+ default_groups_ref: [mrp.group_mrp_manager]
-
!record {model: res.users, id: res_mrp_repair_manager}:
company_id: base.main_company
name: MRP Manager
login: marm
password: marm
- groups_id:
- - mrp.group_mrp_manager
-
Create a user as 'MRP Repair User'
+-
+ !context
+ default_groups_ref: [mrp.group_mrp_user]
-
!record {model: res.users, id: res_mrp_repair_user}:
company_id: base.main_company
name: MRP User
login: maru
password: maru
- groups_id:
- - mrp.group_mrp_user
\ No newline at end of file
diff --git a/addons/purchase/test/ui/purchase_users.yml b/addons/purchase/test/ui/purchase_users.yml
index e36d8140d28..c0a60955d99 100644
--- a/addons/purchase/test/ui/purchase_users.yml
+++ b/addons/purchase/test/ui/purchase_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Purchase manager'
+-
+ !context
+ default_groups_ref: ['purchase.group_purchase_manager']
-
!record {model: res.users, id: res_users_purchase_manager}:
company_id: base.main_company
name: Purchase Manager
login: pm
password: pm
- groups_id:
- - purchase.group_purchase_manager
-
Create a user as 'Purchase user'
+-
+ !context
+ default_groups_ref: ['purchase.group_purchase_user']
-
!record {model: res.users, id: res_users_purchase_user}:
company_id: base.main_company
name: Purchase User
login: pu
password: pu
- groups_id:
- - purchase.group_purchase_user
\ No newline at end of file
diff --git a/addons/purchase_requisition/test/purchase_reqisition_users.yml b/addons/purchase_requisition/test/purchase_reqisition_users.yml
index abf62286998..9a903cde306 100644
--- a/addons/purchase_requisition/test/purchase_reqisition_users.yml
+++ b/addons/purchase_requisition/test/purchase_reqisition_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Purchase Reqisition Manager'
+-
+ !context
+ default_groups_ref: ['purchase_requisition.group_purchase_requisition_manager']
-
!record {model: res.users, id: res_users_purchase_requisition_manager}:
company_id: base.main_company
name: Purchase Reqisition Manager
login: prm
password: prm
- groups_id:
- - purchase_requisition.group_purchase_requisition_manager
-
Create a user as 'Purchase Reqisition User'
+-
+ !context
+ default_groups_ref: ['purchase_requisition.group_purchase_requisition_user']
-
!record {model: res.users, id: res_users_purchase_requisition_user}:
company_id: base.main_company
name: Purchase Reqisition User
login: pru
password: pru
- groups_id:
- - purchase_requisition.group_purchase_requisition_user
\ No newline at end of file
diff --git a/addons/sale/test/create_sale_users.yml b/addons/sale/test/create_sale_users.yml
index a61c4bd7c99..29e09b38b80 100644
--- a/addons/sale/test/create_sale_users.yml
+++ b/addons/sale/test/create_sale_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Salesmanager'
+-
+ !context
+ default_groups_ref: ['base.group_sale_manager']
-
!record {model: res.users, id: res_users_salesmanager}:
company_id: base.main_company
name: Sales manager
login: sm
password: sm
- groups_id:
- - base.group_sale_manager
-
Create a user as 'Salesman'
+-
+ !context
+ default_groups_ref: ['base.group_sale_salesman_all_leads']
-
!record {model: res.users, id: res_users_salesman}:
company_id: base.main_company
name: Salesman
login: su
password: su
- groups_id:
- - base.group_sale_salesman_all_leads
diff --git a/addons/sale_stock/test/sale_stock_users.yml b/addons/sale_stock/test/sale_stock_users.yml
index f9bd1cf1727..d64fcac2503 100644
--- a/addons/sale_stock/test/sale_stock_users.yml
+++ b/addons/sale_stock/test/sale_stock_users.yml
@@ -1,35 +1,41 @@
-
Create a user as 'Salesmanager'
+-
+ !context
+ default_groups_ref: ['base.group_sale_manager']
-
!record {model: res.users, id: res_sale_stock_salesmanager}:
company_id: base.main_company
name: Sales manager
login: ssm
password: ssm
- groups_id:
- - base.group_sale_manager
-
Create a user as 'Salesman'
+-
+ !context
+ default_groups_ref: ['base.group_sale_salesman_all_leads']
-
!record {model: res.users, id: res_sale_stock_salesman}:
company_id: base.main_company
name: Salesman
login: ssu
password: ssu
- groups_id:
- - base.group_sale_salesman_all_leads
-
Create a user as 'Stock User'
+-
+ !context
+ default_groups_ref: ['stock.group_stock_user']
-
!record {model: res.users, id: res_stock_user}:
company_id: base.main_company
name: Stock User
login: sau
password: sau
- groups_id:
- - stock.group_stock_user
-
Create a user as 'Stock Manager'
+-
+ !context
+ default_groups_ref: ['stock.group_stock_manager']
-
!record {model: res.users, id: res_stock_manager}:
company_id: base.main_company
@@ -37,5 +43,3 @@
login: sam
password: sam
email: admin@portal.example.com
- groups_id:
- - stock.group_stock_manager
\ No newline at end of file
diff --git a/addons/stock/test/stock_users.yml b/addons/stock/test/stock_users.yml
index 82d48197b0d..13074d752f3 100644
--- a/addons/stock/test/stock_users.yml
+++ b/addons/stock/test/stock_users.yml
@@ -1,20 +1,22 @@
-
Create a user as 'Stock Manager'
+-
+ !context
+ default_groups_ref: ['stock.group_stock_manager']
-
!record {model: res.users, id: res_users_stock_manager}:
company_id: base.main_company
name: Stock Manager
login: sam
password: sam
- groups_id:
- - stock.group_stock_manager
-
Create a user as 'Stock User'
+-
+ !context
+ default_groups_ref: ['stock.group_stock_user']
-
!record {model: res.users, id: res_users_stock_user}:
company_id: base.main_company
name: Stock User
login: sau
password: sau
- groups_id:
- - stock.group_stock_user
From 1e14b224351b5a784479de09ef51fd2306ec19b3 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Wed, 20 Mar 2013 11:10:49 +0530
Subject: [PATCH 054/256] [IMP]add email to user as per new improvement
bzr revid: sgo@tinyerp.com-20130320054049-4cr1a52dlpfw2lgf
---
addons/account/test/account_test_users.yml | 2 ++
addons/account_voucher/test/account_voucher_users.yml | 2 ++
addons/crm/test/ui/crm_access_group_users.yml | 2 ++
addons/event/test/ui/event_users.yml | 2 ++
addons/hr_recruitment/test/recruitment_process.yml | 1 +
addons/mrp/test/mrp_users.yml | 2 ++
addons/mrp_operations/test/workcenter_operations.yml | 1 +
addons/mrp_repair/test/mrp_repair_users.yml | 2 ++
addons/project/test/project_users.yml | 2 ++
addons/project_issue/test/issue_users.yml | 2 ++
addons/purchase/test/ui/purchase_users.yml | 2 ++
addons/purchase_requisition/__openerp__.py | 2 +-
..._reqisition_users.yml => purchase_requisition_users.yml} | 6 ++++--
addons/sale/test/create_sale_users.yml | 2 ++
addons/sale_stock/test/sale_stock_users.yml | 5 +++++
addons/stock/test/stock_users.yml | 2 ++
16 files changed, 34 insertions(+), 3 deletions(-)
rename addons/purchase_requisition/test/{purchase_reqisition_users.yml => purchase_requisition_users.yml} (77%)
diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml
index 7f4ccf1ed09..322bc527ad7 100644
--- a/addons/account/test/account_test_users.yml
+++ b/addons/account/test/account_test_users.yml
@@ -9,6 +9,7 @@
name: Accountant
login: acc
password: acc
+ email: accountuser@yourcompany.com
-
Create a user as 'Financial Manager'
-
@@ -20,3 +21,4 @@
name: Financial Manager
login: fm
password: fm
+ email: accountmanager@yourcompany.com
diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml
index c1ecb7f59f2..8888885f476 100644
--- a/addons/account_voucher/test/account_voucher_users.yml
+++ b/addons/account_voucher/test/account_voucher_users.yml
@@ -9,6 +9,7 @@
name: Voucher Accountant
login: vacc
password: acc
+ email: accountant@yourcompany.com
-
Create a user as 'Financial Manager for account voucher'
-
@@ -20,3 +21,4 @@
name: Financial Manager for voucher
login: fmv
password: fmv
+ email: finmanager@yourcompany.com
diff --git a/addons/crm/test/ui/crm_access_group_users.yml b/addons/crm/test/ui/crm_access_group_users.yml
index 407eb8007e5..158a11b5881 100644
--- a/addons/crm/test/ui/crm_access_group_users.yml
+++ b/addons/crm/test/ui/crm_access_group_users.yml
@@ -9,6 +9,7 @@
name: Sales manager
login: csm
password: csm
+ email: crmmanager@yourcompany.com
-
Create a user as 'Salesman'
-
@@ -20,3 +21,4 @@
name: Salesman
login: csu
password: csu
+ email: crmuser@yourcompany.com
diff --git a/addons/event/test/ui/event_users.yml b/addons/event/test/ui/event_users.yml
index e08464bdfd8..92c8316a5d9 100644
--- a/addons/event/test/ui/event_users.yml
+++ b/addons/event/test/ui/event_users.yml
@@ -9,6 +9,7 @@
name: Event manager
login: em
password: em
+ email: eventmanager@yourcompany.com
-
Create a user as 'Event user'
-
@@ -20,3 +21,4 @@
name: User
login: eu
password: eu
+ email: eventuser@yourcompany.com
diff --git a/addons/hr_recruitment/test/recruitment_process.yml b/addons/hr_recruitment/test/recruitment_process.yml
index c284aeb2181..930f95039f8 100644
--- a/addons/hr_recruitment/test/recruitment_process.yml
+++ b/addons/hr_recruitment/test/recruitment_process.yml
@@ -9,6 +9,7 @@
name: HR Officer
login: hrro
password: hrro
+ email: hrofcr@yourcompany.com
-
In Order to test process of Recruitment so giving HR officer's rights,
-
diff --git a/addons/mrp/test/mrp_users.yml b/addons/mrp/test/mrp_users.yml
index ccbb37e133c..965a809eddc 100644
--- a/addons/mrp/test/mrp_users.yml
+++ b/addons/mrp/test/mrp_users.yml
@@ -9,6 +9,7 @@
name: MRP Manager
login: mam
password: mam
+ email: mrp_manager@yourcompany.com
-
Create a user as 'MRP User'
-
@@ -20,3 +21,4 @@
name: MRP User
login: mau
password: mau
+ email: mrp_user@yourcompany.com
diff --git a/addons/mrp_operations/test/workcenter_operations.yml b/addons/mrp_operations/test/workcenter_operations.yml
index 14eab36e345..3fc5b332d18 100644
--- a/addons/mrp_operations/test/workcenter_operations.yml
+++ b/addons/mrp_operations/test/workcenter_operations.yml
@@ -9,6 +9,7 @@
name: MRP User
login: maou
password: maou
+ email: mrp_operation_user@yourcompany.com
-
In order to test mrp_operations with OpenERP, I refer created production order of PC Assemble SC349
with routing - Manual Component's Assembly to test complete production process with respect of workcenter with giving access rights of MRP User.
diff --git a/addons/mrp_repair/test/mrp_repair_users.yml b/addons/mrp_repair/test/mrp_repair_users.yml
index 986320ed941..f7ee95314e2 100644
--- a/addons/mrp_repair/test/mrp_repair_users.yml
+++ b/addons/mrp_repair/test/mrp_repair_users.yml
@@ -9,6 +9,7 @@
name: MRP Manager
login: marm
password: marm
+ email: mrp_repair_manager@yourcompany.com
-
Create a user as 'MRP Repair User'
-
@@ -20,3 +21,4 @@
name: MRP User
login: maru
password: maru
+ email: mrp_repair_user@yourcompany.com
diff --git a/addons/project/test/project_users.yml b/addons/project/test/project_users.yml
index ad8503d5f76..8ae3c23dca1 100644
--- a/addons/project/test/project_users.yml
+++ b/addons/project/test/project_users.yml
@@ -9,6 +9,7 @@
name: Project Manager
login: prm
password: prm
+ email: projectmanager@yourcompany.com
-
Create a user as 'Project user'
-
@@ -20,3 +21,4 @@
name: Project User
login: pru
password: pru
+ email: projectuser@yourcompany.com
diff --git a/addons/project_issue/test/issue_users.yml b/addons/project_issue/test/issue_users.yml
index 04789eb6334..402478b1c54 100644
--- a/addons/project_issue/test/issue_users.yml
+++ b/addons/project_issue/test/issue_users.yml
@@ -9,6 +9,7 @@
name: Project Manager
login: prim
password: prim
+ email: issuemanager@yourcompany.com
-
Create a user as 'Project user'
-
@@ -20,3 +21,4 @@
name: Project User
login: priu
password: priu
+ email: issueuser@yourcompany.com
diff --git a/addons/purchase/test/ui/purchase_users.yml b/addons/purchase/test/ui/purchase_users.yml
index c0a60955d99..9dd5e9c0195 100644
--- a/addons/purchase/test/ui/purchase_users.yml
+++ b/addons/purchase/test/ui/purchase_users.yml
@@ -9,6 +9,7 @@
name: Purchase Manager
login: pm
password: pm
+ email: purchasemanager@yourcompany.com
-
Create a user as 'Purchase user'
-
@@ -20,3 +21,4 @@
name: Purchase User
login: pu
password: pu
+ email: purchaseuser@yourcompany.com
diff --git a/addons/purchase_requisition/__openerp__.py b/addons/purchase_requisition/__openerp__.py
index d2a94365318..3f5fce3e8cc 100644
--- a/addons/purchase_requisition/__openerp__.py
+++ b/addons/purchase_requisition/__openerp__.py
@@ -43,7 +43,7 @@ keep track and order all your purchase orders.
],
'auto_install': False,
'test': [
- 'test/purchase_reqisition_users.yml',
+ 'test/purchase_requisition_users.yml',
'test/purchase_requisition_demo.yml',
'test/purchase_requisition.yml',
'test/cancel_purchase_requisition.yml',
diff --git a/addons/purchase_requisition/test/purchase_reqisition_users.yml b/addons/purchase_requisition/test/purchase_requisition_users.yml
similarity index 77%
rename from addons/purchase_requisition/test/purchase_reqisition_users.yml
rename to addons/purchase_requisition/test/purchase_requisition_users.yml
index 9a903cde306..41bc7cd318e 100644
--- a/addons/purchase_requisition/test/purchase_reqisition_users.yml
+++ b/addons/purchase_requisition/test/purchase_requisition_users.yml
@@ -6,9 +6,10 @@
-
!record {model: res.users, id: res_users_purchase_requisition_manager}:
company_id: base.main_company
- name: Purchase Reqisition Manager
+ name: Purchase requisition Manager
login: prm
password: prm
+ email: requisition_manager@yourcompany.com
-
Create a user as 'Purchase Reqisition User'
-
@@ -17,6 +18,7 @@
-
!record {model: res.users, id: res_users_purchase_requisition_user}:
company_id: base.main_company
- name: Purchase Reqisition User
+ name: Purchase requisition User
login: pru
password: pru
+ email: requisition_user@yourcompany.com
diff --git a/addons/sale/test/create_sale_users.yml b/addons/sale/test/create_sale_users.yml
index 29e09b38b80..ed575073f5d 100644
--- a/addons/sale/test/create_sale_users.yml
+++ b/addons/sale/test/create_sale_users.yml
@@ -9,6 +9,7 @@
name: Sales manager
login: sm
password: sm
+ email: salesmanager@yourcompany.com
-
Create a user as 'Salesman'
-
@@ -20,3 +21,4 @@
name: Salesman
login: su
password: su
+ email: salesman@yourcompany.com
diff --git a/addons/sale_stock/test/sale_stock_users.yml b/addons/sale_stock/test/sale_stock_users.yml
index d64fcac2503..c1ebd54392c 100644
--- a/addons/sale_stock/test/sale_stock_users.yml
+++ b/addons/sale_stock/test/sale_stock_users.yml
@@ -9,6 +9,7 @@
name: Sales manager
login: ssm
password: ssm
+ email: ss_salesmanager@yourcompany.com
-
Create a user as 'Salesman'
-
@@ -20,6 +21,7 @@
name: Salesman
login: ssu
password: ssu
+ email: ss_salesman@yourcompany.com
-
Create a user as 'Stock User'
-
@@ -31,6 +33,8 @@
name: Stock User
login: sau
password: sau
+ email: stock_user@yourcompany.com
+
-
Create a user as 'Stock Manager'
-
@@ -43,3 +47,4 @@
login: sam
password: sam
email: admin@portal.example.com
+ email: stock_manager@yourcompany.com
diff --git a/addons/stock/test/stock_users.yml b/addons/stock/test/stock_users.yml
index 13074d752f3..b8fe0783ca1 100644
--- a/addons/stock/test/stock_users.yml
+++ b/addons/stock/test/stock_users.yml
@@ -9,6 +9,7 @@
name: Stock Manager
login: sam
password: sam
+ email: stockmanager@yourcompany.com
-
Create a user as 'Stock User'
-
@@ -20,3 +21,4 @@
name: Stock User
login: sau
password: sau
+ email: stockuser@yourcompany.com
From 21c4fee43dde9587a42527fbb7a809f1a45b1df0 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Thu, 28 Mar 2013 17:28:01 +0530
Subject: [PATCH 055/256] [IMP]improve mrp yaml
bzr revid: sgo@tinyerp.com-20130328115801-kh4k8ctnh3x5oae2
---
addons/mrp/test/order_process.yml | 27 +++++++++++----------------
1 file changed, 11 insertions(+), 16 deletions(-)
diff --git a/addons/mrp/test/order_process.yml b/addons/mrp/test/order_process.yml
index 8aa408483e9..d5cc6dee964 100644
--- a/addons/mrp/test/order_process.yml
+++ b/addons/mrp/test/order_process.yml
@@ -1,8 +1,3 @@
--
- MRP user can doing all process related to Production Order, so let's check data with giving the access rights of user.
--
- !context
- uid: 'res_users_mrp_user'
-
I compute the production order.
-
@@ -28,6 +23,7 @@
I confirm the Production Order.
-
!workflow {model: mrp.production, action: button_confirm, ref: mrp_production_test1}
+
-
I check details of Produce Move of Production Order to trace Final Product.
-
@@ -63,6 +59,16 @@
assert move_line.product_uos.id == order_line.product_uos.id, "UOS is not correspond in 'To consume line'."
assert move_line.location_id.id == routing_loc or order.location_src_id.id, "Source location is not correspond in 'To consume line'."
assert move_line.location_dest_id.id == source_location_id, "Destination Location is not correspond in 'To consume line'."
+-
+ I consume raw materials and put one material in scrap location due to waste it.
+-
+ !python {model: mrp.production}: |
+ scrap_location_ids = self.pool.get('stock.location').search(cr, uid, [('scrap_location','=',True)])
+ scrap_location_id = scrap_location_ids[0]
+ order = self.browse(cr, uid, ref("mrp_production_test1"))
+ for move in order.move_lines:
+ if move.product_id.id == ref("product.product_product_6"):
+ move.action_scrap(5.0, scrap_location_id)
-
I check details of an Internal Shipment after confirmed production order to bring components in Raw Materials Location.
-
@@ -154,17 +160,6 @@
!python {model: mrp.production}: |
order = self.browse(cr, uid, ref("mrp_production_test1"))
assert order.state == 'in_production', 'Production order should be in production State.'
--
- I consume raw materials and put one material in scrap location due to waste it.
--
- !python {model: mrp.production}: |
- scrap_location_ids = self.pool.get('stock.location').search(cr, uid, [('scrap_location','=',True)])
- scrap_location_id = scrap_location_ids[0]
- order = self.browse(cr, uid, ref("mrp_production_test1"))
- for move in order.move_lines:
- move.action_consume(move.product_qty)
- if move.product_id.id == ref("product.product_product_6"):
- move.action_scrap(5.0, scrap_location_id)
-
I produce product.
-
From 037f2f590d8da0cdfda22738a727e2beaa1edcdd Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Thu, 28 Mar 2013 18:29:39 +0530
Subject: [PATCH 056/256] [IMP]improve stock shipment.yml
bzr revid: sgo@tinyerp.com-20130328125939-06de8aj23spjc5uu
---
addons/stock/test/shipment.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index 96273a081b7..4c715584756 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -193,7 +193,7 @@
!python {model: stock.location}: |
ctx = {'product_id': ref('product_product_6')}
monitor_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
- assert monitor_location.stock_real == 136.0, 'stock does not correspond in stock location shop0.'
+ assert monitor_location.stock_real == 132.0, 'stock does not correspond in stock location shop0.'
scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
assert scrapped_location.stock_real == 1*4, 'scraped stock does not correspond in scrap location.'
@@ -202,8 +202,8 @@
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
- assert product.qty_available == 136.0, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
+ assert product.qty_available == 132.0, "Stock does not correspond."
+ assert round(product.virtual_available, 2) == -8.00, "Vitual stock does not correspond."
-
I trace all incoming lots.
-
@@ -277,5 +277,5 @@
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
- assert round(product.qty_available, 2) == 6, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
+ assert round(product.qty_available, 2) == 2, "Stock does not correspond."
+ assert round(product.virtual_available, 2) == -8.00, "Vitual stock does not correspond."
From 412efcda33bbf48aaff3c94dff5c5b8863cfa9cf Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Thu, 28 Mar 2013 18:37:43 +0530
Subject: [PATCH 057/256] [IMP]improve mrp yaml minor changes
bzr revid: sgo@tinyerp.com-20130328130743-ky1hz52gskju1q7t
---
addons/mrp/test/order_process.yml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/addons/mrp/test/order_process.yml b/addons/mrp/test/order_process.yml
index d5cc6dee964..aac6064443d 100644
--- a/addons/mrp/test/order_process.yml
+++ b/addons/mrp/test/order_process.yml
@@ -1,3 +1,8 @@
+-
+ MRP user can doing all process related to Production Order, so let's check data with giving the access rights of user.
+-
+ !context
+ uid: 'res_users_mrp_user'
-
I compute the production order.
-
From 94c7156ebe4218836df56f0407c8853345f66827 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Fri, 29 Mar 2013 14:35:58 +0530
Subject: [PATCH 058/256] [IMP]improve stock shipment yml and add test with
manager
bzr revid: sgo@tinyerp.com-20130329090558-ksv44h3cakskf57l
---
addons/stock/test/shipment.yml | 87 ++++------------------------------
1 file changed, 8 insertions(+), 79 deletions(-)
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index 4c715584756..59b6a6aad48 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -1,3 +1,8 @@
+-
+ Only stock manager can confirm shipment and picking, so let's check data with giving the access rights of manager
+-
+ !context
+ uid: 'res_users_stock_manager'
-
I confirm outgoing shipment of 130 unit 15” LCD Monitor.
-
@@ -91,7 +96,7 @@
!python {model: stock.picking}: |
# the cancel is not on the return, but on the incomming shipment (which now has a quantity of 10, thanks to the
# backorder). This situation is a little weird as we returned a move that we finally cancelled... As result, only
- # 30Kg from the original 50Kg will be counted in the stock (50 - 10 (cancelled quantity) - 10 (returned quantity))
+ # 30Unit from the original 50Unit will be counted in the stock (50 - 10 (cancelled quantity) - 10 (returned quantity))
self.action_cancel(cr, uid, [ref("incomming_shipment")], context=context)
-
I make invoice of backorder of incomming shipment.
@@ -118,41 +123,6 @@
product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.qty_available == 140, "Stock does not correspond."
assert product.virtual_available == 0, "Vitual stock does not correspond."
--
- I split incomming shipment into lots. each lot contain 10 unit 15” LCD Monitor.
--
- !python {model: stock.picking}: |
- shipment = self.browse(cr, uid, ref("incomming_shipment"))
- move_ids = [x.id for x in shipment.backorder_id.move_lines]
- context.update({'active_model': 'stock.move', 'active_id': move_ids[0], 'active_ids': move_ids})
--
- !record {model: stock.move.split, id: split_lot_incomming}:
- line_ids:
- - name: incoming_lot0
- quantity: 10
- - name: incoming_lot1
- quantity: 10
- - name: incoming_lot2
- quantity: 10
- - name: incoming_lot3
- quantity: 10
-
--
- !python {model: stock.move.split }: |
- self.split_lot(cr, uid, [ref('split_lot_incomming')], context=context)
--
- I check move lines after spliting
--
- !python {model: stock.move}: |
- lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incomming'), context=context)
- lot_ids = self.pool.get('stock.production.lot').search(cr, uid, [('name','in',[x.name for x in lot.line_ids])])
- assert len(lot_ids) == 4, 'lots of incomming shipment are not correspond.'
- move_ids = self.search(cr, uid, [('location_dest_id','=',ref('location_monitor')),('prodlot_id','in',lot_ids)])
- assert len(move_ids) == 4, 'move lines are not correspond per prodcution lot after splited.'
- for move in self.browse(cr, uid, move_ids, context=context):
- assert move.prodlot_id.name in ['incoming_lot0', 'incoming_lot1', 'incoming_lot2', 'incoming_lot3'], "lot does not correspond."
- assert move.product_qty == 10, "qty does not correspond per production lot."
- context.update({'active_model':'stock.move', 'active_id':move_ids[0],'active_ids': move_ids})
-
I check the stock valuation account entries.
-
@@ -170,47 +140,6 @@
else:
assert account_move_line.credit == 0.0, "Credit amount does not correspond."
assert account_move_line.debit == 10000.0, "Debit amount does not correspond."
--
- I consume 1 unit 15” LCD Monitor from each incoming lots into internal production.
--
- !record {model: stock.move.consume, id: consume_lot_incomming}:
- product_qty: 1
- location_id: location_monitor
--
- !python {model: stock.move.consume}: |
- self.do_move_consume(cr, uid, [ref('consume_lot_incomming')], context=context)
--
- I scrap 1 unit 15” LCD Monitor from each incoming lots into scrap location.
--
- !record {model: stock.move.scrap, id: scrap_lot_incomming}:
- product_qty: 1
--
- !python {model: stock.move.scrap}: |
- self.move_scrap(cr, uid, [ref('scrap_lot_incomming')], context=context)
--
- I check stock in scrap location and stock location shop0.
--
- !python {model: stock.location}: |
- ctx = {'product_id': ref('product_product_6')}
- monitor_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
- assert monitor_location.stock_real == 132.0, 'stock does not correspond in stock location shop0.'
- scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
- assert scrapped_location.stock_real == 1*4, 'scraped stock does not correspond in scrap location.'
-
--
- I check availabile stock after consumed and scraped.
--
- !python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_product_6'), context=context)
- assert product.qty_available == 132.0, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -8.00, "Vitual stock does not correspond."
--
- I trace all incoming lots.
--
- !python {model: stock.production.lot }: |
- lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incomming'), context=context)
- lot_ids = self.search(cr, uid, [('name', 'in', [x.name for x in lot.line_ids])])
- self.action_traceability(cr, uid, lot_ids, context=context)
-
I check outgoing shipment after stock availablity in Chicago shop.
-
@@ -277,5 +206,5 @@
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
- assert round(product.qty_available, 2) == 2, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -8.00, "Vitual stock does not correspond."
+ assert round(product.qty_available, 2) == 10, "Stock does not correspond."
+
From 382171d4174379567a1abe535a0cb3348ccbe089 Mon Sep 17 00:00:00 2001
From: "Rucha (Open ERP)"
Date: Fri, 29 Mar 2013 21:04:03 +0530
Subject: [PATCH 059/256] [IMP]: stock: Improved shipment.yml for split lot
problem for stock manager
bzr revid: rpa@tinyerp.com-20130329153403-n85d6kwk6edrplor
---
addons/stock/stock_demo.yml | 2 +-
addons/stock/test/shipment.yml | 191 +++++++++++++++++----------------
2 files changed, 100 insertions(+), 93 deletions(-)
diff --git a/addons/stock/stock_demo.yml b/addons/stock/stock_demo.yml
index 64be92f8d06..d925ba4dc8e 100644
--- a/addons/stock/stock_demo.yml
+++ b/addons/stock/stock_demo.yml
@@ -44,7 +44,7 @@
valuation: real_time
cost_method: average
property_stock_account_input: account.conf_o_income
- property_stock_account_output: account.conf_o_expense
+ property_stock_account_output: account.a_expense
-
Stock user can handled production lot,inventory and picking, so let's check data with giving the access rights of user
-
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index 4c715584756..909050d0483 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -1,3 +1,6 @@
+-
+ !context
+ uid: 'res_users_stock_manager'
-
I confirm outgoing shipment of 130 unit 15” LCD Monitor.
-
@@ -16,29 +19,68 @@
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
- product.virtual_available == -30, "Vitual stock is not updated."
+ assert product.virtual_available == -30, "Vitual stock is not updated."
-
I confirm incomming shipment of 50 unit 15” LCD Monitor.
-
!workflow {model: stock.picking, action: button_confirm, ref: incomming_shipment}
+-
+ I split incomming shipment into lots. each lot contain 10 unit 15” LCD Monitor.
+-
+ !python {model: stock.picking}: |
+ shipment = self.browse(cr, uid, ref("incomming_shipment"))
+ move_ids = [x.id for x in shipment.move_lines]
+ context.update({'active_model': 'stock.move', 'active_id': move_ids[0], 'active_ids': move_ids})
+-
+ !record {model: stock.move.split, id: split_lot_incomming}:
+ line_ids:
+ - name: incoming_lot0
+ quantity: 10
+ - name: incoming_lot1
+ quantity: 10
+ - name: incoming_lot2
+ quantity: 10
+ - name: incoming_lot3
+ quantity: 10
+-
+ !python {model: stock.move.split }: |
+ self.split_lot(cr, uid, [ref('split_lot_incomming')], context=context)
+-
+ I check move lines after spliting
+-
+ !python {model: stock.move}: |
+ mm = self.browse(cr, uid, ref("incomming_shipment_monitor"))
+ lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incomming'), context=context)
+ lot_ids = self.pool.get('stock.production.lot').search(cr, uid, [('name','in',[x.name for x in lot.line_ids])])
+ assert len(lot_ids) == 4, 'lots of incomming shipment are not correspond.'
+ move_ids = self.search(cr, uid, [('location_dest_id','=',ref('location_monitor')),('prodlot_id','in',lot_ids)])
+ assert len(move_ids) == 4, 'move lines are not correspond per prodcution lot after splited.'
+ for move in self.browse(cr, uid, move_ids, context=context):
+ assert move.prodlot_id.name in ['incoming_lot0', 'incoming_lot1', 'incoming_lot2', 'incoming_lot3'], "lot does not correspond."
+ assert move.product_qty == 10, "qty does not correspond per production lot."
-
I receive 40 unit 15” LCD Monitor so I make backorder of incomming shipment for 40 unit.
-
!python {model: stock.partial.picking}: |
context.update({'active_model': 'stock.picking', 'active_id': ref('incomming_shipment'), 'active_ids': [ref('incomming_shipment')]})
-
- !record {model: stock.partial.picking, id: partial_incomming}:
- move_ids:
- - quantity: 40
- product_id: product_product_6
- product_uom: product.product_uom_unit
- move_id: incomming_shipment_monitor
- location_id: stock_location_3
- location_dest_id: location_monitor
--
- !python {model: stock.partial.picking }: |
- self.do_partial(cr, uid, [ref('partial_incomming')], context=context)
+ !python {model: stock.partial.picking}: |
+ partial = []
+ for move in self.pool.get('stock.picking').browse(cr, uid, ref("incomming_shipment")).move_lines:
+ if move.prodlot_id:
+ partial.append((0, 0, {
+ 'quantity': move.product_qty,
+ 'product_id': move.product_id.id,
+ 'product_uom': move.product_uom.id,
+ 'move_id': move.id,
+ 'location_id': move.location_id.id,
+ 'location_dest_id': move.location_dest_id.id,
+ 'prodlot_id': move.prodlot_id.id,
+ 'cost': move.product_id.standard_price
+ }))
+ partial_id = self.create(cr, uid, {'move_ids': partial}, context=context)
+ self.do_partial(cr, uid, [partial_id], context=context)
-
I check backorder shipment after received partial shipment.
-
@@ -46,10 +88,12 @@
shipment = self.browse(cr, uid, ref("incomming_shipment"))
backorder = shipment.backorder_id
assert backorder, "Backorder should be created after partial shipment."
- assert backorder.state == 'done', "Backorder should be close after received."
+ assert backorder.state == 'done', "Backorder should be closed after received."
+ qty = 0
for move_line in backorder.move_lines:
- assert move_line.product_qty == 40, "Qty in backorder does not correspond."
assert move_line.state == 'done', "Move line of backorder should be closed."
+ qty += move_line.product_qty
+ assert qty == 40, "Qty in backorder does not correspond."
-
I receive another 10 unit 15” LCD Monitor.
-
@@ -64,18 +108,16 @@
-
!python {model: stock.partial.picking }: |
self.do_partial(cr, uid, [ref('partial_incomming')], context=context)
-
-
- I check incomming shipment after received.
+ I check incomming shipment after receiving it.
-
!python {model: stock.picking}: |
shipment = self.browse(cr, uid, ref("incomming_shipment"))
- assert shipment.state == 'done', "shipment should be close after received."
+ assert shipment.state == 'done', "shipment should be closed after receiving."
for move_line in shipment.move_lines:
assert move_line.product_qty == 10, "Qty does not correspond."
assert move_line.product_id.virtual_available == 20, "Virtual stock does not correspond."
assert move_line.state == 'done', "Move line should be closed."
-
-
I return last incomming shipment for 10 unit 15” LCD Monitor.
-
@@ -91,7 +133,7 @@
!python {model: stock.picking}: |
# the cancel is not on the return, but on the incomming shipment (which now has a quantity of 10, thanks to the
# backorder). This situation is a little weird as we returned a move that we finally cancelled... As result, only
- # 30Kg from the original 50Kg will be counted in the stock (50 - 10 (cancelled quantity) - 10 (returned quantity))
+ # 30Unit from the original 50Unit will be counted in the stock (50 - 10 (cancelled quantity) - 10 (returned quantity))
self.action_cancel(cr, uid, [ref("incomming_shipment")], context=context)
-
I make invoice of backorder of incomming shipment.
@@ -118,41 +160,6 @@
product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.qty_available == 140, "Stock does not correspond."
assert product.virtual_available == 0, "Vitual stock does not correspond."
--
- I split incomming shipment into lots. each lot contain 10 unit 15” LCD Monitor.
--
- !python {model: stock.picking}: |
- shipment = self.browse(cr, uid, ref("incomming_shipment"))
- move_ids = [x.id for x in shipment.backorder_id.move_lines]
- context.update({'active_model': 'stock.move', 'active_id': move_ids[0], 'active_ids': move_ids})
--
- !record {model: stock.move.split, id: split_lot_incomming}:
- line_ids:
- - name: incoming_lot0
- quantity: 10
- - name: incoming_lot1
- quantity: 10
- - name: incoming_lot2
- quantity: 10
- - name: incoming_lot3
- quantity: 10
-
--
- !python {model: stock.move.split }: |
- self.split_lot(cr, uid, [ref('split_lot_incomming')], context=context)
--
- I check move lines after spliting
--
- !python {model: stock.move}: |
- lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incomming'), context=context)
- lot_ids = self.pool.get('stock.production.lot').search(cr, uid, [('name','in',[x.name for x in lot.line_ids])])
- assert len(lot_ids) == 4, 'lots of incomming shipment are not correspond.'
- move_ids = self.search(cr, uid, [('location_dest_id','=',ref('location_monitor')),('prodlot_id','in',lot_ids)])
- assert len(move_ids) == 4, 'move lines are not correspond per prodcution lot after splited.'
- for move in self.browse(cr, uid, move_ids, context=context):
- assert move.prodlot_id.name in ['incoming_lot0', 'incoming_lot1', 'incoming_lot2', 'incoming_lot3'], "lot does not correspond."
- assert move.product_qty == 10, "qty does not correspond per production lot."
- context.update({'active_model':'stock.move', 'active_id':move_ids[0],'active_ids': move_ids})
-
I check the stock valuation account entries.
-
@@ -165,45 +172,11 @@
for account_move_line in account_move.line_id:
for stock_move in incomming_shipment.move_lines:
if account_move_line.account_id.id == stock_move.product_id.property_stock_account_input.id:
- assert account_move_line.credit == 10000.0, "Credit amount does not correspond."
+ assert account_move_line.credit == 14000.0, "Credit amount does not correspond."
assert account_move_line.debit == 0.0, "Debit amount does not correspond."
else:
assert account_move_line.credit == 0.0, "Credit amount does not correspond."
- assert account_move_line.debit == 10000.0, "Debit amount does not correspond."
--
- I consume 1 unit 15” LCD Monitor from each incoming lots into internal production.
--
- !record {model: stock.move.consume, id: consume_lot_incomming}:
- product_qty: 1
- location_id: location_monitor
--
- !python {model: stock.move.consume}: |
- self.do_move_consume(cr, uid, [ref('consume_lot_incomming')], context=context)
--
- I scrap 1 unit 15” LCD Monitor from each incoming lots into scrap location.
--
- !record {model: stock.move.scrap, id: scrap_lot_incomming}:
- product_qty: 1
--
- !python {model: stock.move.scrap}: |
- self.move_scrap(cr, uid, [ref('scrap_lot_incomming')], context=context)
--
- I check stock in scrap location and stock location shop0.
--
- !python {model: stock.location}: |
- ctx = {'product_id': ref('product_product_6')}
- monitor_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
- assert monitor_location.stock_real == 132.0, 'stock does not correspond in stock location shop0.'
- scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
- assert scrapped_location.stock_real == 1*4, 'scraped stock does not correspond in scrap location.'
-
--
- I check availabile stock after consumed and scraped.
--
- !python {model: product.product}: |
- product = self.browse(cr, uid, ref('product_product_6'), context=context)
- assert product.qty_available == 132.0, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -8.00, "Vitual stock does not correspond."
+ assert account_move_line.debit == 14000.0, "Debit amount does not correspond."
-
I trace all incoming lots.
-
@@ -221,6 +194,40 @@
for move_line in shipment.move_lines:
assert move_line.state == "assigned", "Move should be assigned."
self.force_assign(cr, uid, [shipment.id])
+ context.update({'active_model':'stock.move', 'active_id':shipment.move_lines[0].id,'active_ids': [shipment.move_lines[0].id]})
+-
+ I scrap 4 units of 15” LCD Monitor into scrap location.
+-
+ !record {model: stock.move.scrap, id: scrap_monitor1}:
+ product_qty: 4
+-
+ !python {model: stock.move.scrap}: |
+ self.move_scrap(cr, uid, [ref('scrap_monitor1')], context=context)
+-
+ I consume 4 units of 15” LCD Monitor.
+-
+ !record {model: stock.move.consume, id: consume_monitor1}:
+ product_qty: 4
+ location_id: location_monitor
+-
+ !python {model: stock.move.consume}: |
+ self.do_move_consume(cr, uid, [ref('consume_monitor1')], context=context)
+-
+ I check stock in scrap location and stock location shop0.
+-
+ !python {model: stock.location}: |
+ ctx = {'product_id': ref('product_product_6')}
+ monitor_location = self.pool.get('stock.location').browse(cr, uid, ref('location_monitor'), context=ctx)
+ assert monitor_location.stock_real == 132.0, 'stock does not correspond in stock location shop0.'
+ scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
+ assert scrapped_location.stock_real == 4, 'scraped stock does not correspond in scrap location.'
+-
+ I check availabile stock after consumed and scraped.
+-
+ !python {model: product.product}: |
+ product = self.browse(cr, uid, ref('product_product_6'), context=context)
+ assert product.qty_available == 132.0, "Stock does not correspond."
+ assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
-
I deliver 5 unit 15” LCD Monitor to customer so I make partial deliver
-
@@ -277,5 +284,5 @@
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
- assert round(product.qty_available, 2) == 2, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -8.00, "Vitual stock does not correspond."
+ assert round(product.qty_available, 2) == 6, "Stock does not correspond."
+ assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
From 70995ec63dce5ca4191d1e3326b9961a6c38c8e0 Mon Sep 17 00:00:00 2001
From: "Rucha (Open ERP)"
Date: Fri, 29 Mar 2013 21:13:18 +0530
Subject: [PATCH 060/256] [FIX]: stock: Fixed typo and grammar in shipment.yml
bzr revid: rpa@tinyerp.com-20130329154318-2wswvugrl17ncy9l
---
addons/stock/test/shipment.yml | 112 ++++++++++++++++-----------------
1 file changed, 56 insertions(+), 56 deletions(-)
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index 909050d0483..75249aea842 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -2,11 +2,11 @@
!context
uid: 'res_users_stock_manager'
-
- I confirm outgoing shipment of 130 unit 15” LCD Monitor.
+ I confirm outgoing shipment of 130 Unit 15” LCD Monitor.
-
!workflow {model: stock.picking, action: button_confirm, ref: outgoing_shipment}
-
- I check shipment details after confirmed.
+ I check shipment details after confirmation.
-
!python {model: stock.picking}: |
shipment = self.browse(cr, uid, ref("outgoing_shipment"))
@@ -15,25 +15,25 @@
assert move_line.state == "confirmed", "Move should be confirmed."
-
- Now I check vitual stock of 15” LCD Monitor after confirmed outgoing shipment.
+ Now, I check virtual stock of 15” LCD Monitor after confirming outgoing shipment.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
- assert product.virtual_available == -30, "Vitual stock is not updated."
+ assert product.virtual_available == -30, "Virtual stock is not updated."
-
- I confirm incomming shipment of 50 unit 15” LCD Monitor.
+ I confirm incoming shipment of 50 Unit 15” LCD Monitor.
-
- !workflow {model: stock.picking, action: button_confirm, ref: incomming_shipment}
+ !workflow {model: stock.picking, action: button_confirm, ref: incoming_shipment}
-
- I split incomming shipment into lots. each lot contain 10 unit 15” LCD Monitor.
+ I split incoming shipment into lots. each lot contain 10 Unit 15” LCD Monitor.
-
!python {model: stock.picking}: |
- shipment = self.browse(cr, uid, ref("incomming_shipment"))
+ shipment = self.browse(cr, uid, ref("incoming_shipment"))
move_ids = [x.id for x in shipment.move_lines]
context.update({'active_model': 'stock.move', 'active_id': move_ids[0], 'active_ids': move_ids})
-
- !record {model: stock.move.split, id: split_lot_incomming}:
+ !record {model: stock.move.split, id: split_lot_incoming}:
line_ids:
- name: incoming_lot0
quantity: 10
@@ -45,29 +45,29 @@
quantity: 10
-
!python {model: stock.move.split }: |
- self.split_lot(cr, uid, [ref('split_lot_incomming')], context=context)
+ self.split_lot(cr, uid, [ref('split_lot_incoming')], context=context)
-
- I check move lines after spliting
+ I check move lines after splitting.
-
!python {model: stock.move}: |
- mm = self.browse(cr, uid, ref("incomming_shipment_monitor"))
- lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incomming'), context=context)
+ mm = self.browse(cr, uid, ref("incoming_shipment_monitor"))
+ lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incoming'), context=context)
lot_ids = self.pool.get('stock.production.lot').search(cr, uid, [('name','in',[x.name for x in lot.line_ids])])
- assert len(lot_ids) == 4, 'lots of incomming shipment are not correspond.'
+ assert len(lot_ids) == 4, 'lots of incoming shipment are not correspond.'
move_ids = self.search(cr, uid, [('location_dest_id','=',ref('location_monitor')),('prodlot_id','in',lot_ids)])
- assert len(move_ids) == 4, 'move lines are not correspond per prodcution lot after splited.'
+ assert len(move_ids) == 4, 'move lines are not correspond per production lot after splitting.'
for move in self.browse(cr, uid, move_ids, context=context):
assert move.prodlot_id.name in ['incoming_lot0', 'incoming_lot1', 'incoming_lot2', 'incoming_lot3'], "lot does not correspond."
assert move.product_qty == 10, "qty does not correspond per production lot."
-
- I receive 40 unit 15” LCD Monitor so I make backorder of incomming shipment for 40 unit.
+ I receive 40 units of 15” LCD Monitor.
-
!python {model: stock.partial.picking}: |
- context.update({'active_model': 'stock.picking', 'active_id': ref('incomming_shipment'), 'active_ids': [ref('incomming_shipment')]})
+ context.update({'active_model': 'stock.picking', 'active_id': ref('incoming_shipment'), 'active_ids': [ref('incoming_shipment')]})
-
!python {model: stock.partial.picking}: |
partial = []
- for move in self.pool.get('stock.picking').browse(cr, uid, ref("incomming_shipment")).move_lines:
+ for move in self.pool.get('stock.picking').browse(cr, uid, ref("incoming_shipment")).move_lines:
if move.prodlot_id:
partial.append((0, 0, {
'quantity': move.product_qty,
@@ -82,10 +82,10 @@
partial_id = self.create(cr, uid, {'move_ids': partial}, context=context)
self.do_partial(cr, uid, [partial_id], context=context)
-
- I check backorder shipment after received partial shipment.
+ I check backorder shipment after receiving partial shipment.
-
!python {model: stock.picking}: |
- shipment = self.browse(cr, uid, ref("incomming_shipment"))
+ shipment = self.browse(cr, uid, ref("incoming_shipment"))
backorder = shipment.backorder_id
assert backorder, "Backorder should be created after partial shipment."
assert backorder.state == 'done', "Backorder should be closed after received."
@@ -95,82 +95,82 @@
qty += move_line.product_qty
assert qty == 40, "Qty in backorder does not correspond."
-
- I receive another 10 unit 15” LCD Monitor.
+ I receive another 10 units of 15” LCD Monitor.
-
- !record {model: stock.partial.picking, id: partial_incomming}:
+ !record {model: stock.partial.picking, id: partial_incoming}:
move_ids:
- quantity: 10
product_id: product_product_6
product_uom: product.product_uom_unit
- move_id: incomming_shipment_monitor
+ move_id: incoming_shipment_monitor
location_id: stock_location_3
location_dest_id: location_monitor
-
!python {model: stock.partial.picking }: |
- self.do_partial(cr, uid, [ref('partial_incomming')], context=context)
+ self.do_partial(cr, uid, [ref('partial_incoming')], context=context)
-
- I check incomming shipment after receiving it.
+ I check incoming shipment after receiving it.
-
!python {model: stock.picking}: |
- shipment = self.browse(cr, uid, ref("incomming_shipment"))
+ shipment = self.browse(cr, uid, ref("incoming_shipment"))
assert shipment.state == 'done', "shipment should be closed after receiving."
for move_line in shipment.move_lines:
assert move_line.product_qty == 10, "Qty does not correspond."
assert move_line.product_id.virtual_available == 20, "Virtual stock does not correspond."
assert move_line.state == 'done', "Move line should be closed."
-
- I return last incomming shipment for 10 unit 15” LCD Monitor.
+ I return last incoming shipment for 10 Unit 15” LCD Monitor.
-
- !record {model: stock.return.picking, id: return_incomming}:
+ !record {model: stock.return.picking, id: return_incoming}:
invoice_state: none
-
!python {model: stock.return.picking }: |
# this work without giving the id of the picking to return, magically, thanks to the context
- self.create_returns(cr, uid, [ref('return_incomming')], context=context)
+ self.create_returns(cr, uid, [ref('return_incoming')], context=context)
-
- I cancel incomming shipment after return it.
+ I cancel incoming shipment after returning it.
-
!python {model: stock.picking}: |
- # the cancel is not on the return, but on the incomming shipment (which now has a quantity of 10, thanks to the
+ # the cancel is not on the return, but on the incoming shipment (which now has a quantity of 10, thanks to the
# backorder). This situation is a little weird as we returned a move that we finally cancelled... As result, only
# 30Unit from the original 50Unit will be counted in the stock (50 - 10 (cancelled quantity) - 10 (returned quantity))
- self.action_cancel(cr, uid, [ref("incomming_shipment")], context=context)
+ self.action_cancel(cr, uid, [ref("incoming_shipment")], context=context)
-
- I make invoice of backorder of incomming shipment.
+ I make invoice of backorder of incoming shipment.
-
!python {model: stock.invoice.onshipping}: |
- shipment = self.pool.get('stock.picking').browse(cr, uid, ref("incomming_shipment"))
+ shipment = self.pool.get('stock.picking').browse(cr, uid, ref("incoming_shipment"))
context.update({'active_model': 'stock.picking', 'active_id': shipment.backorder_id.id, 'active_ids': [shipment.backorder_id.id]})
-
- !record {model: stock.invoice.onshipping, id: invoice_incomming}:
+ !record {model: stock.invoice.onshipping, id: invoice_incoming}:
group: False
-
!python {model: stock.invoice.onshipping }: |
- self.create_invoice(cr, uid, [ref('invoice_incomming')], context=context)
+ self.create_invoice(cr, uid, [ref('invoice_incoming')], context=context)
-
- I check invoice state of backorder of incomming shipment.
+ I check invoice status of backorder of incoming shipment.
-
!python {model: stock.picking}: |
- shipment = self.browse(cr, uid, ref("incomming_shipment"))
- assert shipment.backorder_id.invoice_state == 'invoiced', 'Invoice state is not upadted.'
+ shipment = self.browse(cr, uid, ref("incoming_shipment"))
+ assert shipment.backorder_id.invoice_state == 'invoiced', 'Invoice state is not updated.'
-
- I check available stock after received incomming shipping.
+ I check available stock after receiving incoming shipping.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.qty_available == 140, "Stock does not correspond."
- assert product.virtual_available == 0, "Vitual stock does not correspond."
+ assert product.virtual_available == 0, "Virtual stock does not correspond."
-
I check the stock valuation account entries.
-
!python {model: account.move}: |
- incomming_shipment = self.pool.get('stock.picking').browse(cr, uid, ref('incomming_shipment'), context=context)
- account_move_ids = self.search(cr, uid, [('ref','=',incomming_shipment.name)])
+ incoming_shipment = self.pool.get('stock.picking').browse(cr, uid, ref('incoming_shipment'), context=context)
+ account_move_ids = self.search(cr, uid, [('ref','=',incoming_shipment.name)])
assert len(account_move_ids), "account move should be created."
account_move = self.browse(cr, uid, account_move_ids[0], context=context)
- assert len(account_move.line_id) == len(incomming_shipment.move_lines) + 1, 'accuont entries are not correspond.'
+ assert len(account_move.line_id) == len(incoming_shipment.move_lines) + 1, 'Accounting entries does not correspond.'
for account_move_line in account_move.line_id:
- for stock_move in incomming_shipment.move_lines:
+ for stock_move in incoming_shipment.move_lines:
if account_move_line.account_id.id == stock_move.product_id.property_stock_account_input.id:
assert account_move_line.credit == 14000.0, "Credit amount does not correspond."
assert account_move_line.debit == 0.0, "Debit amount does not correspond."
@@ -181,11 +181,11 @@
I trace all incoming lots.
-
!python {model: stock.production.lot }: |
- lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incomming'), context=context)
+ lot = self.pool.get('stock.move.split').browse(cr, uid, ref('split_lot_incoming'), context=context)
lot_ids = self.search(cr, uid, [('name', 'in', [x.name for x in lot.line_ids])])
self.action_traceability(cr, uid, lot_ids, context=context)
-
- I check outgoing shipment after stock availablity in Chicago shop.
+ I check outgoing shipment after stock availability in Chicago shop.
-
!python {model: stock.picking}: |
shipment = self.browse(cr, uid, ref("outgoing_shipment"), context=context)
@@ -213,7 +213,7 @@
!python {model: stock.move.consume}: |
self.do_move_consume(cr, uid, [ref('consume_monitor1')], context=context)
-
- I check stock in scrap location and stock location shop0.
+ I check stock in scrap location and stock location.
-
!python {model: stock.location}: |
ctx = {'product_id': ref('product_product_6')}
@@ -222,14 +222,14 @@
scrapped_location = self.browse(cr, uid, ref('stock_location_scrapped'), context=ctx)
assert scrapped_location.stock_real == 4, 'scraped stock does not correspond in scrap location.'
-
- I check availabile stock after consumed and scraped.
+ I check available stock after consumed and scraped move.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert product.qty_available == 132.0, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
+ assert round(product.virtual_available, 2) == -4.00, "Virtual stock does not correspond."
-
- I deliver 5 unit 15” LCD Monitor to customer so I make partial deliver
+ I deliver 5 Unit 15” LCD Monitor to customer partially.
-
!python {model: stock.partial.move}: |
context.update({'active_model': 'stock.move', 'active_id': ref('outgoing_shipment_monitor'), 'active_ids': [ref('outgoing_shipment_monitor')]})
@@ -247,7 +247,7 @@
self.do_partial(cr, uid, [ref('partial_outgoing_monitor')], context=context)
-
- I packing outgoing shipment into box per 10 unit with unique tracking lot.
+ I pack outgoing shipment into box of 10 Unit with unique tracking lot.
-
!python {model: stock.move}: |
stock_split = self.pool.get('stock.split.into')
@@ -260,7 +260,7 @@
stock_split.split(cr, uid, [split_id], context=context)
total_qty -= split_qty
-
- I deliver outgoing shipment.
+ I deliver the outgoing shipment.
-
!python {model: stock.partial.picking}: |
context.update({'active_model': 'stock.picking', 'active_id': ref('outgoing_shipment'), 'active_ids': [ref('outgoing_shipment')]})
@@ -272,7 +272,7 @@
self.do_partial(cr, uid, [ref('partial_outgoing')], context=context)
-
- I check outgoing shipment after deliver.
+ I check outgoing shipment after delivery.
-
!python {model: stock.picking}: |
shipment = self.browse(cr, uid, ref("outgoing_shipment"), context=context)
@@ -280,9 +280,9 @@
for move_line in shipment.move_lines:
assert move_line.state == "done", "Move should be closed."
-
- I check availaible stock after deliver.
+ I check available stock after delivery.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_product_6'), context=context)
assert round(product.qty_available, 2) == 6, "Stock does not correspond."
- assert round(product.virtual_available, 2) == -4.00, "Vitual stock does not correspond."
+ assert round(product.virtual_available, 2) == -4.00, "Virtual stock does not correspond."
From 5848f817fbb7fed0d3cddfec27886952a2a03ff2 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 1 Apr 2013 12:13:59 +0530
Subject: [PATCH 061/256] [IMP]add string in shipment yml
bzr revid: sgo@tinyerp.com-20130401064359-40s1a4azsvd2099v
---
addons/stock/test/shipment.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml
index 86b4a092108..5f2f4686621 100644
--- a/addons/stock/test/shipment.yml
+++ b/addons/stock/test/shipment.yml
@@ -1,3 +1,5 @@
+-
+ Stock manager can only test whole process related to Shipment, so let's check data with stock manager.
-
!context
uid: 'res_users_stock_manager'
From 2a7bb9a9100011088d1f7240ea265881ba1410de Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Wed, 17 Apr 2013 16:05:44 +0530
Subject: [PATCH 062/256] [IMP]Improve yml for issue demo which creates
duplicate issue and improve code
bzr revid: sgo@tinyerp.com-20130417103544-df29vwms1lc5xq6t
---
addons/project_issue/test/issue_demo.yml | 4 ----
addons/project_issue/test/subscribe_issue.yml | 5 -----
2 files changed, 9 deletions(-)
diff --git a/addons/project_issue/test/issue_demo.yml b/addons/project_issue/test/issue_demo.yml
index 7040b631f46..b7342b435ab 100644
--- a/addons/project_issue/test/issue_demo.yml
+++ b/addons/project_issue/test/issue_demo.yml
@@ -3,10 +3,6 @@
-
!context
uid: 'res_users_project_issue_user'
--
- !record {model: project.issue, id: project_task_1, view: False}:
- task_id: 'project.project_task_17'
- name: 'Error in account module'
-
!record {model: project.issue, id: project01, view: False}:
project_id: 'project.project_project_2'
diff --git a/addons/project_issue/test/subscribe_issue.yml b/addons/project_issue/test/subscribe_issue.yml
index e07c8704a14..8d7b038dccc 100644
--- a/addons/project_issue/test/subscribe_issue.yml
+++ b/addons/project_issue/test/subscribe_issue.yml
@@ -1,8 +1,3 @@
--
- Project user can subscribe issue, so let's check data with giving the access rights of user.
--
- !context
- uid: 'res_users_project_issue_user'
-
In Order to test process of Issue in OpenERP, Customer send the issue by email.
-
From bbd2c2ea5e939e6db529742754093def84e9a84c Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 29 Apr 2013 16:10:42 +0530
Subject: [PATCH 063/256] [IMP]give access rights for contact creation
bzr revid: sgo@tinyerp.com-20130429104042-1elyyf00shx03rsk
---
addons/account/test/account_test_users.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml
index 322bc527ad7..be89afefdd1 100644
--- a/addons/account/test/account_test_users.yml
+++ b/addons/account/test/account_test_users.yml
@@ -2,7 +2,7 @@
Create a user as 'Accountant'
-
!context
- default_groups_ref: [account.group_account_user]
+ default_groups_ref: [base.group_partner_manager,account.group_account_user]
-
!record {model: res.users, id: res_users_account_user}:
company_id: base.main_company
From 2f19755c2a46a1c03af81279390013bf333ad4ca Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 29 Apr 2013 16:57:39 +0530
Subject: [PATCH 064/256] [IMP]give access rights for contact creation in
account voucher yml user
bzr revid: sgo@tinyerp.com-20130429112739-f06w3v6g7q9wi2fh
---
addons/account_voucher/test/account_voucher_users.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml
index 8888885f476..0dd8fa528b5 100644
--- a/addons/account_voucher/test/account_voucher_users.yml
+++ b/addons/account_voucher/test/account_voucher_users.yml
@@ -2,7 +2,7 @@
Create a user as 'Accountant for voucher'
-
!context
- default_groups_ref: [account.group_account_user]
+ default_groups_ref: [base.group_partner_manager,account.group_account_user]
-
!record {model: res.users, id: res_users_account_voucher_user}:
company_id: base.main_company
@@ -14,7 +14,7 @@
Create a user as 'Financial Manager for account voucher'
-
!context
- default_groups_ref: [account.group_account_manager]
+ default_groups_ref: [base.group_partner_manager,account.group_account_manager]
-
!record {model: res.users, id: res_users_account_voucher_manager}:
company_id: base.main_company
From d947b32b96e4e5934483ce2a3b57d4a0697daa85 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 30 Apr 2013 16:20:12 +0530
Subject: [PATCH 065/256] [IMP]remove delete access rights from ir_model_data
for all user which are not needed
bzr revid: sgo@tinyerp.com-20130430105012-damnh3zqdr9wdap1
---
openerp/addons/base/security/ir.model.access.csv | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index 3a29108d61e..e104f485a51 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -17,7 +17,7 @@
"access_ir_model_constraint","ir_model_constraint","model_ir_model_constraint",,1,0,0,0
"access_ir_model_relation","ir_model_relation","model_ir_model_relation",,1,0,0,0
"access_ir_model_access_all","ir_model_access_all","model_ir_model_access",,1,0,0,0
-"access_ir_model_data_all","ir_model_data all","model_ir_model_data",,1,1,1,1
+"access_ir_model_data_all","ir_model_data all","model_ir_model_data",,1,1,1,0
"access_ir_model_fields_all","ir_model_fields all","model_ir_model_fields",,1,0,0,0
"access_ir_module_category_group_user","ir_module_category group_user","model_ir_module_category",,1,0,0,0
"access_ir_module_module_group_user","ir_module_module group_user","model_ir_module_module","group_system",1,1,1,1
From ed220968a3f02ee43b5ca542053eae28864f9ad6 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 6 May 2013 15:09:10 +0530
Subject: [PATCH 066/256] [MERGE]put yml in test from demo in purchase
bzr revid: sgo@tinyerp.com-20130506093910-mlyc8a1a1hz4y433
---
addons/purchase/__openerp__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/purchase/__openerp__.py b/addons/purchase/__openerp__.py
index b75c84e332b..9d72579cc77 100644
--- a/addons/purchase/__openerp__.py
+++ b/addons/purchase/__openerp__.py
@@ -67,6 +67,7 @@ Dashboard / Reports for Purchase Management will include:
'res_config_view.xml',
],
'test': [
+ 'test/ui/purchase_users.yml',
'test/process/cancel_order.yml',
'test/process/rfq2order2done.yml',
'test/process/generate_invoice_from_reception.yml',
@@ -79,7 +80,6 @@ Dashboard / Reports for Purchase Management will include:
'test/ui/delete_order.yml',
],
'demo': [
- 'test/ui/purchase_users.yml',
'purchase_order_demo.yml',
'purchase_demo.xml',
],
From ca60821869332c95d0959e8645f16865d66be531 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 6 May 2013 17:05:57 +0530
Subject: [PATCH 067/256] [IMP]improve yml code
bzr revid: sgo@tinyerp.com-20130506113557-mh99n29zv2ve5pv3
---
addons/purchase/purchase_order_demo.yml | 3 ---
1 file changed, 3 deletions(-)
diff --git a/addons/purchase/purchase_order_demo.yml b/addons/purchase/purchase_order_demo.yml
index fb49bfc1248..7a0fae7ef68 100644
--- a/addons/purchase/purchase_order_demo.yml
+++ b/addons/purchase/purchase_order_demo.yml
@@ -1,8 +1,5 @@
-
Give access rights of Purchase user to create purchase order
--
- !context
- uid: 'res_users_purchase_user'
-
!record {model: purchase.order, id: purchase_order_1}:
partner_id: base.res_partner_1
From 5f203720edba35397296bdea2262706b2c3ebeb7 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 11:59:14 +0530
Subject: [PATCH 068/256] [IMP]remove default_groups_ref and added to groups_id
in yml in account and account_Voucher
bzr revid: sgo@tinyerp.com-20130507062914-t8bmem3auvjob5bl
---
addons/account/test/account_test_users.yml | 19 +++++++++++-----
.../test/account_voucher_users.yml | 22 +++++++++++++------
2 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml
index be89afefdd1..3b0fd72f0bf 100644
--- a/addons/account/test/account_test_users.yml
+++ b/addons/account/test/account_test_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Accountant'
--
- !context
- default_groups_ref: [base.group_partner_manager,account.group_account_user]
-
!record {model: res.users, id: res_users_account_user}:
company_id: base.main_company
@@ -11,10 +8,14 @@
password: acc
email: accountuser@yourcompany.com
-
- Create a user as 'Financial Manager'
+ I added groups for Accountant which needed.
-
- !context
- default_groups_ref: [account.group_account_manager]
+ !record {model: res.users, id: res_users_account_user}:
+ groups_id:
+ - account.group_account_user
+ - base.group_partner_manager
+-
+ Create a user as 'Financial Manager'
-
!record {model: res.users, id: res_users_account_manager}:
company_id: base.main_company
@@ -22,3 +23,9 @@
login: fm
password: fm
email: accountmanager@yourcompany.com
+-
+ I added groups for Financial Manager which needed.
+-
+ !record {model: res.users, id: res_users_account_manager}:
+ groups_id:
+ - account.group_account_manager
\ No newline at end of file
diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml
index 0dd8fa528b5..feb19c0e3fb 100644
--- a/addons/account_voucher/test/account_voucher_users.yml
+++ b/addons/account_voucher/test/account_voucher_users.yml
@@ -1,8 +1,5 @@
-
- Create a user as 'Accountant for voucher'
--
- !context
- default_groups_ref: [base.group_partner_manager,account.group_account_user]
+ Create a user as 'Accountant for account voucher'
-
!record {model: res.users, id: res_users_account_voucher_user}:
company_id: base.main_company
@@ -11,10 +8,14 @@
password: acc
email: accountant@yourcompany.com
-
- Create a user as 'Financial Manager for account voucher'
+ I added groups to Accountant for account voucher which needed.
-
- !context
- default_groups_ref: [base.group_partner_manager,account.group_account_manager]
+ !record {model: res.users, id: res_users_account_voucher_user}:
+ groups_id:
+ - base.group_partner_manager
+ - account.group_account_user
+-
+ Create a user as 'Financial Manager for account voucher'
-
!record {model: res.users, id: res_users_account_voucher_manager}:
company_id: base.main_company
@@ -22,3 +23,10 @@
login: fmv
password: fmv
email: finmanager@yourcompany.com
+-
+ I added groups to Financial Manager for account voucher which needed.
+-
+ !record {model: res.users, id: res_users_account_voucher_manager}:
+ groups_id:
+ - base.group_partner_manager
+ - account.group_account_manager
From 0f312151471b625c7ade63004d8293eb8ad950f2 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 12:34:46 +0530
Subject: [PATCH 069/256] [IMP]done changes as per new yml changes in crm
bzr revid: sgo@tinyerp.com-20130507070446-ejlju0cnui74vy21
---
addons/crm/__openerp__.py | 2 +-
addons/crm/test/{ui => }/crm_access_group_users.yml | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename addons/crm/test/{ui => }/crm_access_group_users.yml (100%)
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index ac0a06a384c..bc74c3350ac 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -106,7 +106,7 @@ Dashboard for CRM will include:
'crm_action_rule_demo.xml',
],
'test': [
- 'test/ui/crm_access_group_users.yml',
+ 'test/crm_access_group_users.yml',
'test/crm_lead_message.yml',
'test/lead2opportunity2win.yml',
'test/lead2opportunity_assign_salesmen.yml',
diff --git a/addons/crm/test/ui/crm_access_group_users.yml b/addons/crm/test/crm_access_group_users.yml
similarity index 100%
rename from addons/crm/test/ui/crm_access_group_users.yml
rename to addons/crm/test/crm_access_group_users.yml
From 1373b5cda00c791b3c9a229dd0ef10a28c723679 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 12:49:59 +0530
Subject: [PATCH 070/256] [IMP]improve string and add groups in groups_id in
crm and event
bzr revid: sgo@tinyerp.com-20130507071959-kwqmv7wwy7gqcsfj
---
addons/account/test/account_test_users.yml | 4 ++--
.../test/account_voucher_users.yml | 4 ++--
addons/crm/test/crm_access_group_users.yml | 18 ++++++++++++------
addons/event/test/ui/event_users.yml | 18 ++++++++++++------
4 files changed, 28 insertions(+), 16 deletions(-)
diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml
index 3b0fd72f0bf..b257feedff8 100644
--- a/addons/account/test/account_test_users.yml
+++ b/addons/account/test/account_test_users.yml
@@ -8,7 +8,7 @@
password: acc
email: accountuser@yourcompany.com
-
- I added groups for Accountant which needed.
+ I added groups for Accountant.
-
!record {model: res.users, id: res_users_account_user}:
groups_id:
@@ -24,7 +24,7 @@
password: fm
email: accountmanager@yourcompany.com
-
- I added groups for Financial Manager which needed.
+ I added groups for Financial Manager.
-
!record {model: res.users, id: res_users_account_manager}:
groups_id:
diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml
index feb19c0e3fb..c96645f0190 100644
--- a/addons/account_voucher/test/account_voucher_users.yml
+++ b/addons/account_voucher/test/account_voucher_users.yml
@@ -8,7 +8,7 @@
password: acc
email: accountant@yourcompany.com
-
- I added groups to Accountant for account voucher which needed.
+ I added groups to Accountant for account voucher.
-
!record {model: res.users, id: res_users_account_voucher_user}:
groups_id:
@@ -24,7 +24,7 @@
password: fmv
email: finmanager@yourcompany.com
-
- I added groups to Financial Manager for account voucher which needed.
+ I added groups to Financial Manager for account voucher.
-
!record {model: res.users, id: res_users_account_voucher_manager}:
groups_id:
diff --git a/addons/crm/test/crm_access_group_users.yml b/addons/crm/test/crm_access_group_users.yml
index 158a11b5881..50ee9ed5367 100644
--- a/addons/crm/test/crm_access_group_users.yml
+++ b/addons/crm/test/crm_access_group_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Salesmanager'
--
- !context
- default_groups_ref: ['base.group_sale_manager']
-
!record {model: res.users, id: crm_res_users_salesmanager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: csm
email: crmmanager@yourcompany.com
-
- Create a user as 'Salesman'
+ I added groups for Salesmanager.
-
- !context
- default_groups_ref: ['base.group_sale_salesman_all_leads']
+ !record {model: res.users, id: crm_res_users_salesmanager}:
+ groups_id:
+ - base.group_sale_manager
+-
+ Create a user as 'Salesman'
-
!record {model: res.users, id: crm_res_users_salesman}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: csu
password: csu
email: crmuser@yourcompany.com
+-
+ I added groups for Salesman.
+-
+ !record {model: res.users, id: crm_res_users_salesman}:
+ groups_id:
+ - base.group_sale_salesman_all_leads
diff --git a/addons/event/test/ui/event_users.yml b/addons/event/test/ui/event_users.yml
index 92c8316a5d9..b6927dbdaa6 100644
--- a/addons/event/test/ui/event_users.yml
+++ b/addons/event/test/ui/event_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Event manager'
--
- !context
- default_groups_ref: [event.group_event_manager]
-
!record {model: res.users, id: res_users_eventmanager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: em
email: eventmanager@yourcompany.com
-
- Create a user as 'Event user'
+ I added groups for Event manager.
-
- !context
- default_groups_ref: [event.group_event_user]
+ !record {model: res.users, id: res_users_eventmanager}:
+ groups_id:
+ - event.group_event_manager
+-
+ Create a user as 'Event user'
-
!record {model: res.users, id: res_users_eventuser}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: eu
password: eu
email: eventuser@yourcompany.com
+-
+ I added groups for Event manager.
+-
+ !record {model: res.users, id: res_users_eventuser}:
+ groups_id:
+ - event.group_event_user
From 5f629a2f0bb5a582d366097927eae36321959b95 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 12:57:22 +0530
Subject: [PATCH 071/256] [IMP]improve string
bzr revid: sgo@tinyerp.com-20130507072722-jf0912rfbakddo36
---
addons/event/test/ui/event_users.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/event/test/ui/event_users.yml b/addons/event/test/ui/event_users.yml
index b6927dbdaa6..cda4ebda750 100644
--- a/addons/event/test/ui/event_users.yml
+++ b/addons/event/test/ui/event_users.yml
@@ -23,7 +23,7 @@
password: eu
email: eventuser@yourcompany.com
-
- I added groups for Event manager.
+ I added groups for Event user.
-
!record {model: res.users, id: res_users_eventuser}:
groups_id:
From fbfd8a4b423c232269c6abfafede527bd26ee7e3 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 14:13:16 +0530
Subject: [PATCH 072/256] [IMP]improve string and add groups in groups_id in
sale and sale stock
bzr revid: sgo@tinyerp.com-20130507084316-v1tiw294n8imfj1v
---
addons/crm/test/crm_access_group_users.yml | 12 +++---
addons/sale/test/create_sale_users.yml | 18 ++++++---
addons/sale_stock/test/sale_stock_users.yml | 43 +++++++++++++--------
3 files changed, 45 insertions(+), 28 deletions(-)
diff --git a/addons/crm/test/crm_access_group_users.yml b/addons/crm/test/crm_access_group_users.yml
index 50ee9ed5367..c9080ee530e 100644
--- a/addons/crm/test/crm_access_group_users.yml
+++ b/addons/crm/test/crm_access_group_users.yml
@@ -1,29 +1,29 @@
-
- Create a user as 'Salesmanager'
+ Create a user as 'Crm Salesmanager'
-
!record {model: res.users, id: crm_res_users_salesmanager}:
company_id: base.main_company
- name: Sales manager
+ name: Crm Sales manager
login: csm
password: csm
email: crmmanager@yourcompany.com
-
- I added groups for Salesmanager.
+ I added groups for Crm Salesmanager.
-
!record {model: res.users, id: crm_res_users_salesmanager}:
groups_id:
- base.group_sale_manager
-
- Create a user as 'Salesman'
+ Create a user as 'Crm Salesman'
-
!record {model: res.users, id: crm_res_users_salesman}:
company_id: base.main_company
- name: Salesman
+ name: Crm Salesman
login: csu
password: csu
email: crmuser@yourcompany.com
-
- I added groups for Salesman.
+ I added groups for Crm Salesman.
-
!record {model: res.users, id: crm_res_users_salesman}:
groups_id:
diff --git a/addons/sale/test/create_sale_users.yml b/addons/sale/test/create_sale_users.yml
index ed575073f5d..e86fbc8ebfe 100644
--- a/addons/sale/test/create_sale_users.yml
+++ b/addons/sale/test/create_sale_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Salesmanager'
--
- !context
- default_groups_ref: ['base.group_sale_manager']
-
!record {model: res.users, id: res_users_salesmanager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: sm
email: salesmanager@yourcompany.com
-
- Create a user as 'Salesman'
+ I added groups for Salesmanager.
-
- !context
- default_groups_ref: ['base.group_sale_salesman_all_leads']
+ !record {model: res.users, id: res_users_salesmanager}:
+ groups_id:
+ - base.group_sale_manager
+-
+ Create a user as 'Salesman'
-
!record {model: res.users, id: res_users_salesman}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: su
password: su
email: salesman@yourcompany.com
+-
+ I added groups for Salesman.
+-
+ !record {model: res.users, id: res_users_salesman}:
+ groups_id:
+ - base.group_sale_salesman_all_leads
diff --git a/addons/sale_stock/test/sale_stock_users.yml b/addons/sale_stock/test/sale_stock_users.yml
index c1ebd54392c..594cbe42240 100644
--- a/addons/sale_stock/test/sale_stock_users.yml
+++ b/addons/sale_stock/test/sale_stock_users.yml
@@ -1,32 +1,35 @@
-
- Create a user as 'Salesmanager'
--
- !context
- default_groups_ref: ['base.group_sale_manager']
+ Create a user as 'Stock Salesmanager'
-
!record {model: res.users, id: res_sale_stock_salesmanager}:
company_id: base.main_company
- name: Sales manager
+ name: Stock Sales manager
login: ssm
password: ssm
email: ss_salesmanager@yourcompany.com
-
- Create a user as 'Salesman'
+ I added groups for Salesmanager.
-
- !context
- default_groups_ref: ['base.group_sale_salesman_all_leads']
+ !record {model: res.users, id: res_sale_stock_salesmanager}:
+ groups_id:
+ - base.group_sale_manager
+-
+ Create a user as 'Stock Salesman'
-
!record {model: res.users, id: res_sale_stock_salesman}:
company_id: base.main_company
- name: Salesman
+ name: Stock Salesman
login: ssu
password: ssu
email: ss_salesman@yourcompany.com
-
- Create a user as 'Stock User'
+ I added groups for Stock Salesman.
-
- !context
- default_groups_ref: ['stock.group_stock_user']
+ !record {model: res.users, id: res_sale_stock_salesman}:
+ groups_id:
+ - base.group_sale_salesman_all_leads
+-
+ Create a user as 'Stock User'
-
!record {model: res.users, id: res_stock_user}:
company_id: base.main_company
@@ -34,12 +37,14 @@
login: sau
password: sau
email: stock_user@yourcompany.com
-
+-
+ I added groups for Stock User.
+-
+ !record {model: res.users, id: res_stock_user}:
+ groups_id:
+ - stock.group_stock_user
-
Create a user as 'Stock Manager'
--
- !context
- default_groups_ref: ['stock.group_stock_manager']
-
!record {model: res.users, id: res_stock_manager}:
company_id: base.main_company
@@ -48,3 +53,9 @@
password: sam
email: admin@portal.example.com
email: stock_manager@yourcompany.com
+-
+ I added groups for Stock Manager.
+-
+ !record {model: res.users, id: res_stock_manager}:
+ groups_id:
+ - stock.group_stock_manager
From 76451bef7680e6d309b2c95722587f7c667e1f1a Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 14:19:01 +0530
Subject: [PATCH 073/256] [IMP]add groups in groups_id in purchase and
purchase_requisition
bzr revid: sgo@tinyerp.com-20130507084901-c4n3scfvt7vrs0gg
---
addons/purchase/test/ui/purchase_users.yml | 18 +++++++++++------
.../test/purchase_requisition_users.yml | 20 ++++++++++++-------
2 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/addons/purchase/test/ui/purchase_users.yml b/addons/purchase/test/ui/purchase_users.yml
index 9dd5e9c0195..bfff922bf83 100644
--- a/addons/purchase/test/ui/purchase_users.yml
+++ b/addons/purchase/test/ui/purchase_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Purchase manager'
--
- !context
- default_groups_ref: ['purchase.group_purchase_manager']
-
!record {model: res.users, id: res_users_purchase_manager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: pm
email: purchasemanager@yourcompany.com
-
- Create a user as 'Purchase user'
+ I added groups for Purchase manager.
-
- !context
- default_groups_ref: ['purchase.group_purchase_user']
+ !record {model: res.users, id: res_users_purchase_manager}:
+ groups_id:
+ - purchase.group_purchase_manager
+-
+ Create a user as 'Purchase user'
-
!record {model: res.users, id: res_users_purchase_user}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: pu
password: pu
email: purchaseuser@yourcompany.com
+-
+ I added groups for Purchase user.
+-
+ !record {model: res.users, id: res_users_purchase_user}:
+ groups_id:
+ - purchase.group_purchase_user
diff --git a/addons/purchase_requisition/test/purchase_requisition_users.yml b/addons/purchase_requisition/test/purchase_requisition_users.yml
index 41bc7cd318e..5b5e6d5e8cb 100644
--- a/addons/purchase_requisition/test/purchase_requisition_users.yml
+++ b/addons/purchase_requisition/test/purchase_requisition_users.yml
@@ -1,8 +1,5 @@
-
- Create a user as 'Purchase Reqisition Manager'
--
- !context
- default_groups_ref: ['purchase_requisition.group_purchase_requisition_manager']
+ Create a user as 'Purchase Requisition Manager'
-
!record {model: res.users, id: res_users_purchase_requisition_manager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: prm
email: requisition_manager@yourcompany.com
-
- Create a user as 'Purchase Reqisition User'
+ I added groups for Purchase Requisition Manager.
-
- !context
- default_groups_ref: ['purchase_requisition.group_purchase_requisition_user']
+ !record {model: res.users, id: res_users_purchase_requisition_manager}:
+ groups_id:
+ - purchase_requisition.group_purchase_requisition_manager
+-
+ Create a user as 'Purchase Requisition User'
-
!record {model: res.users, id: res_users_purchase_requisition_user}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: pru
password: pru
email: requisition_user@yourcompany.com
+-
+ I added groups for Purchase Requisition User.
+-
+ !record {model: res.users, id: res_users_purchase_requisition_user}:
+ groups_id:
+ - purchase_requisition.group_purchase_requisition_user
\ No newline at end of file
From df013f64025f72f0770abcf67d76518a2ae2c6b6 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 14:32:04 +0530
Subject: [PATCH 074/256] [IMP]add groups in groups_id for hr, hr_attandance
and recruitment
bzr revid: sgo@tinyerp.com-20130507090204-13gkyryyn3lxtz8z
---
addons/hr/test/hr_users.yml | 27 ++++++++++++-------
.../hr_attendance/test/attendance_process.yml | 9 ++++---
.../test/recruitment_process.yml | 13 +++++----
3 files changed, 32 insertions(+), 17 deletions(-)
diff --git a/addons/hr/test/hr_users.yml b/addons/hr/test/hr_users.yml
index 265482ca457..115924d5c1c 100644
--- a/addons/hr/test/hr_users.yml
+++ b/addons/hr/test/hr_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'HR Manager'
--
- !context
- default_groups_ref: [base.group_hr_manager]
-
!record {model: res.users, id: res_users_hr_manager}:
company_id: base.main_company
@@ -10,10 +7,13 @@
login: hrm
password: hrm
-
- Create a user as 'HR Officer'
+ I added groups for HR Manager.
-
- !context
- default_groups_ref: [base.group_hr_user]
+ !record {model: res.users, id: res_users_hr_manager}:
+ groups_id:
+ - base.group_hr_manager
+-
+ Create a user as 'HR Officer'
-
!record {model: res.users, id: res_users_hr_officer}:
company_id: base.main_company
@@ -21,13 +21,22 @@
login: hro
password: hro
-
- Create a user as 'Employee'
+ I added groups for HR Officer.
-
- !context
- default_groups_ref: [base.group_user]
+ !record {model: res.users, id: res_users_hr_officer}:
+ groups_id:
+ - base.group_hr_user
+-
+ Create a user as 'Employee'
-
!record {model: res.users, id: res_users_employee}:
company_id: base.main_company
name: Employee
login: emp
password: emp
+-
+ I added groups for Employee.
+-
+ !record {model: res.users, id: res_users_employee}:
+ groups_id:
+ - base.group_user
diff --git a/addons/hr_attendance/test/attendance_process.yml b/addons/hr_attendance/test/attendance_process.yml
index 383cf209bd4..814f0ee9b8a 100644
--- a/addons/hr_attendance/test/attendance_process.yml
+++ b/addons/hr_attendance/test/attendance_process.yml
@@ -1,14 +1,17 @@
-
Create a user as 'HR Attendance Officer'
--
- !context
- default_groups_ref: [base.group_hr_user]
-
!record {model: res.users, id: res_users_attendance_officer}:
company_id: base.main_company
name: HR Officer
login: ao
password: ao
+-
+ I added groups for HR Attendance Officer.
+-
+ !record {model: res.users, id: res_users_attendance_officer}:
+ groups_id:
+ - base.group_hr_user
-
Give the access rights of Hr Officer to test attendance process.
-
diff --git a/addons/hr_recruitment/test/recruitment_process.yml b/addons/hr_recruitment/test/recruitment_process.yml
index 2b7c534c270..8cf924b5d05 100644
--- a/addons/hr_recruitment/test/recruitment_process.yml
+++ b/addons/hr_recruitment/test/recruitment_process.yml
@@ -1,15 +1,18 @@
-
Create a user as 'HR Recruitment Officer'
-
- !context
- default_groups_ref: [base.group_hr_user]
--
- !record {model: res.users, id: res_users_hr_officer}:
+ !record {model: res.users, id: res_users_hr_recruitment_officer}:
company_id: base.main_company
- name: HR Officer
+ name: HR Recruitment Officer
login: hrro
password: hrro
email: hrofcr@yourcompany.com
+-
+ I added groups for HR Recruitment Officer.
+-
+ !record {model: res.users, id: res_users_hr_recruitment_officer}:
+ groups_id:
+ - base.group_hr_user
-
In Order to test process of Recruitment so giving HR officer's rights,
-
From a28d4d7cad0a55c4c4f1d97ca17035a2e01c8d07 Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Tue, 7 May 2013 14:39:44 +0530
Subject: [PATCH 075/256] [IMP] add group_id in project and project_issue for
assign groups
bzr revid: fka@tinyerp.com-20130507090944-m8oqqxx7wekyh09c
---
addons/project/test/project_users.yml | 18 ++++++++++++------
addons/project_issue/test/issue_users.yml | 18 ++++++++++++------
2 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/addons/project/test/project_users.yml b/addons/project/test/project_users.yml
index 8ae3c23dca1..f61d2042b6b 100644
--- a/addons/project/test/project_users.yml
+++ b/addons/project/test/project_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Project manager'
--
- !context
- default_groups_ref: ['project.group_project_manager']
-
!record {model: res.users, id: res_users_project_manager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: prm
email: projectmanager@yourcompany.com
-
- Create a user as 'Project user'
+ I added groups for Project manager.
-
- !context
- default_groups_ref: ['project.group_project_user']
+ !record {model: res.users, id: res_users_project_manager}:
+ groups_id:
+ - project.group_project_manager
+-
+ Create a user as 'Project user'
-
!record {model: res.users, id: res_users_project_user}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: pru
password: pru
email: projectuser@yourcompany.com
+-
+ I added groups for Project user.
+-
+ !record {model: res.users, id: res_users_project_user}:
+ groups_id:
+ - project.group_project_user
\ No newline at end of file
diff --git a/addons/project_issue/test/issue_users.yml b/addons/project_issue/test/issue_users.yml
index 402478b1c54..c40d142684a 100644
--- a/addons/project_issue/test/issue_users.yml
+++ b/addons/project_issue/test/issue_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Project manager'
--
- !context
- default_groups_ref: ['project.group_project_manager']
-
!record {model: res.users, id: res_users_project_issue_manager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: prim
email: issuemanager@yourcompany.com
-
- Create a user as 'Project user'
+ I added groups for Project manager.
-
- !context
- default_groups_ref: ['project.group_project_user']
+ !record {model: res.users, id: res_users_project_issue_manager}:
+ groups_id:
+ - project.group_project_manager
+-
+ Create a user as 'Project user'
-
!record {model: res.users, id: res_users_project_issue_user}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: priu
password: priu
email: issueuser@yourcompany.com
+-
+ I added groups for Project user.
+-
+ !record {model: res.users, id: res_users_project_issue_user}:
+ groups_id:
+ - project.group_project_user
\ No newline at end of file
From de7fdad10fe952cb26c4213e3fc0e3c2437dda77 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 14:40:56 +0530
Subject: [PATCH 076/256] [IMP]add groups in groups_id for hr timesheet
bzr revid: sgo@tinyerp.com-20130507091056-ume659zsu3q2fgig
---
.../hr_timesheet/test/hr_timesheet_users.yml | 34 +++++++++++++------
1 file changed, 23 insertions(+), 11 deletions(-)
diff --git a/addons/hr_timesheet/test/hr_timesheet_users.yml b/addons/hr_timesheet/test/hr_timesheet_users.yml
index 4a557bff44f..8b8b799e503 100644
--- a/addons/hr_timesheet/test/hr_timesheet_users.yml
+++ b/addons/hr_timesheet/test/hr_timesheet_users.yml
@@ -1,33 +1,45 @@
-
- Create a user as 'HR Manager'
--
- !context
- default_groups_ref: [base.group_hr_manager]
+ Create a user as 'HR timesheet Manager'
-
!record {model: res.users, id: res_hr_timesheet_manager}:
company_id: base.main_company
- name: HR manager
+ name: HR timesheet manager
login: hrtm
password: hrtm
-
- Create a user as 'HR Officer'
+ I added groups for HR timesheet Manager.
+-
+ !record {model: res.users, id: res_hr_timesheet_manager}:
+ groups_id:
+ - base.group_hr_manager
+-
+ Create a user as 'HR timesheet Officer'
-
!context
default_groups_ref: [base.group_hr_user]
-
!record {model: res.users, id: res_hr_timesheet_officer}:
company_id: base.main_company
- name: HR Officer
+ name: HR timesheet Officer
login: hrto
password: hrto
-
- Create a user as 'Employee'
+ I added groups for HR timesheet Officer.
-
- !context
- default_groups_ref: [base.group_user]
+ !record {model: res.users, id: res_hr_timesheet_officer}:
+ groups_id:
+ - base.group_hr_user
+-
+ Create a user as 'Timesheet Employee'
-
!record {model: res.users, id: res_hr_timesheet_employee}:
company_id: base.main_company
- name: Employee
+ name: Timesheet Employee
login: empt
password: empt
+-
+ I added groups for Timesheet Employee.
+-
+ !record {model: res.users, id: res_hr_timesheet_employee}:
+ groups_id:
+ - base.group_user
From f0ecb9c56a6b4eb850fc67256c5b54ba9a263ee2 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 14:43:12 +0530
Subject: [PATCH 077/256] [IMP]add groups in groups_id for stock
bzr revid: sgo@tinyerp.com-20130507091312-gnrhyr0osq56ddb1
---
addons/stock/test/stock_users.yml | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/addons/stock/test/stock_users.yml b/addons/stock/test/stock_users.yml
index b8fe0783ca1..d0c09dc745d 100644
--- a/addons/stock/test/stock_users.yml
+++ b/addons/stock/test/stock_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'Stock Manager'
--
- !context
- default_groups_ref: ['stock.group_stock_manager']
-
!record {model: res.users, id: res_users_stock_manager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: sam
email: stockmanager@yourcompany.com
-
- Create a user as 'Stock User'
+ I added groups for Stock Manager.
-
- !context
- default_groups_ref: ['stock.group_stock_user']
+ !record {model: res.users, id: res_users_stock_manager}:
+ groups_id:
+ - stock.group_stock_manager
+-
+ Create a user as 'Stock User'
-
!record {model: res.users, id: res_users_stock_user}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: sau
password: sau
email: stockuser@yourcompany.com
+-
+ I added groups for Stock User.
+-
+ !record {model: res.users, id: res_users_stock_user}:
+ groups_id:
+ - stock.group_stock_user
From 693e52e928d76c13d3c814e2b91615fcffbc593c Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 15:04:13 +0530
Subject: [PATCH 078/256] [IMP]improve code
bzr revid: sgo@tinyerp.com-20130507093413-42qiuvslavp0w4n1
---
addons/hr_recruitment/test/recruitment_process.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/hr_recruitment/test/recruitment_process.yml b/addons/hr_recruitment/test/recruitment_process.yml
index 8cf924b5d05..1fa59328ef4 100644
--- a/addons/hr_recruitment/test/recruitment_process.yml
+++ b/addons/hr_recruitment/test/recruitment_process.yml
@@ -17,7 +17,7 @@
In Order to test process of Recruitment so giving HR officer's rights,
-
!context
- uid: 'res_users_hr_officer'
+ uid: 'res_users_hr_recruitment_officer'
-
An applicant is interested in the job position. So he sends a resume by email.
-
From 2126f36ad127802dba5d46f4b04431e3eb7a8a9f Mon Sep 17 00:00:00 2001
From: "Foram Katharotiya (OpenERP)"
Date: Tue, 7 May 2013 15:08:48 +0530
Subject: [PATCH 079/256] [IMP] add group_id in MRP,mrp_repair and
mrp_operations for assign groups
bzr revid: fka@tinyerp.com-20130507093848-lrpqfao6kumlgepy
---
addons/mrp/test/mrp_users.yml | 18 ++++++++++++------
.../test/workcenter_operations.yml | 9 ++++++---
addons/mrp_repair/test/mrp_repair_users.yml | 18 ++++++++++++------
3 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/addons/mrp/test/mrp_users.yml b/addons/mrp/test/mrp_users.yml
index 965a809eddc..b01f4f975bd 100644
--- a/addons/mrp/test/mrp_users.yml
+++ b/addons/mrp/test/mrp_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'MRP Manager'
--
- !context
- default_groups_ref: [mrp.group_mrp_manager]
-
!record {model: res.users, id: res_users_mrp_manager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: mam
email: mrp_manager@yourcompany.com
-
- Create a user as 'MRP User'
+ I added groups for MRP Manager.
-
- !context
- default_groups_ref: [mrp.group_mrp_user]
+ !record {model: res.users, id: res_users_mrp_manager}:
+ groups_id:
+ - mrp.group_mrp_manager
+-
+ Create a user as 'MRP User'
-
!record {model: res.users, id: res_users_mrp_user}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: mau
password: mau
email: mrp_user@yourcompany.com
+-
+ I added groups for MRP User.
+-
+ !record {model: res.users, id: res_users_mrp_user}:
+ groups_id:
+ - mrp.group_mrp_user
\ No newline at end of file
diff --git a/addons/mrp_operations/test/workcenter_operations.yml b/addons/mrp_operations/test/workcenter_operations.yml
index 3fc5b332d18..588ad639659 100644
--- a/addons/mrp_operations/test/workcenter_operations.yml
+++ b/addons/mrp_operations/test/workcenter_operations.yml
@@ -1,8 +1,5 @@
-
Create a user as 'MRP User'
--
- !context
- default_groups_ref: [mrp.group_mrp_user]
-
!record {model: res.users, id: res_mrp_operation_user}:
company_id: base.main_company
@@ -10,6 +7,12 @@
login: maou
password: maou
email: mrp_operation_user@yourcompany.com
+-
+ I added groups for MRP User.
+-
+ !record {model: res.users, id: res_mrp_operation_user}:
+ groups_id:
+ - mrp.group_mrp_user
-
In order to test mrp_operations with OpenERP, I refer created production order of PC Assemble SC349
with routing - Manual Component's Assembly to test complete production process with respect of workcenter with giving access rights of MRP User.
diff --git a/addons/mrp_repair/test/mrp_repair_users.yml b/addons/mrp_repair/test/mrp_repair_users.yml
index f7ee95314e2..d5762e69b27 100644
--- a/addons/mrp_repair/test/mrp_repair_users.yml
+++ b/addons/mrp_repair/test/mrp_repair_users.yml
@@ -1,8 +1,5 @@
-
Create a user as 'MRP Repair Manager'
--
- !context
- default_groups_ref: [mrp.group_mrp_manager]
-
!record {model: res.users, id: res_mrp_repair_manager}:
company_id: base.main_company
@@ -11,10 +8,13 @@
password: marm
email: mrp_repair_manager@yourcompany.com
-
- Create a user as 'MRP Repair User'
+ I added groups for MRP Repair Manager.
-
- !context
- default_groups_ref: [mrp.group_mrp_user]
+ !record {model: res.users, id: res_mrp_repair_manager}:
+ groups_id:
+ - mrp.group_mrp_manager
+-
+ Create a user as 'MRP Repair User'
-
!record {model: res.users, id: res_mrp_repair_user}:
company_id: base.main_company
@@ -22,3 +22,9 @@
login: maru
password: maru
email: mrp_repair_user@yourcompany.com
+-
+ I added groups for MRP Repair User.
+-
+ !record {model: res.users, id: res_mrp_repair_user}:
+ groups_id:
+ - mrp.group_mrp_user
\ No newline at end of file
From 5d267f4b41654b98afabf2551574b46768f8d553 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 15:51:00 +0530
Subject: [PATCH 080/256] [IMP]improve code remove unused code
bzr revid: sgo@tinyerp.com-20130507102100-7x01f5hglnw37jwb
---
addons/hr_timesheet/test/hr_timesheet_users.yml | 3 ---
1 file changed, 3 deletions(-)
diff --git a/addons/hr_timesheet/test/hr_timesheet_users.yml b/addons/hr_timesheet/test/hr_timesheet_users.yml
index 8b8b799e503..cb59f77df69 100644
--- a/addons/hr_timesheet/test/hr_timesheet_users.yml
+++ b/addons/hr_timesheet/test/hr_timesheet_users.yml
@@ -14,9 +14,6 @@
- base.group_hr_manager
-
Create a user as 'HR timesheet Officer'
--
- !context
- default_groups_ref: [base.group_hr_user]
-
!record {model: res.users, id: res_hr_timesheet_officer}:
company_id: base.main_company
From c9271122b24b96198ff62485809d08f0f005cbfc Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 16:46:57 +0530
Subject: [PATCH 081/256] [IMP]remove access rights of account user and improve
code
bzr revid: sgo@tinyerp.com-20130507111657-gg47ca2206q3knd7
---
addons/account/security/ir.model.access.csv | 3 ---
addons/account/test/account_customer_invoice.yml | 15 +++++++++------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv
index d6ba955aabc..d1f0bbab6b5 100644
--- a/addons/account/security/ir.model.access.csv
+++ b/addons/account/security/ir.model.access.csv
@@ -37,7 +37,6 @@ access_account_payment_term_manager,account.payment.term,model_account_payment_t
access_account_payment_term_line_manager,account.payment.term.line,model_account_payment_term_line,account.group_account_manager,1,1,1,1
access_account_tax_manager,account.tax,model_account_tax,account.group_account_manager,1,1,1,1
access_account_journal_manager,account.journal,model_account_journal,account.group_account_manager,1,1,1,1
-access_account_journal_user,account.journal,model_account_journal,account.group_account_user,1,1,1,0
access_account_journal_invoice,account.journal invoice,model_account_journal,account.group_account_invoice,1,0,0,0
access_account_period_manager,account.period,model_account_period,account.group_account_manager,1,1,1,1
access_account_period_invoice,account.period invoice,model_account_period,account.group_account_invoice,1,0,0,0
@@ -72,7 +71,6 @@ access_report_account_type_sales,report.account_type.sales,model_report_account_
access_report_account_sales,report.account.sales,model_report_account_sales,account.group_account_manager,1,1,1,1
access_account_invoice_report,account.invoice.report,model_account_invoice_report,account.group_account_manager,1,1,1,1
access_res_partner_group_account_manager,res_partner group_account_manager,model_res_partner,account.group_account_manager,1,0,0,0
-access_res_partner_group_account_user,res_partner group_account_user,model_res_partner,account.group_account_user,1,1,1,0
access_account_invoice_accountant,account.invoice accountant,model_account_invoice,account.group_account_user,1,0,0,0
access_account_tax_code_accountant,account.tax.code accountant,model_account_tax_code,account.group_account_user,1,1,1,1
access_account_move_line_manager,account.move.line manager,model_account_move_line,account.group_account_manager,1,0,0,0
@@ -100,4 +98,3 @@ access_account_sequence_fiscal_year_sale_manager,account.sequence.fiscalyear.sal
access_account_treasury_report_manager,account.treasury.report.manager,model_account_treasury_report,account.group_account_manager,1,0,0,0
access_account_financial_report,account.financial.report,model_account_financial_report,account.group_account_user,1,1,1,1
access_account_financial_report_invoice,account.financial.report invoice,model_account_financial_report,account.group_account_invoice,1,0,0,0
-access_res_partner_bank,res.partner.bank,model_res_partner_bank,account.group_account_user,1,1,1,0
diff --git a/addons/account/test/account_customer_invoice.yml b/addons/account/test/account_customer_invoice.yml
index a41ca9bbfe4..d0b445fcff5 100644
--- a/addons/account/test/account_customer_invoice.yml
+++ b/addons/account/test/account_customer_invoice.yml
@@ -1,12 +1,10 @@
--
- Test with that user which have rights to make Invoicing and payment and who is accountant.
--
- !context
- uid: 'res_users_account_user'
-
In order to test account invoice I create a new customer invoice
-
- I will create bank detail
+ I will create bank detail with using manager access rights because account manager can only create bank details.
+-
+ !context
+ uid: 'res_users_account_manager'
-
!record {model: res.partner.bank, id: res_partner_bank_0}:
state: bank
@@ -16,6 +14,11 @@
footer: True
bank: base.res_bank_1
bank_name: Reserve
+-
+ Test with that user which have rights to make Invoicing and payment and who is accountant.
+-
+ !context
+ uid: 'res_users_account_user'
-
I create a customer invoice
-
From c9688c9d5d503df98e58f15632a075247837f2cf Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 17:04:19 +0530
Subject: [PATCH 082/256] [IMP] improve code
bzr revid: sgo@tinyerp.com-20130507113419-hvk6cc9u3fxbpjl5
---
addons/account/test/account_test_users.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/addons/account/test/account_test_users.yml b/addons/account/test/account_test_users.yml
index b257feedff8..143e8bb5473 100644
--- a/addons/account/test/account_test_users.yml
+++ b/addons/account/test/account_test_users.yml
@@ -28,4 +28,5 @@
-
!record {model: res.users, id: res_users_account_manager}:
groups_id:
- - account.group_account_manager
\ No newline at end of file
+ - account.group_account_manager
+ - base.group_partner_manager
\ No newline at end of file
From bdc2be1aa4a6d348e30acab83ed975e18f494cf3 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Tue, 7 May 2013 18:11:54 +0530
Subject: [PATCH 083/256] [IMP]remove access rights of crm user and improve
code
bzr revid: sgo@tinyerp.com-20130507124154-4mrcpbocuafv4ny7
---
addons/crm/security/ir.model.access.csv | 1 -
addons/crm/test/crm_access_group_users.yml | 1 +
addons/crm/test/crm_lead_message.yml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 1589b50c016..b09c201df2b 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -35,6 +35,5 @@ access_crm_lead_partner_manager,crm.lead.partner.manager,model_crm_lead,base.gro
access_crm_phonecall_partner_manager,crm.phonecall.partner.manager,model_crm_phonecall,base.group_partner_manager,1,1,1,1
access_crm_payment_mode_user,crm.payment.mode,model_crm_payment_mode,base.group_sale_salesman,1,0,0,0
access_crm_payment_mode,crm.payment.mode,model_crm_payment_mode,base.group_sale_manager,1,1,1,1
-access_ir_property_salesman,ir_property_salesman,base.model_ir_property,base.group_sale_salesman,1,1,1,0
access_base_partner_merge_line_manager,base_partner_merge_line.manager,model_base_partner_merge_line,base.group_system,1,1,1,1
access_base_partner_merge_manager,base_partner_merge.manager,model_base_partner_merge_automatic_wizard,base.group_system,1,1,1,1
diff --git a/addons/crm/test/crm_access_group_users.yml b/addons/crm/test/crm_access_group_users.yml
index c9080ee530e..51cf52c0abc 100644
--- a/addons/crm/test/crm_access_group_users.yml
+++ b/addons/crm/test/crm_access_group_users.yml
@@ -28,3 +28,4 @@
!record {model: res.users, id: crm_res_users_salesman}:
groups_id:
- base.group_sale_salesman_all_leads
+ - base.group_partner_manager
diff --git a/addons/crm/test/crm_lead_message.yml b/addons/crm/test/crm_lead_message.yml
index f2463bab255..fcfe658a8f5 100644
--- a/addons/crm/test/crm_lead_message.yml
+++ b/addons/crm/test/crm_lead_message.yml
@@ -1,5 +1,5 @@
-
- Give the access rights of Salesman to communicat with customer.
+ Give the access rights of Salesman to communicate with customer.
-
!context
uid: 'crm_res_users_salesman'
From c697009dea09da447564095d4b0a7df0b6c6bfdc Mon Sep 17 00:00:00 2001
From: Martin Trigaux
Date: Tue, 14 May 2013 15:26:29 +0200
Subject: [PATCH 084/256] [IMP]mrp: avoid adding account rights to users and
create lines with superuser_id
bzr revid: mat@openerp.com-20130514132629-91qrc32j04a1t5he
---
addons/mrp/mrp.py | 9 ++++++---
addons/mrp/security/ir.model.access.csv | 1 -
addons/mrp/test/mrp_users.yml | 1 +
addons/mrp/test/order_process.yml | 8 +++++++-
4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py
index 7fb40ce12af..f3b89a86d65 100644
--- a/addons/mrp/mrp.py
+++ b/addons/mrp/mrp.py
@@ -27,7 +27,7 @@ from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
from openerp.tools import float_compare
from openerp.tools.translate import _
-from openerp import tools
+from openerp import tools, SUPERUSER_ID
#----------------------------------------------------------
# Work Centers
@@ -803,7 +803,10 @@ class mrp_production(osv.osv):
account = wc.costs_hour_account_id.id
if value and account:
amount += value
- analytic_line_obj.create(cr, uid, {
+ # we user SUPERUSER_ID as we do not garantee an mrp user
+ # has access to account analytic lines but still should be
+ # able to produce orders
+ analytic_line_obj.create(cr, SUPERUSER_ID, {
'name': wc_line.name + ' (H)',
'amount': value,
'account_id': account,
@@ -819,7 +822,7 @@ class mrp_production(osv.osv):
account = wc.costs_cycle_account_id.id
if value and account:
amount += value
- analytic_line_obj.create(cr, uid, {
+ analytic_line_obj.create(cr, SUPERUSER_ID, {
'name': wc_line.name+' (C)',
'amount': value,
'account_id': account,
diff --git a/addons/mrp/security/ir.model.access.csv b/addons/mrp/security/ir.model.access.csv
index 2d0a478c33d..7a94b1a660c 100644
--- a/addons/mrp/security/ir.model.access.csv
+++ b/addons/mrp/security/ir.model.access.csv
@@ -1,5 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_account_analytic_line_user,account.analytic.line,account.model_account_analytic_line,group_mrp_user,1,1,1,0
access_mrp_workcenter,mrp.workcenter,model_mrp_workcenter,mrp.group_mrp_user,1,0,0,0
access_mrp_routing,mrp.routing,model_mrp_routing,mrp.group_mrp_user,1,0,0,0
access_mrp_routing_workcenter,mrp.routing.workcenter,model_mrp_routing_workcenter,mrp.group_mrp_user,1,0,0,0
diff --git a/addons/mrp/test/mrp_users.yml b/addons/mrp/test/mrp_users.yml
index b01f4f975bd..05c4889cf28 100644
--- a/addons/mrp/test/mrp_users.yml
+++ b/addons/mrp/test/mrp_users.yml
@@ -13,6 +13,7 @@
!record {model: res.users, id: res_users_mrp_manager}:
groups_id:
- mrp.group_mrp_manager
+ - account.group_account_user
-
Create a user as 'MRP User'
-
diff --git a/addons/mrp/test/order_process.yml b/addons/mrp/test/order_process.yml
index 4d45b1d0968..ba4e1b79d66 100644
--- a/addons/mrp/test/order_process.yml
+++ b/addons/mrp/test/order_process.yml
@@ -183,7 +183,10 @@
order = self.browse(cr, uid, ref("mrp_production_test1"))
assert order.state == 'done', "Production order should be closed."
-
- I check Total Costs at End of Production.
+ I check Total Costs at End of Production as a manager.
+-
+ !context
+ uid: 'res_users_mrp_manager'
-
!python {model: mrp.production}: |
order = self.browse(cr, uid, ref("mrp_production_test1"))
@@ -217,6 +220,9 @@
assert line.product_uom_id.id == wc.product_id.uom_id.id, "UOM is not correspond."
-
I print a "BOM Structure".
+-
+ !context
+ uid: 'res_users_mrp_user'
-
!python {model: mrp.production}: |
import os
From 143b485baf8adb7936d7784e314ccd405d1b8c14 Mon Sep 17 00:00:00 2001
From: Martin Trigaux
Date: Tue, 14 May 2013 15:42:27 +0200
Subject: [PATCH 085/256] [IMP]resource: avoid better permissions on
calendar_leaves
bzr revid: mat@openerp.com-20130514134227-xnvyzjikwph8bp5k
---
addons/project/security/ir.model.access.csv | 1 -
addons/resource/__openerp__.py | 1 +
addons/resource/security/ir.model.access.csv | 1 +
addons/resource/security/resource_security.xml | 16 ++++++++++++++++
4 files changed, 18 insertions(+), 1 deletion(-)
create mode 100644 addons/resource/security/resource_security.xml
diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv
index 1428f445e43..a76faa0fcce 100644
--- a/addons/project/security/ir.model.access.csv
+++ b/addons/project/security/ir.model.access.csv
@@ -19,7 +19,6 @@ access_project_task_history,project.task.history project,project.model_project_t
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0
access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0
-access_resource_calendar_leaves,project.resource_calendar_leaves manager,resource.model_resource_calendar_leaves,project.group_project_manager,1,0,0,0
access_project_category,project.project_category,model_project_category,,1,0,0,0
access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1
diff --git a/addons/resource/__openerp__.py b/addons/resource/__openerp__.py
index b8a9f57c910..806e02e8c2b 100644
--- a/addons/resource/__openerp__.py
+++ b/addons/resource/__openerp__.py
@@ -38,6 +38,7 @@ associated to every resource. It also manages the leaves of every resource.
'depends': ['process'],
'data': [
'security/ir.model.access.csv',
+ 'security/resource_security.xml',
'resource_view.xml',
],
'demo': ['resource_demo.xml'],
diff --git a/addons/resource/security/ir.model.access.csv b/addons/resource/security/ir.model.access.csv
index 533ad419274..058626831c6 100644
--- a/addons/resource/security/ir.model.access.csv
+++ b/addons/resource/security/ir.model.access.csv
@@ -3,4 +3,5 @@ access_resource_calendar,resource.calendar,model_resource_calendar,base.group_sy
access_resource_calendar_attendance,resource.calendar.attendance,model_resource_calendar_attendance,base.group_system,1,1,1,1
access_resource_resource,resource.resource,model_resource_resource,base.group_system,1,0,0,0
access_resource_resource_all,resource.resource all,model_resource_resource,,1,0,0,0
+access_resource_calendar_leaves_all,resource.calendar.leaves,model_resource_calendar_leaves,,1,0,0,0
access_resource_calendar_leaves,resource.calendar.leaves,model_resource_calendar_leaves,base.group_system,1,1,1,1
diff --git a/addons/resource/security/resource_security.xml b/addons/resource/security/resource_security.xml
new file mode 100644
index 00000000000..77d9a11751a
--- /dev/null
+++ b/addons/resource/security/resource_security.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Resource: see own leaves
+
+
+ ['|',
+ ('resource_id', '=', False),
+ ('resource_id.user_id', '=', user.id),
+ ]
+
+
+
+
From 489196ee763b2e242aa89269504e6d1ce8447bf7 Mon Sep 17 00:00:00 2001
From: "Chirag Dodiya (OpenERP Trainee)"
Date: Tue, 11 Jun 2013 17:43:10 +0530
Subject: [PATCH 086/256] Added selection field into company, set font
dynamically to all para style in header
bzr revid: chiragdd7@gmail.com-20130611121310-35ogs0ler806hkov
---
openerp/addons/base/res/res_company.py | 43 +++++++++++++++++++-
openerp/addons/base/res/res_company_view.xml | 3 ++
openerp/report/render/rml2pdf/customfonts.py | 1 +
openerp/report/render/rml2pdf/trml2pdf.py | 15 ++++++-
4 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py
index 02dfefe1e38..6705a893910 100644
--- a/openerp/addons/base/res/res_company.py
+++ b/openerp/addons/base/res/res_company.py
@@ -20,7 +20,7 @@
##############################################################################
import os
-
+import re
import openerp
from openerp import SUPERUSER_ID, tools
from openerp.osv import fields, osv
@@ -28,6 +28,25 @@ from openerp.tools.translate import _
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools import image_resize_image
+_select_font=[ ('DejaVu Sans',"DejaVu Sans"),
+ ('DejaVu Sans Bold',"DejaVu Sans Bold"),
+ ('DejaVu Sans Oblique',"DejaVu Sans Oblique"),
+ ('DejaVu Sans BoldOblique',"DejaVu Sans BoldOblique"),
+ ('Liberation Serif',"Liberation Serif"),
+ ('Liberation Serif Bold',"Liberation Serif Bold"),
+ ('Liberation Serif Italic',"Liberation Serif Italic"),
+ ('Liberation Serif BoldItalic',"Liberation Serif BoldItalic"),
+ ('Liberation Serif',"Liberation Serif"),
+ ('Liberation Serif Bold',"Liberation Serif Bold"),
+ ('Liberation Serif Italic',"Liberation Serif Italic"),
+ ('Liberation Serif BoldItalic',"Liberation Serif BoldItalic"),
+ ('FreeMono',"FreeMono"),
+ ('FreeMono Bold',"FreeMono Bold"),
+ ('FreeMono Oblique',"FreeMono Oblique"),
+ ('FreeMono BoldOblique',"FreeMono BoldOblique"),
+ ('Sun-ExtA',"Sun-ExtA")
+]
+
class multi_company_default(osv.osv):
"""
Manage multi company default value
@@ -108,7 +127,7 @@ class res_company(osv.osv):
size = (180, None)
result[record.id] = image_resize_image(record.partner_id.image, size)
return result
-
+
def _get_companies_from_partner(self, cr, uid, ids, context=None):
return self.pool['res.company'].search(cr, uid, [('partner_id', 'in', ids)], context=context)
@@ -147,6 +166,7 @@ class res_company(osv.osv):
'vat': fields.related('partner_id', 'vat', string="Tax ID", type="char", size=32),
'company_registry': fields.char('Company Registry', size=64),
'paper_format': fields.selection([('a4', 'A4'), ('us_letter', 'US Letter')], "Paper Format", required=True),
+ 'font': fields.selection(_select_font, "Select Font"),
}
_sql_constraints = [
('name_uniq', 'unique (name)', 'The company name must be unique !')
@@ -178,6 +198,25 @@ class res_company(osv.osv):
if state_id:
return {'value':{'country_id': self.pool.get('res.country.state').browse(cr, uid, state_id, context).country_id.id }}
return {}
+
+ def onchange_font_name(self, cr, uid, ids, font, context=None):
+ """
+ To change default header style of all and drawstring.
+ """
+ def _change_header(header,font):
+ """
+ Replace default fontname use in header and setfont tag
+ """
+ default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"'% font,header)
+ return re.sub('("%s"\g<3>'% font,default_para)
+ if not ids: return {}
+ data = self.browse(cr, uid, ids[0], context=context)
+ return {'value':{
+ 'rml_header': _change_header(data.rml_header,font),
+ 'rml_header2':_change_header(data.rml_header2,font),
+ 'rml_header3':_change_header(data.rml_header3,font)
+ }}
+
def on_change_country(self, cr, uid, ids, country_id, context=None):
res = {'domain': {'state_id': []}}
currency_id = self._get_euro(cr, uid, context=context)
diff --git a/openerp/addons/base/res/res_company_view.xml b/openerp/addons/base/res/res_company_view.xml
index b261f80822b..57b487cace1 100644
--- a/openerp/addons/base/res/res_company_view.xml
+++ b/openerp/addons/base/res/res_company_view.xml
@@ -83,6 +83,9 @@
+
+
+
diff --git a/openerp/report/render/rml2pdf/customfonts.py b/openerp/report/render/rml2pdf/customfonts.py
index 85874f2b82f..cb5b3d69e0b 100644
--- a/openerp/report/render/rml2pdf/customfonts.py
+++ b/openerp/report/render/rml2pdf/customfonts.py
@@ -28,6 +28,7 @@ from reportlab import rl_config
from openerp.tools import config
+#.apidoc title: TTF Font Table
"""This module allows the mapping of some system-available TTF fonts to
the reportlab engine.
diff --git a/openerp/report/render/rml2pdf/trml2pdf.py b/openerp/report/render/rml2pdf/trml2pdf.py
index 243cc02a0b3..2052b128ad9 100644
--- a/openerp/report/render/rml2pdf/trml2pdf.py
+++ b/openerp/report/render/rml2pdf/trml2pdf.py
@@ -160,7 +160,7 @@ class _rml_styles(object,):
for style in node.findall('paraStyle'):
sname = style.get('name')
self.styles[sname] = self._para_style_update(style)
- if sname in self.default_style:
+ if self.default_style.has_key(sname):
for key, value in self.styles[sname].items():
setattr(self.default_style[sname], key, value)
else:
@@ -277,11 +277,24 @@ class _rml_doc(object):
fname = font.get('fontFile').encode('ascii')
if name not in pdfmetrics._fonts:
pdfmetrics.registerFont(TTFont(name, fname))
+ #by default, we map the fontName to each style (bold, italic, bold and italic), so that
+ #if there isn't any font defined for one of these style (via a font family), the system
+ #will fallback on the normal font.
addMapping(name, 0, 0, name) #normal
addMapping(name, 0, 1, name) #italic
addMapping(name, 1, 0, name) #bold
addMapping(name, 1, 1, name) #italic and bold
+ #if registerFontFamily is defined, we register the mapping of the fontName to use for each style.
+ for font_family in node.findall('registerFontFamily'):
+ family_name = font_family.get('normal').encode('ascii')
+ if font_family.get('italic'):
+ addMapping(family_name, 0, 1, font_family.get('italic').encode('ascii'))
+ if font_family.get('bold'):
+ addMapping(family_name, 1, 0, font_family.get('bold').encode('ascii'))
+ if font_family.get('boldItalic'):
+ addMapping(family_name, 1, 1, font_family.get('boldItalic').encode('ascii'))
+
def setTTFontMapping(self,face, fontname, filename, mode='all'):
from reportlab.lib.fonts import addMapping
from reportlab.pdfbase import pdfmetrics
From 3398bd14021b7fbbff5b92b4cc93b80081817783 Mon Sep 17 00:00:00 2001
From: "Chirag Dodiya (OpenERP Trainee)"
Date: Wed, 12 Jun 2013 12:37:28 +0530
Subject: [PATCH 087/256] [IMP]Changed In Parastyle
bzr revid: chiragdd7@gmail.com-20130612070728-yk4t52kpczdtt7bq
---
openerp/addons/base/res/res_company.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py
index 6705a893910..7611af4862c 100644
--- a/openerp/addons/base/res/res_company.py
+++ b/openerp/addons/base/res/res_company.py
@@ -357,7 +357,7 @@ class res_company(osv.osv):
-
+
From 72604e8b121ec715791eefbf8ad0fdc6f3064b3a Mon Sep 17 00:00:00 2001
From: "Chirag Dodiya (OpenERP Trainee)"
Date: Wed, 12 Jun 2013 19:02:27 +0530
Subject: [PATCH 088/256] [IMP] Improved Code as per Review suggestion
bzr revid: chiragdd7@gmail.com-20130612133227-xjf0d22bnwk4eota
---
openerp/addons/base/res/res_company.py | 25 ++++++++++----------
openerp/addons/base/res/res_company_view.xml | 2 +-
2 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py
index 7611af4862c..be5225a9282 100644
--- a/openerp/addons/base/res/res_company.py
+++ b/openerp/addons/base/res/res_company.py
@@ -199,22 +199,20 @@ class res_company(osv.osv):
return {'value':{'country_id': self.pool.get('res.country.state').browse(cr, uid, state_id, context).country_id.id }}
return {}
- def onchange_font_name(self, cr, uid, ids, font, context=None):
- """
- To change default header style of all and drawstring.
- """
+ def onchange_font_name(self, cr, uid, ids, font, rml_header, rml_header2, rml_header3, context=None):
+
+ """ To change default header style of all and drawstring. """
+
def _change_header(header,font):
- """
- Replace default fontname use in header and setfont tag
- """
+
+ """ Replace default fontname use in header and setfont tag """
+
default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"'% font,header)
return re.sub('("%s"\g<3>'% font,default_para)
- if not ids: return {}
- data = self.browse(cr, uid, ids[0], context=context)
return {'value':{
- 'rml_header': _change_header(data.rml_header,font),
- 'rml_header2':_change_header(data.rml_header2,font),
- 'rml_header3':_change_header(data.rml_header3,font)
+ 'rml_header': _change_header(rml_header,font),
+ 'rml_header2':_change_header(rml_header2,font),
+ 'rml_header3':_change_header(rml_header3,font)
}}
def on_change_country(self, cr, uid, ids, country_id, context=None):
@@ -413,7 +411,8 @@ class res_company(osv.osv):
'rml_header':_get_header,
'rml_header2': _header2,
'rml_header3': _header3,
- 'logo':_get_logo
+ 'logo':_get_logo,
+ 'font':'DejaVu Sans'
}
_constraints = [
diff --git a/openerp/addons/base/res/res_company_view.xml b/openerp/addons/base/res/res_company_view.xml
index 57b487cace1..bf117e2c8c1 100644
--- a/openerp/addons/base/res/res_company_view.xml
+++ b/openerp/addons/base/res/res_company_view.xml
@@ -84,7 +84,7 @@
-
+
From ce8fb2e1b73d2672927936f2e1eb69217bc1f61d Mon Sep 17 00:00:00 2001
From: "Chirag Dodiya (OpenERP Trainee)"
Date: Tue, 18 Jun 2013 15:06:55 +0530
Subject: [PATCH 089/256] [IMP] Puted help as per review suggestion
bzr revid: chiragdd7@gmail.com-20130618093655-tnwc1jkbnz5hi1b1
---
openerp/addons/base/res/res_company.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py
index 233e9727c4d..9780f13cf55 100644
--- a/openerp/addons/base/res/res_company.py
+++ b/openerp/addons/base/res/res_company.py
@@ -166,7 +166,7 @@ class res_company(osv.osv):
'vat': fields.related('partner_id', 'vat', string="Tax ID", type="char", size=32),
'company_registry': fields.char('Company Registry', size=64),
'paper_format': fields.selection([('a4', 'A4'), ('us_letter', 'US Letter')], "Paper Format", required=True),
- 'font': fields.selection(_select_font, "Select Font"),
+ 'font': fields.selection(_select_font, "Select Font",help="Set your favorite font into company header"),
}
_sql_constraints = [
('name_uniq', 'unique (name)', 'The company name must be unique !')
@@ -200,11 +200,9 @@ class res_company(osv.osv):
return {}
def onchange_font_name(self, cr, uid, ids, font, rml_header, rml_header2, rml_header3, context=None):
-
""" To change default header style of all and drawstring. """
def _change_header(header,font):
-
""" Replace default fontname use in header and setfont tag """
default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"'% font,header)
From ff73b066ed6adee1cd00b70241a17394d55f7436 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Wed, 26 Jun 2013 17:29:47 +0530
Subject: [PATCH 090/256] [IMP]only manager can cancel lead so give access
rights of manager for test
bzr revid: sgo@tinyerp.com-20130626115947-idn10u0vtvkrvtb8
---
addons/crm/test/crm_lead_cancel.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/crm/test/crm_lead_cancel.yml b/addons/crm/test/crm_lead_cancel.yml
index 13c3c18ed26..4d9b2e82925 100644
--- a/addons/crm/test/crm_lead_cancel.yml
+++ b/addons/crm/test/crm_lead_cancel.yml
@@ -2,7 +2,7 @@
I set a new sale team (with Marketing at parent) and I cancel unqualified lead giving access rights of salesman.
-
!context
- uid: 'crm_res_users_salesman'
+ uid: 'crm_res_users_salesmanager'
-
!python {model: crm.lead}: |
section_id = self.pool.get('crm.case.section').create(cr, uid, {'name': "Phone Marketing", 'parent_id': ref("crm.crm_case_section_2")})
From 7ec16a69c4e13b9b3223bfc752d699addf6f0b9c Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 1 Jul 2013 16:12:29 +0530
Subject: [PATCH 091/256] [IMP]only manager can create issue
bzr revid: sgo@tinyerp.com-20130701104229-2xq8pyxkbymhdtfh
---
addons/project_issue/test/issue_demo.yml | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/addons/project_issue/test/issue_demo.yml b/addons/project_issue/test/issue_demo.yml
index b7342b435ab..f84c716baeb 100644
--- a/addons/project_issue/test/issue_demo.yml
+++ b/addons/project_issue/test/issue_demo.yml
@@ -1,9 +1,14 @@
-
- Test the whole create project issue with project user.
+ Test the whole create project issue with project manager.
-
!context
- uid: 'res_users_project_issue_user'
+ uid: 'res_users_project_issue_manager'
+-
+ !record {model: project.issue, id: project_task_1, view: False}:
+ task_id: 'project.project_task_17'
+ name: 'Error in account module'
-
!record {model: project.issue, id: project01, view: False}:
project_id: 'project.project_project_2'
name: 'OpenERP Integration'
+
From 9349ba848497b9c5b7ded809a4db6b1e3b26b6a6 Mon Sep 17 00:00:00 2001
From: "sgo@tinyerp.com" <>
Date: Mon, 1 Jul 2013 17:16:25 +0530
Subject: [PATCH 092/256] [IMP]define context to yml function
bzr revid: sgo@tinyerp.com-20130701114625-vshpugzujnvgdf5x
---
addons/sale_stock/test/cancel_order_sale_stock.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/addons/sale_stock/test/cancel_order_sale_stock.yml b/addons/sale_stock/test/cancel_order_sale_stock.yml
index 0278bd52713..29581b944bf 100644
--- a/addons/sale_stock/test/cancel_order_sale_stock.yml
+++ b/addons/sale_stock/test/cancel_order_sale_stock.yml
@@ -10,6 +10,7 @@
I send delivery in two shipments, so I am doing a partial delivery order.
-
!python {model: stock.picking}: |
+ context={}
delivery_orders = self.search(cr, uid, [('sale_id','=',ref("sale.sale_order_8"))])
first_picking = self.browse(cr, uid, delivery_orders[-1], context=context)
if first_picking.force_assign(cr, uid, first_picking):
From 3cf3519e82efe735f5228bc83bced6366ec895af Mon Sep 17 00:00:00 2001
From: Darshan Kalola
Date: Wed, 24 Jul 2013 19:15:48 +0530
Subject: [PATCH 093/256] [IMP]improve help of setting/configuration menus
bzr revid: darshankalola@gmail.com-20130724134548-jmgot5zt3xpcx1ct
---
addons/account/res_config.py | 36 +++++++-------
addons/base_setup/res_config.py | 21 ++++----
addons/crm/res_config.py | 4 +-
addons/hr_recruitment/res_config.py | 8 ++--
addons/hr_timesheet_sheet/res_config.py | 4 +-
addons/knowledge/res_config.py | 14 +++---
addons/marketing/res_config.py | 14 +++---
addons/mrp/res_config.py | 64 ++++++++++++-------------
addons/project/res_config.py | 34 ++++++-------
addons/purchase/res_config.py | 28 +++++------
addons/sale/res_config.py | 42 ++++++++--------
addons/sale_stock/res_config.py | 8 ++--
addons/stock/res_config.py | 26 +++++-----
13 files changed, 151 insertions(+), 152 deletions(-)
diff --git a/addons/account/res_config.py b/addons/account/res_config.py
index 89d238b16e9..edb34f54be2 100644
--- a/addons/account/res_config.py
+++ b/addons/account/res_config.py
@@ -81,31 +81,31 @@ class account_config_settings(osv.osv_memory):
'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier credit note number'),
'module_account_check_writing': fields.boolean('Pay your suppliers by check',
- help="""This allows you to check writing and printing.
- This installs the module account_check_writing."""),
+ help='This allows you to check writing and printing.\n'
+ 'This installs the module account_check_writing.'),
'module_account_accountant': fields.boolean('Full accounting features: journals, legal statements, chart of accounts, etc.',
help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of Accounts, ...)"""),
'module_account_asset': fields.boolean('Assets management',
- help="""This allows you to manage the assets owned by a company or a person.
- It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.
- This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments,
- but not accounting (Journal Items, Chart of Accounts, ...)"""),
+ help='This allows you to manage the assets owned by a company or a person.\n'
+ 'It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.\n'
+ 'This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments, '
+ 'but not accounting (Journal Items, Chart of Accounts, ...)'),
'module_account_budget': fields.boolean('Budget management',
- help="""This allows accountants to manage analytic and crossovered budgets.
- Once the master budgets and the budgets are defined,
- the project managers can set the planned amount on each analytic account.
- This installs the module account_budget."""),
+ help='This allows accountants to manage analytic and crossovered budgets. '
+ 'Once the master budgets and the budgets are defined, '
+ 'the project managers can set the planned amount on each analytic account.\n'
+ 'This installs the module account_budget.'),
'module_account_payment': fields.boolean('Manage payment orders',
- help="""This allows you to create and manage your payment orders, with purposes to
- * serve as base for an easy plug-in of various automated payment mechanisms, and
- * provide a more efficient way to manage invoice payments.
- This installs the module account_payment."""),
+ help='This allows you to create and manage your payment orders, with purposes to \n'
+ '* serve as base for an easy plug-in of various automated payment mechanisms, and \n'
+ '* provide a more efficient way to manage invoice payments.\n'
+ 'This installs the module account_payment.' ),
'module_account_voucher': fields.boolean('Manage customer payments',
- help="""This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.
- This installs the module account_voucher."""),
+ help='This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.\n'
+ 'This installs the module account_voucher.'),
'module_account_followup': fields.boolean('Manage customer payment follow-ups',
- help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
- This installs the module account_followup."""),
+ help='This allows to automate letters for unpaid invoices, with multi-level recalls.\n'
+ 'This installs the module account_followup.'),
'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
implied_group='account.group_proforma_invoices',
help="Allows you to put invoices in pro-forma state."),
diff --git a/addons/base_setup/res_config.py b/addons/base_setup/res_config.py
index 6b8578bbf47..82828e5f30d 100644
--- a/addons/base_setup/res_config.py
+++ b/addons/base_setup/res_config.py
@@ -64,18 +64,17 @@ class sale_config_settings(osv.osv_memory):
'module_crm': fields.boolean('CRM'),
'module_sale' : fields.boolean('SALE'),
'module_plugin_thunderbird': fields.boolean('Enable Thunderbird plug-in',
- help="""The plugin allows you archive email and its attachments to the selected
- OpenERP objects. You can select a partner, or a lead and
- attach the selected mail as a .eml file in
- the attachment of a selected record. You can create documents for CRM Lead,
- Partner from the selected emails.
- This installs the module plugin_thunderbird."""),
+ help='The plugin allows you archive email and its attachments to the selected '
+ 'OpenERP objects. You can select a partner, or a lead and '
+ 'attach the selected mail as a .eml file in '
+ 'the attachment of a selected record. You can create documents for CRM Lead, '
+ 'Partner from the selected emails.\n'
+ 'This installs the module plugin_thunderbird.'),
'module_plugin_outlook': fields.boolean('Enable Outlook plug-in',
- help="""The Outlook plugin allows you to select an object that you would like to add
- to your email and its attachments from MS Outlook. You can select a partner,
- or a lead object and archive a selected
- email into an OpenERP mail message with attachments.
- This installs the module plugin_outlook."""),
+ help='The Outlook plugin allows you to select an object that you would like to add '
+ 'to your email and its attachments from MS Outlook. You can select a partner, '
+ 'or a lead object and archive a selected email into an OpenERP mail message with attachments.\n'
+ 'This installs the module plugin_outlook.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/res_config.py b/addons/crm/res_config.py
index ea37c24fe06..622af04ef75 100644
--- a/addons/crm/res_config.py
+++ b/addons/crm/res_config.py
@@ -58,8 +58,8 @@ class crm_configuration(osv.TransientModel):
implied_group='crm.group_fund_raising',
help="""Allows you to trace and manage your activities for fund raising."""),
'module_crm_claim': fields.boolean("Manage Customer Claims",
- help="""Allows you to track your customers/suppliers claims and grievances.
- This installs the module crm_claim."""),
+ help='Allows you to track your customers/suppliers claims and grievances.\n'
+ 'This installs the module crm_claim.'),
'module_crm_helpdesk': fields.boolean("Manage Helpdesk and Support",
help="""Allows you to communicate with Customer, process Customer query, and provide better help and support. This installs the module crm_helpdesk."""),
'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
diff --git a/addons/hr_recruitment/res_config.py b/addons/hr_recruitment/res_config.py
index ac53bf74ff3..a15ae541e7f 100644
--- a/addons/hr_recruitment/res_config.py
+++ b/addons/hr_recruitment/res_config.py
@@ -27,11 +27,11 @@ class hr_applicant_settings(osv.osv_memory):
_columns = {
'module_document_ftp': fields.boolean('Allow the automatic indexation of resumes',
- help="""Manage your CV's and motivation letter related to all applicants.
- This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)"""),
+ help='Manage your CV\'s and motivation letter related to all applicants.\n'
+ 'This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)'),
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
- help ="""Allow applicants to send their job application to an email address (jobs@mycompany.com),
- and create automatically application documents in the system."""),
+ help ='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
+ 'and create automatically application documents in the system.'),
}
diff --git a/addons/hr_timesheet_sheet/res_config.py b/addons/hr_timesheet_sheet/res_config.py
index e767b4da8fa..f3ef5b8496a 100644
--- a/addons/hr_timesheet_sheet/res_config.py
+++ b/addons/hr_timesheet_sheet/res_config.py
@@ -28,8 +28,8 @@ class hr_timesheet_settings(osv.osv_memory):
'timesheet_range': fields.selection([('day','Day'),('week','Week'),('month','Month')],
'Validate timesheets every', help="Periodicity on which you validate your timesheets."),
'timesheet_max_difference': fields.float('Allow a difference of time between timesheets and attendances of (in hours)',
- help="""Allowed difference in hours between the sign in/out and the timesheet
- computation for one sheet. Set this to 0 if you do not want any control."""),
+ help='Allowed difference in hours between the sign in/out and the timesheet '
+ 'computation for one sheet. Set this to 0 if you do not want any control.'),
}
def get_default_timesheet(self, cr, uid, fields, context=None):
diff --git a/addons/knowledge/res_config.py b/addons/knowledge/res_config.py
index fddbb5ebdc3..fd033772133 100644
--- a/addons/knowledge/res_config.py
+++ b/addons/knowledge/res_config.py
@@ -28,15 +28,15 @@ class knowledge_config_settings(osv.osv_memory):
'module_document_page': fields.boolean('Create static web pages',
help="""This installs the module document_page."""),
'module_document': fields.boolean('Manage documents',
- help="""This is a complete document management system, with: user authentication,
- full document search (but pptx and docx are not supported), and a document dashboard.
- This installs the module document."""),
+ help='This is a complete document management system, with: user authentication, '
+ 'full document search (but pptx and docx are not supported), and a document dashboard.\n'
+ 'This installs the module document.'),
'module_document_ftp': fields.boolean('Share repositories (FTP)',
- help="""Access your documents in OpenERP through an FTP interface.
- This installs the module document_ftp."""),
+ help='Access your documents in OpenERP through an FTP interface.\n'
+ 'This installs the module document_ftp.'),
'module_document_webdav': fields.boolean('Share repositories (WebDAV)',
- help="""Access your documents in OpenERP through WebDAV.
- This installs the module document_webdav."""),
+ help='Access your documents in OpenERP through WebDAV.\n'
+ 'This installs the module document_webdav.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/marketing/res_config.py b/addons/marketing/res_config.py
index 3c161c4d0d6..3d8eaf71fe6 100644
--- a/addons/marketing/res_config.py
+++ b/addons/marketing/res_config.py
@@ -26,15 +26,15 @@ class marketing_config_settings(osv.osv_memory):
_inherit = 'res.config.settings'
_columns = {
'module_marketing_campaign': fields.boolean('Marketing campaigns',
- help="""Provides leads automation through marketing campaigns.
- Campaigns can in fact be defined on any resource, not just CRM leads.
- This installs the module marketing_campaign."""),
+ help='Provides leads automation through marketing campaigns. '
+ 'Campaigns can in fact be defined on any resource, not just CRM leads.\n'
+ 'This installs the module marketing_campaign.'),
'module_marketing_campaign_crm_demo': fields.boolean('Demo data for marketing campaigns',
- help="""Installs demo data like leads, campaigns and segments for Marketing Campaigns.
- This installs the module marketing_campaign_crm_demo."""),
+ help='Installs demo data like leads, campaigns and segments for Marketing Campaigns.\n'
+ 'This installs the module marketing_campaign_crm_demo.'),
'module_crm_profiling': fields.boolean('Track customer profile to focus your campaigns',
- help="""Allows users to perform segmentation within partners.
- This installs the module crm_profiling."""),
+ help='Allows users to perform segmentation within partners.\n'
+ 'This installs the module crm_profiling.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mrp/res_config.py b/addons/mrp/res_config.py
index 22a14addcd1..7f54999e15b 100644
--- a/addons/mrp/res_config.py
+++ b/addons/mrp/res_config.py
@@ -28,48 +28,48 @@ class mrp_config_settings(osv.osv_memory):
_columns = {
'module_mrp_repair': fields.boolean("Manage repairs of products ",
- help="""Allows to manage all product repairs.
- * Add/remove products in the reparation
- * Impact for stocks
- * Invoicing (products and/or services)
- * Warranty concept
- * Repair quotation report
- * Notes for the technician and for the final customer.
- This installs the module mrp_repair."""),
+ help='Allows to manage all product repairs.\n'
+ '* Add/remove products in the reparation\n'
+ '* Impact for stocks\n'
+ '* Invoicing (products and/or services)\n'
+ '* Warranty concept\n'
+ '* Repair quotation report\n'
+ '* Notes for the technician and for the final customer.\n'
+ 'This installs the module mrp_repair.'),
'module_mrp_operations': fields.boolean("Allow detailed planning of work order",
- help="""This allows to add state, date_start,date_stop in production order operation lines (in the "Work Centers" tab).
- This installs the module mrp_operations."""),
+ help='This allows to add state, date_start,date_stop in production order operation lines (in the "Work Centers" tab).\n'
+ 'This installs the module mrp_operations.'),
'module_mrp_byproduct': fields.boolean("Produce several products from one manufacturing order",
- help="""You can configure by-products in the bill of material.
- Without this module: A + B + C -> D.
- With this module: A + B + C -> D + E.
- This installs the module mrp_byproduct."""),
+ help='You can configure by-products in the bill of material.\n'
+ 'Without this module: A + B + C -> D.\n'
+ 'With this module: A + B + C -> D + E.\n'
+ 'This installs the module mrp_byproduct.'),
'module_mrp_jit': fields.boolean("Generate procurement in real time",
- help="""This allows Just In Time computation of procurement orders.
- All procurement orders will be processed immediately, which could in some
- cases entail a small performance impact.
- This installs the module mrp_jit."""),
+ help='This allows Just In Time computation of procurement orders.\n'
+ 'All procurement orders will be processed immediately, which could in some '
+ 'cases entail a small performance impact.\n'
+ 'This installs the module mrp_jit.'),
'module_stock_no_autopicking': fields.boolean("Manage manual picking to fulfill manufacturing orders ",
- help="""This module allows an intermediate picking process to provide raw materials to production orders.
- For example to manage production made by your suppliers (sub-contracting).
- To achieve this, set the assembled product which is sub-contracted to "No Auto-Picking"
- and put the location of the supplier in the routing of the assembly operation.
- This installs the module stock_no_autopicking."""),
+ help='This module allows an intermediate picking process to provide raw materials to production orders.\n'
+ 'For example to manage production made by your suppliers (sub-contracting).\n'
+ 'To achieve this, set the assembled product which is sub-contracted to "No Auto-Picking" '
+ 'and put the location of the supplier in the routing of the assembly operation.\n'
+ 'This installs the module stock_no_autopicking.'),
'group_mrp_routings': fields.boolean("Manage routings and work orders ",
implied_group='mrp.group_mrp_routings',
- help="""Routings allow you to create and manage the manufacturing operations that should be followed
- within your work centers in order to produce a product. They are attached to bills of materials
- that will define the required raw materials."""),
+ help='Routings allow you to create and manage the manufacturing operations that should be followed '
+ 'within your work centers in order to produce a product. They are attached to bills of materials '
+ 'that will define the required raw materials.'),
'group_mrp_properties': fields.boolean("Allow several bill of materials per products using properties",
implied_group='product.group_mrp_properties',
help="""The selection of the right Bill of Material to use will depend on the properties specified on the sales order and the Bill of Material."""),
'module_product_manufacturer': fields.boolean("Define manufacturers on products ",
- help="""This allows you to define the following for a product:
- * Manufacturer
- * Manufacturer Product Name
- * Manufacturer Product Code
- * Product Attributes.
- This installs the module product_manufacturer."""),
+ help='This allows you to define the following for a product:\n'
+ '* Manufacturer\n'
+ '* Manufacturer Product Name\n'
+ '* Manufacturer Product Code\n'
+ '* Product Attributes.\n'
+ 'This installs the module product_manufacturer.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/res_config.py b/addons/project/res_config.py
index ddf98582ef3..a8b8af3b90b 100644
--- a/addons/project/res_config.py
+++ b/addons/project/res_config.py
@@ -28,30 +28,30 @@ class project_configuration(osv.osv_memory):
_columns = {
'module_project_mrp': fields.boolean('Generate tasks from sale orders',
- help ="""This feature automatically creates project tasks from service products in sale orders.
- More precisely, tasks are created for procurement lines with product of type 'Service',
- procurement method 'Make to Order', and supply method 'Manufacture'.
- This installs the module project_mrp."""),
+ help ='This feature automatically creates project tasks from service products in sale orders. '
+ 'More precisely, tasks are created for procurement lines with product of type \'Service\', '
+ 'procurement method \'Make to Order\', and supply method \'Manufacture\'.\n'
+ 'This installs the module project_mrp.'),
'module_pad': fields.boolean("Use integrated collaborative note pads on task",
- help="""Lets the company customize which Pad installation should be used to link to new pads
- (for example: http://ietherpad.com/).
- This installs the module pad."""),
+ help='Lets the company customize which Pad installation should be used to link to new pads '
+ '(for example: http://ietherpad.com/).\n'
+ 'This installs the module pad.'),
'module_project_timesheet': fields.boolean("Record timesheet lines per tasks",
- help="""This allows you to transfer the entries under tasks defined for Project Management to
- the timesheet line entries for particular date and user, with the effect of creating,
- editing and deleting either ways.
- This installs the module project_timesheet."""),
+ help='This allows you to transfer the entries under tasks defined for Project Management to '
+ 'the timesheet line entries for particular date and user, with the effect of creating, '
+ 'editing and deleting either ways.\n'
+ 'This installs the module project_timesheet.'),
'module_project_long_term': fields.boolean("Manage resources planning on gantt view",
- help="""A long term project management module that tracks planning, scheduling, and resource allocation.
- This installs the module project_long_term."""),
+ help='A long term project management module that tracks planning, scheduling, and resource allocation.\n'
+ 'This installs the module project_long_term.'),
'module_project_issue': fields.boolean("Track issues and bugs",
- help="""Provides management of issues/bugs in projects.
- This installs the module project_issue."""),
+ help='Provides management of issues/bugs in projects.\n'
+ 'This installs the module project_issue.'),
'time_unit': fields.many2one('product.uom', 'Working time unit', required=True,
help="""This will set the unit of measure used in projects and tasks."""),
'module_project_issue_sheet': fields.boolean("Invoice working time on issues",
- help="""Provides timesheet support for the issues/bugs management in project.
- This installs the module project_issue_sheet."""),
+ help='Provides timesheet support for the issues/bugs management in project.\n'
+ 'This installs the module project_issue_sheet.'),
'group_tasks_work_on_tasks': fields.boolean("Log work activities on tasks",
implied_group='project.group_tasks_work_on_tasks',
help="Allows you to compute work on tasks."),
diff --git a/addons/purchase/res_config.py b/addons/purchase/res_config.py
index f5f2dfcdda5..930ee5f9f29 100644
--- a/addons/purchase/res_config.py
+++ b/addons/purchase/res_config.py
@@ -34,8 +34,8 @@ class purchase_config_settings(osv.osv_memory):
], 'Default invoicing control method', required=True, default_model='purchase.order'),
'group_purchase_pricelist':fields.boolean("Manage pricelist per supplier",
implied_group='product.group_purchase_pricelist',
- help="""Allows to manage different prices based on rules per category of Supplier.
- Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
+ help='Allows to manage different prices based on rules per category of Supplier.\n'
+ 'Example: 10% for retailers, promotion of 5 EUR on this product, etc.'),
'group_uom':fields.boolean("Manage different units of measure for products",
implied_group='product.group_uom',
help="""Allows you to select and maintain different units of measure for products."""),
@@ -43,19 +43,19 @@ class purchase_config_settings(osv.osv_memory):
implied_group='product.group_costing_method',
help="""Allows you to compute product cost price based on average cost."""),
'module_warning': fields.boolean("Alerts by products or supplier",
- help="""Allow to configure notification on products and trigger them when a user wants to purchase a given product or a given supplier.
-Example: Product: this product is deprecated, do not purchase more than 5.
- Supplier: don't forget to ask for an express delivery."""),
+ help='Allow to configure notification on products and trigger them when a user wants to purchase a given product or a given supplier.\n'
+ 'Example: Product: this product is deprecated, do not purchase more than 5.\n'
+ 'Supplier: don\'t forget to ask for an express delivery.'),
'module_purchase_double_validation': fields.boolean("Force two levels of approvals",
- help="""Provide a double validation mechanism for purchases exceeding minimum amount.
- This installs the module purchase_double_validation."""),
+ help='Provide a double validation mechanism for purchases exceeding minimum amount.\n'
+ 'This installs the module purchase_double_validation.'),
'module_purchase_requisition': fields.boolean("Manage purchase requisitions",
- help="""Purchase Requisitions are used when you want to request quotations from several suppliers for a given set of products.
- You can configure per product if you directly do a Request for Quotation
- to one supplier or if you want a purchase requisition to negotiate with several suppliers."""),
+ help='Purchase Requisitions are used when you want to request quotations from several suppliers for a given set of products.\n'
+ 'You can configure per product if you directly do a Request for Quotation '
+ 'to one supplier or if you want a purchase requisition to negotiate with several suppliers.'),
'module_purchase_analytic_plans': fields.boolean('Use multiple analytic accounts on purchase orders',
- help ="""Allows the user to maintain several analysis plans. These let you split lines on a purchase order between several accounts and analytic plans.
- This installs the module purchase_analytic_plans."""),
+ help ='Allows the user to maintain several analysis plans. These let you split lines on a purchase order between several accounts and analytic plans.\n'
+ 'This installs the module purchase_analytic_plans.'),
'group_analytic_account_for_purchases': fields.boolean('Analytic accounting for purchases',
implied_group='purchase.group_analytic_accounting',
help="Allows you to specify an analytic account on purchase orders."),
@@ -77,8 +77,8 @@ class account_config_settings(osv.osv_memory):
_inherit = 'account.config.settings'
_columns = {
'module_purchase_analytic_plans': fields.boolean('Use multiple analytic accounts on orders',
- help ="""Allows the user to maintain several analysis plans. These let you split lines on a purchase order between several accounts and analytic plans.
- This installs the module purchase_analytic_plans."""),
+ help ='Allows the user to maintain several analysis plans. These let you split lines on a purchase order between several accounts and analytic plans.\n'
+ 'This installs the module purchase_analytic_plans.'),
'group_analytic_account_for_purchases': fields.boolean('Analytic accounting for purchases',
implied_group='purchase.group_analytic_accounting',
help="Allows you to specify an analytic account on purchase orders."),
diff --git a/addons/sale/res_config.py b/addons/sale/res_config.py
index bfbaa5f0a6f..d570dfc8183 100644
--- a/addons/sale/res_config.py
+++ b/addons/sale/res_config.py
@@ -34,15 +34,15 @@ class sale_configuration(osv.osv_memory):
implied_group='sale.group_invoice_so_lines',
help="To allow your salesman to make invoices for sales order lines using the menu 'Lines to Invoice'."),
'timesheet': fields.boolean('Prepare invoices based on timesheets',
- help = """For modifying account analytic view to show important data to project manager of services companies.
- You can also view the report of account analytic summary user-wise as well as month wise.
- This installs the module account_analytic_analysis."""),
+ help = 'For modifying account analytic view to show important data to project manager of services companies.'
+ 'You can also view the report of account analytic summary user-wise as well as month wise.\n'
+ 'This installs the module account_analytic_analysis.'),
'module_account_analytic_analysis': fields.boolean('Use contracts management',
- help = """Allows to define your customer contracts conditions: invoicing
- method (fixed price, on timesheet, advance invoice), the exact pricing
- (650€/day for a developer), the duration (one year support contract).
- You will be able to follow the progress of the contract and invoice automatically.
- It installs the account_analytic_analysis module."""),
+ help = 'Allows to define your customer contracts conditions: invoicing '
+ 'method (fixed price, on timesheet, advance invoice), the exact pricing '
+ '(650€/day for a developer), the duration (one year support contract).\n'
+ 'You will be able to follow the progress of the contract and invoice automatically.\n'
+ 'It installs the account_analytic_analysis module.'),
'time_unit': fields.many2one('product.uom', 'The default working time unit for services is'),
'group_sale_pricelist':fields.boolean("Use pricelists to adapt your price per customers",
implied_group='product.group_sale_pricelist',
@@ -58,22 +58,22 @@ Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
implied_group='product.group_product_variant',
help="""Allow to manage several variants per product. As an example, if you sell T-Shirts, for the same "Linux T-Shirt", you may have variants on sizes or colors; S, M, L, XL, XXL."""),
'module_warning': fields.boolean("Allow configuring alerts by customer or products",
- help="""Allow to configure notification on products and trigger them when a user wants to sell a given product or a given customer.
-Example: Product: this product is deprecated, do not purchase more than 5.
- Supplier: don't forget to ask for an express delivery."""),
+ help='Allow to configure notification on products and trigger them when a user wants to sell a given product or a given customer.\n'
+ 'Example: Product: this product is deprecated, do not purchase more than 5.\n'
+ 'Supplier: don\'t forget to ask for an express delivery.'),
'module_sale_margin': fields.boolean("Display margins on sales orders",
- help="""This adds the 'Margin' on sales order.
- This gives the profitability by calculating the difference between the Unit Price and Cost Price.
- This installs the module sale_margin."""),
+ help='This adds the \'Margin\' on sales order.\n'
+ 'This gives the profitability by calculating the difference between the Unit Price and Cost Price.\n'
+ 'This installs the module sale_margin.'),
'module_sale_journal': fields.boolean("Allow batch invoicing of delivery orders through journals",
- help="""Allows you to categorize your sales and deliveries (picking lists) between different journals,
- and perform batch operations on journals.
- This installs the module sale_journal."""),
+ help='Allows you to categorize your sales and deliveries (picking lists) between different journals, '
+ 'and perform batch operations on journals.\n'
+ 'This installs the module sale_journal.'),
'module_analytic_user_function': fields.boolean("One employee can have different roles per contract",
- help="""Allows you to define what is the default function of a specific user on a given account.
- This is mostly used when a user encodes his timesheet. The values are retrieved and the fields are auto-filled.
- But the possibility to change these values is still available.
- This installs the module analytic_user_function."""),
+ help='Allows you to define what is the default function of a specific user on a given account.\n'
+ 'This is mostly used when a user encodes his timesheet. The values are retrieved and the fields are auto-filled. '
+ 'But the possibility to change these values is still available.\n'
+ 'This installs the module analytic_user_function.'),
'module_project': fields.boolean("Project"),
'module_sale_stock': fields.boolean("Trigger delivery orders automatically from sales orders",
help="""Allows you to Make Quotation, Sale Order using different Order policy and Manage Related Stock.
diff --git a/addons/sale_stock/res_config.py b/addons/sale_stock/res_config.py
index 4c3b92f1d33..ae81989aa52 100644
--- a/addons/sale_stock/res_config.py
+++ b/addons/sale_stock/res_config.py
@@ -33,10 +33,10 @@ class sale_configuration(osv.osv_memory):
implied_group='sale_stock.group_invoice_deli_orders',
help="To allow your salesman to make invoices for Delivery Orders using the menu 'Deliveries to Invoice'."),
'task_work': fields.boolean("Prepare invoices based on task's activities",
- help="""Lets you transfer the entries under tasks defined for Project Management to
- the Timesheet line entries for particular date and particular user with the effect of creating, editing and deleting either ways
- and to automatically creates project tasks from procurement lines.
- This installs the modules project_timesheet and project_mrp."""),
+ help='Lets you transfer the entries under tasks defined for Project Management to '
+ 'the Timesheet line entries for particular date and particular user with the effect of creating, editing and deleting either ways '
+ 'and to automatically creates project tasks from procurement lines.\n'
+ 'This installs the modules project_timesheet and project_mrp.'),
'default_order_policy': fields.selection(
[('manual', 'Invoice based on sales orders'), ('picking', 'Invoice based on deliveries')],
'The default invoicing method is', default_model='sale.order',
diff --git a/addons/stock/res_config.py b/addons/stock/res_config.py
index d0b0563875c..4da23389214 100644
--- a/addons/stock/res_config.py
+++ b/addons/stock/res_config.py
@@ -27,12 +27,12 @@ class stock_config_settings(osv.osv_memory):
_columns = {
'module_claim_from_delivery': fields.boolean("Allow claim on deliveries",
- help="""Adds a Claim link to the delivery order.
- This installs the module claim_from_delivery."""),
+ help='Adds a Claim link to the delivery order.\n'
+ 'This installs the module claim_from_delivery.'),
'module_stock_invoice_directly': fields.boolean("Create and open the invoice when the user finish a delivery order",
- help="""This allows to automatically launch the invoicing wizard if the delivery is
- to be invoiced when you send or deliver goods.
- This installs the module stock_invoice_directly."""),
+ help='This allows to automatically launch the invoicing wizard if the delivery is '
+ 'to be invoiced when you send or deliver goods.\n'
+ 'This installs the module stock_invoice_directly.'),
'module_product_expiry': fields.boolean("Expiry date on serial numbers",
help="""Track different dates on products and serial numbers.
The following dates can be tracked:
@@ -42,17 +42,17 @@ The following dates can be tracked:
- alert date.
This installs the module product_expiry."""),
'module_stock_location': fields.boolean("Create push/pull logistic rules",
- help="""Provide push and pull inventory flows. Typical uses of this feature are:
- manage product manufacturing chains, manage default locations per product,
- define routes within your warehouse according to business needs, etc.
- This installs the module stock_location."""),
+ help='Provide push and pull inventory flows. Typical uses of this feature are: '
+ 'manage product manufacturing chains, manage default locations per product, '
+ 'define routes within your warehouse according to business needs, etc.\n'
+ 'This installs the module stock_location.'),
'group_uom': fields.boolean("Manage different units of measure for products",
implied_group='product.group_uom',
help="""Allows you to select and maintain different units of measure for products."""),
'group_uos': fields.boolean("Invoice products in a different unit of measure than the sales order",
implied_group='product.group_uos',
- help="""Allows you to sell units of a product, but invoice based on a different unit of measure.
- For instance, you can sell pieces of meat that you invoice based on their weight."""),
+ help='Allows you to sell units of a product, but invoice based on a different unit of measure.\n'
+ 'For instance, you can sell pieces of meat that you invoice based on their weight.'),
'group_stock_packaging': fields.boolean("Allow to define several packaging methods on products",
implied_group='product.group_stock_packaging',
help="""Allows you to create and manage your packaging dimensions and types you want to be maintained in your system."""),
@@ -67,8 +67,8 @@ This installs the module product_expiry."""),
help="""Allows to configure inventory valuations on products and product categories."""),
'group_stock_multiple_locations': fields.boolean("Manage multiple locations and warehouses",
implied_group='stock.group_locations',
- help="""This allows to configure and use multiple stock locations and warehouses,
- instead of having a single default one."""),
+ help='This allows to configure and use multiple stock locations and warehouses, '
+ 'instead of having a single default one.'),
'decimal_precision': fields.integer('Decimal precision on weight', help="As an example, a decimal precision of 2 will allow weights like: 9.99 kg, whereas a decimal precision of 4 will allow weights like: 0.0231 kg."),
}
From c0a72b34914436bcee627d829ac7e89f3cac87f9 Mon Sep 17 00:00:00 2001
From: Darshan Kalola
Date: Thu, 25 Jul 2013 11:44:42 +0530
Subject: [PATCH 094/256] [IMP]improve help of sales and General-setting menus
in setting/configuration
bzr revid: darshankalola@gmail.com-20130725061442-kchwc3eattox148j
---
addons/base_setup/res_config.py | 4 ++--
addons/sale/res_config.py | 4 ++--
addons/sale_stock/res_config.py | 6 +++---
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/addons/base_setup/res_config.py b/addons/base_setup/res_config.py
index 82828e5f30d..c7f6d4245ec 100644
--- a/addons/base_setup/res_config.py
+++ b/addons/base_setup/res_config.py
@@ -26,8 +26,8 @@ class base_config_settings(osv.osv_memory):
_inherit = 'res.config.settings'
_columns = {
'module_multi_company': fields.boolean('Manage multiple companies',
- help="""Work in multi-company environments, with appropriate security access between companies.
- This installs the module multi_company."""),
+ help='Work in multi-company environments, with appropriate security access between companies.\n'
+ 'This installs the module multi_company.'),
'module_share': fields.boolean('Allow documents sharing',
help="""Share or embbed any screen of openerp."""),
'module_portal': fields.boolean('Activate the customer portal',
diff --git a/addons/sale/res_config.py b/addons/sale/res_config.py
index d570dfc8183..fe81a7cc6d8 100644
--- a/addons/sale/res_config.py
+++ b/addons/sale/res_config.py
@@ -76,8 +76,8 @@ Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
'This installs the module analytic_user_function.'),
'module_project': fields.boolean("Project"),
'module_sale_stock': fields.boolean("Trigger delivery orders automatically from sales orders",
- help="""Allows you to Make Quotation, Sale Order using different Order policy and Manage Related Stock.
- This installs the module sale_stock."""),
+ help='Allows you to Make Quotation, Sale Order using different Order policy and Manage Related Stock.\n'
+ 'This installs the module sale_stock.'),
}
def default_get(self, cr, uid, fields, context=None):
diff --git a/addons/sale_stock/res_config.py b/addons/sale_stock/res_config.py
index ae81989aa52..76cfbcb6fd3 100644
--- a/addons/sale_stock/res_config.py
+++ b/addons/sale_stock/res_config.py
@@ -42,9 +42,9 @@ class sale_configuration(osv.osv_memory):
'The default invoicing method is', default_model='sale.order',
help="You can generate invoices based on sales orders or based on shippings."),
'module_delivery': fields.boolean('Allow adding shipping costs',
- help ="""Allows you to add delivery methods in sales orders and delivery orders.
- You can define your own carrier and delivery grids for prices.
- This installs the module delivery."""),
+ help ='Allows you to add delivery methods in sales orders and delivery orders.\n'
+ 'You can define your own carrier and delivery grids for prices.\n'
+ 'This installs the module delivery.'),
'default_picking_policy' : fields.boolean("Deliver all at once when all products are available.",
help = "Sales order by default will be configured to deliver all products at once instead of delivering each product when it is available. This may have an impact on the shipping price."),
'group_mrp_properties': fields.boolean('Product properties on order lines',
From 497ba7dab96a15c965e1e2824d9111772a98210b Mon Sep 17 00:00:00 2001
From: "Mansi Kariya (OpenERP Trainee)"
Date: Tue, 30 Jul 2013 17:17:45 +0530
Subject: [PATCH 095/256] [IMP] purchases Configuration menus location and name
changed
bzr revid: mansi.mk.179@gmail.com-20130730114745-o2xzb0h63ze29stq
---
addons/purchase/purchase_view.xml | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index dffe1ed54ff..0463527461e 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -14,9 +14,6 @@
-
Pricelist Versions
@@ -36,14 +33,18 @@
+
+
+
-
From d1e7ad9be6f01644567df0fb8d0e68e6c40aff5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Tue, 6 Aug 2013 17:10:18 +0200
Subject: [PATCH 096/256] [IMP] tools: added a regex for bounce email addresses
bzr revid: tde@openerp.com-20130806151018-0uom07dbr8b7ycb3
---
openerp/tools/mail.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py
index 5970ce47040..36a4c1610c4 100644
--- a/openerp/tools/mail.py
+++ b/openerp/tools/mail.py
@@ -297,6 +297,11 @@ command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
# group(1) = the record ID ; group(2) = the model (if any) ; group(3) = the domain
reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?.*@(.*)>", re.UNICODE)
+# Bounce regex
+# Typical form of bounce is bounce-128-crm.lead-34@domain
+# group(1) = the mail ID; group(2) = the model (if any); group(3) = the record ID
+bounce_re = re.compile("[\w]+-(\d+)-?([\w.]+)?-?(\d+)?", re.UNICODE)
+
def generate_tracking_message_id(res_id):
"""Returns a string that can be used in the Message-ID RFC822 header field
From 38a534dee0bf15a56a97874a01172cb67cd6b94a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Tue, 6 Aug 2013 17:11:43 +0200
Subject: [PATCH 097/256] [IMP] mail: first implementation of tracking and
bounce management. Added 2 fields on mail_mail to count read/bounce. Added
bounce alias bounce-mail_id-model-res_id. Added message_receive_bounce method
that try to incremetn message_bounce field.
bzr revid: tde@openerp.com-20130806151143-7dw6xlj8n7mh0nqe
---
addons/crm/crm_lead.py | 2 +
addons/crm/crm_lead_view.xml | 1 +
addons/mail/controllers/main.py | 21 ++++++---
addons/mail/data/mail_data.xml | 6 +++
addons/mail/mail_mail.py | 83 ++++++++++++++++++++++++---------
addons/mail/mail_mail_view.xml | 40 ++++++++++------
addons/mail/mail_thread.py | 28 +++++++++++
7 files changed, 136 insertions(+), 45 deletions(-)
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index c0f003f9792..52987de00e1 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -273,6 +273,8 @@ class crm_lead(format_address, osv.osv):
selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
help='The Status is set to \'Draft\', when a case is created. If the case is in progress the Status is set to \'Open\'. When the case is over, the Status is set to \'Done\'. If the case needs to be reviewed then the Status is set to \'Pending\'.'),
+ # Messaging and marketing
+ 'message_bounce': fields.integer('Bounce'),
# Only used for type opportunity
'probability': fields.float('Success Rate (%)',group_operator="avg"),
'planned_revenue': fields.float('Expected Revenue', track_visibility='always'),
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index aa5aac76b9d..113355eea83 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -182,6 +182,7 @@
+
diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py
index ef3a60987d6..27373c34add 100644
--- a/addons/mail/controllers/main.py
+++ b/addons/mail/controllers/main.py
@@ -1,17 +1,17 @@
import base64
+import psycopg2
import openerp
from openerp import SUPERUSER_ID
-import openerp.addons.web.http as oeweb
+import openerp.addons.web.http as http
from openerp.addons.web.controllers.main import content_disposition
+from openerp.addons.web.http import request
-#----------------------------------------------------------
-# Controller
-#----------------------------------------------------------
-class MailController(oeweb.Controller):
+
+class MailController(http.Controller):
_cp_path = '/mail'
- @oeweb.httprequest
+ @http.httprequest
def download_attachment(self, req, model, id, method, attachment_id, **kw):
Model = req.session.model(model)
res = getattr(Model, method)(int(id), int(attachment_id))
@@ -24,7 +24,7 @@ class MailController(oeweb.Controller):
('Content-Disposition', content_disposition(filename, req))])
return req.not_found()
- @oeweb.jsonrequest
+ @http.jsonrequest
def receive(self, req):
""" End-point to receive mail from an external SMTP server. """
dbs = req.jsonrequest.get('databases')
@@ -38,3 +38,10 @@ class MailController(oeweb.Controller):
except psycopg2.Error:
pass
return True
+
+ @http.route('/mail/track//blank.gif', type='http', auth='admin')
+ def track_read_email(self, mail_id):
+ """ Email tracking. """
+ mail_mail = request.registry.get('mail.mail')
+ mail_mail.set_opened(request.cr, request.uid, [mail_id])
+ return False
diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml
index 4e935e4aa88..8ba92a8205c 100644
--- a/addons/mail/data/mail_data.xml
+++ b/addons/mail/data/mail_data.xml
@@ -46,6 +46,12 @@
catchall
+
+
+ mail.bounce.alias
+ bounce
+
+
Discussions
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index bf76216e7e4..5548608aeeb 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -61,15 +61,16 @@ class mail_mail(osv.Model):
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
# and during unlink() we will not cascade delete the parent and its attachments
'notification': fields.boolean('Is Notification',
- help='Mail has been created to notify people of an existing mail.message')
+ help='Mail has been created to notify people of an existing mail.message'),
+ # Bounce and tracking
+ 'opened': fields.integer(
+ 'Opened',
+ help='Number of times this email has been seen, using the OpenERP tracking.'),
+ 'replied': fields.integer(
+ 'Reply Received',
+ help='If checked, a reply to this email has been received.'),
}
- def _get_default_from(self, cr, uid, context=None):
- """ Kept for compatibility
- TDE TODO: remove me in 8.0
- """
- return self.pool['mail.message']._get_default_from(cr, uid, context=context)
-
_defaults = {
'state': 'outgoing',
}
@@ -158,6 +159,18 @@ class mail_mail(osv.Model):
def cancel(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
+ def set_opened(self, cr, uid, ids, context=None):
+ """ Increment opened counter """
+ for mail in self.browse(cr, uid, ids, context=context):
+ self.write(cr, uid, [mail.id], {'opened': (mail.opened + 1)}, context=context)
+ return True
+
+ def set_replied(self, cr, uid, ids, context=None):
+ """ Increment replied counter """
+ for mail in self.browse(cr, uid, ids, context=context):
+ self.write(cr, uid, [mail.id], {'replied': (mail.replied + 1)}, context=context)
+ return True
+
def process_email_queue(self, cr, uid, ids=None, context=None):
"""Send immediately queued messages, committing after each
message is sent - this is not transactional and should
@@ -226,13 +239,22 @@ class mail_mail(osv.Model):
}
if mail.notification:
fragment.update({
- 'message_id': mail.mail_message_id.id,
- })
+ 'message_id': mail.mail_message_id.id,
+ })
url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment)))
return _("""Access your messages and documents in OpenERP""") % url
else:
return None
+ def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
+ if not mail.auto_delete:
+ base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
+ track_url = urljoin(base_url, 'mail/track/%d/blank.gif' % mail.id)
+ print base_url, track_url
+ return '' % track_url
+ else:
+ return ''
+
def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
""" If subject is void and record_name defined: ' posted on '
@@ -257,8 +279,11 @@ class mail_mail(osv.Model):
# generate footer
link = self._get_partner_access_link(cr, uid, mail, partner, context=context)
+ tracking_url = self._get_tracking_url(cr, uid, mail, partner, context=context)
if link:
body = tools.append_content_to_html(body, link, plaintext=False, container_tag='div')
+ if tracking_url:
+ body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
return body
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
@@ -319,25 +344,37 @@ class mail_mail(osv.Model):
email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))
for partner in mail.recipient_ids:
email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
+ # headers
+ headers = {}
+ bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
+ catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
+ if bounce_alias and catchall_domain:
+ if mail.model and mail.res_id:
+ headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain)
+ else:
+ headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain)
# build an RFC2822 email.message.Message object and send it without queuing
+ res = None
for email in email_list:
msg = ir_mail_server.build_email(
- email_from = mail.email_from,
- email_to = email.get('email_to'),
- subject = email.get('subject'),
- body = email.get('body'),
- body_alternative = email.get('body_alternative'),
- email_cc = tools.email_split(mail.email_cc),
- reply_to = mail.reply_to,
- attachments = attachments,
- message_id = mail.message_id,
- references = mail.references,
- object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
- subtype = 'html',
- subtype_alternative = 'plain')
+ email_from=mail.email_from,
+ email_to=email.get('email_to'),
+ subject=email.get('subject'),
+ body=email.get('body'),
+ body_alternative=email.get('body_alternative'),
+ email_cc=tools.email_split(mail.email_cc),
+ reply_to=mail.reply_to,
+ attachments=attachments,
+ message_id=mail.message_id,
+ references=mail.references,
+ object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
+ subtype='html',
+ subtype_alternative='plain',
+ headers=headers)
res = ir_mail_server.send_email(cr, uid, msg,
- mail_server_id=mail.mail_server_id.id, context=context)
+ mail_server_id=mail.mail_server_id.id,
+ context=context)
if res:
mail.write({'state': 'sent', 'message_id': res})
mail_sent = True
diff --git a/addons/mail/mail_mail_view.xml b/addons/mail/mail_mail_view.xml
index f3728d4653a..3901a192225 100644
--- a/addons/mail/mail_mail_view.xml
+++ b/addons/mail/mail_mail_view.xml
@@ -14,7 +14,7 @@
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 306e5fd5905..89a9a4694b6 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -772,6 +772,7 @@ class mail_thread(osv.AbstractModel):
"""
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
fallback_model = model
+ bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
# Get email.message.Message variables for future processing
message_id = message.get('Message-Id')
@@ -780,6 +781,24 @@ class mail_thread(osv.AbstractModel):
references = decode_header(message, 'References')
in_reply_to = decode_header(message, 'In-Reply-To')
+ # 0. Verify whether this is a bounced email (wrong destination,...) -> use it to collect data, such as dead leads
+ if bounce_alias in email_to:
+ bounce_match = tools.bounce_re.search(email_to)
+ if bounce_match:
+ bounced_mail_id = bounce_match.group(1)
+ if self.pool['mail.mail'].exists(cr, uid, bounced_mail_id):
+ mail = self.pool['mail.mail'].browse(cr, uid, bounced_mail_id, context=context)
+ bounced_model = mail.model
+ bounced_thread_id = mail.res_id
+ else:
+ bounced_model = bounce_match.group(2)
+ bounced_thread_id = int(bounce_match.group(3)) if bounce_match.group(3) else 0
+ _logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s',
+ email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id)
+ if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce'):
+ self.pool[bounced_model].message_receive_bounce(cr, uid, [bounced_thread_id], mail_id=bounced_mail_id, context=context)
+ return []
+
# 1. Verify if this is a reply to an existing thread
thread_references = references or in_reply_to
ref_match = thread_references and tools.reference_re.search(thread_references)
@@ -1018,6 +1037,15 @@ class mail_thread(osv.AbstractModel):
self.write(cr, uid, ids, update_vals, context=context)
return True
+ def message_receive_bounce(self, cr, uid, ids, mail_id=None, context=None):
+ """Called by ``message_process`` when a bounce email (such as Undelivered
+ Mail Returned to Sender) is received for an existing thread. The default
+ behavior is to check is an integer ``message_bounce`` column exists.
+ If it is the case, its content is incremented. """
+ if self._all_columns.get('message_bounce'):
+ for obj in self.browse(cr, uid, ids, context=context):
+ self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
+
def _message_extract_payload(self, message, save_original=False):
"""Extract body as HTML and attachments from the mail message"""
attachments = []
From fd100054ccdb4cedcb2411bdf38a7388338ea814 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Wed, 7 Aug 2013 15:01:18 +0200
Subject: [PATCH 098/256] [FIX] mail_mail: fixed reply_to computation (before
create was leading to a wrong message_id to the embedded mail_message);
mail_thread; fixed private discussion not going through the route checking;
improved replying through mailgateway now incrementing the replied field of
mail_mail.
bzr revid: tde@openerp.com-20130807130118-yggvmontssofxt0q
---
addons/mail/mail_mail.py | 11 +++++++++--
addons/mail/mail_thread.py | 18 ++++++++++++------
2 files changed, 21 insertions(+), 8 deletions(-)
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index 5548608aeeb..130e63deefd 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -140,10 +140,17 @@ class mail_mail(osv.Model):
# notification field: if not set, set if mail comes from an existing mail.message
if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True
+ mail_id = super(mail_mail, self).create(cr, uid, values, context=context)
+
# reply_to: if not set, set with default values that require creation values
+ # but delegate after creation because of mail_message.message_id automatic
+ # creation using existence of reply_to
if not values.get('reply_to'):
- values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
- return super(mail_mail, self).create(cr, uid, values, context=context)
+ reply_to = self._get_reply_to(cr, uid, values, context=context)
+ if reply_to:
+ self.write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
+
+ return mail_id
def unlink(self, cr, uid, ids, context=None):
# cascade-delete the parent message for all mails that are not created for a notification
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 89a9a4694b6..f03f1239bd8 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -706,7 +706,7 @@ class mail_thread(osv.AbstractModel):
return ()
# New Document: check model accepts the mailgateway
- if not thread_id and not hasattr(model_pool, 'message_new'):
+ if not thread_id and model and not hasattr(model_pool, 'message_new'):
if assert_model:
assert hasattr(model_pool, 'message_new'), 'Model %s does not accept document creation, crashing' % model
_warn('model %s does not accept document creation, skipping' % model)
@@ -817,16 +817,16 @@ class mail_thread(osv.AbstractModel):
# 2. Reply to a private message
if in_reply_to:
- message_ids = self.pool.get('mail.message').search(cr, uid, [
+ mail_message_ids = self.pool.get('mail.message').search(cr, uid, [
('message_id', '=', in_reply_to),
'!', ('message_id', 'ilike', 'reply_to')
], limit=1, context=context)
- if message_ids:
- message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
+ if mail_message_ids:
+ mail_message = self.pool.get('mail.message').browse(cr, uid, mail_message_ids[0], context=context)
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
- email_from, email_to, message_id, message.id, custom_values, uid)
+ email_from, email_to, message_id, mail_message.id, custom_values, uid)
route = self.message_route_verify(cr, uid, message, message_dict,
- (message.model, message.res_id, custom_values, uid, None),
+ (mail_message.model, mail_message.res_id, custom_values, uid, None),
update_author=True, assert_model=True, create_fallback=True, context=context)
return route and [route] or []
@@ -1415,6 +1415,12 @@ class mail_thread(osv.AbstractModel):
parent_id = message_ids and message_ids[0] or False
# we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
elif parent_id:
+ # update original mail_mail if exists
+ if type == 'email':
+ mail_mail_ids = self.pool['mail.mail'].search(cr, SUPERUSER_ID, [('mail_message_id', '=', parent_id)], context=context)
+ for mail in self.pool['mail.mail'].browse(cr, SUPERUSER_ID, mail_mail_ids, context=context):
+ self.pool['mail.mail'].write(cr, SUPERUSER_ID, [mail.id], {'replied': mail.replied + 1}, context=context)
+
message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context)
# avoid loops when finding ancestors
processed_list = []
From 33fd9e01d6f50d7d6a3e379fd8544bb069a5d21d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Wed, 7 Aug 2013 15:03:34 +0200
Subject: [PATCH 099/256] [ADD] mass_mailing: added module for mass mailign
campaigns. First draft of new module : simple mass mailing campaign model,
linked to emails, compute some statistics mail.compose.message updated to the
mass mailing campaigns in mass mail mode
bzr revid: tde@openerp.com-20130807130334-nwd34fgsz4lc6lt1
---
addons/mass_mailing/__init__.py | 24 ++++++
addons/mass_mailing/__openerp__.py | 39 +++++++++
addons/mass_mailing/mail_mail.py | 35 ++++++++
addons/mass_mailing/mail_mail_view.xml | 18 ++++
addons/mass_mailing/mass_mailing.py | 84 +++++++++++++++++++
addons/mass_mailing/mass_mailing_view.xml | 48 +++++++++++
.../mass_mailing/security/ir.model.access.csv | 3 +
addons/mass_mailing/wizard/__init__.py | 22 +++++
.../wizard/mail_compose_message.py | 51 +++++++++++
.../wizard/mail_compose_message_view.xml | 20 +++++
10 files changed, 344 insertions(+)
create mode 100644 addons/mass_mailing/__init__.py
create mode 100644 addons/mass_mailing/__openerp__.py
create mode 100644 addons/mass_mailing/mail_mail.py
create mode 100644 addons/mass_mailing/mail_mail_view.xml
create mode 100644 addons/mass_mailing/mass_mailing.py
create mode 100644 addons/mass_mailing/mass_mailing_view.xml
create mode 100644 addons/mass_mailing/security/ir.model.access.csv
create mode 100644 addons/mass_mailing/wizard/__init__.py
create mode 100644 addons/mass_mailing/wizard/mail_compose_message.py
create mode 100644 addons/mass_mailing/wizard/mail_compose_message_view.xml
diff --git a/addons/mass_mailing/__init__.py b/addons/mass_mailing/__init__.py
new file mode 100644
index 00000000000..37f3850306a
--- /dev/null
+++ b/addons/mass_mailing/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import mass_mailing
+import mail_mail
+import wizard
diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py
new file mode 100644
index 00000000000..e6fbb42e886
--- /dev/null
+++ b/addons/mass_mailing/__openerp__.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+{
+ 'name': 'Mass Mailing Campaigns',
+ 'version': '1.0',
+ 'author': 'OpenERP',
+ 'website': 'http://www.openerp.com',
+ 'category': 'Marketing',
+ 'depends': ['mail', 'email_template'],
+ 'description': """TODO""",
+ 'data': [
+ 'mass_mailing_view.xml',
+ 'mail_mail_view.xml',
+ 'wizard/mail_compose_message_view.xml',
+ 'security/ir.model.access.csv',
+ ],
+ 'demo': [],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/addons/mass_mailing/mail_mail.py b/addons/mass_mailing/mail_mail.py
new file mode 100644
index 00000000000..d7f46446b56
--- /dev/null
+++ b/addons/mass_mailing/mail_mail.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+
+
+class MailMail(osv.Model):
+ """Add the mass mailing campaign data to mail"""
+ _name = 'mail.mail'
+ _inherit = ['mail.mail']
+
+ _columns = {
+ 'mass_mailing_campaign_id': fields.many2one(
+ 'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
+ ondelete='set null',
+ ),
+ }
diff --git a/addons/mass_mailing/mail_mail_view.xml b/addons/mass_mailing/mail_mail_view.xml
new file mode 100644
index 00000000000..dd66aede66f
--- /dev/null
+++ b/addons/mass_mailing/mail_mail_view.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ mail.mail.form.mass_mailing
+ mail.mail
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mass_mailing/mass_mailing.py b/addons/mass_mailing/mass_mailing.py
new file mode 100644
index 00000000000..91392759b0e
--- /dev/null
+++ b/addons/mass_mailing/mass_mailing.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+
+
+class MassMailingCampaign(osv.Model):
+ """Model of mass mailing campaigns.
+ """
+ _name = "mail.mass_mailing.campaign"
+ _description = 'Mass Mailing Campaign'
+
+ def _get_statistics(self, cr, uid, ids, name, arg, context=None):
+ """ Compute statistics of the mass mailing campaign """
+ results = dict.fromkeys(ids, False)
+ for campaign in self.browse(cr, uid, ids, context=context):
+ if not campaign.mail_ids:
+ results[campaign.id] = {
+ 'sent': 0,
+ 'opened_ratio': 0.0,
+ 'replied_ratio': 0.0,
+ 'bounce_ratio': 0.0,
+ }
+ continue
+ results[campaign.id] = {
+ 'sent': len(campaign.mail_ids),
+ 'opened_ratio': len([mail for mail in campaign.mail_ids if mail.opened]) * 1.0 / len(campaign.mail_ids),
+ 'replied_ratio': len([mail for mail in campaign.mail_ids if mail.replied]) * 1.0 / len(campaign.mail_ids),
+ 'bounce_ratio': 0.0,
+ }
+ return results
+
+ _columns = {
+ 'name': fields.char(
+ 'Campaign Name', required=True,
+ ),
+ 'template_id': fields.many2one(
+ 'email.template', 'Email Template',
+ ondelete='set null',
+ ),
+ 'mail_ids': fields.one2many(
+ 'mail.mail', 'mass_mailing_campaign_id',
+ 'Send Emails',
+ ),
+ # stat fields
+ 'sent': fields.function(
+ _get_statistics,
+ string='Sent Emails',
+ type='integer', multi='_get_statistics'
+ ),
+ 'opened_ratio': fields.function(
+ _get_statistics,
+ string='Opened Ratio',
+ type='float', multi='_get_statistics',
+ ),
+ 'replied_ratio': fields.function(
+ _get_statistics,
+ string='Replied Ratio',
+ type='float', multi='_get_statistics'
+ ),
+ 'bounce_ratio': fields.function(
+ _get_statistics,
+ string='Bounce Ratio',
+ type='float', multi='_get_statistics'
+ ),
+ }
diff --git a/addons/mass_mailing/mass_mailing_view.xml b/addons/mass_mailing/mass_mailing_view.xml
new file mode 100644
index 00000000000..bc28827343d
--- /dev/null
+++ b/addons/mass_mailing/mass_mailing_view.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+ mail.mass_mailing.campaign.tree
+ mail.mass_mailing.campaign
+ 10
+
+
+
+
+
+
+
+
+ mail.mass_mailing.campaign.form
+ mail.mass_mailing.campaign
+
+
+
+
+
+
+ Mass Mailing Campaigns
+ mail.mass_mailing.campaign
+ form
+ tree,form
+
+
+
+
+
+
+
diff --git a/addons/mass_mailing/security/ir.model.access.csv b/addons/mass_mailing/security/ir.model.access.csv
new file mode 100644
index 00000000000..af6eaa7bb06
--- /dev/null
+++ b/addons/mass_mailing/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_mass_mailing_campaign,mail.mass_mailing.campaign.template,model_mail_mass_mailing_campaign,,1,1,1,0
+access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
diff --git a/addons/mass_mailing/wizard/__init__.py b/addons/mass_mailing/wizard/__init__.py
new file mode 100644
index 00000000000..155849362cd
--- /dev/null
+++ b/addons/mass_mailing/wizard/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import mail_compose_message
diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py
new file mode 100644
index 00000000000..2f74ef2c732
--- /dev/null
+++ b/addons/mass_mailing/wizard/mail_compose_message.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+
+
+class MailComposeMessage(osv.TransientModel):
+ """Add concept of mass mailing campaign to the mail.compose.message wizard
+ """
+ _inherit = 'mail.compose.message'
+
+ _columns = {
+ 'mass_mail_campaign_id': fields.many2one(
+ 'mail.mass_mailing.campaign', 'Mass mailing campaign'
+ ),
+ }
+
+ def onchange_mass_mail_campaign_id(self, cr, uid, ids, mass_mail_campaign_id, context=None):
+ values = {}
+ if mass_mail_campaign_id:
+ campaign = self.pool['mail.mass_mailing.campaign'].browse(cr, uid, mass_mail_campaign_id, context=context)
+ if campaign and campaign.template_id:
+ values['template_id'] = campaign.template_id.id
+ return {'value': values}
+
+ def render_message(self, cr, uid, wizard, res_id, context=None):
+ """ Override method that generated the mail content by adding the mass
+ mailing campaign, when doing pure email mass mailing. """
+ res = super(MailComposeMessage, self).render_message(cr, uid, wizard, res_id, context=context)
+ print res, wizard.mass_mail_campaign_id
+ if wizard.composition_mode == 'mass_mail' and wizard.mass_mail_campaign_id: # TODO: which kind of mass mailing ?
+ res['mass_mailing_campaign_id'] = wizard.mass_mail_campaign_id.id
+ return res
diff --git a/addons/mass_mailing/wizard/mail_compose_message_view.xml b/addons/mass_mailing/wizard/mail_compose_message_view.xml
new file mode 100644
index 00000000000..bc5f1a9eb6a
--- /dev/null
+++ b/addons/mass_mailing/wizard/mail_compose_message_view.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ mail.compose.message.form.mass_mailing
+ mail.compose.message
+
+
+
+
+
+
+
+
+
+
From 70d2a3859240498816682418ab173dd9af89c4ac Mon Sep 17 00:00:00 2001
From: "Mansi Kariya (OpenERP Trainee)"
Date: Mon, 12 Aug 2013 14:43:21 +0530
Subject: [PATCH 100/256] [IMP] Optimized code
bzr revid: mansi.mk.179@gmail.com-20130812091321-o6ql1csd9sq78vs7
---
addons/purchase/purchase_view.xml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 0463527461e..ab32eb0372f 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -33,14 +33,13 @@
-
+ action="product.product_pricelist_action_for_purchase" id="menu_product_pricelist_action2_purchase"
+ parent="menu_purchase_config_pricelist" sequence="1" groups="product.group_purchase_pricelist" />
+
+
+
+
+
+
+
+
From 3a67d7dc992963c27a993010ab355cc3b6d88a78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Mon, 26 Aug 2013 11:56:00 +0200
Subject: [PATCH 103/256] [FIX] crm: fixed a bug introduced when merging trunk
bzr revid: tde@openerp.com-20130826095600-bgvdzah9nwb2vu0l
---
addons/crm/crm_lead_view.xml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index acbec848da0..da3d91491b4 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -173,8 +173,7 @@
-
+ widget="selection"/>
From b35a44f66d947b03542b8eb3899fcd3adcf476ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Tue, 27 Aug 2013 15:30:58 +0200
Subject: [PATCH 104/256] [IMP] mail_mail, mail_message: various improvements
to try to improve message creation time Replaced some read by browse Moved
get_reply_to from mail_mail to mail_message Hint: specifying email_from,
reply_to help to enhance computation time
[REF] mail: cleaned some tests, renamed a file, moved mail_group tests into a dedicated file
bzr revid: tde@openerp.com-20130827133058-ko0g0ib0f0jihmdk
---
addons/email_template/tests/test_mail.py | 4 +-
addons/mail/mail_mail.py | 70 +------
addons/mail/mail_message.py | 99 +++++++---
addons/mail/tests/__init__.py | 3 +-
.../tests/{test_mail_base.py => common.py} | 58 +++++-
addons/mail/tests/test_invite.py | 4 +-
addons/mail/tests/test_mail_features.py | 4 +-
addons/mail/tests/test_mail_gateway.py | 174 +----------------
addons/mail/tests/test_mail_group.py | 71 +++++++
addons/mail/tests/test_mail_message.py | 179 +++++++++++++-----
addons/mail/tests/test_message_read.py | 4 +-
addons/portal/tests/test_portal.py | 4 +-
addons/project/tests/test_project_base.py | 4 +-
13 files changed, 346 insertions(+), 332 deletions(-)
rename addons/mail/tests/{test_mail_base.py => common.py} (66%)
create mode 100644 addons/mail/tests/test_mail_group.py
diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py
index d5feea9ebc4..adb550aa617 100644
--- a/addons/email_template/tests/test_mail.py
+++ b/addons/email_template/tests/test_mail.py
@@ -20,10 +20,10 @@
##############################################################################
import base64
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
-class test_message_compose(TestMailBase):
+class test_message_compose(TestMail):
def setUp(self):
super(test_message_compose, self).setUp()
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index f3cf5ce9e57..e5cb2b56eb3 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -82,74 +82,11 @@ class mail_mail(osv.Model):
context = dict(context, default_type=None)
return super(mail_mail, self).default_get(cr, uid, fields, context=context)
- def _get_reply_to(self, cr, uid, values, context=None):
- """ Return a specific reply_to: alias of the document through message_get_reply_to
- or take the email_from
- """
- # if value specified: directly return it
- if values.get('reply_to'):
- return values.get('reply_to')
- format_name = True # whether to use a 'Followers of Pigs catchall alias
- if not email_reply_to:
- catchall_alias = ir_config_parameter.get_param(cr, uid, "mail.catchall.alias", context=context)
- if catchall_domain and catchall_alias:
- email_reply_to = '%s@%s' % (catchall_alias, catchall_domain)
-
- # still no reply_to -> reply_to will be the email_from
- if not email_reply_to and email_from:
- email_reply_to = email_from
-
- # format 'Document name '
- if email_reply_to and model and res_id and format_name:
- emails = tools.email_split(email_reply_to)
- if emails:
- email_reply_to = emails[0]
- document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0]
- if document_name:
- # sanitize document name
- sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1])
- # generate reply to
- email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to)
-
- return email_reply_to
-
def create(self, cr, uid, values, context=None):
# notification field: if not set, set if mail comes from an existing mail.message
if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True
- mail_id = super(mail_mail, self).create(cr, uid, values, context=context)
-
- # reply_to: if not set, set with default values that require creation values
- # but delegate after creation because of mail_message.message_id automatic
- # creation using existence of reply_to
- if not values.get('reply_to'):
- reply_to = self._get_reply_to(cr, uid, values, context=context)
- if reply_to:
- self.write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
- return mail_id
+ return super(mail_mail, self).create(cr, uid, values, context=context)
def unlink(self, cr, uid, ids, context=None):
# cascade-delete the parent message for all mails that are not created for a notification
@@ -226,11 +163,6 @@ class mail_mail(osv.Model):
# mail_mail formatting, tools and send mechanism
#------------------------------------------------------
- # TODO in 8.0(+): maybe factorize this to enable in modules link generation
- # independently of mail_mail model
- # TODO in 8.0(+): factorize doc name sanitized and 'Followers of ...' formatting
- # because it begins to appear everywhere
-
def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None):
""" Generate URLs for links in mails:
- partner is an user and has read access to the document: direct link to document with model, res_id
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index bcb66433964..d63025b0912 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -20,6 +20,8 @@
##############################################################################
import logging
+import re
+
from openerp import tools
from email.header import decode_header
@@ -87,6 +89,7 @@ class mail_message(osv.Model):
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
""" Return the related document name, using name_get. It is done using
SUPERUSER_ID, to be sure to have the record name correctly stored. """
+ # return dict.fromkeys(ids, False)
# TDE note: regroup by model/ids, to have less queries to perform
result = dict.fromkeys(ids, False)
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
@@ -206,11 +209,11 @@ class mail_message(osv.Model):
raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias."))
def _get_default_author(self, cr, uid, context=None):
- return self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
+ return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
_defaults = {
'type': 'email',
- 'date': lambda *a: fields.datetime.now(),
+ 'date': fields.datetime.now(),
'author_id': lambda self, cr, uid, ctx=None: self._get_default_author(cr, uid, ctx),
'body': '',
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
@@ -645,7 +648,8 @@ class mail_message(osv.Model):
elif not ids:
return ids
- pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
+ # pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
+ pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
model_ids = {}
@@ -705,7 +709,7 @@ class mail_message(osv.Model):
ids = [ids]
not_obj = self.pool.get('mail.notification')
fol_obj = self.pool.get('mail.followers')
- partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
+ partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id
# Read mail_message.ids to have their values
message_values = dict.fromkeys(ids)
@@ -774,17 +778,66 @@ class mail_message(osv.Model):
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
(self._description, operation))
+ def _get_reply_to(self, cr, uid, values, context=None):
+ """ Return a specific reply_to: alias of the document through message_get_reply_to
+ or take the email_from
+ """
+ email_reply_to = None
+
+ ir_config_parameter = self.pool.get("ir.config_parameter")
+ catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context)
+
+ # model, res_id, email_from: comes from values OR related message
+ model, res_id, email_from = values.get('model'), values.get('res_id'), values.get('email_from')
+
+ # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
+ if not email_reply_to and model and res_id and catchall_domain and hasattr(self.pool[model], 'message_get_reply_to'):
+ email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0]
+ # no alias reply_to -> catchall alias
+ if not email_reply_to and catchall_domain:
+ catchall_alias = ir_config_parameter.get_param(cr, uid, "mail.catchall.alias", context=context)
+ if catchall_alias:
+ email_reply_to = '%s@%s' % (catchall_alias, catchall_domain)
+ # still no reply_to -> reply_to will be the email_from
+ if not email_reply_to and email_from:
+ email_reply_to = email_from
+
+ # format 'Document name '
+ if email_reply_to and model and res_id:
+ emails = tools.email_split(email_reply_to)
+ if emails:
+ email_reply_to = emails[0]
+ document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0]
+ if document_name:
+ # sanitize document name
+ sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1])
+ # generate reply to
+ email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to)
+
+ return email_reply_to
+
+ def _get_message_id(self, cr, uid, values, context=None):
+ message_id = None
+ if not values.get('message_id') and values.get('reply_to'):
+ message_id = tools.generate_tracking_message_id('reply_to')
+ elif not values.get('message_id') and values.get('res_id') and values.get('model'):
+ message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
+ elif not values.get('message_id'):
+ message_id = tools.generate_tracking_message_id('private')
+ return message_id
+
def create(self, cr, uid, values, context=None):
if context is None:
context = {}
default_starred = context.pop('default_starred', False)
- # generate message_id, to redirect answers to the right discussion thread
- if not values.get('message_id') and values.get('reply_to'):
- values['message_id'] = tools.generate_tracking_message_id('reply_to')
- elif not values.get('message_id') and values.get('res_id') and values.get('model'):
- values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
- elif not values.get('message_id'):
- values['message_id'] = tools.generate_tracking_message_id('private')
+
+ if not values.get('email_from'): # needed to compute reply_to
+ values['email_from'] = self._get_default_from(cr, uid, context=context)
+ if not values.get('message_id'):
+ values['message_id'] = self._get_message_id(cr, uid, values, context=context)
+ if not values.get('reply_to'):
+ values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
+
newid = super(mail_message, self).create(cr, uid, values, context)
self._notify(cr, uid, newid, context=context,
force_send=context.get('mail_notify_force_send', True),
@@ -914,26 +967,28 @@ class mail_message(osv.Model):
if message.subtype_id and message.model and message.res_id:
fol_obj = self.pool.get("mail.followers")
# browse as SUPERUSER because rules could restrict the search results
- fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
- ('res_model', '=', message.model),
- ('res_id', '=', message.res_id),
- ('subtype_ids', 'in', message.subtype_id.id)
+ fol_ids = fol_obj.search(
+ cr, SUPERUSER_ID, [
+ ('res_model', '=', message.model),
+ ('res_id', '=', message.res_id),
+ ('subtype_ids', 'in', message.subtype_id.id)
], context=context)
- partners_to_notify |= set(fo.partner_id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
+ partners_to_notify |= set(fo.partner_id.id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
# remove me from notified partners, unless the message is written on my own wall
if message.subtype_id and message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id:
- partners_to_notify |= set([message.author_id])
+ partners_to_notify |= set([message.author_id.id])
elif message.author_id:
- partners_to_notify -= set([message.author_id])
+ partners_to_notify -= set([message.author_id.id])
# all partner_ids of the mail.message have to be notified regardless of the above (even the author if explicitly added!)
if message.partner_ids:
- partners_to_notify |= set(message.partner_ids)
+ partners_to_notify |= set([p.id for p in message.partner_ids])
# notify
- if partners_to_notify:
- notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context,
- force_send=force_send, user_signature=user_signature)
+ notification_obj._notify(
+ cr, uid, newid, partners_to_notify=list(partners_to_notify), context=context,
+ force_send=force_send, user_signature=user_signature
+ )
message.refresh()
# An error appear when a user receive a notification without notifying
diff --git a/addons/mail/tests/__init__.py b/addons/mail/tests/__init__.py
index ff1080b0580..242beb60bf1 100644
--- a/addons/mail/tests/__init__.py
+++ b/addons/mail/tests/__init__.py
@@ -19,9 +19,10 @@
#
##############################################################################
-from . import test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
+from . import test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
checks = [
+ test_mail_group,
test_mail_message,
test_mail_features,
test_mail_gateway,
diff --git a/addons/mail/tests/test_mail_base.py b/addons/mail/tests/common.py
similarity index 66%
rename from addons/mail/tests/test_mail_base.py
rename to addons/mail/tests/common.py
index b0f9f72b6f7..68a329e043a 100644
--- a/addons/mail/tests/test_mail_base.py
+++ b/addons/mail/tests/common.py
@@ -22,7 +22,7 @@
from openerp.tests import common
-class TestMailBase(common.TransactionCase):
+class TestMail(common.TransactionCase):
def _mock_smtp_gateway(self, *args, **kwargs):
return args[2]['Message-Id']
@@ -39,7 +39,7 @@ class TestMailBase(common.TransactionCase):
return self._build_email(*args, **kwargs)
def setUp(self):
- super(TestMailBase, self).setUp()
+ super(TestMail, self).setUp()
cr, uid = self.cr, self.uid
# Install mock SMTP gateway
@@ -68,12 +68,46 @@ class TestMailBase(common.TransactionCase):
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
+ # Partner Data
+
+ # User Data: employee, noone
+ self.user_employee_id = self.res_users.create(cr, uid, {
+ 'name': 'Ernest Employee',
+ 'login': 'ernest',
+ 'alias_name': 'ernest',
+ 'email': 'e.e@example.com',
+ 'signature': '--\nErnest',
+ 'notification_email_send': 'comment',
+ 'groups_id': [(6, 0, [self.group_employee_id])]
+ }, {'no_reset_password': True})
+ self.user_noone_id = self.res_users.create(cr, uid, {
+ 'name': 'Noemie NoOne',
+ 'login': 'noemie',
+ 'alias_name': 'noemie',
+ 'email': 'n.n@example.com',
+ 'signature': '--\nNoemie',
+ 'notification_email_send': 'comment',
+ 'groups_id': [(6, 0, [])]
+ }, {'no_reset_password': True})
+
# Test users to use through the various tests
self.res_users.write(cr, uid, uid, {'name': 'Administrator'})
- self.user_raoul_id = self.res_users.create(cr, uid,
- {'name': 'Raoul Grosbedon', 'signature': 'SignRaoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'alias_name': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
- self.user_bert_id = self.res_users.create(cr, uid,
- {'name': 'Bert Tartignole', 'signature': 'SignBert', 'email': 'bert@bert.fr', 'login': 'bert', 'alias_name': 'bert', 'groups_id': [(6, 0, [])]})
+ self.user_raoul_id = self.res_users.create(cr, uid, {
+ 'name': 'Raoul Grosbedon',
+ 'signature': 'SignRaoul',
+ 'email': 'raoul@raoul.fr',
+ 'login': 'raoul',
+ 'alias_name': 'raoul',
+ 'groups_id': [(6, 0, [self.group_employee_id])]
+ })
+ self.user_bert_id = self.res_users.create(cr, uid, {
+ 'name': 'Bert Tartignole',
+ 'signature': 'SignBert',
+ 'email': 'bert@bert.fr',
+ 'login': 'bert',
+ 'alias_name': 'bert',
+ 'groups_id': [(6, 0, [])]
+ })
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id)
self.user_admin = self.res_users.browse(cr, uid, uid)
@@ -82,13 +116,19 @@ class TestMailBase(common.TransactionCase):
self.partner_bert_id = self.user_bert.partner_id.id
# Test 'pigs' group to use through the various tests
- self.group_pigs_id = self.mail_group.create(cr, uid,
+ self.group_pigs_id = self.mail_group.create(
+ cr, uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !', 'alias_name': 'group+pigs'},
- {'mail_create_nolog': True})
+ {'mail_create_nolog': True}
+ )
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+ # Test mail.group: public to provide access to everyone
+ self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
+ # Test mail.group: private to restrict access
+ self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
def tearDown(self):
# Remove mocks
self.registry('ir.mail_server').build_email = self._build_email
self.registry('ir.mail_server').send_email = self._send_email
- super(TestMailBase, self).tearDown()
+ super(TestMail, self).tearDown()
diff --git a/addons/mail/tests/test_invite.py b/addons/mail/tests/test_invite.py
index faa9fd55793..c4484d254bb 100644
--- a/addons/mail/tests/test_invite.py
+++ b/addons/mail/tests/test_invite.py
@@ -19,10 +19,10 @@
#
##############################################################################
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
-class test_invite(TestMailBase):
+class test_invite(TestMail):
def test_00_basic_invite(self):
cr, uid = self.cr, self.uid
diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py
index e9563e19246..970f07f519e 100644
--- a/addons/mail/tests/test_mail_features.py
+++ b/addons/mail/tests/test_mail_features.py
@@ -21,12 +21,12 @@
from openerp.addons.mail.mail_mail import mail_mail
from openerp.addons.mail.mail_thread import mail_thread
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
from openerp.tools import mute_logger, email_split
from openerp.tools.mail import html_sanitize
-class test_mail(TestMailBase):
+class test_mail(TestMail):
def test_000_alias_setup(self):
""" Test basic mail.alias setup works, before trying to use them for routing """
diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py
index 3fd8ebd3101..b2a1c3250aa 100644
--- a/addons/mail/tests/test_mail_gateway.py
+++ b/addons/mail/tests/test_mail_gateway.py
@@ -19,7 +19,7 @@
#
##############################################################################
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
from openerp.tools import mute_logger
MAIL_TEMPLATE = """Return-Path:
@@ -143,173 +143,9 @@ dGVzdAo=
--089e01536c4ed4d17204e49b8e96--"""
-class TestMailgateway(TestMailBase):
+class TestMailgateway(TestMail):
- def test_00_partner_find_from_email(self):
- """ Tests designed for partner fetch based on emails. """
- cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
-
- # --------------------------------------------------
- # Data creation
- # --------------------------------------------------
- # 1 - Partner ARaoul
- p_a_id = self.res_partner.create(cr, uid, {'name': 'ARaoul', 'email': 'test@test.fr'})
-
- # --------------------------------------------------
- # CASE1: without object
- # --------------------------------------------------
-
- # Do: find partner with email -> first partner should be found
- partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul '], link_mail=False)[0]
- self.assertEqual(partner_info['full_name'], 'Maybe Raoul ',
- 'mail_thread: message_partner_info_from_emails did not handle email')
- self.assertEqual(partner_info['partner_id'], p_a_id,
- 'mail_thread: message_partner_info_from_emails wrong partner found')
-
- # Data: add some data about partners
- # 2 - User BRaoul
- p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
-
- # Do: find partner with email -> first user should be found
- partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul '], link_mail=False)[0]
- self.assertEqual(partner_info['partner_id'], p_b_id,
- 'mail_thread: message_partner_info_from_emails wrong partner found')
-
- # --------------------------------------------------
- # CASE1: with object
- # --------------------------------------------------
-
- # Do: find partner in group where there is a follower with the email -> should be taken
- self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
- partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul '], link_mail=False)[0]
- self.assertEqual(partner_info['partner_id'], p_b_id,
- 'mail_thread: message_partner_info_from_emails wrong partner found')
-
- def test_05_mail_message_mail_mail(self):
- """ Tests designed for testing email values based on mail.message, aliases, ... """
- cr, uid, user_raoul_id = self.cr, self.uid, self.user_raoul_id
-
- # Data: update + generic variables
- reply_to1 = '_reply_to1@example.com'
- reply_to2 = '_reply_to2@example.com'
- email_from1 = 'from@example.com'
- alias_domain = 'schlouby.fr'
- raoul_from = 'Raoul Grosbedon '
- raoul_from_alias = 'Raoul Grosbedon '
- raoul_reply = '"Followers of Pigs" '
- raoul_reply_alias = '"Followers of Pigs" '
- # Data: remove alias_domain to see emails with alias
- param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
- self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
-
- # Do: free message; specified values > default values
- msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1})
- msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
- # Test: message content
- self.assertIn('reply_to', msg.message_id,
- 'mail_message: message_id should be specific to a mail_message with a given reply_to')
- self.assertEqual(msg.reply_to, reply_to1,
- 'mail_message: incorrect reply_to: should come from values')
- self.assertEqual(msg.email_from, email_from1,
- 'mail_message: incorrect email_from: should come from values')
- # Do: create a mail_mail with the previous mail_message
- mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, reply_to1,
- 'mail_mail: incorrect reply_to: should come from mail.message')
- self.assertEqual(mail.email_from, email_from1,
- 'mail_mail: incorrect email_from: should come from mail.message')
- # Do: create a mail_mail with the previous mail_message + specified reply_to
- mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2})
- mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, reply_to2,
- 'mail_mail: incorrect reply_to: should come from values')
- self.assertEqual(mail.email_from, email_from1,
- 'mail_mail: incorrect email_from: should come from mail.message')
-
- # Do: mail_message attached to a document
- msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
- msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
- # Test: message content
- self.assertIn('mail.group', msg.message_id,
- 'mail_message: message_id should contain model')
- self.assertIn('%s' % self.group_pigs_id, msg.message_id,
- 'mail_message: message_id should contain res_id')
- self.assertFalse(msg.reply_to,
- 'mail_message: incorrect reply_to: should not be generated if not specified')
- self.assertEqual(msg.email_from, raoul_from,
- 'mail_message: incorrect email_from: should be Raoul')
- # Do: create a mail_mail based on the previous mail_message
- mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, raoul_reply,
- 'mail_mail: incorrect reply_to: should be Raoul')
-
- # Data: set catchall domain
- self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
- self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
-
- # Update message
- self.mail_message.write(cr, user_raoul_id, [msg_id], {'email_from': False, 'reply_to': False})
- msg.refresh()
- # Do: create a mail_mail based on the previous mail_message
- mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, raoul_reply_alias,
- 'mail_mail: incorrect reply_to: should be Pigs alias')
-
- # Update message: test alias on email_from
- msg_id = self.mail_message.create(cr, user_raoul_id, {})
- msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
- # Do: create a mail_mail based on the previous mail_message
- mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, raoul_from_alias,
- 'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
-
- # Update message
- self.mail_message.write(cr, user_raoul_id, [msg_id], {'res_id': False, 'email_from': 'someone@schlouby.fr', 'reply_to': False})
- msg.refresh()
- # Do: create a mail_mail based on the previous mail_message
- mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, msg.email_from,
- 'mail_mail: incorrect reply_to: should be message email_from')
-
- # Data: set catchall alias
- self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
-
- # Update message
- self.mail_message.write(cr, uid, [msg_id], {'email_from': False, 'reply_to': False})
- msg.refresh()
- # Do: create a mail_mail based on the previous mail_message
- mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, uid, mail_id)
- # Test: mail_mail Content-Type
- self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
- 'mail_mail: reply_to should equal the catchall email alias')
-
- # Do: create a mail_mail
- mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel'})
- mail = self.mail_mail.browse(cr, uid, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
- 'mail_mail: reply_to should equal the catchall email alias')
-
- # Do: create a mail_mail
- mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
- mail = self.mail_mail.browse(cr, uid, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, 'someone@example.com',
- 'mail_mail: reply_to should equal the rpely_to given to create')
-
- def test_09_message_parse(self):
+ def test_00_message_parse(self):
""" Testing incoming emails parsing """
cr, uid = self.cr, self.uid
@@ -738,9 +574,7 @@ class TestMailgateway(TestMailBase):
'message_post: private discussion: incorrect notified recipients')
self.assertEqual(msg.model, False,
'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
- # Test: message reply_to and message-id
- self.assertFalse(msg.reply_to,
- 'message_post: private discussion: initial message should not have any reply_to specified')
+ # Test: message-id
self.assertIn('openerp-private', msg.message_id,
'message_post: private discussion: message-id should contain the private keyword')
diff --git a/addons/mail/tests/test_mail_group.py b/addons/mail/tests/test_mail_group.py
new file mode 100644
index 00000000000..c1512441b13
--- /dev/null
+++ b/addons/mail/tests/test_mail_group.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2012-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.mail.tests.common import TestMail
+from openerp.osv.orm import except_orm
+from openerp.tools import mute_logger
+
+
+class TestMailGroup(TestMail):
+
+ @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
+ def test_00_mail_group_access_rights(self):
+ """ Testing mail_group access rights and basic mail_thread features """
+ cr, uid, user_noone_id, user_employee_id = self.cr, self.uid, self.user_noone_id, self.user_employee_id
+
+ # Do: Bert reads Jobs -> ok, public
+ self.mail_group.read(cr, user_noone_id, [self.group_jobs_id])
+ # Do: Bert read Pigs -> ko, restricted to employees
+ with self.assertRaises(except_orm):
+ self.mail_group.read(cr, user_noone_id, [self.group_pigs_id])
+ # Do: Raoul read Pigs -> ok, belong to employees
+ self.mail_group.read(cr, user_employee_id, [self.group_pigs_id])
+
+ # Do: Bert creates a group -> ko, no access rights
+ with self.assertRaises(except_orm):
+ self.mail_group.create(cr, user_noone_id, {'name': 'Test'})
+ # Do: Raoul creates a restricted group -> ok
+ new_group_id = self.mail_group.create(cr, user_employee_id, {'name': 'Test'})
+ # Do: Bert added in followers, read -> ok, in followers
+ self.mail_group.message_subscribe_users(cr, uid, [new_group_id], [user_noone_id])
+ self.mail_group.read(cr, user_noone_id, [new_group_id])
+
+ # Do: Raoul reads Priv -> ko, private
+ with self.assertRaises(except_orm):
+ self.mail_group.read(cr, user_employee_id, [self.group_priv_id])
+ # Do: Raoul added in follower, read -> ok, in followers
+ self.mail_group.message_subscribe_users(cr, uid, [self.group_priv_id], [user_employee_id])
+ self.mail_group.read(cr, user_employee_id, [self.group_priv_id])
+
+ # Do: Raoul write on Jobs -> ok
+ self.mail_group.write(cr, user_employee_id, [self.group_priv_id], {'name': 'modified'})
+ # Do: Bert cannot write on Private -> ko (read but no write)
+ with self.assertRaises(except_orm):
+ self.mail_group.write(cr, user_noone_id, [self.group_priv_id], {'name': 're-modified'})
+ # Test: Bert cannot unlink the group
+ with self.assertRaises(except_orm):
+ self.mail_group.unlink(cr, user_noone_id, [self.group_priv_id])
+ # Do: Raoul unlinks the group, there are no followers and messages left
+ self.mail_group.unlink(cr, user_employee_id, [self.group_priv_id])
+ fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
+ self.assertFalse(fol_ids, 'unlinked document should not have any followers left')
+ msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
+ self.assertFalse(msg_ids, 'unlinked document should not have any followers left')
diff --git a/addons/mail/tests/test_mail_message.py b/addons/mail/tests/test_mail_message.py
index f422219000c..5dc5e9e247d 100644
--- a/addons/mail/tests/test_mail_message.py
+++ b/addons/mail/tests/test_mail_message.py
@@ -19,66 +19,147 @@
#
##############################################################################
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
-class test_mail_access_rights(TestMailBase):
+class TestMailMail(TestMail):
- def setUp(self):
- super(test_mail_access_rights, self).setUp()
- cr, uid = self.cr, self.uid
+ def test_00_partner_find_from_email(self):
+ """ Tests designed for partner fetch based on emails. """
+ cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
- # Test mail.group: public to provide access to everyone
- self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
- # Test mail.group: private to restrict access
- self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
+ # --------------------------------------------------
+ # Data creation
+ # --------------------------------------------------
+ # 1 - Partner ARaoul
+ p_a_id = self.res_partner.create(cr, uid, {'name': 'ARaoul', 'email': 'test@test.fr'})
- @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
- def test_00_mail_group_access_rights(self):
- """ Testing mail_group access rights and basic mail_thread features """
- cr, uid, user_bert_id, user_raoul_id = self.cr, self.uid, self.user_bert_id, self.user_raoul_id
+ # --------------------------------------------------
+ # CASE1: without object
+ # --------------------------------------------------
- # Do: Bert reads Jobs -> ok, public
- self.mail_group.read(cr, user_bert_id, [self.group_jobs_id])
- # Do: Bert read Pigs -> ko, restricted to employees
- self.assertRaises(except_orm, self.mail_group.read,
- cr, user_bert_id, [self.group_pigs_id])
- # Do: Raoul read Pigs -> ok, belong to employees
- self.mail_group.read(cr, user_raoul_id, [self.group_pigs_id])
+ # Do: find partner with email -> first partner should be found
+ partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul '], link_mail=False)[0]
+ self.assertEqual(partner_info['full_name'], 'Maybe Raoul ',
+ 'mail_thread: message_partner_info_from_emails did not handle email')
+ self.assertEqual(partner_info['partner_id'], p_a_id,
+ 'mail_thread: message_partner_info_from_emails wrong partner found')
- # Do: Bert creates a group -> ko, no access rights
- self.assertRaises(except_orm, self.mail_group.create,
- cr, user_bert_id, {'name': 'Test'})
- # Do: Raoul creates a restricted group -> ok
- new_group_id = self.mail_group.create(cr, user_raoul_id, {'name': 'Test'})
- # Do: Bert added in followers, read -> ok, in followers
- self.mail_group.message_subscribe_users(cr, uid, [new_group_id], [user_bert_id])
- self.mail_group.read(cr, user_bert_id, [new_group_id])
+ # Data: add some data about partners
+ # 2 - User BRaoul
+ p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
- # Do: Raoul reads Priv -> ko, private
- self.assertRaises(except_orm, self.mail_group.read,
- cr, user_raoul_id, [self.group_priv_id])
- # Do: Raoul added in follower, read -> ok, in followers
- self.mail_group.message_subscribe_users(cr, uid, [self.group_priv_id], [user_raoul_id])
- self.mail_group.read(cr, user_raoul_id, [self.group_priv_id])
+ # Do: find partner with email -> first user should be found
+ partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul '], link_mail=False)[0]
+ self.assertEqual(partner_info['partner_id'], p_b_id,
+ 'mail_thread: message_partner_info_from_emails wrong partner found')
- # Do: Raoul write on Jobs -> ok
- self.mail_group.write(cr, user_raoul_id, [self.group_priv_id], {'name': 'modified'})
- # Do: Bert cannot write on Private -> ko (read but no write)
- self.assertRaises(except_orm, self.mail_group.write,
- cr, user_bert_id, [self.group_priv_id], {'name': 're-modified'})
- # Test: Bert cannot unlink the group
- self.assertRaises(except_orm,
- self.mail_group.unlink,
- cr, user_bert_id, [self.group_priv_id])
- # Do: Raoul unlinks the group, there are no followers and messages left
- self.mail_group.unlink(cr, user_raoul_id, [self.group_priv_id])
- fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
- self.assertFalse(fol_ids, 'unlinked document should not have any followers left')
- msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
- self.assertFalse(msg_ids, 'unlinked document should not have any followers left')
+ # --------------------------------------------------
+ # CASE1: with object
+ # --------------------------------------------------
+
+ # Do: find partner in group where there is a follower with the email -> should be taken
+ self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
+ partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul '], link_mail=False)[0]
+ self.assertEqual(partner_info['partner_id'], p_b_id,
+ 'mail_thread: message_partner_info_from_emails wrong partner found')
+
+
+class TestMailMessage(TestMail):
+
+ def test_00_mail_message_values(self):
+ """ Tests designed for testing email values based on mail.message, aliases, ... """
+ cr, uid, user_raoul_id = self.cr, self.uid, self.user_raoul_id
+
+ # Data: update + generic variables
+ reply_to1 = '_reply_to1@example.com'
+ reply_to2 = '_reply_to2@example.com'
+ email_from1 = 'from@example.com'
+ alias_domain = 'schlouby.fr'
+ raoul_from = 'Raoul Grosbedon '
+ raoul_from_alias = 'Raoul Grosbedon '
+ raoul_reply = '"Followers of Pigs" '
+ raoul_reply_alias = '"Followers of Pigs" '
+
+ # --------------------------------------------------
+ # Case1: without alias_domain
+ # --------------------------------------------------
+ param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
+ self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
+
+ # Do: free message; specified values > default values
+ msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1})
+ msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
+ # Test: message content
+ self.assertIn('reply_to', msg.message_id,
+ 'mail_message: message_id should be specific to a mail_message with a given reply_to')
+ self.assertEqual(msg.reply_to, reply_to1,
+ 'mail_message: incorrect reply_to: should come from values')
+ self.assertEqual(msg.email_from, email_from1,
+ 'mail_message: incorrect email_from: should come from values')
+
+ # Do: create a mail_mail with the previous mail_message + specified reply_to
+ mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2})
+ mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
+ # Test: mail_mail content
+ self.assertEqual(mail.reply_to, reply_to2,
+ 'mail_mail: incorrect reply_to: should come from values')
+ self.assertEqual(mail.email_from, email_from1,
+ 'mail_mail: incorrect email_from: should come from mail.message')
+
+ # Do: mail_message attached to a document
+ msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
+ msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
+ # Test: message content
+ self.assertIn('mail.group', msg.message_id,
+ 'mail_message: message_id should contain model')
+ self.assertIn('%s' % self.group_pigs_id, msg.message_id,
+ 'mail_message: message_id should contain res_id')
+ self.assertEqual(msg.reply_to, raoul_reply,
+ 'mail_message: incorrect reply_to: should be Raoul')
+ self.assertEqual(msg.email_from, raoul_from,
+ 'mail_message: incorrect email_from: should be Raoul')
+
+ # --------------------------------------------------
+ # Case2: with alias_domain, without catchall alias
+ # --------------------------------------------------
+ self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
+ self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
+
+ # Update message
+ msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
+ msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
+ # Test: generated reply_to
+ self.assertEqual(msg.reply_to, raoul_reply_alias,
+ 'mail_mail: incorrect reply_to: should be Pigs alias')
+
+ # Update message: test alias on email_from
+ msg_id = self.mail_message.create(cr, user_raoul_id, {})
+ msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
+ # Test: generated reply_to
+ self.assertEqual(msg.reply_to, raoul_from_alias,
+ 'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
+
+ # --------------------------------------------------
+ # Case2: with alias_domain and catchall alias
+ # --------------------------------------------------
+ self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
+
+ # Update message
+ msg_id = self.mail_message.create(cr, user_raoul_id, {})
+ msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
+ # Test: generated reply_to
+ self.assertEqual(msg.reply_to, 'gateway@schlouby.fr',
+ 'mail_mail: reply_to should equal the catchall email alias')
+
+ # Do: create a mail_mail
+ mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
+ mail = self.mail_mail.browse(cr, uid, mail_id)
+ # Test: mail_mail content
+ self.assertEqual(mail.reply_to, 'someone@example.com',
+ 'mail_mail: reply_to should equal the rpely_to given to create')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_10_mail_message_search_access_rights(self):
diff --git a/addons/mail/tests/test_message_read.py b/addons/mail/tests/test_message_read.py
index a4ff3685788..c02e9a32278 100644
--- a/addons/mail/tests/test_message_read.py
+++ b/addons/mail/tests/test_message_read.py
@@ -19,10 +19,10 @@
#
##############################################################################
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
-class test_mail_access_rights(TestMailBase):
+class test_mail_access_rights(TestMail):
def test_00_message_read(self):
""" Tests for message_read and expandables. """
diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py
index e2b66e65af6..4d301458365 100644
--- a/addons/portal/tests/test_portal.py
+++ b/addons/portal/tests/test_portal.py
@@ -19,12 +19,12 @@
#
##############################################################################
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
from openerp.osv.orm import except_orm
from openerp.tools.misc import mute_logger
-class test_portal(TestMailBase):
+class test_portal(TestMail):
def setUp(self):
super(test_portal, self).setUp()
diff --git a/addons/project/tests/test_project_base.py b/addons/project/tests/test_project_base.py
index f82561766fe..70f8b59812f 100644
--- a/addons/project/tests/test_project_base.py
+++ b/addons/project/tests/test_project_base.py
@@ -19,10 +19,10 @@
#
##############################################################################
-from openerp.addons.mail.tests.test_mail_base import TestMailBase
+from openerp.addons.mail.tests.common import TestMail
-class TestProjectBase(TestMailBase):
+class TestProjectBase(TestMail):
def setUp(self):
super(TestProjectBase, self).setUp()
From 4779df339eb8d7790cb7c6ac61d32f4d1d193f43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Tue, 27 Aug 2013 17:38:40 +0200
Subject: [PATCH 105/256] [REF] mail_followers: cleaned notify methods,
lessening the number of queries.
bzr revid: tde@openerp.com-20130827153840-fo9s2lc35fvld3fb
---
addons/mail/mail_followers.py | 118 +++++++++++++++++-----------------
addons/mail/mail_message.py | 1 -
2 files changed, 60 insertions(+), 59 deletions(-)
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index ac350810aa6..0059df90530 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -77,7 +77,7 @@ class mail_notification(osv.Model):
if not cr.fetchone():
cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
- def get_partners_to_notify(self, cr, uid, message, partners_to_notify=None, context=None):
+ def get_partners_to_email(self, cr, uid, ids, message, context=None):
""" Return the list of partners to notify, based on their preferences.
:param browse_record message: mail.message to notify
@@ -85,13 +85,10 @@ class mail_notification(osv.Model):
the notifications to process
"""
notify_pids = []
- for notification in message.notification_ids:
+ for notification in self.browse(cr, uid, ids, context=context):
if notification.read:
continue
partner = notification.partner_id
- # If partners_to_notify specified: restrict to them
- if partners_to_notify is not None and partner.id not in partners_to_notify:
- continue
# Do not send to partners without email address defined
if not partner.email:
continue
@@ -143,14 +140,62 @@ class mail_notification(osv.Model):
company = user.company_id.name
sent_by = _('Sent by %(company)s using %(openerp)s.')
signature_company = '%s' % (sent_by % {
- 'company': company,
- 'openerp': "OpenERP"
- })
+ 'company': company,
+ 'openerp': "OpenERP"
+ })
footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div')
return footer
- def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None,
+ def update_message_notification(self, cr, uid, ids, message_id, partner_ids, context=None):
+ existing_pids = set()
+ new_pids = set()
+ new_notif_ids = []
+
+ for notification in self.browse(cr, uid, ids, context=context):
+ existing_pids.add(notification.partner_id.id)
+
+ # update existing notifications
+ self.write(cr, uid, ids, {'read': False}, context=context)
+
+ # create new notifications
+ new_pids = set(partner_ids) - existing_pids
+ for new_pid in new_pids:
+ new_notif_ids.append(self.create(cr, uid, {'message_id': message_id, 'partner_id': new_pid, 'read': False}, context=context))
+ return new_notif_ids
+
+ def _notify_email(self, cr, uid, ids, message_id, force_send=False, user_signature=True, context=None):
+ message = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
+
+ # compute partners
+ email_pids = self.get_partners_to_email(cr, uid, ids, message, context=None)
+ if not email_pids:
+ return True
+
+ # compute email body (signature, company data)
+ body_html = message.body
+ user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None
+ if user_signature:
+ signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context)
+ body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
+
+ # compute email references
+ references = message.parent_id.message_id if message.parent_id else False
+
+ # create email values
+ mail_values = {
+ 'mail_message_id': message.id,
+ 'auto_delete': True,
+ 'body_html': body_html,
+ 'recipient_ids': [(4, id) for id in email_pids],
+ 'references': references,
+ }
+ email_notif_id = self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
+ if force_send:
+ self.pool.get('mail.mail').send(cr, uid, [email_notif_id], context=context)
+ return True
+
+ def _notify(self, cr, uid, message_id, partners_to_notify=None, context=None,
force_send=False, user_signature=True):
""" Send by email the notification depending on the user preferences
@@ -162,57 +207,14 @@ class mail_notification(osv.Model):
:param bool user_signature: if True, the generated mail.mail body is
the body of the related mail.message with the author's signature
"""
- if context is None:
- context = {}
- mail_message_obj = self.pool.get('mail.message')
+ notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', message_id), ('partner_id', 'in', partners_to_notify)], context=context)
- # optional list of partners to notify: subscribe them if not already done or update the notification
- if partners_to_notify:
- notifications_to_update = []
- notified_partners = []
- notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', msg_id), ('partner_id', 'in', partners_to_notify)], context=context)
- for notification in self.browse(cr, SUPERUSER_ID, notif_ids, context=context):
- notified_partners.append(notification.partner_id.id)
- notifications_to_update.append(notification.id)
- partners_to_notify = filter(lambda item: item not in notified_partners, partners_to_notify)
- if notifications_to_update:
- self.write(cr, SUPERUSER_ID, notifications_to_update, {'read': False}, context=context)
- mail_message_obj.write(cr, uid, msg_id, {'notified_partner_ids': [(4, id) for id in partners_to_notify]}, context=context)
+ # update or create notifications
+ new_notif_ids = self.update_message_notification(cr, SUPERUSER_ID, notif_ids, message_id, partners_to_notify, context=context)
# mail_notify_noemail (do not send email) or no partner_ids: do not send, return
- if context.get('mail_notify_noemail'):
+ if context and context.get('mail_notify_noemail'):
return True
+
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
- msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)
- notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, partners_to_notify=partners_to_notify, context=context)
- if not notify_partner_ids:
- return True
-
- # add the context in the email
- # TDE FIXME: commented, to be improved in a future branch
- # quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
-
- # add signature
- body_html = msg.body
- user_id = msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0] and msg.author_id.user_ids[0].id or None
- if user_signature:
- signature_company = self.get_signature_footer(cr, uid, user_id, res_model=msg.model, res_id=msg.res_id, context=context)
- body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
-
- references = False
- if msg.parent_id:
- references = msg.parent_id.message_id
-
- mail_values = {
- 'mail_message_id': msg.id,
- 'auto_delete': True,
- 'body_html': body_html,
- 'recipient_ids': [(4, id) for id in notify_partner_ids],
- 'references': references,
- }
- mail_mail = self.pool.get('mail.mail')
- email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
-
- if force_send:
- mail_mail.send(cr, uid, [email_notif_id], context=context)
- return True
+ self._notify_email(cr, SUPERUSER_ID, new_notif_ids, message_id, force_send, user_signature, context=context)
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index d63025b0912..e3a1ae93342 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -89,7 +89,6 @@ class mail_message(osv.Model):
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
""" Return the related document name, using name_get. It is done using
SUPERUSER_ID, to be sure to have the record name correctly stored. """
- # return dict.fromkeys(ids, False)
# TDE note: regroup by model/ids, to have less queries to perform
result = dict.fromkeys(ids, False)
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
From 0dfe3a2e677aad8a4c03e215a81787f299707d98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Tue, 27 Aug 2013 18:18:01 +0200
Subject: [PATCH 106/256] [FIX] crm: fixed regression in merge_notify, due to a
cleaning in opportunities merging a few months ago
bzr revid: tde@openerp.com-20130827161801-d8i11n5uq7n4v9r8
---
addons/crm/crm_lead.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 6e8e966f5b0..9d17526c19f 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -636,7 +636,7 @@ class crm_lead(format_address, osv.osv):
# Merge notifications about loss of information
opportunities = [highest]
opportunities.extend(opportunities_rest)
- self._merge_notify(cr, uid, highest, opportunities, context=context)
+ self._merge_notify(cr, uid, highest.id, opportunities, context=context)
# Check if the stage is in the stages of the sales team. If not, assign the stage with the lowest sequence
if merged_data.get('section_id'):
section_stage_ids = self.pool.get('crm.case.stage').search(cr, uid, [('section_ids', 'in', merged_data['section_id']), ('type', '=', merged_data.get('type'))], order='sequence', context=context)
From eaf07ae5c45be7ee3e55bf405556aa50d8a59091 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Wed, 28 Aug 2013 16:08:45 +0200
Subject: [PATCH 107/256] [FIX] mail_message: fixed reply_to and email_from
computation in create: take also False values, only undefined values trigger
the default one
bzr revid: tde@openerp.com-20130828140845-uuv8ouoto4q7g96j
---
addons/mail/mail_message.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index e3a1ae93342..ef3b70bed13 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -91,6 +91,7 @@ class mail_message(osv.Model):
SUPERUSER_ID, to be sure to have the record name correctly stored. """
# TDE note: regroup by model/ids, to have less queries to perform
result = dict.fromkeys(ids, False)
+ # return result
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
if not message.get('model') or not message.get('res_id') or message['model'] not in self.pool:
continue
@@ -830,11 +831,11 @@ class mail_message(osv.Model):
context = {}
default_starred = context.pop('default_starred', False)
- if not values.get('email_from'): # needed to compute reply_to
+ if 'email_from' not in values: # needed to compute reply_to
values['email_from'] = self._get_default_from(cr, uid, context=context)
if not values.get('message_id'):
values['message_id'] = self._get_message_id(cr, uid, values, context=context)
- if not values.get('reply_to'):
+ if 'reply_to' not in values:
values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
newid = super(mail_message, self).create(cr, uid, values, context)
From 9ef46123a6c7c7dc1c136bbfcf9eb522cc4cf78c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Wed, 28 Aug 2013 16:09:29 +0200
Subject: [PATCH 108/256] [IMP] mail.compose.message, email.template: rendering
is now done in batch.
Also refactored send_mail in mail.compose.message in order to be able to override
mail values without having to intefere with the send_mail behavior.
bzr revid: tde@openerp.com-20130828140929-xe9hbmbo6jxgs9mh
---
addons/email_template/email_template.py | 197 +++++++++++-------
.../wizard/mail_compose_message.py | 69 +++---
addons/mail/wizard/mail_compose_message.py | 184 ++++++++++------
3 files changed, 272 insertions(+), 178 deletions(-)
diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py
index 2e3405ed079..2e1c52d0ef9 100644
--- a/addons/email_template/email_template.py
+++ b/addons/email_template/email_template.py
@@ -67,7 +67,7 @@ class email_template(osv.osv):
_description = 'Email Templates'
_order = 'name'
- def render_template(self, cr, uid, template, model, res_id, context=None):
+ def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
"""Render the given template text, replace mako expressions ``${expr}``
with the result of evaluating these expressions with
an evaluation context containing:
@@ -79,46 +79,60 @@ class email_template(osv.osv):
:param str template: the template text to render
:param str model: model name of the document record this mail is related to.
- :param int res_id: id of the document record this mail is related to.
+ :param int res_ids: list of ids of document records those mails are related to.
"""
- if not template:
- return u""
if context is None:
context = {}
- try:
- template = tools.ustr(template)
- record = None
- if res_id:
- record = self.pool[model].browse(cr, uid, res_id, context=context)
- user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- variables = {
- 'object': record,
- 'user': user,
- 'ctx': context, # context kw would clash with mako internals
- }
- result = mako_template_env.from_string(template).render(variables)
- if result == u"False":
- result = u""
- return result
- except Exception:
- _logger.exception("failed to render mako template value %r", template)
- return u""
+ results = dict.fromkeys(res_ids, u"")
- def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
+ # try to load the template
+ try:
+ template = mako_template_env.from_string(tools.ustr(template))
+ except Exception:
+ _logger.exception("Failed to load template %r", template)
+ return results
+
+ # prepare template variables
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ records = self.pool[model].browse(cr, uid, res_ids, context=context) or [None]
+ variables = {
+ 'user': user,
+ 'ctx': context, # context kw would clash with mako internals
+ }
+ for record in records:
+ res_id = record.id if record else None
+ variables['object'] = record
+ try:
+ render_result = template.render(variables)
+ except Exception:
+ _logger.exception("Failed to render template %r using values %r" % (template, variables))
+ render_result = u""
+ if render_result == u"False":
+ render_result = u""
+ results[res_id] = render_result
+ return results
+
+ def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None):
if context is None:
context = {}
+ if res_ids is None:
+ res_ids = [None]
+ results = dict.fromkeys(res_ids, False)
+
if not template_id:
- return False
+ return results
template = self.browse(cr, uid, template_id, context)
- lang = self.render_template(cr, uid, template.lang, template.model, record_id, context)
- if lang:
- # Use translated template if necessary
- ctx = context.copy()
- ctx['lang'] = lang
- template = self.browse(cr, uid, template.id, ctx)
- else:
- template = self.browse(cr, uid, int(template_id), context)
- return template
+ langs = self.render_template_batch(cr, uid, template.lang, template.model, res_ids, context)
+ for res_id, lang in langs.iteritems():
+ if lang:
+ # Use translated template if necessary
+ ctx = context.copy()
+ ctx['lang'] = lang
+ template = self.browse(cr, uid, template.id, ctx)
+ else:
+ template = self.browse(cr, uid, int(template_id), context)
+ results[res_id] = template
+ return results
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
mod_name = False
@@ -300,64 +314,75 @@ class email_template(osv.osv):
})
return {'value': result}
- def generate_email(self, cr, uid, template_id, res_id, context=None):
- """Generates an email from the template for given (model, res_id) pair.
+ def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
+ """Generates an email from the template for given the given model based on
+ records given by res_ids.
- :param template_id: id of the template to render.
- :param res_id: id of the record to use for rendering the template (model
- is taken from template definition)
- :returns: a dict containing all relevant fields for creating a new
- mail.mail entry, with one extra key ``attachments``, in the
- format expected by :py:meth:`mail_thread.message_post`.
+ :param template_id: id of the template to render.
+ :param res_id: id of the record to use for rendering the template (model
+ is taken from template definition)
+ :returns: a dict containing all relevant fields for creating a new
+ mail.mail entry, with one extra key ``attachments``, in the
+ format expected by :py:meth:`mail_thread.message_post`.
"""
if context is None:
context = {}
+ if fields is None:
+ fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']
+
report_xml_pool = self.pool.get('ir.actions.report.xml')
- template = self.get_email_template(cr, uid, template_id, res_id, context)
- values = {}
- for field in ['subject', 'body_html', 'email_from',
- 'email_to', 'partner_to', 'email_cc', 'reply_to']:
- values[field] = self.render_template(cr, uid, getattr(template, field),
- template.model, res_id, context=context) \
- or False
- if template.user_signature:
- signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
- values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
+ res_ids_to_templates = self.get_email_template_batch(cr, uid, template_id, res_ids, context)
- if values['body_html']:
- values['body'] = tools.html_sanitize(values['body_html'])
+ # templates: res_id -> template; template -> res_ids
+ templates_to_res_ids = {}
+ for res_id, template in res_ids_to_templates.iteritems():
+ templates_to_res_ids.setdefault(template, []).append(res_id)
- values.update(mail_server_id=template.mail_server_id.id or False,
- auto_delete=template.auto_delete,
- model=template.model,
- res_id=res_id or False)
+ results = dict()
+ for template, template_res_ids in templates_to_res_ids.iteritems():
+ # generate fields value for all res_ids linked to the current template
+ for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']:
+ generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context)
+ for res_id, field_value in generated_field_values.iteritems():
+ results.setdefault(res_id, dict())[field] = field_value
+ # update values for all res_ids
+ for res_id in template_res_ids:
+ values = results[res_id]
+ if template.user_signature:
+ signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
+ values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
+ if values['body_html']:
+ values['body'] = tools.html_sanitize(values['body_html'])
+ values.update(
+ mail_server_id=template.mail_server_id.id or False,
+ auto_delete=template.auto_delete,
+ model=template.model,
+ res_id=res_id or False,
+ attachment_ids=[attach.id for attach in template.attachment_ids],
+ )
- attachments = []
- # Add report in attachments
- if template.report_template:
- report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
- report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
- # Ensure report is rendered using template's language
- ctx = context.copy()
- if template.lang:
- ctx['lang'] = self.render_template(cr, uid, template.lang, template.model, res_id, context)
- result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
- result = base64.b64encode(result)
- if not report_name:
- report_name = 'report.' + report_service
- ext = "." + format
- if not report_name.endswith(ext):
- report_name += ext
- attachments.append((report_name, result))
+ # Add report in attachments
+ if template.report_template:
+ for res_id in template_res_ids:
+ attachments = []
+ report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
+ report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
+ # Ensure report is rendered using template's language
+ ctx = context.copy()
+ if template.lang:
+ ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, res_id, context) # take 0 ?
+ result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
+ result = base64.b64encode(result)
+ if not report_name:
+ report_name = 'report.' + report_service
+ ext = "." + format
+ if not report_name.endswith(ext):
+ report_name += ext
+ attachments.append((report_name, result))
- attachment_ids = []
- # Add template attachments
- for attach in template.attachment_ids:
- attachment_ids.append(attach.id)
+ values['attachments'] = attachments
- values['attachments'] = attachments
- values['attachment_ids'] = attachment_ids
- return values
+ return results
def send_mail(self, cr, uid, template_id, res_id, force_send=False, raise_exception=False, context=None):
"""Generates a new mail message for the given template and record,
@@ -404,4 +429,14 @@ class email_template(osv.osv):
mail_mail.send(cr, uid, [msg_id], raise_exception=raise_exception, context=context)
return msg_id
+ # Compatibility method
+ def render_template(self, cr, uid, template, model, res_id, context=None):
+ return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id]
+
+ def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
+ return self.get_email_template_batch(cr, uid, template_id, [record_id], context)[record_id]
+
+ def generate_email(self, cr, uid, template_id, res_id, context=None):
+ return self.generate_email_batch(cr, uid, template_id, [res_id], context)[res_id]
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py
index 0aab765efbf..bae7a0d8f9c 100644
--- a/addons/email_template/wizard/mail_compose_message.py
+++ b/addons/email_template/wizard/mail_compose_message.py
@@ -62,6 +62,7 @@ class mail_compose_message(osv.TransientModel):
for wizard in self.browse(cr, uid, ids, context=context):
if wizard.template_id:
wizard_context['mail_notify_user_signature'] = False # template user_signature is added when generating body_html
+ wizard_context['mail_auto_delete'] = wizard.template_id.auto_delete # mass mailing: use template auto_delete value -> note, for emails mass mailing only
if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id:
continue
new_attachment_ids = []
@@ -81,7 +82,7 @@ class mail_compose_message(osv.TransientModel):
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
elif template_id:
- values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
+ values = self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context=context)[res_id]
# transform attachments into attachment_ids; not attached to the document because this will
# be done further in the posting process, allowing to clean database if email not send
values['attachment_ids'] = values.pop('attachment_ids', [])
@@ -147,45 +148,55 @@ class mail_compose_message(osv.TransientModel):
partner_ids.append(int(partner_id))
return partner_ids
- def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
+ def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None):
""" Call email_template.generate_email(), get fields relevant for
mail.compose.message, transform email_cc and email_to into partner_ids """
- template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
# filter template values
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
- values = dict((field, template_values[field]) for field in fields if template_values.get(field))
- values['body'] = values.pop('body_html', '')
+ values = dict.fromkeys(res_ids, False)
- # transform email_to, email_cc into partner_ids
- ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
- partner_ids = self._get_or_create_partners_from_values(cr, uid, values, context=ctx)
- # legacy template behavior: void values do not erase existing values and the
- # related key is removed from the values dict
- if partner_ids:
- values['partner_ids'] = list(partner_ids)
+ template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
+ for res_id in res_ids:
+ res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))
+ res_id_values['body'] = res_id_values.pop('body_html', '')
+ # transform email_to, email_cc into partner_ids
+ ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
+ partner_ids = self._get_or_create_partners_from_values(cr, uid, res_id_values, context=ctx)
+ # legacy template behavior: void values do not erase existing values and the
+ # related key is removed from the values dict
+ if partner_ids:
+ res_id_values['partner_ids'] = list(partner_ids)
+
+ values[res_id] = res_id_values
return values
- def render_message(self, cr, uid, wizard, res_id, context=None):
+ def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
""" Override to handle templates. """
- # generate the composer email
+ # generate template-based values
if wizard.template_id:
- values = self.generate_email_for_composer(cr, uid, wizard.template_id.id, res_id, context=context)
+ template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context)
else:
- values = {}
- # remove attachments as they should not be rendered
- values.pop('attachment_ids', None)
- # get values to return
- email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
- # those values are not managed; they are readonly
- email_dict.pop('email_to', None)
- email_dict.pop('email_cc', None)
- email_dict.pop('partner_to', None)
- # update template values by wizard values
- values.update(email_dict)
- return values
+ template_values = dict.fromkeys(res_ids, dict())
+ # generate composer values
+ composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
- def render_template(self, cr, uid, template, model, res_id, context=None):
- return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
+ for res_id in res_ids:
+ # remove attachments from template values as they should not be rendered
+ template_values[res_id].pop('attachment_ids', None)
+ # remove some keys from composer that are readonly
+ composer_values[res_id].pop('email_to', None)
+ composer_values[res_id].pop('email_cc', None)
+ composer_values[res_id].pop('partner_to', None)
+ # update template values by composer values
+ template_values[res_id].update(composer_values[res_id])
+ return template_values
+
+ def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
+ return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context)
+
+ # Compatibility methods
+ def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
+ return self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context)[res_id]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 6ab1f0e81ff..34c3db4de9b 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -233,7 +233,11 @@ class mail_compose_message(osv.TransientModel):
email(s), rendering any template patterns on the fly if needed. """
if context is None:
context = {}
- ir_attachment_obj = self.pool.get('ir.attachment')
+ # clean the context (hint: mass mailing sets some default values that
+ # could be wrongly interpreted by mail_mail)
+ context.pop('default_email_to', None)
+ context.pop('default_partner_ids', None)
+
active_ids = context.get('active_ids')
is_log = context.get('mail_compose_log', False)
@@ -252,43 +256,11 @@ class mail_compose_message(osv.TransientModel):
else:
res_ids = [wizard.res_id]
- for res_id in res_ids:
- # mail.message values, according to the wizard options
- post_values = {
- 'subject': wizard.subject,
- 'body': wizard.body,
- 'parent_id': wizard.parent_id and wizard.parent_id.id,
- 'partner_ids': [partner.id for partner in wizard.partner_ids],
- 'attachment_ids': [attach.id for attach in wizard.attachment_ids],
- }
- # mass mailing: render and override default values
- if mass_mail_mode and wizard.model:
- email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
- post_values['partner_ids'] += email_dict.pop('partner_ids', [])
- post_values['attachments'] = email_dict.pop('attachments', [])
- attachment_ids = []
- for attach_id in post_values.pop('attachment_ids'):
- new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
- attachment_ids.append(new_attach_id)
- post_values['attachment_ids'] = attachment_ids
- # email_from: mass mailing only can specify another email_from
- if email_dict.get('email_from'):
- post_values['email_from'] = email_dict.pop('email_from')
- # replies redirection: mass mailing only
- if not wizard.same_thread:
- post_values['reply_to'] = email_dict.pop('reply_to')
- else:
- email_dict.pop('reply_to')
- post_values.update(email_dict)
- # clean the context (hint: mass mailing sets some default values that
- # could be wrongly interpreted by mail_mail)
- context.pop('default_email_to', None)
- context.pop('default_partner_ids', None)
- # post the message
+ all_mail_values = self.get_mail_values(cr, uid, wizard, res_ids, context=context)
+
+ for res_id, mail_values in all_mail_values.iteritems():
if mass_mail_mode and not wizard.post:
- post_values['body_html'] = post_values.get('body', '')
- post_values['recipient_ids'] = [(4, id) for id in post_values.pop('partner_ids', [])]
- self.pool.get('mail.mail').create(cr, uid, post_values, context=context)
+ self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
else:
subtype = 'mail.mt_comment'
if is_log: # log a note: subtype is False
@@ -297,46 +269,122 @@ class mail_compose_message(osv.TransientModel):
if not wizard.notify:
subtype = False
context = dict(context,
- mail_notify_force_send=False, # do not send emails directly but use the queue instead
- mail_create_nosubscribe=True) # add context key to avoid subscribing the author
- active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values)
+ mail_notify_force_send=False, # do not send emails directly but use the queue instead
+ mail_create_nosubscribe=True) # add context key to avoid subscribing the author
+ active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **mail_values)
return {'type': 'ir.actions.act_window_close'}
- def render_message(self, cr, uid, wizard, res_id, context=None):
- """ Generate an email from the template for given (wizard.model, res_id)
- pair. This method is meant to be inherited by email_template that
- will produce a more complete dictionary. """
- return {
- 'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context),
- 'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context),
- 'email_from': self.render_template(cr, uid, wizard.email_from, wizard.model, res_id, context),
- 'reply_to': self.render_template(cr, uid, wizard.reply_to, wizard.model, res_id, context),
- }
+ def get_mail_values(self, cr, uid, wizard, res_ids, context=None):
+ """Generate the values that will be used by send_mail to create mail_messages
+ or mail_mails. """
+ results = dict.fromkeys(res_ids, False)
+ mass_mail_mode = wizard.composition_mode == 'mass_mail'
- def render_template(self, cr, uid, template, model, res_id, context=None):
+ # render all template-based value at once
+ if mass_mail_mode and wizard.model:
+ rendered_values = self.render_message_batch(cr, uid, wizard, res_ids, context=context)
+
+ for res_id in res_ids:
+ # static wizard (mail.message) values
+ mail_values = {
+ 'subject': wizard.subject,
+ 'body': wizard.body,
+ 'parent_id': wizard.parent_id and wizard.parent_id.id,
+ 'partner_ids': [partner.id for partner in wizard.partner_ids],
+ 'attachment_ids': [attach.id for attach in wizard.attachment_ids],
+ }
+ # mass mailing: rendering override wizard static values
+ if mass_mail_mode and wizard.model:
+ email_dict = rendered_values[res_id]
+ mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
+ mail_values['attachments'] = email_dict.pop('attachments', [])
+ attachment_ids = []
+ for attach_id in mail_values.pop('attachment_ids'):
+ new_attach_id = self.pool.get('ir.attachment').copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
+ attachment_ids.append(new_attach_id)
+ mail_values['attachment_ids'] = attachment_ids
+ # email_from: mass mailing only can specify another email_from
+ if email_dict.get('email_from'):
+ mail_values['email_from'] = email_dict.pop('email_from')
+ # replies redirection: mass mailing only
+ if not wizard.same_thread:
+ mail_values['reply_to'] = email_dict.pop('reply_to')
+ else:
+ email_dict.pop('reply_to')
+ mail_values.update(email_dict)
+ # mass mailing without post: mail_mail values
+ if mass_mail_mode and not wizard.post:
+ if 'mail_auto_delete' in context:
+ mail_values['auto_delete'] = context.get('mail_auto_delete')
+ mail_values['body_html'] = mail_values.get('body', '')
+ mail_values['recipient_ids'] = [(4, id) for id in mail_values.pop('partner_ids', [])]
+ results[res_id] = mail_values
+ return results
+
+ def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
+ """Generate template-based values of wizard, for the document records given
+ by res_ids. This method is meant to be inherited by email_template that
+ will produce a more complete dictionary, using Jinja2 templates.
+
+ Each template is generated for all res_ids, allowing to parse the template
+ once, and render it multiple times. This is useful for mass mailing where
+ template rendering represent a significant part of the process.
+
+ :param browse wizard: current mail.compose.message browse record
+ :param list res_ids: list of record ids
+
+ :return dict results: for each res_id, the generated template values for
+ subject, body, email_from and reply_to
+ """
+ subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context)
+ bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context)
+ emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context)
+ replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context)
+
+ results = dict.fromkeys(res_ids, False)
+ for res_id in res_ids:
+ results[res_id] = {
+ 'subject': subjects[res_id],
+ 'body': bodies[res_id],
+ 'email_from': emails_from[res_id],
+ 'reply_to': replies_to[res_id],
+ }
+ return results
+
+ def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
""" Render the given template text, replace mako-like expressions ``${expr}``
- with the result of evaluating these expressions with an evaluation context
- containing:
+ with the result of evaluating these expressions with an evaluation context
+ containing:
- * ``user``: browse_record of the current user
- * ``object``: browse_record of the document record this mail is
- related to
- * ``context``: the context passed to the mail composition wizard
+ * ``user``: browse_record of the current user
+ * ``object``: browse_record of the document record this mail is
+ related to
+ * ``context``: the context passed to the mail composition wizard
- :param str template: the template text to render
- :param str model: model name of the document record this mail is related to.
- :param int res_id: id of the document record this mail is related to.
+ :param str template: the template text to render
+ :param str model: model name of the document record this mail is related to
+ :param list res_ids: list of record ids
"""
if context is None:
context = {}
+ results = dict.fromkeys(res_ids, False)
- def merge(match):
- exp = str(match.group()[2:-1]).strip()
- result = eval(exp, {
- 'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
- 'object': self.pool[model].browse(cr, uid, res_id, context=context),
- 'context': dict(context), # copy context to prevent side-effects of eval
+ for res_id in res_ids:
+ def merge(match):
+ exp = str(match.group()[2:-1]).strip()
+ result = eval(exp, {
+ 'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
+ 'object': self.pool[model].browse(cr, uid, res_id, context=context),
+ 'context': dict(context), # copy context to prevent side-effects of eval
})
- return result and tools.ustr(result) or ''
- return template and EXPRESSION_PATTERN.sub(merge, template)
+ return result and tools.ustr(result) or ''
+ results[res_id] = template and EXPRESSION_PATTERN.sub(merge, template)
+ return results
+
+ # Compatibility methods
+ def render_template(self, cr, uid, template, model, res_id, context=None):
+ return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id]
+
+ def render_message(self, cr, uid, wizard, res_id, context=None):
+ return self.render_message_batch(cr, uid, wizard, [res_id], context)[res_id]
From 14a855f297a02765bf8ae1adc13bdb57750560f2 Mon Sep 17 00:00:00 2001
From: "Atul Patel (OpenERP)"
Date: Wed, 28 Aug 2013 20:31:05 +0530
Subject: [PATCH 109/256] [FIX]: Remove onchange from xml part.
bzr revid: atp@tinyerp.com-20130828150105-ylwdzki1viq9n8kl
---
addons/base_setup/res_config_view.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/base_setup/res_config_view.xml b/addons/base_setup/res_config_view.xml
index cc290f59156..8004d444ed9 100644
--- a/addons/base_setup/res_config_view.xml
+++ b/addons/base_setup/res_config_view.xml
@@ -94,7 +94,7 @@
-
+
From f70313103a082044d21e4b2290348bfe4d398bc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Thu, 29 Aug 2013 12:09:32 +0200
Subject: [PATCH 110/256] [IMP] mail_message: cleaned search view
bzr revid: tde@openerp.com-20130829100932-xrh11khzghwxzyuf
---
addons/mail/mail_message_view.xml | 19 +++++--------------
1 file changed, 5 insertions(+), 14 deletions(-)
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
index e4442e366e3..e2d1deb028b 100644
--- a/addons/mail/mail_message_view.xml
+++ b/addons/mail/mail_message_view.xml
@@ -56,7 +56,8 @@
25
-
+
+
@@ -66,23 +67,13 @@
-
-
-
-
-
+
+
+
From e0ceeb8214f7c821498569e6a76f423e37eea927 Mon Sep 17 00:00:00 2001
From: "Mehul Mehta (OpenERP)"
Date: Thu, 29 Aug 2013 21:52:42 +0800
Subject: [PATCH 111/256] [IMP]in general setting for select dynamic font and
set a font for company header and footer
bzr revid: mme@tinyerp.com-20130829135242-ov2b3v1yvbtvliv2
---
addons/base_setup/res_config.py | 26 ++++++++------------------
addons/base_setup/res_config_view.xml | 2 +-
2 files changed, 9 insertions(+), 19 deletions(-)
diff --git a/addons/base_setup/res_config.py b/addons/base_setup/res_config.py
index 753c063ed34..bbaa6713c82 100644
--- a/addons/base_setup/res_config.py
+++ b/addons/base_setup/res_config.py
@@ -21,24 +21,14 @@
from openerp.osv import fields, osv
import re
+import matplotlib.font_manager
-_select_font=[ ('DejaVu Sans',"DejaVu Sans"),
- ('DejaVu Sans Bold',"DejaVu Sans Bold"),
- ('DejaVu Sans Oblique',"DejaVu Sans Oblique"),
- ('DejaVu Sans BoldOblique',"DejaVu Sans BoldOblique"),
- ('Liberation Serif',"Liberation Serif"),
- ('Liberation Serif Bold',"Liberation Serif Bold"),
- ('Liberation Serif Italic',"Liberation Serif Italic"),
- ('Liberation Serif BoldItalic',"Liberation Serif BoldItalic"),
- ('Liberation Serif',"Liberation Serif"),
- ('Liberation Serif Bold',"Liberation Serif Bold"),
- ('Liberation Serif Italic',"Liberation Serif Italic"),
- ('Liberation Serif BoldItalic',"Liberation Serif BoldItalic"),
- ('FreeMono',"FreeMono"),
- ('FreeMono Bold',"FreeMono Bold"),
- ('FreeMono Oblique',"FreeMono Oblique"),
- ('FreeMono BoldOblique',"FreeMono BoldOblique"),
-]
+_lst_font=[]
+for i in matplotlib.font_manager.findSystemFonts(fontpaths=None, fontext='ttf'):
+ m=re.sub('(.*/)','', i)
+ n=m.strip('.ttf')
+ n=n.replace('-',' ')
+ _lst_font.append((n,n))
class base_config_settings(osv.osv_memory):
_name = 'base.config.settings'
@@ -57,7 +47,7 @@ class base_config_settings(osv.osv_memory):
'module_base_import': fields.boolean("Allow users to import data from CSV files"),
'module_google_drive': fields.boolean('Attach Google documents to any record',
help="""This installs the module google_docs."""),
- 'font': fields.selection(_select_font, "Select Font",help="Set your favorite font into company header"),
+ 'font': fields.selection(_lst_font, "Select Font",help="Set your favorite font into company header"),
}
def open_company(self, cr, uid, ids, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context)
diff --git a/addons/base_setup/res_config_view.xml b/addons/base_setup/res_config_view.xml
index 8004d444ed9..e9be52c66d8 100644
--- a/addons/base_setup/res_config_view.xml
+++ b/addons/base_setup/res_config_view.xml
@@ -91,7 +91,7 @@
-
+
From 80da7ddd6029e4d29684c9b15ea79defe45f8d0f Mon Sep 17 00:00:00 2001
From: "Darshan Kalola (OpenERP)"
Date: Fri, 30 Aug 2013 15:15:12 +0530
Subject: [PATCH 112/256] [IMP]Add - before the the sentence This installs the
module in help
bzr revid: dka@tinyerp.com-20130830094512-n3nqjtt1g0w8m94v
---
addons/account/res_config.py | 12 ++++++------
addons/base_setup/res_config.py | 6 +++---
addons/crm/res_config.py | 2 +-
addons/hr_recruitment/res_config.py | 2 +-
addons/knowledge/res_config.py | 6 +++---
addons/marketing/res_config.py | 6 +++---
addons/mrp/res_config.py | 12 ++++++------
addons/project/res_config.py | 12 ++++++------
addons/purchase/res_config.py | 6 +++---
addons/sale/res_config.py | 12 ++++++------
addons/sale_stock/res_config.py | 4 ++--
addons/stock/res_config.py | 6 +++---
12 files changed, 43 insertions(+), 43 deletions(-)
diff --git a/addons/account/res_config.py b/addons/account/res_config.py
index 1d79fa0ae11..9349818a47e 100644
--- a/addons/account/res_config.py
+++ b/addons/account/res_config.py
@@ -82,30 +82,30 @@ class account_config_settings(osv.osv_memory):
'module_account_check_writing': fields.boolean('Pay your suppliers by check',
help='This allows you to check writing and printing.\n'
- 'This installs the module account_check_writing.'),
+ '-This installs the module account_check_writing.'),
'module_account_accountant': fields.boolean('Full accounting features: journals, legal statements, chart of accounts, etc.',
help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of Accounts, ...)"""),
'module_account_asset': fields.boolean('Assets management',
help='This allows you to manage the assets owned by a company or a person.\n'
'It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.\n'
- 'This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments, '
+ '-This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments, '
'but not accounting (Journal Items, Chart of Accounts, ...)'),
'module_account_budget': fields.boolean('Budget management',
help='This allows accountants to manage analytic and crossovered budgets. '
'Once the master budgets and the budgets are defined, '
'the project managers can set the planned amount on each analytic account.\n'
- 'This installs the module account_budget.'),
+ '-This installs the module account_budget.'),
'module_account_payment': fields.boolean('Manage payment orders',
help='This allows you to create and manage your payment orders, with purposes to \n'
'* serve as base for an easy plug-in of various automated payment mechanisms, and \n'
'* provide a more efficient way to manage invoice payments.\n'
- 'This installs the module account_payment.' ),
+ '-This installs the module account_payment.' ),
'module_account_voucher': fields.boolean('Manage customer payments',
help='This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.\n'
- 'This installs the module account_voucher.'),
+ '-This installs the module account_voucher.'),
'module_account_followup': fields.boolean('Manage customer payment follow-ups',
help='This allows to automate letters for unpaid invoices, with multi-level recalls.\n'
- 'This installs the module account_followup.'),
+ '-This installs the module account_followup.'),
'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
implied_group='account.group_proforma_invoices',
help="Allows you to put invoices in pro-forma state."),
diff --git a/addons/base_setup/res_config.py b/addons/base_setup/res_config.py
index c7f6d4245ec..42e17d9c704 100644
--- a/addons/base_setup/res_config.py
+++ b/addons/base_setup/res_config.py
@@ -27,7 +27,7 @@ class base_config_settings(osv.osv_memory):
_columns = {
'module_multi_company': fields.boolean('Manage multiple companies',
help='Work in multi-company environments, with appropriate security access between companies.\n'
- 'This installs the module multi_company.'),
+ '-This installs the module multi_company.'),
'module_share': fields.boolean('Allow documents sharing',
help="""Share or embbed any screen of openerp."""),
'module_portal': fields.boolean('Activate the customer portal',
@@ -69,12 +69,12 @@ class sale_config_settings(osv.osv_memory):
'attach the selected mail as a .eml file in '
'the attachment of a selected record. You can create documents for CRM Lead, '
'Partner from the selected emails.\n'
- 'This installs the module plugin_thunderbird.'),
+ '-This installs the module plugin_thunderbird.'),
'module_plugin_outlook': fields.boolean('Enable Outlook plug-in',
help='The Outlook plugin allows you to select an object that you would like to add '
'to your email and its attachments from MS Outlook. You can select a partner, '
'or a lead object and archive a selected email into an OpenERP mail message with attachments.\n'
- 'This installs the module plugin_outlook.'),
+ '-This installs the module plugin_outlook.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/res_config.py b/addons/crm/res_config.py
index 622af04ef75..d572e50059a 100644
--- a/addons/crm/res_config.py
+++ b/addons/crm/res_config.py
@@ -59,7 +59,7 @@ class crm_configuration(osv.TransientModel):
help="""Allows you to trace and manage your activities for fund raising."""),
'module_crm_claim': fields.boolean("Manage Customer Claims",
help='Allows you to track your customers/suppliers claims and grievances.\n'
- 'This installs the module crm_claim.'),
+ '-This installs the module crm_claim.'),
'module_crm_helpdesk': fields.boolean("Manage Helpdesk and Support",
help="""Allows you to communicate with Customer, process Customer query, and provide better help and support. This installs the module crm_helpdesk."""),
'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
diff --git a/addons/hr_recruitment/res_config.py b/addons/hr_recruitment/res_config.py
index a15ae541e7f..53fd01546ec 100644
--- a/addons/hr_recruitment/res_config.py
+++ b/addons/hr_recruitment/res_config.py
@@ -28,7 +28,7 @@ class hr_applicant_settings(osv.osv_memory):
_columns = {
'module_document_ftp': fields.boolean('Allow the automatic indexation of resumes',
help='Manage your CV\'s and motivation letter related to all applicants.\n'
- 'This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)'),
+ '-This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)'),
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
help ='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
diff --git a/addons/knowledge/res_config.py b/addons/knowledge/res_config.py
index fd033772133..0800cd84856 100644
--- a/addons/knowledge/res_config.py
+++ b/addons/knowledge/res_config.py
@@ -30,13 +30,13 @@ class knowledge_config_settings(osv.osv_memory):
'module_document': fields.boolean('Manage documents',
help='This is a complete document management system, with: user authentication, '
'full document search (but pptx and docx are not supported), and a document dashboard.\n'
- 'This installs the module document.'),
+ '-This installs the module document.'),
'module_document_ftp': fields.boolean('Share repositories (FTP)',
help='Access your documents in OpenERP through an FTP interface.\n'
- 'This installs the module document_ftp.'),
+ '-This installs the module document_ftp.'),
'module_document_webdav': fields.boolean('Share repositories (WebDAV)',
help='Access your documents in OpenERP through WebDAV.\n'
- 'This installs the module document_webdav.'),
+ '-This installs the module document_webdav.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/marketing/res_config.py b/addons/marketing/res_config.py
index 3d8eaf71fe6..b0a0679d68e 100644
--- a/addons/marketing/res_config.py
+++ b/addons/marketing/res_config.py
@@ -28,13 +28,13 @@ class marketing_config_settings(osv.osv_memory):
'module_marketing_campaign': fields.boolean('Marketing campaigns',
help='Provides leads automation through marketing campaigns. '
'Campaigns can in fact be defined on any resource, not just CRM leads.\n'
- 'This installs the module marketing_campaign.'),
+ '-This installs the module marketing_campaign.'),
'module_marketing_campaign_crm_demo': fields.boolean('Demo data for marketing campaigns',
help='Installs demo data like leads, campaigns and segments for Marketing Campaigns.\n'
- 'This installs the module marketing_campaign_crm_demo.'),
+ '-This installs the module marketing_campaign_crm_demo.'),
'module_crm_profiling': fields.boolean('Track customer profile to focus your campaigns',
help='Allows users to perform segmentation within partners.\n'
- 'This installs the module crm_profiling.'),
+ '-This installs the module crm_profiling.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/mrp/res_config.py b/addons/mrp/res_config.py
index 7f54999e15b..45a3ce41cf6 100644
--- a/addons/mrp/res_config.py
+++ b/addons/mrp/res_config.py
@@ -35,26 +35,26 @@ class mrp_config_settings(osv.osv_memory):
'* Warranty concept\n'
'* Repair quotation report\n'
'* Notes for the technician and for the final customer.\n'
- 'This installs the module mrp_repair.'),
+ '-This installs the module mrp_repair.'),
'module_mrp_operations': fields.boolean("Allow detailed planning of work order",
help='This allows to add state, date_start,date_stop in production order operation lines (in the "Work Centers" tab).\n'
- 'This installs the module mrp_operations.'),
+ '-This installs the module mrp_operations.'),
'module_mrp_byproduct': fields.boolean("Produce several products from one manufacturing order",
help='You can configure by-products in the bill of material.\n'
'Without this module: A + B + C -> D.\n'
'With this module: A + B + C -> D + E.\n'
- 'This installs the module mrp_byproduct.'),
+ '-This installs the module mrp_byproduct.'),
'module_mrp_jit': fields.boolean("Generate procurement in real time",
help='This allows Just In Time computation of procurement orders.\n'
'All procurement orders will be processed immediately, which could in some '
'cases entail a small performance impact.\n'
- 'This installs the module mrp_jit.'),
+ '-This installs the module mrp_jit.'),
'module_stock_no_autopicking': fields.boolean("Manage manual picking to fulfill manufacturing orders ",
help='This module allows an intermediate picking process to provide raw materials to production orders.\n'
'For example to manage production made by your suppliers (sub-contracting).\n'
'To achieve this, set the assembled product which is sub-contracted to "No Auto-Picking" '
'and put the location of the supplier in the routing of the assembly operation.\n'
- 'This installs the module stock_no_autopicking.'),
+ '-This installs the module stock_no_autopicking.'),
'group_mrp_routings': fields.boolean("Manage routings and work orders ",
implied_group='mrp.group_mrp_routings',
help='Routings allow you to create and manage the manufacturing operations that should be followed '
@@ -69,7 +69,7 @@ class mrp_config_settings(osv.osv_memory):
'* Manufacturer Product Name\n'
'* Manufacturer Product Code\n'
'* Product Attributes.\n'
- 'This installs the module product_manufacturer.'),
+ '-This installs the module product_manufacturer.'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/res_config.py b/addons/project/res_config.py
index a8b8af3b90b..78b05144208 100644
--- a/addons/project/res_config.py
+++ b/addons/project/res_config.py
@@ -31,27 +31,27 @@ class project_configuration(osv.osv_memory):
help ='This feature automatically creates project tasks from service products in sale orders. '
'More precisely, tasks are created for procurement lines with product of type \'Service\', '
'procurement method \'Make to Order\', and supply method \'Manufacture\'.\n'
- 'This installs the module project_mrp.'),
+ '-This installs the module project_mrp.'),
'module_pad': fields.boolean("Use integrated collaborative note pads on task",
help='Lets the company customize which Pad installation should be used to link to new pads '
'(for example: http://ietherpad.com/).\n'
- 'This installs the module pad.'),
+ '-This installs the module pad.'),
'module_project_timesheet': fields.boolean("Record timesheet lines per tasks",
help='This allows you to transfer the entries under tasks defined for Project Management to '
'the timesheet line entries for particular date and user, with the effect of creating, '
'editing and deleting either ways.\n'
- 'This installs the module project_timesheet.'),
+ '-This installs the module project_timesheet.'),
'module_project_long_term': fields.boolean("Manage resources planning on gantt view",
help='A long term project management module that tracks planning, scheduling, and resource allocation.\n'
- 'This installs the module project_long_term.'),
+ '-This installs the module project_long_term.'),
'module_project_issue': fields.boolean("Track issues and bugs",
help='Provides management of issues/bugs in projects.\n'
- 'This installs the module project_issue.'),
+ '-This installs the module project_issue.'),
'time_unit': fields.many2one('product.uom', 'Working time unit', required=True,
help="""This will set the unit of measure used in projects and tasks."""),
'module_project_issue_sheet': fields.boolean("Invoice working time on issues",
help='Provides timesheet support for the issues/bugs management in project.\n'
- 'This installs the module project_issue_sheet.'),
+ '-This installs the module project_issue_sheet.'),
'group_tasks_work_on_tasks': fields.boolean("Log work activities on tasks",
implied_group='project.group_tasks_work_on_tasks',
help="Allows you to compute work on tasks."),
diff --git a/addons/purchase/res_config.py b/addons/purchase/res_config.py
index 930ee5f9f29..82b07a839d2 100644
--- a/addons/purchase/res_config.py
+++ b/addons/purchase/res_config.py
@@ -48,14 +48,14 @@ class purchase_config_settings(osv.osv_memory):
'Supplier: don\'t forget to ask for an express delivery.'),
'module_purchase_double_validation': fields.boolean("Force two levels of approvals",
help='Provide a double validation mechanism for purchases exceeding minimum amount.\n'
- 'This installs the module purchase_double_validation.'),
+ '-This installs the module purchase_double_validation.'),
'module_purchase_requisition': fields.boolean("Manage purchase requisitions",
help='Purchase Requisitions are used when you want to request quotations from several suppliers for a given set of products.\n'
'You can configure per product if you directly do a Request for Quotation '
'to one supplier or if you want a purchase requisition to negotiate with several suppliers.'),
'module_purchase_analytic_plans': fields.boolean('Use multiple analytic accounts on purchase orders',
help ='Allows the user to maintain several analysis plans. These let you split lines on a purchase order between several accounts and analytic plans.\n'
- 'This installs the module purchase_analytic_plans.'),
+ '-This installs the module purchase_analytic_plans.'),
'group_analytic_account_for_purchases': fields.boolean('Analytic accounting for purchases',
implied_group='purchase.group_analytic_accounting',
help="Allows you to specify an analytic account on purchase orders."),
@@ -78,7 +78,7 @@ class account_config_settings(osv.osv_memory):
_columns = {
'module_purchase_analytic_plans': fields.boolean('Use multiple analytic accounts on orders',
help ='Allows the user to maintain several analysis plans. These let you split lines on a purchase order between several accounts and analytic plans.\n'
- 'This installs the module purchase_analytic_plans.'),
+ '-This installs the module purchase_analytic_plans.'),
'group_analytic_account_for_purchases': fields.boolean('Analytic accounting for purchases',
implied_group='purchase.group_analytic_accounting',
help="Allows you to specify an analytic account on purchase orders."),
diff --git a/addons/sale/res_config.py b/addons/sale/res_config.py
index fe81a7cc6d8..7c080d8168f 100644
--- a/addons/sale/res_config.py
+++ b/addons/sale/res_config.py
@@ -36,13 +36,13 @@ class sale_configuration(osv.osv_memory):
'timesheet': fields.boolean('Prepare invoices based on timesheets',
help = 'For modifying account analytic view to show important data to project manager of services companies.'
'You can also view the report of account analytic summary user-wise as well as month wise.\n'
- 'This installs the module account_analytic_analysis.'),
+ '-This installs the module account_analytic_analysis.'),
'module_account_analytic_analysis': fields.boolean('Use contracts management',
help = 'Allows to define your customer contracts conditions: invoicing '
'method (fixed price, on timesheet, advance invoice), the exact pricing '
'(650€/day for a developer), the duration (one year support contract).\n'
'You will be able to follow the progress of the contract and invoice automatically.\n'
- 'It installs the account_analytic_analysis module.'),
+ '-It installs the account_analytic_analysis module.'),
'time_unit': fields.many2one('product.uom', 'The default working time unit for services is'),
'group_sale_pricelist':fields.boolean("Use pricelists to adapt your price per customers",
implied_group='product.group_sale_pricelist',
@@ -64,20 +64,20 @@ Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
'module_sale_margin': fields.boolean("Display margins on sales orders",
help='This adds the \'Margin\' on sales order.\n'
'This gives the profitability by calculating the difference between the Unit Price and Cost Price.\n'
- 'This installs the module sale_margin.'),
+ '-This installs the module sale_margin.'),
'module_sale_journal': fields.boolean("Allow batch invoicing of delivery orders through journals",
help='Allows you to categorize your sales and deliveries (picking lists) between different journals, '
'and perform batch operations on journals.\n'
- 'This installs the module sale_journal.'),
+ '-This installs the module sale_journal.'),
'module_analytic_user_function': fields.boolean("One employee can have different roles per contract",
help='Allows you to define what is the default function of a specific user on a given account.\n'
'This is mostly used when a user encodes his timesheet. The values are retrieved and the fields are auto-filled. '
'But the possibility to change these values is still available.\n'
- 'This installs the module analytic_user_function.'),
+ '-This installs the module analytic_user_function.'),
'module_project': fields.boolean("Project"),
'module_sale_stock': fields.boolean("Trigger delivery orders automatically from sales orders",
help='Allows you to Make Quotation, Sale Order using different Order policy and Manage Related Stock.\n'
- 'This installs the module sale_stock.'),
+ '-This installs the module sale_stock.'),
}
def default_get(self, cr, uid, fields, context=None):
diff --git a/addons/sale_stock/res_config.py b/addons/sale_stock/res_config.py
index 76cfbcb6fd3..01871bf6f1d 100644
--- a/addons/sale_stock/res_config.py
+++ b/addons/sale_stock/res_config.py
@@ -36,7 +36,7 @@ class sale_configuration(osv.osv_memory):
help='Lets you transfer the entries under tasks defined for Project Management to '
'the Timesheet line entries for particular date and particular user with the effect of creating, editing and deleting either ways '
'and to automatically creates project tasks from procurement lines.\n'
- 'This installs the modules project_timesheet and project_mrp.'),
+ '-This installs the modules project_timesheet and project_mrp.'),
'default_order_policy': fields.selection(
[('manual', 'Invoice based on sales orders'), ('picking', 'Invoice based on deliveries')],
'The default invoicing method is', default_model='sale.order',
@@ -44,7 +44,7 @@ class sale_configuration(osv.osv_memory):
'module_delivery': fields.boolean('Allow adding shipping costs',
help ='Allows you to add delivery methods in sales orders and delivery orders.\n'
'You can define your own carrier and delivery grids for prices.\n'
- 'This installs the module delivery.'),
+ '-This installs the module delivery.'),
'default_picking_policy' : fields.boolean("Deliver all at once when all products are available.",
help = "Sales order by default will be configured to deliver all products at once instead of delivering each product when it is available. This may have an impact on the shipping price."),
'group_mrp_properties': fields.boolean('Product properties on order lines',
diff --git a/addons/stock/res_config.py b/addons/stock/res_config.py
index 4da23389214..1ce5d9d25fa 100644
--- a/addons/stock/res_config.py
+++ b/addons/stock/res_config.py
@@ -28,11 +28,11 @@ class stock_config_settings(osv.osv_memory):
_columns = {
'module_claim_from_delivery': fields.boolean("Allow claim on deliveries",
help='Adds a Claim link to the delivery order.\n'
- 'This installs the module claim_from_delivery.'),
+ '-This installs the module claim_from_delivery.'),
'module_stock_invoice_directly': fields.boolean("Create and open the invoice when the user finish a delivery order",
help='This allows to automatically launch the invoicing wizard if the delivery is '
'to be invoiced when you send or deliver goods.\n'
- 'This installs the module stock_invoice_directly.'),
+ '-This installs the module stock_invoice_directly.'),
'module_product_expiry': fields.boolean("Expiry date on serial numbers",
help="""Track different dates on products and serial numbers.
The following dates can be tracked:
@@ -45,7 +45,7 @@ This installs the module product_expiry."""),
help='Provide push and pull inventory flows. Typical uses of this feature are: '
'manage product manufacturing chains, manage default locations per product, '
'define routes within your warehouse according to business needs, etc.\n'
- 'This installs the module stock_location.'),
+ '-This installs the module stock_location.'),
'group_uom': fields.boolean("Manage different units of measure for products",
implied_group='product.group_uom',
help="""Allows you to select and maintain different units of measure for products."""),
From b3e6145e048b9eefebc6f3d297cecda54269fe0c Mon Sep 17 00:00:00 2001
From: "Darshan Kalola (OpenERP)"
Date: Fri, 30 Aug 2013 16:15:24 +0530
Subject: [PATCH 113/256] [IMP]improve help in setting/configuration/sales
bzr revid: dka@tinyerp.com-20130830104524-pbxm2p32n1widzqd
---
addons/crm/res_config.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/addons/crm/res_config.py b/addons/crm/res_config.py
index d572e50059a..c7ad5fb3d7e 100644
--- a/addons/crm/res_config.py
+++ b/addons/crm/res_config.py
@@ -61,7 +61,8 @@ class crm_configuration(osv.TransientModel):
help='Allows you to track your customers/suppliers claims and grievances.\n'
'-This installs the module crm_claim.'),
'module_crm_helpdesk': fields.boolean("Manage Helpdesk and Support",
- help="""Allows you to communicate with Customer, process Customer query, and provide better help and support. This installs the module crm_helpdesk."""),
+ help='Allows you to communicate with Customer, process Customer query, and provide better help and support.\n'
+ '-This installs the module crm_helpdesk.'),
'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
implied_group='base.group_multi_salesteams',
help="""Allows you to use Sales Teams to manage your leads and opportunities."""),
From 4bc4547cb90f158d80ffb3275ae47fe2ead66192 Mon Sep 17 00:00:00 2001
From: "Bharat R. Devnani (OpenERP)"
Date: Fri, 30 Aug 2013 19:26:43 +0530
Subject: [PATCH 114/256] [ADD] added the functionalities mentioned on pad
bzr revid: bde@tinyerp.com-20130830135643-ehzm0bdtvq8lltdi
---
addons/account/account.py | 4 ++++
addons/account/account_installer.xml | 2 +-
addons/l10n_ar/l10n_ar_wizard.xml | 10 +++++++---
addons/l10n_at/l10n_chart_at_wizard.xml | 10 +++++++---
addons/l10n_be/wizard/account_wizard.xml | 12 +++++++++---
addons/l10n_bo/l10n_bo_wizard.xml | 10 +++++++---
addons/l10n_br/l10n_br_wizard.xml | 12 +++++++++---
addons/l10n_ca/l10n_ca_wizard.xml | 6 +++++-
addons/l10n_ch/wizard.xml | 6 ++++++
addons/l10n_cl/l10n_cl_wizard.xml | 7 ++++++-
addons/l10n_cn/l10n_chart_cn_wizard.xml | 7 ++++++-
addons/l10n_co/data/l10n_chart_mx_wizard.xml | 6 ++++++
addons/l10n_cr/l10n_wizard.xml | 7 ++++++-
addons/l10n_de/l10n_de_wizard.xml | 6 +++++-
addons/l10n_ec/l10n_chart_ec_wizard.xml | 7 ++++++-
addons/l10n_es/l10n_es_wizard.xml | 5 +++++
addons/l10n_et/l10n_et_wizard.xml | 6 ++++++
addons/l10n_fr/l10n_fr_wizard.xml | 5 +++++
addons/l10n_gr/l10n_gr_wizard.xml | 7 ++++++-
addons/l10n_gt/l10n_gt_base.xml | 4 ++++
addons/l10n_hn/l10n_hn_base.xml | 4 ++++
addons/l10n_hr/l10n_hr_wizard.xml | 6 ++++++
addons/l10n_in/l10n_in_wizard.xml | 5 +++++
addons/l10n_it/l10n_chart_it_generic.xml | 6 ++++++
addons/l10n_lu/l10n_lu_wizard.xml | 5 +++++
addons/l10n_ma/l10n_ma_tax.xml | 3 ++-
addons/l10n_ma/l10n_ma_wizard.xml | 5 +++++
addons/l10n_mx/data/l10n_chart_mx_wizard.xml | 12 +++++++++---
addons/l10n_nl/l10n_nl_wizard.xml | 7 ++++++-
addons/l10n_pa/l10n_pa_wizard.xml | 4 ++++
addons/l10n_pe/l10n_pe_wizard.xml | 4 ++++
addons/l10n_pl/l10n_chart_pl_wizard.xml | 5 +++++
addons/l10n_pt/l10n_chart_pt_wizard.xml | 5 +++++
addons/l10n_ro/l10n_chart_ro_wizard.xml | 5 +++++
addons/l10n_si/l10n_si_wizard.xml | 6 ++++++
.../l10n_syscohada/l10n_syscohada_wizard.xml | 6 ++++++
addons/l10n_th/account_data.xml | 6 ++++++
addons/l10n_tr/l10n_tr_wizard.xml | 6 ++++++
addons/l10n_uk/l10n_uk_wizard.xml | 5 +++++
addons/l10n_us/__openerp__.py | 3 ++-
addons/l10n_us/l10n_us_wizard.xml | 11 ++++++++++-
addons/l10n_uy/l10n_uy_wizard.xml | 5 +++++
addons/l10n_ve/data/account_tax.xml | 18 ++----------------
addons/l10n_ve/data/l10n_chart_ve_wizard.xml | 5 +++++
addons/l10n_vn/l10n_vn_wizard.xml | 4 ++++
45 files changed, 244 insertions(+), 46 deletions(-)
diff --git a/addons/account/account.py b/addons/account/account.py
index 33b52b643ed..3a5e2f12cfc 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -3030,6 +3030,9 @@ class wizard_multi_charts_accounts(osv.osv_memory):
}
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
+ if context.get('default_currency_id', False):
+ dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', context.get('default_currency_id'))
+ return {'value': {'currency_id': view_id}}
currency_id = False
if company_id:
currency_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.id
@@ -3067,6 +3070,7 @@ class wizard_multi_charts_accounts(osv.osv_memory):
res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id})
if 'currency_id' in fields:
company_id = res.get('company_id') or False
+
if company_id:
company_obj = self.pool.get('res.company')
country_id = company_obj.browse(cr, uid, company_id, context=context).country_id.id
diff --git a/addons/account/account_installer.xml b/addons/account/account_installer.xml
index b03babc63ac..79557b3f406 100644
--- a/addons/account/account_installer.xml
+++ b/addons/account/account_installer.xml
@@ -51,6 +51,6 @@
3automatic
-
+
diff --git a/addons/l10n_ar/l10n_ar_wizard.xml b/addons/l10n_ar/l10n_ar_wizard.xml
index 82e7671b4d3..f21b5cd8c11 100644
--- a/addons/l10n_ar/l10n_ar_wizard.xml
+++ b/addons/l10n_ar/l10n_ar_wizard.xml
@@ -2,9 +2,13 @@
-
- open
-
+
+ open
+
+
+
+ done
+
diff --git a/addons/l10n_at/l10n_chart_at_wizard.xml b/addons/l10n_at/l10n_chart_at_wizard.xml
index 19d27fb47c7..350ea995edc 100644
--- a/addons/l10n_at/l10n_chart_at_wizard.xml
+++ b/addons/l10n_at/l10n_chart_at_wizard.xml
@@ -1,9 +1,13 @@
-
- open
-
+
+ open
+
+
+
+ done
+
diff --git a/addons/l10n_be/wizard/account_wizard.xml b/addons/l10n_be/wizard/account_wizard.xml
index dde7a77516a..27785c1b6cf 100644
--- a/addons/l10n_be/wizard/account_wizard.xml
+++ b/addons/l10n_be/wizard/account_wizard.xml
@@ -1,7 +1,13 @@
-
- open
-
+
+
+ open
+
+
+
+ done
+
+
diff --git a/addons/l10n_bo/l10n_bo_wizard.xml b/addons/l10n_bo/l10n_bo_wizard.xml
index 4de61247b90..cc2f17058ff 100644
--- a/addons/l10n_bo/l10n_bo_wizard.xml
+++ b/addons/l10n_bo/l10n_bo_wizard.xml
@@ -2,9 +2,13 @@
-
- open
-
+
+ open
+
+
+
+ done
+
diff --git a/addons/l10n_br/l10n_br_wizard.xml b/addons/l10n_br/l10n_br_wizard.xml
index 748b8913660..5988a2ccd40 100644
--- a/addons/l10n_br/l10n_br_wizard.xml
+++ b/addons/l10n_br/l10n_br_wizard.xml
@@ -1,8 +1,14 @@
-
- open
-
+
+
+ open
+
+
+
+ done
+
+
\ No newline at end of file
diff --git a/addons/l10n_ca/l10n_ca_wizard.xml b/addons/l10n_ca/l10n_ca_wizard.xml
index 19d27fb47c7..007f2badc7a 100644
--- a/addons/l10n_ca/l10n_ca_wizard.xml
+++ b/addons/l10n_ca/l10n_ca_wizard.xml
@@ -4,6 +4,10 @@
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_ch/wizard.xml b/addons/l10n_ch/wizard.xml
index dde7a77516a..8247cf51cc0 100644
--- a/addons/l10n_ch/wizard.xml
+++ b/addons/l10n_ch/wizard.xml
@@ -1,7 +1,13 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_cl/l10n_cl_wizard.xml b/addons/l10n_cl/l10n_cl_wizard.xml
index 704ac4daf38..f05a8d4d664 100644
--- a/addons/l10n_cl/l10n_cl_wizard.xml
+++ b/addons/l10n_cl/l10n_cl_wizard.xml
@@ -1,9 +1,14 @@
+
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_cn/l10n_chart_cn_wizard.xml b/addons/l10n_cn/l10n_chart_cn_wizard.xml
index 52aaa88fdab..d93d83a37aa 100644
--- a/addons/l10n_cn/l10n_chart_cn_wizard.xml
+++ b/addons/l10n_cn/l10n_chart_cn_wizard.xml
@@ -1,8 +1,13 @@
+
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_co/data/l10n_chart_mx_wizard.xml b/addons/l10n_co/data/l10n_chart_mx_wizard.xml
index e73c75c3506..c6f013cbdb3 100644
--- a/addons/l10n_co/data/l10n_chart_mx_wizard.xml
+++ b/addons/l10n_co/data/l10n_chart_mx_wizard.xml
@@ -1,8 +1,14 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_cr/l10n_wizard.xml b/addons/l10n_cr/l10n_wizard.xml
index 6919eb199b6..2199faa9366 100644
--- a/addons/l10n_cr/l10n_wizard.xml
+++ b/addons/l10n_cr/l10n_wizard.xml
@@ -1,8 +1,13 @@
+
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_de/l10n_de_wizard.xml b/addons/l10n_de/l10n_de_wizard.xml
index 19d27fb47c7..007f2badc7a 100644
--- a/addons/l10n_de/l10n_de_wizard.xml
+++ b/addons/l10n_de/l10n_de_wizard.xml
@@ -4,6 +4,10 @@
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_ec/l10n_chart_ec_wizard.xml b/addons/l10n_ec/l10n_chart_ec_wizard.xml
index 52aaa88fdab..71b98ed174b 100644
--- a/addons/l10n_ec/l10n_chart_ec_wizard.xml
+++ b/addons/l10n_ec/l10n_chart_ec_wizard.xml
@@ -1,8 +1,13 @@
+
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_es/l10n_es_wizard.xml b/addons/l10n_es/l10n_es_wizard.xml
index 52aaa88fdab..22953449166 100644
--- a/addons/l10n_es/l10n_es_wizard.xml
+++ b/addons/l10n_es/l10n_es_wizard.xml
@@ -1,8 +1,13 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_et/l10n_et_wizard.xml b/addons/l10n_et/l10n_et_wizard.xml
index cbbb56b7989..1027cc5874f 100644
--- a/addons/l10n_et/l10n_et_wizard.xml
+++ b/addons/l10n_et/l10n_et_wizard.xml
@@ -1,8 +1,14 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_fr/l10n_fr_wizard.xml b/addons/l10n_fr/l10n_fr_wizard.xml
index 704ac4daf38..193de99eeb4 100644
--- a/addons/l10n_fr/l10n_fr_wizard.xml
+++ b/addons/l10n_fr/l10n_fr_wizard.xml
@@ -1,9 +1,14 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_gr/l10n_gr_wizard.xml b/addons/l10n_gr/l10n_gr_wizard.xml
index 52aaa88fdab..71b98ed174b 100644
--- a/addons/l10n_gr/l10n_gr_wizard.xml
+++ b/addons/l10n_gr/l10n_gr_wizard.xml
@@ -1,8 +1,13 @@
+
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_gt/l10n_gt_base.xml b/addons/l10n_gt/l10n_gt_base.xml
index 8caf6ebe48c..ddce9748193 100644
--- a/addons/l10n_gt/l10n_gt_base.xml
+++ b/addons/l10n_gt/l10n_gt_base.xml
@@ -4,6 +4,10 @@
open
+
+
+ done
+
diff --git a/addons/l10n_hn/l10n_hn_base.xml b/addons/l10n_hn/l10n_hn_base.xml
index b8b85c3f2a7..63cf79a441f 100644
--- a/addons/l10n_hn/l10n_hn_base.xml
+++ b/addons/l10n_hn/l10n_hn_base.xml
@@ -5,6 +5,10 @@
open
+
+
+ done
+
diff --git a/addons/l10n_hr/l10n_hr_wizard.xml b/addons/l10n_hr/l10n_hr_wizard.xml
index b736f241607..25cc74eb4a6 100644
--- a/addons/l10n_hr/l10n_hr_wizard.xml
+++ b/addons/l10n_hr/l10n_hr_wizard.xml
@@ -1,8 +1,14 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_in/l10n_in_wizard.xml b/addons/l10n_in/l10n_in_wizard.xml
index 704ac4daf38..193de99eeb4 100644
--- a/addons/l10n_in/l10n_in_wizard.xml
+++ b/addons/l10n_in/l10n_in_wizard.xml
@@ -1,9 +1,14 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_it/l10n_chart_it_generic.xml b/addons/l10n_it/l10n_chart_it_generic.xml
index dde7a77516a..71b98ed174b 100644
--- a/addons/l10n_it/l10n_chart_it_generic.xml
+++ b/addons/l10n_it/l10n_chart_it_generic.xml
@@ -1,7 +1,13 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_lu/l10n_lu_wizard.xml b/addons/l10n_lu/l10n_lu_wizard.xml
index 52aaa88fdab..22953449166 100644
--- a/addons/l10n_lu/l10n_lu_wizard.xml
+++ b/addons/l10n_lu/l10n_lu_wizard.xml
@@ -1,8 +1,13 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_ma/l10n_ma_tax.xml b/addons/l10n_ma/l10n_ma_tax.xml
index 070d2d4fc9b..f296b22edca 100644
--- a/addons/l10n_ma/l10n_ma_tax.xml
+++ b/addons/l10n_ma/l10n_ma_tax.xml
@@ -742,7 +742,8 @@
-
+
+
diff --git a/addons/l10n_ma/l10n_ma_wizard.xml b/addons/l10n_ma/l10n_ma_wizard.xml
index 52aaa88fdab..22953449166 100644
--- a/addons/l10n_ma/l10n_ma_wizard.xml
+++ b/addons/l10n_ma/l10n_ma_wizard.xml
@@ -1,8 +1,13 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_mx/data/l10n_chart_mx_wizard.xml b/addons/l10n_mx/data/l10n_chart_mx_wizard.xml
index e73c75c3506..baf1a49fc54 100644
--- a/addons/l10n_mx/data/l10n_chart_mx_wizard.xml
+++ b/addons/l10n_mx/data/l10n_chart_mx_wizard.xml
@@ -1,8 +1,14 @@
-
- open
-
+
+
+ open
+
+
+
+ done
+
+
diff --git a/addons/l10n_nl/l10n_nl_wizard.xml b/addons/l10n_nl/l10n_nl_wizard.xml
index 52aaa88fdab..3b11bd90c90 100644
--- a/addons/l10n_nl/l10n_nl_wizard.xml
+++ b/addons/l10n_nl/l10n_nl_wizard.xml
@@ -1,8 +1,13 @@
+
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_pa/l10n_pa_wizard.xml b/addons/l10n_pa/l10n_pa_wizard.xml
index 4de61247b90..8acb61d7edf 100644
--- a/addons/l10n_pa/l10n_pa_wizard.xml
+++ b/addons/l10n_pa/l10n_pa_wizard.xml
@@ -6,5 +6,9 @@
open
+
+ done
+
+
diff --git a/addons/l10n_pe/l10n_pe_wizard.xml b/addons/l10n_pe/l10n_pe_wizard.xml
index 82e7671b4d3..b18ee6e26ca 100644
--- a/addons/l10n_pe/l10n_pe_wizard.xml
+++ b/addons/l10n_pe/l10n_pe_wizard.xml
@@ -6,5 +6,9 @@
open
+
+ done
+
+
diff --git a/addons/l10n_pl/l10n_chart_pl_wizard.xml b/addons/l10n_pl/l10n_chart_pl_wizard.xml
index 52aaa88fdab..22953449166 100644
--- a/addons/l10n_pl/l10n_chart_pl_wizard.xml
+++ b/addons/l10n_pl/l10n_chart_pl_wizard.xml
@@ -1,8 +1,13 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_pt/l10n_chart_pt_wizard.xml b/addons/l10n_pt/l10n_chart_pt_wizard.xml
index 52aaa88fdab..22953449166 100644
--- a/addons/l10n_pt/l10n_chart_pt_wizard.xml
+++ b/addons/l10n_pt/l10n_chart_pt_wizard.xml
@@ -1,8 +1,13 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_ro/l10n_chart_ro_wizard.xml b/addons/l10n_ro/l10n_chart_ro_wizard.xml
index 52aaa88fdab..22953449166 100644
--- a/addons/l10n_ro/l10n_chart_ro_wizard.xml
+++ b/addons/l10n_ro/l10n_chart_ro_wizard.xml
@@ -1,8 +1,13 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_si/l10n_si_wizard.xml b/addons/l10n_si/l10n_si_wizard.xml
index 5a4b51a01e9..65e60217eb3 100644
--- a/addons/l10n_si/l10n_si_wizard.xml
+++ b/addons/l10n_si/l10n_si_wizard.xml
@@ -1,8 +1,14 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_syscohada/l10n_syscohada_wizard.xml b/addons/l10n_syscohada/l10n_syscohada_wizard.xml
index dde7a77516a..2f815a22ff6 100644
--- a/addons/l10n_syscohada/l10n_syscohada_wizard.xml
+++ b/addons/l10n_syscohada/l10n_syscohada_wizard.xml
@@ -1,7 +1,13 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_th/account_data.xml b/addons/l10n_th/account_data.xml
index 9ae3d9e3364..5df58bc3867 100644
--- a/addons/l10n_th/account_data.xml
+++ b/addons/l10n_th/account_data.xml
@@ -645,8 +645,14 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_tr/l10n_tr_wizard.xml b/addons/l10n_tr/l10n_tr_wizard.xml
index 66b9d37221b..a2c8c45c2de 100644
--- a/addons/l10n_tr/l10n_tr_wizard.xml
+++ b/addons/l10n_tr/l10n_tr_wizard.xml
@@ -1,8 +1,14 @@
+
open
+
+
+ done
+
+
diff --git a/addons/l10n_uk/l10n_uk_wizard.xml b/addons/l10n_uk/l10n_uk_wizard.xml
index 8a6f1a5a83c..b488b5b53f0 100644
--- a/addons/l10n_uk/l10n_uk_wizard.xml
+++ b/addons/l10n_uk/l10n_uk_wizard.xml
@@ -1,9 +1,14 @@
+
open
+
+ done
+
+
diff --git a/addons/l10n_us/__openerp__.py b/addons/l10n_us/__openerp__.py
index 90c0e6fd4ad..cc0de9350f2 100644
--- a/addons/l10n_us/__openerp__.py
+++ b/addons/l10n_us/__openerp__.py
@@ -36,7 +36,8 @@ United States - Chart of accounts.
'account_tax_code_template.xml',
'account_tax_template.xml',
'account_chart_template_after.xml',
- 'l10n_us_wizard.xml'
+ 'l10n_us_wizard.xml',
+ 'account_chart_template_set_accounts.xml'
],
'demo': [],
'test': [],
diff --git a/addons/l10n_us/l10n_us_wizard.xml b/addons/l10n_us/l10n_us_wizard.xml
index 52aaa88fdab..7d53a61910c 100644
--- a/addons/l10n_us/l10n_us_wizard.xml
+++ b/addons/l10n_us/l10n_us_wizard.xml
@@ -1,8 +1,17 @@
+
+
+ {'default_currency_id': "USD"}
+
+
open
-
+
+
+ done
+
+
diff --git a/addons/l10n_uy/l10n_uy_wizard.xml b/addons/l10n_uy/l10n_uy_wizard.xml
index 3dea57dc5c2..4a82f72ca23 100644
--- a/addons/l10n_uy/l10n_uy_wizard.xml
+++ b/addons/l10n_uy/l10n_uy_wizard.xml
@@ -4,5 +4,10 @@
open
+
+
+ done
+
+
diff --git a/addons/l10n_ve/data/account_tax.xml b/addons/l10n_ve/data/account_tax.xml
index d28b0d1d8f3..915761fc92a 100644
--- a/addons/l10n_ve/data/account_tax.xml
+++ b/addons/l10n_ve/data/account_tax.xml
@@ -1,12 +1,12 @@
-
+ Exento0.00000percent
- sale
+ all
@@ -14,7 +14,6 @@
-
IVA (12.0%) ventas
@@ -54,19 +53,6 @@
-
-
- Exento
- 0.00000
- percent
- purchase
-
-
-
-
-
-
- IVA (12.0%) compras
diff --git a/addons/l10n_ve/data/l10n_chart_ve_wizard.xml b/addons/l10n_ve/data/l10n_chart_ve_wizard.xml
index dde7a77516a..e772699014f 100644
--- a/addons/l10n_ve/data/l10n_chart_ve_wizard.xml
+++ b/addons/l10n_ve/data/l10n_chart_ve_wizard.xml
@@ -3,5 +3,10 @@
open
+
+
+ done
+
+
diff --git a/addons/l10n_vn/l10n_vn_wizard.xml b/addons/l10n_vn/l10n_vn_wizard.xml
index 368a3cbd8b3..f84b8d654e4 100755
--- a/addons/l10n_vn/l10n_vn_wizard.xml
+++ b/addons/l10n_vn/l10n_vn_wizard.xml
@@ -5,6 +5,10 @@
open
+
+
+ done
+
\ No newline at end of file
From 00711ec5bdd7df523196f3f5a2d712f47ccace9a Mon Sep 17 00:00:00 2001
From: "Turkesh Patel (Open ERP)"
Date: Mon, 2 Sep 2013 16:06:29 +0530
Subject: [PATCH 115/256] [IMP] set defalut cot as per last configuration
wizard.
bzr revid: tpa@tinyerp.com-20130902103629-9d1joumo51b7w8qg
---
addons/account/account.py | 5 ++++-
addons/account/account_installer.xml | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/addons/account/account.py b/addons/account/account.py
index 33b52b643ed..4773547df30 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -3076,7 +3076,10 @@ class wizard_multi_charts_accounts(osv.osv_memory):
ids = self.pool.get('account.chart.template').search(cr, uid, [('visible', '=', True)], context=context)
if ids:
if 'chart_template_id' in fields:
- res.update({'only_one_chart_template': len(ids) == 1, 'chart_template_id': ids[0]})
+ chart_id = ids[0]
+ if context.get("default_charts"):
+ chart_id = self.pool.get('ir.model.data').search_read(cr, uid, [('model','=','account.chart.template'),('module','=',context.get("default_charts"))], ['res_id'], context=context)[0]['res_id']
+ res.update({'only_one_chart_template': len(ids) == 1, 'chart_template_id': chart_id})
if 'sale_tax' in fields:
sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
, "=", ids[0]), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
diff --git a/addons/account/account_installer.xml b/addons/account/account_installer.xml
index b03babc63ac..8d1b25b299f 100644
--- a/addons/account/account_installer.xml
+++ b/addons/account/account_installer.xml
@@ -10,7 +10,7 @@
From 1b0ad7b55f013f94e39aac141d4f8edf0cd31847 Mon Sep 17 00:00:00 2001
From: "Turkesh Patel (Open ERP)"
Date: Mon, 2 Sep 2013 16:59:43 +0530
Subject: [PATCH 116/256] [IMP] l10n_us: improved code to set default recivable
and payble accounts.
bzr revid: tpa@tinyerp.com-20130902112943-cj3jbtn1bt5n15y2
---
addons/account/account.py | 1 +
addons/l10n_us/account_chart_template_after.xml | 2 ++
2 files changed, 3 insertions(+)
diff --git a/addons/account/account.py b/addons/account/account.py
index c298a15646b..45308a9e7d4 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -3030,6 +3030,7 @@ class wizard_multi_charts_accounts(osv.osv_memory):
}
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
+ if context is None:context = {}
if context.get('default_currency_id', False):
dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', context.get('default_currency_id'))
return {'value': {'currency_id': view_id}}
diff --git a/addons/l10n_us/account_chart_template_after.xml b/addons/l10n_us/account_chart_template_after.xml
index 5ca3a169aba..4dea930f09f 100644
--- a/addons/l10n_us/account_chart_template_after.xml
+++ b/addons/l10n_us/account_chart_template_after.xml
@@ -6,6 +6,8 @@
+
+
From 34862b9d247571606b11b751542751b9720d5627 Mon Sep 17 00:00:00 2001
From: "Turkesh Patel (Open ERP)"
Date: Mon, 2 Sep 2013 18:38:39 +0530
Subject: [PATCH 117/256] [ADD] added currency_id in account.chart.template and
remove onchange from company_id to set currency in account data configuration
wizard and set currency_in in l10n_us.
bzr revid: tpa@tinyerp.com-20130902130839-mpvu7nfzefmzh9kq
---
addons/account/account.py | 14 +++-----------
addons/account/account_view.xml | 2 +-
addons/l10n_us/account_chart_template.xml | 9 +++++++++
addons/l10n_us/l10n_us_wizard.xml | 8 ++------
4 files changed, 15 insertions(+), 18 deletions(-)
diff --git a/addons/account/account.py b/addons/account/account.py
index 45308a9e7d4..1a593e4ad59 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -2774,6 +2774,7 @@ class account_chart_template(osv.osv):
'parent_id': fields.many2one('account.chart.template', 'Parent Chart Template'),
'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
'visible': fields.boolean('Can be Visible?', help="Set this to False if you don't want this template to be used actively in the wizard that generate Chart of Accounts from templates, this is useful when you want to generate accounts of this template only when loading its child template."),
+ 'currency_id': fields.many2one('res.currency', 'Currency'),
'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sale and purchase rates or choose from list of taxes. This last choice assumes that the set of tax defined on this template is complete'),
'account_root_id': fields.many2one('account.account.template', 'Root Account', domain=[('parent_id','=',False)]),
'tax_code_root_id': fields.many2one('account.tax.code.template', 'Root Tax Code', domain=[('parent_id','=',False)]),
@@ -3029,16 +3030,6 @@ class wizard_multi_charts_accounts(osv.osv_memory):
'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sales and purchase rates or use the usual m2o fields. This last choice assumes that the set of tax defined for the chosen template is complete'),
}
- def onchange_company_id(self, cr, uid, ids, company_id, context=None):
- if context is None:context = {}
- if context.get('default_currency_id', False):
- dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', context.get('default_currency_id'))
- return {'value': {'currency_id': view_id}}
- currency_id = False
- if company_id:
- currency_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.id
- return {'value': {'currency_id': currency_id}}
-
def onchange_tax_rate(self, cr, uid, ids, rate=False, context=None):
return {'value': {'purchase_tax_rate': rate or False}}
@@ -3048,7 +3039,8 @@ class wizard_multi_charts_accounts(osv.osv_memory):
res['value'] = {'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False}
if chart_template_id:
data = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
- res['value'].update({'complete_tax_set': data.complete_tax_set})
+ currency_id = data.currency_id.id or self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
+ res['value'].update({'complete_tax_set': data.complete_tax_set, 'currency_id': currency_id})
if data.complete_tax_set:
# default tax is given by the lowest sequence. For same sequence we will take the latest created as it will be the case for tax created while isntalling the generic chart of account
sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml
index 91c6d6608fd..cb53d7d8fdc 100644
--- a/addons/account/account_view.xml
+++ b/addons/account/account_view.xml
@@ -2120,7 +2120,7 @@
-
+
diff --git a/addons/l10n_us/account_chart_template.xml b/addons/l10n_us/account_chart_template.xml
index 0863680981f..704afbad152 100644
--- a/addons/l10n_us/account_chart_template.xml
+++ b/addons/l10n_us/account_chart_template.xml
@@ -6,6 +6,7 @@
Basic Chart of Account
+
@@ -13,45 +14,53 @@
+ Advertising
+ Agriculture
+ Construction Trades (Plumber, Electrician, HVAC, etc.)
+ Financial Services other than Accounting or Bookkeeping
+ General Service-Based Business
+ Legal Services
+ General Product-Based Business
+
diff --git a/addons/l10n_us/l10n_us_wizard.xml b/addons/l10n_us/l10n_us_wizard.xml
index 9373935b22c..19d27fb47c7 100644
--- a/addons/l10n_us/l10n_us_wizard.xml
+++ b/addons/l10n_us/l10n_us_wizard.xml
@@ -1,13 +1,9 @@
-
-
- {'default_currency_id': "USD"}
-
-
+
open
-
+
From 7acb2f3105d61ae668e6bd2042be789d965e2e10 Mon Sep 17 00:00:00 2001
From: "Mehul Mehta (OpenERP)"
Date: Mon, 2 Sep 2013 21:08:40 +0800
Subject: [PATCH 118/256] [IMP]view a dynaemic font in general setting for
select font and set a font for company header and footer
bzr revid: mme@tinyerp.com-20130902130840-pvtegpqlpxkip46k
---
addons/base_setup/res_config.py | 52 +++++++++++++++++++++++++++++----
1 file changed, 46 insertions(+), 6 deletions(-)
diff --git a/addons/base_setup/res_config.py b/addons/base_setup/res_config.py
index bbaa6713c82..b9d3280e49e 100644
--- a/addons/base_setup/res_config.py
+++ b/addons/base_setup/res_config.py
@@ -21,14 +21,54 @@
from openerp.osv import fields, osv
import re
-import matplotlib.font_manager
+import os
+import platform
+from reportlab import rl_config
+from openerp.tools import config
_lst_font=[]
-for i in matplotlib.font_manager.findSystemFonts(fontpaths=None, fontext='ttf'):
- m=re.sub('(.*/)','', i)
- n=m.strip('.ttf')
- n=n.replace('-',' ')
- _lst_font.append((n,n))
+TTFSearchPath_Linux = [
+ '/usr/share/fonts/truetype', # SuSE
+ '/usr/share/fonts/dejavu', '/usr/share/fonts/liberation', # Fedora, RHEL
+ '/usr/share/fonts/truetype/*', # Ubuntu,
+ '/usr/share/fonts/TTF/*', # at Mandriva/Mageia
+ '/usr/share/fonts/TTF', # Arch Linux
+ ]
+
+TTFSearchPath_Windows = [
+ 'c:/winnt/fonts',
+ 'c:/windows/fonts'
+ ]
+
+TTFSearchPath_Darwin = [
+ '~/Library/Fonts',
+ '/Library/Fonts',
+ '/Network/Library/Fonts',
+ '/System/Library/Fonts',
+ ]
+
+TTFSearchPathMap = {
+ 'Darwin': TTFSearchPath_Darwin,
+ 'Windows': TTFSearchPath_Windows,
+ 'Linux': TTFSearchPath_Linux,
+}
+searchpath = []
+
+if config.get('fonts_search_path'):
+ searchpath += map(str.strip, config.get('fonts_search_path').split(','))
+
+local_platform = platform.system()
+if local_platform in TTFSearchPathMap:
+ searchpath += TTFSearchPathMap[local_platform]
+
+searchpath += rl_config.TTFSearchPath
+for dirglob in searchpath:
+ if os.path.isdir(dirglob):
+ for file in os.listdir('/'+dirglob):
+ if os.path.isfile('/'+dirglob+'/'+file):
+ font=file.strip('.ttf')
+ font=font.replace('-',' ')
+ _lst_font.append((font,font))
class base_config_settings(osv.osv_memory):
_name = 'base.config.settings'
From e7ef5b87909dc6acaedf8b0660b2ba87189e1d6c Mon Sep 17 00:00:00 2001
From: "Turkesh Patel (Open ERP)"
Date: Tue, 3 Sep 2013 11:07:15 +0530
Subject: [PATCH 119/256] [IMP] in order to set default chart which was last
created set max of ids.
bzr revid: tpa@tinyerp.com-20130903053715-zyduv0q0b81lz0vq
---
addons/account/account.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/addons/account/account.py b/addons/account/account.py
index 1a593e4ad59..9f9ee187d69 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -3072,7 +3072,8 @@ class wizard_multi_charts_accounts(osv.osv_memory):
ids = self.pool.get('account.chart.template').search(cr, uid, [('visible', '=', True)], context=context)
if ids:
if 'chart_template_id' in fields:
- chart_id = ids[0]
+ #in order to get set default chart which was last created set max of ids.
+ chart_id = max(ids)
if context.get("default_charts"):
chart_id = self.pool.get('ir.model.data').search_read(cr, uid, [('model','=','account.chart.template'),('module','=',context.get("default_charts"))], ['res_id'], context=context)[0]['res_id']
res.update({'only_one_chart_template': len(ids) == 1, 'chart_template_id': chart_id})
From 305e9f5cddf209db45e5bb5c6cfa7d7838b171f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Tue, 3 Sep 2013 16:58:40 +0200
Subject: [PATCH 120/256] [ADD] web, web_kanban: moved sparkline and gauge
widgets from crm and sale_crm into web, because other modules will have to
use it in kanban veiws.
bzr revid: tde@openerp.com-20130903145840-uxt0blhjcly1oz3o
---
addons/web/__openerp__.py | 2 +
.../lib/jquery.sparkline/jquery.sparkline.js | 3047 +++++++++++++++++
.../web/static/lib/justgage/justgage.1.0.1.js | 946 +++++
addons/web/static/lib/qweb/qweb2.js | 1 +
addons/web_kanban/static/src/js/kanban.js | 142 +
5 files changed, 4138 insertions(+)
create mode 100644 addons/web/static/lib/jquery.sparkline/jquery.sparkline.js
create mode 100644 addons/web/static/lib/justgage/justgage.1.0.1.js
diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py
index 5c8d27e11cb..32a108c131d 100644
--- a/addons/web/__openerp__.py
+++ b/addons/web/__openerp__.py
@@ -32,9 +32,11 @@ This module provides the core of the OpenERP Web Client.
"static/lib/jquery.ui.notify/js/jquery.notify.js",
"static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
"static/lib/jquery.scrollTo/jquery.scrollTo-min.js",
+ "static/lib/jquery.sparkline/jquery.sparkline.js",
"static/lib/jquery.tipsy/jquery.tipsy.js",
"static/lib/jquery.textext/jquery.textext.js",
"static/lib/jquery.timeago/jquery.timeago.js",
+ "static/lib/justgage/justgage.1.0.1.js",
"static/lib/qweb/qweb2.js",
"static/lib/underscore/underscore.js",
"static/lib/underscore.string/lib/underscore.string.js",
diff --git a/addons/web/static/lib/jquery.sparkline/jquery.sparkline.js b/addons/web/static/lib/jquery.sparkline/jquery.sparkline.js
new file mode 100644
index 00000000000..8069db45fe9
--- /dev/null
+++ b/addons/web/static/lib/jquery.sparkline/jquery.sparkline.js
@@ -0,0 +1,3047 @@
+/**
+*
+* jquery.sparkline.js
+*
+* v2.1.1
+* (c) Splunk, Inc
+* Contact: Gareth Watts (gareth@splunk.com)
+* http://omnipotent.net/jquery.sparkline/
+*
+* Generates inline sparkline charts from data supplied either to the method
+* or inline in HTML
+*
+* Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
+* (Firefox 2.0+, Safari, Opera, etc)
+*
+* License: New BSD License
+*
+* Copyright (c) 2012, Splunk Inc.
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification,
+* are permitted provided that the following conditions are met:
+*
+* * Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright notice,
+* this list of conditions and the following disclaimer in the documentation
+* and/or other materials provided with the distribution.
+* * Neither the name of Splunk Inc nor the names of its contributors may
+* be used to endorse or promote products derived from this software without
+* specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*
+* Usage:
+* $(selector).sparkline(values, options)
+*
+* If values is undefined or set to 'html' then the data values are read from the specified tag:
+*
Sparkline: 1,4,6,6,8,5,3,5
+* $('.sparkline').sparkline();
+* There must be no spaces in the enclosed data set
+*
+* Otherwise values must be an array of numbers or null values
+*
Sparkline: This text replaced if the browser is compatible
+* $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])
+* $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])
+*
+* Values can also be specified in an HTML comment, or as a values attribute:
+*
Sparkline:
+*
Sparkline:
+* $('.sparkline').sparkline();
+*
+* For line charts, x values can also be specified:
+*
Sparkline: 1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5
+* $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])
+*
+* By default, options should be passed in as teh second argument to the sparkline function:
+* $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})
+*
+* Options can also be set by passing them on the tag itself. This feature is disabled by default though
+* as there's a slight performance overhead:
+* $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})
+*
Sparkline: loading
+* Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix)
+*
+* Supported options:
+* lineColor - Color of the line used for the chart
+* fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart
+* width - Width of the chart - Defaults to 3 times the number of values in pixels
+* height - Height of the chart - Defaults to the height of the containing element
+* chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied
+* chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied
+* chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax
+* chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied
+* chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied
+* composite - If true then don't erase any existing chart attached to the tag, but draw
+* another chart over the top - Note that width and height are ignored if an
+* existing chart is detected.
+* tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
+* enableTagOptions - Whether to check tags for sparkline options
+* tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
+* disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
+* hidden dom element, avoding a browser reflow
+* disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
+* making the plugin perform much like it did in 1.x
+* disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
+* disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
+* defaults to false (highlights enabled)
+* highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
+* tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
+* tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
+* tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
+* tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
+* tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip
+* callback is given arguments of (sparkline, options, fields)
+* tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
+* tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries)
+* to control the format of the tooltip
+* tooltipPrefix - A string to prepend to each field displayed in a tooltip
+* tooltipSuffix - A string to append to each field displayed in a tooltip
+* tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
+* tooltipValueLookups - An object or range map to map field values to tooltip strings
+* (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
+* numberFormatter - Optional callback for formatting numbers in tooltips
+* numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
+* numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
+* numberDigitGroupCount - Number of digits between group separator - Defaults to 3
+*
+* There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
+* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
+* line - Line chart. Options:
+* spotColor - Set to '' to not end each line in a circular spot
+* minSpotColor - If set, color of spot at minimum value
+* maxSpotColor - If set, color of spot at maximum value
+* spotRadius - Radius in pixels
+* lineWidth - Width of line in pixels
+* normalRangeMin
+* normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
+* or expected range of values
+* normalRangeColor - Color to use for the above bar
+* drawNormalOnTop - Draw the normal range above the chart fill color if true
+* defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
+* highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
+* highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
+* valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map
+*
+* bar - Bar chart. Options:
+* barColor - Color of bars for postive values
+* negBarColor - Color of bars for negative values
+* zeroColor - Color of bars with zero values
+* nullColor - Color of bars with null values - Defaults to omitting the bar entirely
+* barWidth - Width of bars in pixels
+* colorMap - Optional mappnig of values to colors to override the *BarColor values above
+* can be an Array of values to control the color of individual bars or a range map
+* to specify colors for individual ranges of values
+* barSpacing - Gap between bars in pixels
+* zeroAxis - Centers the y-axis around zero if true
+*
+* tristate - Charts values of win (>0), lose (<0) or draw (=0)
+* posBarColor - Color of win values
+* negBarColor - Color of lose values
+* zeroBarColor - Color of draw values
+* barWidth - Width of bars in pixels
+* barSpacing - Gap between bars in pixels
+* colorMap - Optional mappnig of values to colors to override the *BarColor values above
+* can be an Array of values to control the color of individual bars or a range map
+* to specify colors for individual ranges of values
+*
+* discrete - Options:
+* lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
+* thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
+* thresholdColor
+*
+* bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
+* options:
+* targetColor - The color of the vertical target marker
+* targetWidth - The width of the target marker in pixels
+* performanceColor - The color of the performance measure horizontal bar
+* rangeColors - Colors to use for each qualitative range background color
+*
+* pie - Pie chart. Options:
+* sliceColors - An array of colors to use for pie slices
+* offset - Angle in degrees to offset the first slice - Try -90 or +90
+* borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
+* borderColor - Color to use for the pie chart border - Defaults to #000
+*
+* box - Box plot. Options:
+* raw - Set to true to supply pre-computed plot points as values
+* values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
+* When set to false you can supply any number of values and the box plot will
+* be computed for you. Default is false.
+* showOutliers - Set to true (default) to display outliers as circles
+* outlierIQR - Interquartile range used to determine outliers. Default 1.5
+* boxLineColor - Outline color of the box
+* boxFillColor - Fill color for the box
+* whiskerColor - Line color used for whiskers
+* outlierLineColor - Outline color of outlier circles
+* outlierFillColor - Fill color of the outlier circles
+* spotRadius - Radius of outlier circles
+* medianColor - Line color of the median line
+* target - Draw a target cross hair at the supplied value (default undefined)
+*
+*
+*
+* Examples:
+* $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
+* $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
+* $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
+* $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
+* $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
+* $('#pie').sparkline([1,1,2], { type:'pie' });
+*/
+
+/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
+
+(function(factory) {
+ if(typeof define === 'function' && define.amd) {
+ define(['jquery'], factory);
+ }
+ else {
+ factory(jQuery);
+ }
+}
+(function($) {
+ 'use strict';
+
+ var UNSET_OPTION = {},
+ getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
+ remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
+ MouseHandler, Tooltip, barHighlightMixin,
+ line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,
+ VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;
+
+ /**
+ * Default configuration settings
+ */
+ getDefaults = function () {
+ return {
+ // Settings common to most/all chart types
+ common: {
+ type: 'line',
+ lineColor: '#00f',
+ fillColor: '#cdf',
+ defaultPixelsPerValue: 3,
+ width: 'auto',
+ height: 'auto',
+ composite: false,
+ tagValuesAttribute: 'values',
+ tagOptionsPrefix: 'spark',
+ enableTagOptions: false,
+ enableHighlight: true,
+ highlightLighten: 1.4,
+ tooltipSkipNull: true,
+ tooltipPrefix: '',
+ tooltipSuffix: '',
+ disableHiddenCheck: false,
+ numberFormatter: false,
+ numberDigitGroupCount: 3,
+ numberDigitGroupSep: ',',
+ numberDecimalMark: '.',
+ disableTooltips: false,
+ disableInteraction: false
+ },
+ // Defaults for line charts
+ line: {
+ spotColor: '#f80',
+ highlightSpotColor: '#5f5',
+ highlightLineColor: '#f22',
+ spotRadius: 1.5,
+ minSpotColor: '#f80',
+ maxSpotColor: '#f80',
+ lineWidth: 1,
+ normalRangeMin: undefined,
+ normalRangeMax: undefined,
+ normalRangeColor: '#ccc',
+ drawNormalOnTop: false,
+ chartRangeMin: undefined,
+ chartRangeMax: undefined,
+ chartRangeMinX: undefined,
+ chartRangeMaxX: undefined,
+ tooltipFormat: new SPFormat('● {{prefix}}{{y}}{{suffix}}')
+ },
+ // Defaults for bar charts
+ bar: {
+ barColor: '#3366cc',
+ negBarColor: '#f44',
+ stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
+ '#dd4477', '#0099c6', '#990099'],
+ zeroColor: undefined,
+ nullColor: undefined,
+ zeroAxis: true,
+ barWidth: 4,
+ barSpacing: 1,
+ chartRangeMax: undefined,
+ chartRangeMin: undefined,
+ chartRangeClip: false,
+ colorMap: undefined,
+ tooltipFormat: new SPFormat('● {{prefix}}{{value}}{{suffix}}')
+ },
+ // Defaults for tristate charts
+ tristate: {
+ barWidth: 4,
+ barSpacing: 1,
+ posBarColor: '#6f6',
+ negBarColor: '#f44',
+ zeroBarColor: '#999',
+ colorMap: {},
+ tooltipFormat: new SPFormat('● {{value:map}}'),
+ tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
+ },
+ // Defaults for discrete charts
+ discrete: {
+ lineHeight: 'auto',
+ thresholdColor: undefined,
+ thresholdValue: 0,
+ chartRangeMax: undefined,
+ chartRangeMin: undefined,
+ chartRangeClip: false,
+ tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')
+ },
+ // Defaults for bullet charts
+ bullet: {
+ targetColor: '#f33',
+ targetWidth: 3, // width of the target bar in pixels
+ performanceColor: '#33f',
+ rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],
+ base: undefined, // set this to a number to change the base start number
+ tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),
+ tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }
+ },
+ // Defaults for pie charts
+ pie: {
+ offset: 0,
+ sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
+ '#dd4477', '#0099c6', '#990099'],
+ borderWidth: 0,
+ borderColor: '#000',
+ tooltipFormat: new SPFormat('● {{value}} ({{percent.1}}%)')
+ },
+ // Defaults for box plots
+ box: {
+ raw: false,
+ boxLineColor: '#000',
+ boxFillColor: '#cdf',
+ whiskerColor: '#000',
+ outlierLineColor: '#333',
+ outlierFillColor: '#fff',
+ medianColor: '#f00',
+ showOutliers: true,
+ outlierIQR: 1.5,
+ spotRadius: 1.5,
+ target: undefined,
+ targetColor: '#4a2',
+ chartRangeMax: undefined,
+ chartRangeMin: undefined,
+ tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),
+ tooltipFormatFieldlistKey: 'field',
+ tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',
+ uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',
+ lw: 'Left Whisker', rw: 'Right Whisker'} }
+ }
+ };
+ };
+
+ // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
+ defaultStyles = '.jqstooltip { ' +
+ 'position: absolute;' +
+ 'left: 0px;' +
+ 'top: 0px;' +
+ 'visibility: hidden;' +
+ 'background: rgb(0, 0, 0) transparent;' +
+ 'background-color: rgba(0,0,0,0.6);' +
+ 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
+ '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
+ 'color: white;' +
+ 'font: 10px arial, san serif;' +
+ 'text-align: left;' +
+ 'white-space: nowrap;' +
+ 'padding: 5px;' +
+ 'border: 1px solid white;' +
+ 'z-index: 10000;' +
+ '}' +
+ '.jqsfield { ' +
+ 'color: white;' +
+ 'font: 10px arial, san serif;' +
+ 'text-align: left;' +
+ '}';
+
+ /**
+ * Utilities
+ */
+
+ createClass = function (/* [baseclass, [mixin, ...]], definition */) {
+ var Class, args;
+ Class = function () {
+ this.init.apply(this, arguments);
+ };
+ if (arguments.length > 1) {
+ if (arguments[0]) {
+ Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);
+ Class._super = arguments[0].prototype;
+ } else {
+ Class.prototype = arguments[arguments.length - 1];
+ }
+ if (arguments.length > 2) {
+ args = Array.prototype.slice.call(arguments, 1, -1);
+ args.unshift(Class.prototype);
+ $.extend.apply($, args);
+ }
+ } else {
+ Class.prototype = arguments[0];
+ }
+ Class.prototype.cls = Class;
+ return Class;
+ };
+
+ /**
+ * Wraps a format string for tooltips
+ * {{x}}
+ * {{x.2}
+ * {{x:months}}
+ */
+ $.SPFormatClass = SPFormat = createClass({
+ fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
+ precre: /(\w+)\.(\d+)/,
+
+ init: function (format, fclass) {
+ this.format = format;
+ this.fclass = fclass;
+ },
+
+ render: function (fieldset, lookups, options) {
+ var self = this,
+ fields = fieldset,
+ match, token, lookupkey, fieldvalue, prec;
+ return this.format.replace(this.fre, function () {
+ var lookup;
+ token = arguments[1];
+ lookupkey = arguments[3];
+ match = self.precre.exec(token);
+ if (match) {
+ prec = match[2];
+ token = match[1];
+ } else {
+ prec = false;
+ }
+ fieldvalue = fields[token];
+ if (fieldvalue === undefined) {
+ return '';
+ }
+ if (lookupkey && lookups && lookups[lookupkey]) {
+ lookup = lookups[lookupkey];
+ if (lookup.get) { // RangeMap
+ return lookups[lookupkey].get(fieldvalue) || fieldvalue;
+ } else {
+ return lookups[lookupkey][fieldvalue] || fieldvalue;
+ }
+ }
+ if (isNumber(fieldvalue)) {
+ if (options.get('numberFormatter')) {
+ fieldvalue = options.get('numberFormatter')(fieldvalue);
+ } else {
+ fieldvalue = formatNumber(fieldvalue, prec,
+ options.get('numberDigitGroupCount'),
+ options.get('numberDigitGroupSep'),
+ options.get('numberDecimalMark'));
+ }
+ }
+ return fieldvalue;
+ });
+ }
+ });
+
+ // convience method to avoid needing the new operator
+ $.spformat = function(format, fclass) {
+ return new SPFormat(format, fclass);
+ };
+
+ clipval = function (val, min, max) {
+ if (val < min) {
+ return min;
+ }
+ if (val > max) {
+ return max;
+ }
+ return val;
+ };
+
+ quartile = function (values, q) {
+ var vl;
+ if (q === 2) {
+ vl = Math.floor(values.length / 2);
+ return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;
+ } else {
+ if (values.length % 2 ) { // odd
+ vl = (values.length * q + q) / 4;
+ return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
+ } else { //even
+ vl = (values.length * q + 2) / 4;
+ return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
+
+ }
+ }
+ };
+
+ normalizeValue = function (val) {
+ var nf;
+ switch (val) {
+ case 'undefined':
+ val = undefined;
+ break;
+ case 'null':
+ val = null;
+ break;
+ case 'true':
+ val = true;
+ break;
+ case 'false':
+ val = false;
+ break;
+ default:
+ nf = parseFloat(val);
+ if (val == nf) {
+ val = nf;
+ }
+ }
+ return val;
+ };
+
+ normalizeValues = function (vals) {
+ var i, result = [];
+ for (i = vals.length; i--;) {
+ result[i] = normalizeValue(vals[i]);
+ }
+ return result;
+ };
+
+ remove = function (vals, filter) {
+ var i, vl, result = [];
+ for (i = 0, vl = vals.length; i < vl; i++) {
+ if (vals[i] !== filter) {
+ result.push(vals[i]);
+ }
+ }
+ return result;
+ };
+
+ isNumber = function (num) {
+ return !isNaN(parseFloat(num)) && isFinite(num);
+ };
+
+ formatNumber = function (num, prec, groupsize, groupsep, decsep) {
+ var p, i;
+ num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');
+ p = (p = $.inArray('.', num)) < 0 ? num.length : p;
+ if (p < num.length) {
+ num[p] = decsep;
+ }
+ for (i = p - groupsize; i > 0; i -= groupsize) {
+ num.splice(i, 0, groupsep);
+ }
+ return num.join('');
+ };
+
+ // determine if all values of an array match a value
+ // returns true if the array is empty
+ all = function (val, arr, ignoreNull) {
+ var i;
+ for (i = arr.length; i--; ) {
+ if (ignoreNull && arr[i] === null) continue;
+ if (arr[i] !== val) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ // sums the numeric values in an array, ignoring other values
+ sum = function (vals) {
+ var total = 0, i;
+ for (i = vals.length; i--;) {
+ total += typeof vals[i] === 'number' ? vals[i] : 0;
+ }
+ return total;
+ };
+
+ ensureArray = function (val) {
+ return $.isArray(val) ? val : [val];
+ };
+
+ // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
+ addCSS = function(css) {
+ var tag;
+ //if ('\v' == 'v') /* ie only */ {
+ if (document.createStyleSheet) {
+ document.createStyleSheet().cssText = css;
+ } else {
+ tag = document.createElement('style');
+ tag.type = 'text/css';
+ document.getElementsByTagName('head')[0].appendChild(tag);
+ tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;
+ }
+ };
+
+ // Provide a cross-browser interface to a few simple drawing primitives
+ $.fn.simpledraw = function (width, height, useExisting, interact) {
+ var target, mhandler;
+ if (useExisting && (target = this.data('_jqs_vcanvas'))) {
+ return target;
+ }
+ if (width === undefined) {
+ width = $(this).innerWidth();
+ }
+ if (height === undefined) {
+ height = $(this).innerHeight();
+ }
+ if ($.fn.sparkline.hasCanvas) {
+ target = new VCanvas_canvas(width, height, this, interact);
+ } else if ($.fn.sparkline.hasVML) {
+ target = new VCanvas_vml(width, height, this);
+ } else {
+ return false;
+ }
+ mhandler = $(this).data('_jqs_mhandler');
+ if (mhandler) {
+ mhandler.registerCanvas(target);
+ }
+ return target;
+ };
+
+ $.fn.cleardraw = function () {
+ var target = this.data('_jqs_vcanvas');
+ if (target) {
+ target.reset();
+ }
+ };
+
+ $.RangeMapClass = RangeMap = createClass({
+ init: function (map) {
+ var key, range, rangelist = [];
+ for (key in map) {
+ if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {
+ range = key.split(':');
+ range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);
+ range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);
+ range[2] = map[key];
+ rangelist.push(range);
+ }
+ }
+ this.map = map;
+ this.rangelist = rangelist || false;
+ },
+
+ get: function (value) {
+ var rangelist = this.rangelist,
+ i, range, result;
+ if ((result = this.map[value]) !== undefined) {
+ return result;
+ }
+ if (rangelist) {
+ for (i = rangelist.length; i--;) {
+ range = rangelist[i];
+ if (range[0] <= value && range[1] >= value) {
+ return range[2];
+ }
+ }
+ }
+ return undefined;
+ }
+ });
+
+ // Convenience function
+ $.range_map = function(map) {
+ return new RangeMap(map);
+ };
+
+ MouseHandler = createClass({
+ init: function (el, options) {
+ var $el = $(el);
+ this.$el = $el;
+ this.options = options;
+ this.currentPageX = 0;
+ this.currentPageY = 0;
+ this.el = el;
+ this.splist = [];
+ this.tooltip = null;
+ this.over = false;
+ this.displayTooltips = !options.get('disableTooltips');
+ this.highlightEnabled = !options.get('disableHighlight');
+ },
+
+ registerSparkline: function (sp) {
+ this.splist.push(sp);
+ if (this.over) {
+ this.updateDisplay();
+ }
+ },
+
+ registerCanvas: function (canvas) {
+ var $canvas = $(canvas.canvas);
+ this.canvas = canvas;
+ this.$canvas = $canvas;
+ $canvas.mouseenter($.proxy(this.mouseenter, this));
+ $canvas.mouseleave($.proxy(this.mouseleave, this));
+ $canvas.click($.proxy(this.mouseclick, this));
+ },
+
+ reset: function (removeTooltip) {
+ this.splist = [];
+ if (this.tooltip && removeTooltip) {
+ this.tooltip.remove();
+ this.tooltip = undefined;
+ }
+ },
+
+ mouseclick: function (e) {
+ var clickEvent = $.Event('sparklineClick');
+ clickEvent.originalEvent = e;
+ clickEvent.sparklines = this.splist;
+ this.$el.trigger(clickEvent);
+ },
+
+ mouseenter: function (e) {
+ $(document.body).unbind('mousemove.jqs');
+ $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
+ this.over = true;
+ this.currentPageX = e.pageX;
+ this.currentPageY = e.pageY;
+ this.currentEl = e.target;
+ if (!this.tooltip && this.displayTooltips) {
+ this.tooltip = new Tooltip(this.options);
+ this.tooltip.updatePosition(e.pageX, e.pageY);
+ }
+ this.updateDisplay();
+ },
+
+ mouseleave: function () {
+ $(document.body).unbind('mousemove.jqs');
+ var splist = this.splist,
+ spcount = splist.length,
+ needsRefresh = false,
+ sp, i;
+ this.over = false;
+ this.currentEl = null;
+
+ if (this.tooltip) {
+ this.tooltip.remove();
+ this.tooltip = null;
+ }
+
+ for (i = 0; i < spcount; i++) {
+ sp = splist[i];
+ if (sp.clearRegionHighlight()) {
+ needsRefresh = true;
+ }
+ }
+
+ if (needsRefresh) {
+ this.canvas.render();
+ }
+ },
+
+ mousemove: function (e) {
+ this.currentPageX = e.pageX;
+ this.currentPageY = e.pageY;
+ this.currentEl = e.target;
+ if (this.tooltip) {
+ this.tooltip.updatePosition(e.pageX, e.pageY);
+ }
+ this.updateDisplay();
+ },
+
+ updateDisplay: function () {
+ var splist = this.splist,
+ spcount = splist.length,
+ needsRefresh = false,
+ offset = this.$canvas.offset(),
+ localX = this.currentPageX - offset.left,
+ localY = this.currentPageY - offset.top,
+ tooltiphtml, sp, i, result, changeEvent;
+ if (!this.over) {
+ return;
+ }
+ for (i = 0; i < spcount; i++) {
+ sp = splist[i];
+ result = sp.setRegionHighlight(this.currentEl, localX, localY);
+ if (result) {
+ needsRefresh = true;
+ }
+ }
+ if (needsRefresh) {
+ changeEvent = $.Event('sparklineRegionChange');
+ changeEvent.sparklines = this.splist;
+ this.$el.trigger(changeEvent);
+ if (this.tooltip) {
+ tooltiphtml = '';
+ for (i = 0; i < spcount; i++) {
+ sp = splist[i];
+ tooltiphtml += sp.getCurrentRegionTooltip();
+ }
+ this.tooltip.setContent(tooltiphtml);
+ }
+ if (!this.disableHighlight) {
+ this.canvas.render();
+ }
+ }
+ if (result === null) {
+ this.mouseleave();
+ }
+ }
+ });
+
+
+ Tooltip = createClass({
+ sizeStyle: 'position: static !important;' +
+ 'display: block !important;' +
+ 'visibility: hidden !important;' +
+ 'float: left !important;',
+
+ init: function (options) {
+ var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
+ sizetipStyle = this.sizeStyle,
+ offset;
+ this.container = options.get('tooltipContainer') || document.body;
+ this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
+ this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
+ // remove any previous lingering tooltip
+ $('#jqssizetip').remove();
+ $('#jqstooltip').remove();
+ this.sizetip = $('', {
+ id: 'jqssizetip',
+ style: sizetipStyle,
+ 'class': tooltipClassname
+ });
+ this.tooltip = $('', {
+ id: 'jqstooltip',
+ 'class': tooltipClassname
+ }).appendTo(this.container);
+ // account for the container's location
+ offset = this.tooltip.offset();
+ this.offsetLeft = offset.left;
+ this.offsetTop = offset.top;
+ this.hidden = true;
+ $(window).unbind('resize.jqs scroll.jqs');
+ $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
+ this.updateWindowDims();
+ },
+
+ updateWindowDims: function () {
+ this.scrollTop = $(window).scrollTop();
+ this.scrollLeft = $(window).scrollLeft();
+ this.scrollRight = this.scrollLeft + $(window).width();
+ this.updatePosition();
+ },
+
+ getSize: function (content) {
+ this.sizetip.html(content).appendTo(this.container);
+ this.width = this.sizetip.width() + 1;
+ this.height = this.sizetip.height();
+ this.sizetip.remove();
+ },
+
+ setContent: function (content) {
+ if (!content) {
+ this.tooltip.css('visibility', 'hidden');
+ this.hidden = true;
+ return;
+ }
+ this.getSize(content);
+ this.tooltip.html(content)
+ .css({
+ 'width': this.width,
+ 'height': this.height,
+ 'visibility': 'visible'
+ });
+ if (this.hidden) {
+ this.hidden = false;
+ this.updatePosition();
+ }
+ },
+
+ updatePosition: function (x, y) {
+ if (x === undefined) {
+ if (this.mousex === undefined) {
+ return;
+ }
+ x = this.mousex - this.offsetLeft;
+ y = this.mousey - this.offsetTop;
+
+ } else {
+ this.mousex = x = x - this.offsetLeft;
+ this.mousey = y = y - this.offsetTop;
+ }
+ if (!this.height || !this.width || this.hidden) {
+ return;
+ }
+
+ y -= this.height + this.tooltipOffsetY;
+ x += this.tooltipOffsetX;
+
+ if (y < this.scrollTop) {
+ y = this.scrollTop;
+ }
+ if (x < this.scrollLeft) {
+ x = this.scrollLeft;
+ } else if (x + this.width > this.scrollRight) {
+ x = this.scrollRight - this.width;
+ }
+
+ this.tooltip.css({
+ 'left': x,
+ 'top': y
+ });
+ },
+
+ remove: function () {
+ this.tooltip.remove();
+ this.sizetip.remove();
+ this.sizetip = this.tooltip = undefined;
+ $(window).unbind('resize.jqs scroll.jqs');
+ }
+ });
+
+ initStyles = function() {
+ addCSS(defaultStyles);
+ };
+
+ $(initStyles);
+
+ pending = [];
+ $.fn.sparkline = function (userValues, userOptions) {
+ return this.each(function () {
+ var options = new $.fn.sparkline.options(this, userOptions),
+ $this = $(this),
+ render, i;
+ render = function () {
+ var values, width, height, tmp, mhandler, sp, vals;
+ if (userValues === 'html' || userValues === undefined) {
+ vals = this.getAttribute(options.get('tagValuesAttribute'));
+ if (vals === undefined || vals === null) {
+ vals = $this.html();
+ }
+ values = vals.replace(/(^\s*\s*$)|\s+/g, '').split(',');
+ } else {
+ values = userValues;
+ }
+
+ width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');
+ if (options.get('height') === 'auto') {
+ if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {
+ // must be a better way to get the line height
+ tmp = document.createElement('span');
+ tmp.innerHTML = 'a';
+ $this.html(tmp);
+ height = $(tmp).innerHeight() || $(tmp).height();
+ $(tmp).remove();
+ tmp = null;
+ }
+ } else {
+ height = options.get('height');
+ }
+
+ if (!options.get('disableInteraction')) {
+ mhandler = $.data(this, '_jqs_mhandler');
+ if (!mhandler) {
+ mhandler = new MouseHandler(this, options);
+ $.data(this, '_jqs_mhandler', mhandler);
+ } else if (!options.get('composite')) {
+ mhandler.reset();
+ }
+ } else {
+ mhandler = false;
+ }
+
+ if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {
+ if (!$.data(this, '_jqs_errnotify')) {
+ alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
+ $.data(this, '_jqs_errnotify', true);
+ }
+ return;
+ }
+
+ sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);
+
+ sp.render();
+
+ if (mhandler) {
+ mhandler.registerSparkline(sp);
+ }
+ };
+ // jQuery 1.3.0 completely changed the meaning of :hidden :-/
+ if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || ($.fn.jquery < '1.3.0' && $(this).parents().is(':hidden')) || !$(this).parents('body').length) {
+ if (!options.get('composite') && $.data(this, '_jqs_pending')) {
+ // remove any existing references to the element
+ for (i = pending.length; i; i--) {
+ if (pending[i - 1][0] == this) {
+ pending.splice(i - 1, 1);
+ }
+ }
+ }
+ pending.push([this, render]);
+ $.data(this, '_jqs_pending', true);
+ } else {
+ render.call(this);
+ }
+ });
+ };
+
+ $.fn.sparkline.defaults = getDefaults();
+
+
+ $.sparkline_display_visible = function () {
+ var el, i, pl;
+ var done = [];
+ for (i = 0, pl = pending.length; i < pl; i++) {
+ el = pending[i][0];
+ if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
+ pending[i][1].call(el);
+ $.data(pending[i][0], '_jqs_pending', false);
+ done.push(i);
+ } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {
+ // element has been inserted and removed from the DOM
+ // If it was not yet inserted into the dom then the .data request
+ // will return true.
+ // removing from the dom causes the data to be removed.
+ $.data(pending[i][0], '_jqs_pending', false);
+ done.push(i);
+ }
+ }
+ for (i = done.length; i; i--) {
+ pending.splice(done[i - 1], 1);
+ }
+ };
+
+
+ /**
+ * User option handler
+ */
+ $.fn.sparkline.options = createClass({
+ init: function (tag, userOptions) {
+ var extendedOptions, defaults, base, tagOptionType;
+ this.userOptions = userOptions = userOptions || {};
+ this.tag = tag;
+ this.tagValCache = {};
+ defaults = $.fn.sparkline.defaults;
+ base = defaults.common;
+ this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
+
+ tagOptionType = this.getTagSetting('type');
+ if (tagOptionType === UNSET_OPTION) {
+ extendedOptions = defaults[userOptions.type || base.type];
+ } else {
+ extendedOptions = defaults[tagOptionType];
+ }
+ this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
+ },
+
+
+ getTagSetting: function (key) {
+ var prefix = this.tagOptionsPrefix,
+ val, i, pairs, keyval;
+ if (prefix === false || prefix === undefined) {
+ return UNSET_OPTION;
+ }
+ if (this.tagValCache.hasOwnProperty(key)) {
+ val = this.tagValCache.key;
+ } else {
+ val = this.tag.getAttribute(prefix + key);
+ if (val === undefined || val === null) {
+ val = UNSET_OPTION;
+ } else if (val.substr(0, 1) === '[') {
+ val = val.substr(1, val.length - 2).split(',');
+ for (i = val.length; i--;) {
+ val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
+ }
+ } else if (val.substr(0, 1) === '{') {
+ pairs = val.substr(1, val.length - 2).split(',');
+ val = {};
+ for (i = pairs.length; i--;) {
+ keyval = pairs[i].split(':', 2);
+ val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
+ }
+ } else {
+ val = normalizeValue(val);
+ }
+ this.tagValCache.key = val;
+ }
+ return val;
+ },
+
+ get: function (key, defaultval) {
+ var tagOption = this.getTagSetting(key),
+ result;
+ if (tagOption !== UNSET_OPTION) {
+ return tagOption;
+ }
+ return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
+ }
+ });
+
+
+ $.fn.sparkline._base = createClass({
+ disabled: false,
+
+ init: function (el, values, options, width, height) {
+ this.el = el;
+ this.$el = $(el);
+ this.values = values;
+ this.options = options;
+ this.width = width;
+ this.height = height;
+ this.currentRegion = undefined;
+ },
+
+ /**
+ * Setup the canvas
+ */
+ initTarget: function () {
+ var interactive = !this.options.get('disableInteraction');
+ if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
+ this.disabled = true;
+ } else {
+ this.canvasWidth = this.target.pixelWidth;
+ this.canvasHeight = this.target.pixelHeight;
+ }
+ },
+
+ /**
+ * Actually render the chart to the canvas
+ */
+ render: function () {
+ if (this.disabled) {
+ this.el.innerHTML = '';
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Return a region id for a given x/y co-ordinate
+ */
+ getRegion: function (x, y) {
+ },
+
+ /**
+ * Highlight an item based on the moused-over x,y co-ordinate
+ */
+ setRegionHighlight: function (el, x, y) {
+ var currentRegion = this.currentRegion,
+ highlightEnabled = !this.options.get('disableHighlight'),
+ newRegion;
+ if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {
+ return null;
+ }
+ newRegion = this.getRegion(el, x, y);
+ if (currentRegion !== newRegion) {
+ if (currentRegion !== undefined && highlightEnabled) {
+ this.removeHighlight();
+ }
+ this.currentRegion = newRegion;
+ if (newRegion !== undefined && highlightEnabled) {
+ this.renderHighlight();
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Reset any currently highlighted item
+ */
+ clearRegionHighlight: function () {
+ if (this.currentRegion !== undefined) {
+ this.removeHighlight();
+ this.currentRegion = undefined;
+ return true;
+ }
+ return false;
+ },
+
+ renderHighlight: function () {
+ this.changeHighlight(true);
+ },
+
+ removeHighlight: function () {
+ this.changeHighlight(false);
+ },
+
+ changeHighlight: function (highlight) {},
+
+ /**
+ * Fetch the HTML to display as a tooltip
+ */
+ getCurrentRegionTooltip: function () {
+ var options = this.options,
+ header = '',
+ entries = [],
+ fields, formats, formatlen, fclass, text, i,
+ showFields, showFieldsKey, newFields, fv,
+ formatter, format, fieldlen, j;
+ if (this.currentRegion === undefined) {
+ return '';
+ }
+ fields = this.getCurrentRegionFields();
+ formatter = options.get('tooltipFormatter');
+ if (formatter) {
+ return formatter(this, options, fields);
+ }
+ if (options.get('tooltipChartTitle')) {
+ header += '
' + options.get('tooltipChartTitle') + '
\n';
+ }
+ formats = this.options.get('tooltipFormat');
+ if (!formats) {
+ return '';
+ }
+ if (!$.isArray(formats)) {
+ formats = [formats];
+ }
+ if (!$.isArray(fields)) {
+ fields = [fields];
+ }
+ showFields = this.options.get('tooltipFormatFieldlist');
+ showFieldsKey = this.options.get('tooltipFormatFieldlistKey');
+ if (showFields && showFieldsKey) {
+ // user-selected ordering of fields
+ newFields = [];
+ for (i = fields.length; i--;) {
+ fv = fields[i][showFieldsKey];
+ if ((j = $.inArray(fv, showFields)) != -1) {
+ newFields[j] = fields[i];
+ }
+ }
+ fields = newFields;
+ }
+ formatlen = formats.length;
+ fieldlen = fields.length;
+ for (i = 0; i < formatlen; i++) {
+ format = formats[i];
+ if (typeof format === 'string') {
+ format = new SPFormat(format);
+ }
+ fclass = format.fclass || 'jqsfield';
+ for (j = 0; j < fieldlen; j++) {
+ if (!fields[j].isNull || !options.get('tooltipSkipNull')) {
+ $.extend(fields[j], {
+ prefix: options.get('tooltipPrefix'),
+ suffix: options.get('tooltipSuffix')
+ });
+ text = format.render(fields[j], options.get('tooltipValueLookups'), options);
+ entries.push('
' + text + '
');
+ }
+ }
+ }
+ if (entries.length) {
+ return header + entries.join('\n');
+ }
+ return '';
+ },
+
+ getCurrentRegionFields: function () {},
+
+ calcHighlightColor: function (color, options) {
+ var highlightColor = options.get('highlightColor'),
+ lighten = options.get('highlightLighten'),
+ parse, mult, rgbnew, i;
+ if (highlightColor) {
+ return highlightColor;
+ }
+ if (lighten) {
+ // extract RGB values
+ parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
+ if (parse) {
+ rgbnew = [];
+ mult = color.length === 4 ? 16 : 1;
+ for (i = 0; i < 3; i++) {
+ rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
+ }
+ return 'rgb(' + rgbnew.join(',') + ')';
+ }
+
+ }
+ return color;
+ }
+
+ });
+
+ barHighlightMixin = {
+ changeHighlight: function (highlight) {
+ var currentRegion = this.currentRegion,
+ target = this.target,
+ shapeids = this.regionShapes[currentRegion],
+ newShapes;
+ // will be null if the region value was null
+ if (shapeids) {
+ newShapes = this.renderRegion(currentRegion, highlight);
+ if ($.isArray(newShapes) || $.isArray(shapeids)) {
+ target.replaceWithShapes(shapeids, newShapes);
+ this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {
+ return newShape.id;
+ });
+ } else {
+ target.replaceWithShape(shapeids, newShapes);
+ this.regionShapes[currentRegion] = newShapes.id;
+ }
+ }
+ },
+
+ render: function () {
+ var values = this.values,
+ target = this.target,
+ regionShapes = this.regionShapes,
+ shapes, ids, i, j;
+
+ if (!this.cls._super.render.call(this)) {
+ return;
+ }
+ for (i = values.length; i--;) {
+ shapes = this.renderRegion(i);
+ if (shapes) {
+ if ($.isArray(shapes)) {
+ ids = [];
+ for (j = shapes.length; j--;) {
+ shapes[j].append();
+ ids.push(shapes[j].id);
+ }
+ regionShapes[i] = ids;
+ } else {
+ shapes.append();
+ regionShapes[i] = shapes.id; // store just the shapeid
+ }
+ } else {
+ // null value
+ regionShapes[i] = null;
+ }
+ }
+ target.render();
+ }
+ };
+
+ /**
+ * Line charts
+ */
+ $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {
+ type: 'line',
+
+ init: function (el, values, options, width, height) {
+ line._super.init.call(this, el, values, options, width, height);
+ this.vertices = [];
+ this.regionMap = [];
+ this.xvalues = [];
+ this.yvalues = [];
+ this.yminmax = [];
+ this.hightlightSpotId = null;
+ this.lastShapeId = null;
+ this.initTarget();
+ },
+
+ getRegion: function (el, x, y) {
+ var i,
+ regionMap = this.regionMap; // maps regions to value positions
+ for (i = regionMap.length; i--;) {
+ if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {
+ return regionMap[i][2];
+ }
+ }
+ return undefined;
+ },
+
+ getCurrentRegionFields: function () {
+ var currentRegion = this.currentRegion;
+ return {
+ isNull: this.yvalues[currentRegion] === null,
+ x: this.xvalues[currentRegion],
+ y: this.yvalues[currentRegion],
+ color: this.options.get('lineColor'),
+ fillColor: this.options.get('fillColor'),
+ offset: currentRegion
+ };
+ },
+
+ renderHighlight: function () {
+ var currentRegion = this.currentRegion,
+ target = this.target,
+ vertex = this.vertices[currentRegion],
+ options = this.options,
+ spotRadius = options.get('spotRadius'),
+ highlightSpotColor = options.get('highlightSpotColor'),
+ highlightLineColor = options.get('highlightLineColor'),
+ highlightSpot, highlightLine;
+
+ if (!vertex) {
+ return;
+ }
+ if (spotRadius && highlightSpotColor) {
+ highlightSpot = target.drawCircle(vertex[0], vertex[1],
+ spotRadius, undefined, highlightSpotColor);
+ this.highlightSpotId = highlightSpot.id;
+ target.insertAfterShape(this.lastShapeId, highlightSpot);
+ }
+ if (highlightLineColor) {
+ highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],
+ this.canvasTop + this.canvasHeight, highlightLineColor);
+ this.highlightLineId = highlightLine.id;
+ target.insertAfterShape(this.lastShapeId, highlightLine);
+ }
+ },
+
+ removeHighlight: function () {
+ var target = this.target;
+ if (this.highlightSpotId) {
+ target.removeShapeId(this.highlightSpotId);
+ this.highlightSpotId = null;
+ }
+ if (this.highlightLineId) {
+ target.removeShapeId(this.highlightLineId);
+ this.highlightLineId = null;
+ }
+ },
+
+ scanValues: function () {
+ var values = this.values,
+ valcount = values.length,
+ xvalues = this.xvalues,
+ yvalues = this.yvalues,
+ yminmax = this.yminmax,
+ i, val, isStr, isArray, sp;
+ for (i = 0; i < valcount; i++) {
+ val = values[i];
+ isStr = typeof(values[i]) === 'string';
+ isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;
+ sp = isStr && values[i].split(':');
+ if (isStr && sp.length === 2) { // x:y
+ xvalues.push(Number(sp[0]));
+ yvalues.push(Number(sp[1]));
+ yminmax.push(Number(sp[1]));
+ } else if (isArray) {
+ xvalues.push(val[0]);
+ yvalues.push(val[1]);
+ yminmax.push(val[1]);
+ } else {
+ xvalues.push(i);
+ if (values[i] === null || values[i] === 'null') {
+ yvalues.push(null);
+ } else {
+ yvalues.push(Number(val));
+ yminmax.push(Number(val));
+ }
+ }
+ }
+ if (this.options.get('xvalues')) {
+ xvalues = this.options.get('xvalues');
+ }
+
+ this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);
+ this.miny = this.minyorg = Math.min.apply(Math, yminmax);
+
+ this.maxx = Math.max.apply(Math, xvalues);
+ this.minx = Math.min.apply(Math, xvalues);
+
+ this.xvalues = xvalues;
+ this.yvalues = yvalues;
+ this.yminmax = yminmax;
+
+ },
+
+ processRangeOptions: function () {
+ var options = this.options,
+ normalRangeMin = options.get('normalRangeMin'),
+ normalRangeMax = options.get('normalRangeMax');
+
+ if (normalRangeMin !== undefined) {
+ if (normalRangeMin < this.miny) {
+ this.miny = normalRangeMin;
+ }
+ if (normalRangeMax > this.maxy) {
+ this.maxy = normalRangeMax;
+ }
+ }
+ if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {
+ this.miny = options.get('chartRangeMin');
+ }
+ if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {
+ this.maxy = options.get('chartRangeMax');
+ }
+ if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {
+ this.minx = options.get('chartRangeMinX');
+ }
+ if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {
+ this.maxx = options.get('chartRangeMaxX');
+ }
+
+ },
+
+ drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {
+ var normalRangeMin = this.options.get('normalRangeMin'),
+ normalRangeMax = this.options.get('normalRangeMax'),
+ ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),
+ height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);
+ this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();
+ },
+
+ render: function () {
+ var options = this.options,
+ target = this.target,
+ canvasWidth = this.canvasWidth,
+ canvasHeight = this.canvasHeight,
+ vertices = this.vertices,
+ spotRadius = options.get('spotRadius'),
+ regionMap = this.regionMap,
+ rangex, rangey, yvallast,
+ canvasTop, canvasLeft,
+ vertex, path, paths, x, y, xnext, xpos, xposnext,
+ last, next, yvalcount, lineShapes, fillShapes, plen,
+ valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;
+
+ if (!line._super.render.call(this)) {
+ return;
+ }
+
+ this.scanValues();
+ this.processRangeOptions();
+
+ xvalues = this.xvalues;
+ yvalues = this.yvalues;
+
+ if (!this.yminmax.length || this.yvalues.length < 2) {
+ // empty or all null valuess
+ return;
+ }
+
+ canvasTop = canvasLeft = 0;
+
+ rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;
+ rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;
+ yvallast = this.yvalues.length - 1;
+
+ if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {
+ spotRadius = 0;
+ }
+ if (spotRadius) {
+ // adjust the canvas size as required so that spots will fit
+ hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction');
+ if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {
+ canvasHeight -= Math.ceil(spotRadius);
+ }
+ if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {
+ canvasHeight -= Math.ceil(spotRadius);
+ canvasTop += Math.ceil(spotRadius);
+ }
+ if (hlSpotsEnabled ||
+ ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {
+ canvasLeft += Math.ceil(spotRadius);
+ canvasWidth -= Math.ceil(spotRadius);
+ }
+ if (hlSpotsEnabled || options.get('spotColor') ||
+ (options.get('minSpotColor') || options.get('maxSpotColor') &&
+ (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {
+ canvasWidth -= Math.ceil(spotRadius);
+ }
+ }
+
+
+ canvasHeight--;
+
+ if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {
+ this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
+ }
+
+ path = [];
+ paths = [path];
+ last = next = null;
+ yvalcount = yvalues.length;
+ for (i = 0; i < yvalcount; i++) {
+ x = xvalues[i];
+ xnext = xvalues[i + 1];
+ y = yvalues[i];
+ xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));
+ xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;
+ next = xpos + ((xposnext - xpos) / 2);
+ regionMap[i] = [last || 0, next, i];
+ last = next;
+ if (y === null) {
+ if (i) {
+ if (yvalues[i - 1] !== null) {
+ path = [];
+ paths.push(path);
+ }
+ vertices.push(null);
+ }
+ } else {
+ if (y < this.miny) {
+ y = this.miny;
+ }
+ if (y > this.maxy) {
+ y = this.maxy;
+ }
+ if (!path.length) {
+ // previous value was null
+ path.push([xpos, canvasTop + canvasHeight]);
+ }
+ vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];
+ path.push(vertex);
+ vertices.push(vertex);
+ }
+ }
+
+ lineShapes = [];
+ fillShapes = [];
+ plen = paths.length;
+ for (i = 0; i < plen; i++) {
+ path = paths[i];
+ if (path.length) {
+ if (options.get('fillColor')) {
+ path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);
+ fillShapes.push(path.slice(0));
+ path.pop();
+ }
+ // if there's only a single point in this path, then we want to display it
+ // as a vertical line which means we keep path[0] as is
+ if (path.length > 2) {
+ // else we want the first value
+ path[0] = [path[0][0], path[1][1]];
+ }
+ lineShapes.push(path);
+ }
+ }
+
+ // draw the fill first, then optionally the normal range, then the line on top of that
+ plen = fillShapes.length;
+ for (i = 0; i < plen; i++) {
+ target.drawShape(fillShapes[i],
+ options.get('fillColor'), options.get('fillColor')).append();
+ }
+
+ if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {
+ this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
+ }
+
+ plen = lineShapes.length;
+ for (i = 0; i < plen; i++) {
+ target.drawShape(lineShapes[i], options.get('lineColor'), undefined,
+ options.get('lineWidth')).append();
+ }
+
+ if (spotRadius && options.get('valueSpots')) {
+ valueSpots = options.get('valueSpots');
+ if (valueSpots.get === undefined) {
+ valueSpots = new RangeMap(valueSpots);
+ }
+ for (i = 0; i < yvalcount; i++) {
+ color = valueSpots.get(yvalues[i]);
+ if (color) {
+ target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),
+ spotRadius, undefined,
+ color).append();
+ }
+ }
+
+ }
+ if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) {
+ target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),
+ spotRadius, undefined,
+ options.get('spotColor')).append();
+ }
+ if (this.maxy !== this.minyorg) {
+ if (spotRadius && options.get('minSpotColor')) {
+ x = xvalues[$.inArray(this.minyorg, yvalues)];
+ target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),
+ spotRadius, undefined,
+ options.get('minSpotColor')).append();
+ }
+ if (spotRadius && options.get('maxSpotColor')) {
+ x = xvalues[$.inArray(this.maxyorg, yvalues)];
+ target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
+ canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),
+ spotRadius, undefined,
+ options.get('maxSpotColor')).append();
+ }
+ }
+
+ this.lastShapeId = target.getLastShapeId();
+ this.canvasTop = canvasTop;
+ target.render();
+ }
+ });
+
+ /**
+ * Bar charts
+ */
+ $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {
+ type: 'bar',
+
+ init: function (el, values, options, width, height) {
+ var barWidth = parseInt(options.get('barWidth'), 10),
+ barSpacing = parseInt(options.get('barSpacing'), 10),
+ chartRangeMin = options.get('chartRangeMin'),
+ chartRangeMax = options.get('chartRangeMax'),
+ chartRangeClip = options.get('chartRangeClip'),
+ stackMin = Infinity,
+ stackMax = -Infinity,
+ isStackString, groupMin, groupMax, stackRanges,
+ numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,
+ stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;
+ bar._super.init.call(this, el, values, options, width, height);
+
+ // scan values to determine whether to stack bars
+ for (i = 0, vlen = values.length; i < vlen; i++) {
+ val = values[i];
+ isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;
+ if (isStackString || $.isArray(val)) {
+ stacked = true;
+ if (isStackString) {
+ val = values[i] = normalizeValues(val.split(':'));
+ }
+ val = remove(val, null); // min/max will treat null as zero
+ groupMin = Math.min.apply(Math, val);
+ groupMax = Math.max.apply(Math, val);
+ if (groupMin < stackMin) {
+ stackMin = groupMin;
+ }
+ if (groupMax > stackMax) {
+ stackMax = groupMax;
+ }
+ }
+ }
+
+ this.stacked = stacked;
+ this.regionShapes = {};
+ this.barWidth = barWidth;
+ this.barSpacing = barSpacing;
+ this.totalBarWidth = barWidth + barSpacing;
+ this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
+
+ this.initTarget();
+
+ if (chartRangeClip) {
+ clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;
+ clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;
+ }
+
+ numValues = [];
+ stackRanges = stacked ? [] : numValues;
+ var stackTotals = [];
+ var stackRangesNeg = [];
+ for (i = 0, vlen = values.length; i < vlen; i++) {
+ if (stacked) {
+ vlist = values[i];
+ values[i] = svals = [];
+ stackTotals[i] = 0;
+ stackRanges[i] = stackRangesNeg[i] = 0;
+ for (j = 0, slen = vlist.length; j < slen; j++) {
+ val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];
+ if (val !== null) {
+ if (val > 0) {
+ stackTotals[i] += val;
+ }
+ if (stackMin < 0 && stackMax > 0) {
+ if (val < 0) {
+ stackRangesNeg[i] += Math.abs(val);
+ } else {
+ stackRanges[i] += val;
+ }
+ } else {
+ stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));
+ }
+ numValues.push(val);
+ }
+ }
+ } else {
+ val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];
+ val = values[i] = normalizeValue(val);
+ if (val !== null) {
+ numValues.push(val);
+ }
+ }
+ }
+ this.max = max = Math.max.apply(Math, numValues);
+ this.min = min = Math.min.apply(Math, numValues);
+ this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;
+ this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;
+
+ if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {
+ min = options.get('chartRangeMin');
+ }
+ if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {
+ max = options.get('chartRangeMax');
+ }
+
+ this.zeroAxis = zeroAxis = options.get('zeroAxis', true);
+ if (min <= 0 && max >= 0 && zeroAxis) {
+ xaxisOffset = 0;
+ } else if (zeroAxis == false) {
+ xaxisOffset = min;
+ } else if (min > 0) {
+ xaxisOffset = min;
+ } else {
+ xaxisOffset = max;
+ }
+ this.xaxisOffset = xaxisOffset;
+
+ range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;
+
+ // as we plot zero/min values a single pixel line, we add a pixel to all other
+ // values - Reduce the effective canvas size to suit
+ this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;
+
+ if (min < xaxisOffset) {
+ yMaxCalc = (stacked && max >= 0) ? stackMax : max;
+ yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;
+ if (yoffset !== Math.ceil(yoffset)) {
+ this.canvasHeightEf -= 2;
+ yoffset = Math.ceil(yoffset);
+ }
+ } else {
+ yoffset = this.canvasHeight;
+ }
+ this.yoffset = yoffset;
+
+ if ($.isArray(options.get('colorMap'))) {
+ this.colorMapByIndex = options.get('colorMap');
+ this.colorMapByValue = null;
+ } else {
+ this.colorMapByIndex = null;
+ this.colorMapByValue = options.get('colorMap');
+ if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
+ this.colorMapByValue = new RangeMap(this.colorMapByValue);
+ }
+ }
+
+ this.range = range;
+ },
+
+ getRegion: function (el, x, y) {
+ var result = Math.floor(x / this.totalBarWidth);
+ return (result < 0 || result >= this.values.length) ? undefined : result;
+ },
+
+ getCurrentRegionFields: function () {
+ var currentRegion = this.currentRegion,
+ values = ensureArray(this.values[currentRegion]),
+ result = [],
+ value, i;
+ for (i = values.length; i--;) {
+ value = values[i];
+ result.push({
+ isNull: value === null,
+ value: value,
+ color: this.calcColor(i, value, currentRegion),
+ offset: currentRegion
+ });
+ }
+ return result;
+ },
+
+ calcColor: function (stacknum, value, valuenum) {
+ var colorMapByIndex = this.colorMapByIndex,
+ colorMapByValue = this.colorMapByValue,
+ options = this.options,
+ color, newColor;
+ if (this.stacked) {
+ color = options.get('stackedBarColor');
+ } else {
+ color = (value < 0) ? options.get('negBarColor') : options.get('barColor');
+ }
+ if (value === 0 && options.get('zeroColor') !== undefined) {
+ color = options.get('zeroColor');
+ }
+ if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
+ color = newColor;
+ } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
+ color = colorMapByIndex[valuenum];
+ }
+ return $.isArray(color) ? color[stacknum % color.length] : color;
+ },
+
+ /**
+ * Render bar(s) for a region
+ */
+ renderRegion: function (valuenum, highlight) {
+ var vals = this.values[valuenum],
+ options = this.options,
+ xaxisOffset = this.xaxisOffset,
+ result = [],
+ range = this.range,
+ stacked = this.stacked,
+ target = this.target,
+ x = valuenum * this.totalBarWidth,
+ canvasHeightEf = this.canvasHeightEf,
+ yoffset = this.yoffset,
+ y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;
+
+ vals = $.isArray(vals) ? vals : [vals];
+ valcount = vals.length;
+ val = vals[0];
+ isNull = all(null, vals);
+ allMin = all(xaxisOffset, vals, true);
+
+ if (isNull) {
+ if (options.get('nullColor')) {
+ color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);
+ y = (yoffset > 0) ? yoffset - 1 : yoffset;
+ return target.drawRect(x, y, this.barWidth - 1, 0, color, color);
+ } else {
+ return undefined;
+ }
+ }
+ yoffsetNeg = yoffset;
+ for (i = 0; i < valcount; i++) {
+ val = vals[i];
+
+ if (stacked && val === xaxisOffset) {
+ if (!allMin || minPlotted) {
+ continue;
+ }
+ minPlotted = true;
+ }
+
+ if (range > 0) {
+ height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;
+ } else {
+ height = 1;
+ }
+ if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {
+ y = yoffsetNeg;
+ yoffsetNeg += height;
+ } else {
+ y = yoffset - height;
+ yoffset -= height;
+ }
+ color = this.calcColor(i, val, valuenum);
+ if (highlight) {
+ color = this.calcHighlightColor(color, options);
+ }
+ result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));
+ }
+ if (result.length === 1) {
+ return result[0];
+ }
+ return result;
+ }
+ });
+
+ /**
+ * Tristate charts
+ */
+ $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {
+ type: 'tristate',
+
+ init: function (el, values, options, width, height) {
+ var barWidth = parseInt(options.get('barWidth'), 10),
+ barSpacing = parseInt(options.get('barSpacing'), 10);
+ tristate._super.init.call(this, el, values, options, width, height);
+
+ this.regionShapes = {};
+ this.barWidth = barWidth;
+ this.barSpacing = barSpacing;
+ this.totalBarWidth = barWidth + barSpacing;
+ this.values = $.map(values, Number);
+ this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
+
+ if ($.isArray(options.get('colorMap'))) {
+ this.colorMapByIndex = options.get('colorMap');
+ this.colorMapByValue = null;
+ } else {
+ this.colorMapByIndex = null;
+ this.colorMapByValue = options.get('colorMap');
+ if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
+ this.colorMapByValue = new RangeMap(this.colorMapByValue);
+ }
+ }
+ this.initTarget();
+ },
+
+ getRegion: function (el, x, y) {
+ return Math.floor(x / this.totalBarWidth);
+ },
+
+ getCurrentRegionFields: function () {
+ var currentRegion = this.currentRegion;
+ return {
+ isNull: this.values[currentRegion] === undefined,
+ value: this.values[currentRegion],
+ color: this.calcColor(this.values[currentRegion], currentRegion),
+ offset: currentRegion
+ };
+ },
+
+ calcColor: function (value, valuenum) {
+ var values = this.values,
+ options = this.options,
+ colorMapByIndex = this.colorMapByIndex,
+ colorMapByValue = this.colorMapByValue,
+ color, newColor;
+
+ if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
+ color = newColor;
+ } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
+ color = colorMapByIndex[valuenum];
+ } else if (values[valuenum] < 0) {
+ color = options.get('negBarColor');
+ } else if (values[valuenum] > 0) {
+ color = options.get('posBarColor');
+ } else {
+ color = options.get('zeroBarColor');
+ }
+ return color;
+ },
+
+ renderRegion: function (valuenum, highlight) {
+ var values = this.values,
+ options = this.options,
+ target = this.target,
+ canvasHeight, height, halfHeight,
+ x, y, color;
+
+ canvasHeight = target.pixelHeight;
+ halfHeight = Math.round(canvasHeight / 2);
+
+ x = valuenum * this.totalBarWidth;
+ if (values[valuenum] < 0) {
+ y = halfHeight;
+ height = halfHeight - 1;
+ } else if (values[valuenum] > 0) {
+ y = 0;
+ height = halfHeight - 1;
+ } else {
+ y = halfHeight - 1;
+ height = 2;
+ }
+ color = this.calcColor(values[valuenum], valuenum);
+ if (color === null) {
+ return;
+ }
+ if (highlight) {
+ color = this.calcHighlightColor(color, options);
+ }
+ return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);
+ }
+ });
+
+ /**
+ * Discrete charts
+ */
+ $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {
+ type: 'discrete',
+
+ init: function (el, values, options, width, height) {
+ discrete._super.init.call(this, el, values, options, width, height);
+
+ this.regionShapes = {};
+ this.values = values = $.map(values, Number);
+ this.min = Math.min.apply(Math, values);
+ this.max = Math.max.apply(Math, values);
+ this.range = this.max - this.min;
+ this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;
+ this.interval = Math.floor(width / values.length);
+ this.itemWidth = width / values.length;
+ if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {
+ this.min = options.get('chartRangeMin');
+ }
+ if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {
+ this.max = options.get('chartRangeMax');
+ }
+ this.initTarget();
+ if (this.target) {
+ this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');
+ }
+ },
+
+ getRegion: function (el, x, y) {
+ return Math.floor(x / this.itemWidth);
+ },
+
+ getCurrentRegionFields: function () {
+ var currentRegion = this.currentRegion;
+ return {
+ isNull: this.values[currentRegion] === undefined,
+ value: this.values[currentRegion],
+ offset: currentRegion
+ };
+ },
+
+ renderRegion: function (valuenum, highlight) {
+ var values = this.values,
+ options = this.options,
+ min = this.min,
+ max = this.max,
+ range = this.range,
+ interval = this.interval,
+ target = this.target,
+ canvasHeight = this.canvasHeight,
+ lineHeight = this.lineHeight,
+ pheight = canvasHeight - lineHeight,
+ ytop, val, color, x;
+
+ val = clipval(values[valuenum], min, max);
+ x = valuenum * interval;
+ ytop = Math.round(pheight - pheight * ((val - min) / range));
+ color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');
+ if (highlight) {
+ color = this.calcHighlightColor(color, options);
+ }
+ return target.drawLine(x, ytop, x, ytop + lineHeight, color);
+ }
+ });
+
+ /**
+ * Bullet charts
+ */
+ $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {
+ type: 'bullet',
+
+ init: function (el, values, options, width, height) {
+ var min, max, vals;
+ bullet._super.init.call(this, el, values, options, width, height);
+
+ // values: target, performance, range1, range2, range3
+ this.values = values = normalizeValues(values);
+ // target or performance could be null
+ vals = values.slice();
+ vals[0] = vals[0] === null ? vals[2] : vals[0];
+ vals[1] = values[1] === null ? vals[2] : vals[1];
+ min = Math.min.apply(Math, values);
+ max = Math.max.apply(Math, values);
+ if (options.get('base') === undefined) {
+ min = min < 0 ? min : 0;
+ } else {
+ min = options.get('base');
+ }
+ this.min = min;
+ this.max = max;
+ this.range = max - min;
+ this.shapes = {};
+ this.valueShapes = {};
+ this.regiondata = {};
+ this.width = width = options.get('width') === 'auto' ? '4.0em' : width;
+ this.target = this.$el.simpledraw(width, height, options.get('composite'));
+ if (!values.length) {
+ this.disabled = true;
+ }
+ this.initTarget();
+ },
+
+ getRegion: function (el, x, y) {
+ var shapeid = this.target.getShapeAt(el, x, y);
+ return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
+ },
+
+ getCurrentRegionFields: function () {
+ var currentRegion = this.currentRegion;
+ return {
+ fieldkey: currentRegion.substr(0, 1),
+ value: this.values[currentRegion.substr(1)],
+ region: currentRegion
+ };
+ },
+
+ changeHighlight: function (highlight) {
+ var currentRegion = this.currentRegion,
+ shapeid = this.valueShapes[currentRegion],
+ shape;
+ delete this.shapes[shapeid];
+ switch (currentRegion.substr(0, 1)) {
+ case 'r':
+ shape = this.renderRange(currentRegion.substr(1), highlight);
+ break;
+ case 'p':
+ shape = this.renderPerformance(highlight);
+ break;
+ case 't':
+ shape = this.renderTarget(highlight);
+ break;
+ }
+ this.valueShapes[currentRegion] = shape.id;
+ this.shapes[shape.id] = currentRegion;
+ this.target.replaceWithShape(shapeid, shape);
+ },
+
+ renderRange: function (rn, highlight) {
+ var rangeval = this.values[rn],
+ rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),
+ color = this.options.get('rangeColors')[rn - 2];
+ if (highlight) {
+ color = this.calcHighlightColor(color, this.options);
+ }
+ return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);
+ },
+
+ renderPerformance: function (highlight) {
+ var perfval = this.values[1],
+ perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),
+ color = this.options.get('performanceColor');
+ if (highlight) {
+ color = this.calcHighlightColor(color, this.options);
+ }
+ return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,
+ Math.round(this.canvasHeight * 0.4) - 1, color, color);
+ },
+
+ renderTarget: function (highlight) {
+ var targetval = this.values[0],
+ x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),
+ targettop = Math.round(this.canvasHeight * 0.10),
+ targetheight = this.canvasHeight - (targettop * 2),
+ color = this.options.get('targetColor');
+ if (highlight) {
+ color = this.calcHighlightColor(color, this.options);
+ }
+ return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);
+ },
+
+ render: function () {
+ var vlen = this.values.length,
+ target = this.target,
+ i, shape;
+ if (!bullet._super.render.call(this)) {
+ return;
+ }
+ for (i = 2; i < vlen; i++) {
+ shape = this.renderRange(i).append();
+ this.shapes[shape.id] = 'r' + i;
+ this.valueShapes['r' + i] = shape.id;
+ }
+ if (this.values[1] !== null) {
+ shape = this.renderPerformance().append();
+ this.shapes[shape.id] = 'p1';
+ this.valueShapes.p1 = shape.id;
+ }
+ if (this.values[0] !== null) {
+ shape = this.renderTarget().append();
+ this.shapes[shape.id] = 't0';
+ this.valueShapes.t0 = shape.id;
+ }
+ target.render();
+ }
+ });
+
+ /**
+ * Pie charts
+ */
+ $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {
+ type: 'pie',
+
+ init: function (el, values, options, width, height) {
+ var total = 0, i;
+
+ pie._super.init.call(this, el, values, options, width, height);
+
+ this.shapes = {}; // map shape ids to value offsets
+ this.valueShapes = {}; // maps value offsets to shape ids
+ this.values = values = $.map(values, Number);
+
+ if (options.get('width') === 'auto') {
+ this.width = this.height;
+ }
+
+ if (values.length > 0) {
+ for (i = values.length; i--;) {
+ total += values[i];
+ }
+ }
+ this.total = total;
+ this.initTarget();
+ this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);
+ },
+
+ getRegion: function (el, x, y) {
+ var shapeid = this.target.getShapeAt(el, x, y);
+ return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
+ },
+
+ getCurrentRegionFields: function () {
+ var currentRegion = this.currentRegion;
+ return {
+ isNull: this.values[currentRegion] === undefined,
+ value: this.values[currentRegion],
+ percent: this.values[currentRegion] / this.total * 100,
+ color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],
+ offset: currentRegion
+ };
+ },
+
+ changeHighlight: function (highlight) {
+ var currentRegion = this.currentRegion,
+ newslice = this.renderSlice(currentRegion, highlight),
+ shapeid = this.valueShapes[currentRegion];
+ delete this.shapes[shapeid];
+ this.target.replaceWithShape(shapeid, newslice);
+ this.valueShapes[currentRegion] = newslice.id;
+ this.shapes[newslice.id] = currentRegion;
+ },
+
+ renderSlice: function (valuenum, highlight) {
+ var target = this.target,
+ options = this.options,
+ radius = this.radius,
+ borderWidth = options.get('borderWidth'),
+ offset = options.get('offset'),
+ circle = 2 * Math.PI,
+ values = this.values,
+ total = this.total,
+ next = offset ? (2*Math.PI)*(offset/360) : 0,
+ start, end, i, vlen, color;
+
+ vlen = values.length;
+ for (i = 0; i < vlen; i++) {
+ start = next;
+ end = next;
+ if (total > 0) { // avoid divide by zero
+ end = next + (circle * (values[i] / total));
+ }
+ if (valuenum === i) {
+ color = options.get('sliceColors')[i % options.get('sliceColors').length];
+ if (highlight) {
+ color = this.calcHighlightColor(color, options);
+ }
+
+ return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);
+ }
+ next = end;
+ }
+ },
+
+ render: function () {
+ var target = this.target,
+ values = this.values,
+ options = this.options,
+ radius = this.radius,
+ borderWidth = options.get('borderWidth'),
+ shape, i;
+
+ if (!pie._super.render.call(this)) {
+ return;
+ }
+ if (borderWidth) {
+ target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),
+ options.get('borderColor'), undefined, borderWidth).append();
+ }
+ for (i = values.length; i--;) {
+ if (values[i]) { // don't render zero values
+ shape = this.renderSlice(i).append();
+ this.valueShapes[i] = shape.id; // store just the shapeid
+ this.shapes[shape.id] = i;
+ }
+ }
+ target.render();
+ }
+ });
+
+ /**
+ * Box plots
+ */
+ $.fn.sparkline.box = box = createClass($.fn.sparkline._base, {
+ type: 'box',
+
+ init: function (el, values, options, width, height) {
+ box._super.init.call(this, el, values, options, width, height);
+ this.values = $.map(values, Number);
+ this.width = options.get('width') === 'auto' ? '4.0em' : width;
+ this.initTarget();
+ if (!this.values.length) {
+ this.disabled = 1;
+ }
+ },
+
+ /**
+ * Simulate a single region
+ */
+ getRegion: function () {
+ return 1;
+ },
+
+ getCurrentRegionFields: function () {
+ var result = [
+ { field: 'lq', value: this.quartiles[0] },
+ { field: 'med', value: this.quartiles[1] },
+ { field: 'uq', value: this.quartiles[2] }
+ ];
+ if (this.loutlier !== undefined) {
+ result.push({ field: 'lo', value: this.loutlier});
+ }
+ if (this.routlier !== undefined) {
+ result.push({ field: 'ro', value: this.routlier});
+ }
+ if (this.lwhisker !== undefined) {
+ result.push({ field: 'lw', value: this.lwhisker});
+ }
+ if (this.rwhisker !== undefined) {
+ result.push({ field: 'rw', value: this.rwhisker});
+ }
+ return result;
+ },
+
+ render: function () {
+ var target = this.target,
+ values = this.values,
+ vlen = values.length,
+ options = this.options,
+ canvasWidth = this.canvasWidth,
+ canvasHeight = this.canvasHeight,
+ minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),
+ maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),
+ canvasLeft = 0,
+ lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,
+ size, unitSize;
+
+ if (!box._super.render.call(this)) {
+ return;
+ }
+
+ if (options.get('raw')) {
+ if (options.get('showOutliers') && values.length > 5) {
+ loutlier = values[0];
+ lwhisker = values[1];
+ q1 = values[2];
+ q2 = values[3];
+ q3 = values[4];
+ rwhisker = values[5];
+ routlier = values[6];
+ } else {
+ lwhisker = values[0];
+ q1 = values[1];
+ q2 = values[2];
+ q3 = values[3];
+ rwhisker = values[4];
+ }
+ } else {
+ values.sort(function (a, b) { return a - b; });
+ q1 = quartile(values, 1);
+ q2 = quartile(values, 2);
+ q3 = quartile(values, 3);
+ iqr = q3 - q1;
+ if (options.get('showOutliers')) {
+ lwhisker = rwhisker = undefined;
+ for (i = 0; i < vlen; i++) {
+ if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {
+ lwhisker = values[i];
+ }
+ if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {
+ rwhisker = values[i];
+ }
+ }
+ loutlier = values[0];
+ routlier = values[vlen - 1];
+ } else {
+ lwhisker = values[0];
+ rwhisker = values[vlen - 1];
+ }
+ }
+ this.quartiles = [q1, q2, q3];
+ this.lwhisker = lwhisker;
+ this.rwhisker = rwhisker;
+ this.loutlier = loutlier;
+ this.routlier = routlier;
+
+ unitSize = canvasWidth / (maxValue - minValue + 1);
+ if (options.get('showOutliers')) {
+ canvasLeft = Math.ceil(options.get('spotRadius'));
+ canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));
+ unitSize = canvasWidth / (maxValue - minValue + 1);
+ if (loutlier < lwhisker) {
+ target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,
+ canvasHeight / 2,
+ options.get('spotRadius'),
+ options.get('outlierLineColor'),
+ options.get('outlierFillColor')).append();
+ }
+ if (routlier > rwhisker) {
+ target.drawCircle((routlier - minValue) * unitSize + canvasLeft,
+ canvasHeight / 2,
+ options.get('spotRadius'),
+ options.get('outlierLineColor'),
+ options.get('outlierFillColor')).append();
+ }
+ }
+
+ // box
+ target.drawRect(
+ Math.round((q1 - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight * 0.1),
+ Math.round((q3 - q1) * unitSize),
+ Math.round(canvasHeight * 0.8),
+ options.get('boxLineColor'),
+ options.get('boxFillColor')).append();
+ // left whisker
+ target.drawLine(
+ Math.round((lwhisker - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight / 2),
+ Math.round((q1 - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight / 2),
+ options.get('lineColor')).append();
+ target.drawLine(
+ Math.round((lwhisker - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight / 4),
+ Math.round((lwhisker - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight - canvasHeight / 4),
+ options.get('whiskerColor')).append();
+ // right whisker
+ target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight / 2),
+ Math.round((q3 - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight / 2),
+ options.get('lineColor')).append();
+ target.drawLine(
+ Math.round((rwhisker - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight / 4),
+ Math.round((rwhisker - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight - canvasHeight / 4),
+ options.get('whiskerColor')).append();
+ // median line
+ target.drawLine(
+ Math.round((q2 - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight * 0.1),
+ Math.round((q2 - minValue) * unitSize + canvasLeft),
+ Math.round(canvasHeight * 0.9),
+ options.get('medianColor')).append();
+ if (options.get('target')) {
+ size = Math.ceil(options.get('spotRadius'));
+ target.drawLine(
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
+ Math.round((canvasHeight / 2) - size),
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
+ Math.round((canvasHeight / 2) + size),
+ options.get('targetColor')).append();
+ target.drawLine(
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),
+ Math.round(canvasHeight / 2),
+ Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),
+ Math.round(canvasHeight / 2),
+ options.get('targetColor')).append();
+ }
+ target.render();
+ }
+ });
+
+ // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
+ // This is accessible as $(foo).simpledraw()
+
+ // Detect browser renderer support
+ (function() {
+ if (document.namespaces && !document.namespaces.v) {
+ $.fn.sparkline.hasVML = true;
+ document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
+ } else {
+ $.fn.sparkline.hasVML = false;
+ }
+
+ var el = document.createElement('canvas');
+ $.fn.sparkline.hasCanvas = !!(el.getContext && el.getContext('2d'));
+
+ })()
+
+ VShape = createClass({
+ init: function (target, id, type, args) {
+ this.target = target;
+ this.id = id;
+ this.type = type;
+ this.args = args;
+ },
+ append: function () {
+ this.target.appendShape(this);
+ return this;
+ }
+ });
+
+ VCanvas_base = createClass({
+ _pxregex: /(\d+)(px)?\s*$/i,
+
+ init: function (width, height, target) {
+ if (!width) {
+ return;
+ }
+ this.width = width;
+ this.height = height;
+ this.target = target;
+ this.lastShapeId = null;
+ if (target[0]) {
+ target = target[0];
+ }
+ $.data(target, '_jqs_vcanvas', this);
+ },
+
+ drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {
+ return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);
+ },
+
+ drawShape: function (path, lineColor, fillColor, lineWidth) {
+ return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);
+ },
+
+ drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {
+ return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);
+ },
+
+ drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {
+ return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);
+ },
+
+ drawRect: function (x, y, width, height, lineColor, fillColor) {
+ return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);
+ },
+
+ getElement: function () {
+ return this.canvas;
+ },
+
+ /**
+ * Return the most recently inserted shape id
+ */
+ getLastShapeId: function () {
+ return this.lastShapeId;
+ },
+
+ /**
+ * Clear and reset the canvas
+ */
+ reset: function () {
+ alert('reset not implemented');
+ },
+
+ _insert: function (el, target) {
+ $(target).html(el);
+ },
+
+ /**
+ * Calculate the pixel dimensions of the canvas
+ */
+ _calculatePixelDims: function (width, height, canvas) {
+ // XXX This should probably be a configurable option
+ var match;
+ match = this._pxregex.exec(height);
+ if (match) {
+ this.pixelHeight = match[1];
+ } else {
+ this.pixelHeight = $(canvas).height();
+ }
+ match = this._pxregex.exec(width);
+ if (match) {
+ this.pixelWidth = match[1];
+ } else {
+ this.pixelWidth = $(canvas).width();
+ }
+ },
+
+ /**
+ * Generate a shape object and id for later rendering
+ */
+ _genShape: function (shapetype, shapeargs) {
+ var id = shapeCount++;
+ shapeargs.unshift(id);
+ return new VShape(this, id, shapetype, shapeargs);
+ },
+
+ /**
+ * Add a shape to the end of the render queue
+ */
+ appendShape: function (shape) {
+ alert('appendShape not implemented');
+ },
+
+ /**
+ * Replace one shape with another
+ */
+ replaceWithShape: function (shapeid, shape) {
+ alert('replaceWithShape not implemented');
+ },
+
+ /**
+ * Insert one shape after another in the render queue
+ */
+ insertAfterShape: function (shapeid, shape) {
+ alert('insertAfterShape not implemented');
+ },
+
+ /**
+ * Remove a shape from the queue
+ */
+ removeShapeId: function (shapeid) {
+ alert('removeShapeId not implemented');
+ },
+
+ /**
+ * Find a shape at the specified x/y co-ordinates
+ */
+ getShapeAt: function (el, x, y) {
+ alert('getShapeAt not implemented');
+ },
+
+ /**
+ * Render all queued shapes onto the canvas
+ */
+ render: function () {
+ alert('render not implemented');
+ }
+ });
+
+ VCanvas_canvas = createClass(VCanvas_base, {
+ init: function (width, height, target, interact) {
+ VCanvas_canvas._super.init.call(this, width, height, target);
+ this.canvas = document.createElement('canvas');
+ if (target[0]) {
+ target = target[0];
+ }
+ $.data(target, '_jqs_vcanvas', this);
+ $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });
+ this._insert(this.canvas, target);
+ this._calculatePixelDims(width, height, this.canvas);
+ this.canvas.width = this.pixelWidth;
+ this.canvas.height = this.pixelHeight;
+ this.interact = interact;
+ this.shapes = {};
+ this.shapeseq = [];
+ this.currentTargetShapeId = undefined;
+ $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});
+ },
+
+ _getContext: function (lineColor, fillColor, lineWidth) {
+ var context = this.canvas.getContext('2d');
+ if (lineColor !== undefined) {
+ context.strokeStyle = lineColor;
+ }
+ context.lineWidth = lineWidth === undefined ? 1 : lineWidth;
+ if (fillColor !== undefined) {
+ context.fillStyle = fillColor;
+ }
+ return context;
+ },
+
+ reset: function () {
+ var context = this._getContext();
+ context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
+ this.shapes = {};
+ this.shapeseq = [];
+ this.currentTargetShapeId = undefined;
+ },
+
+ _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
+ var context = this._getContext(lineColor, fillColor, lineWidth),
+ i, plen;
+ context.beginPath();
+ context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);
+ for (i = 1, plen = path.length; i < plen; i++) {
+ context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
+ }
+ if (lineColor !== undefined) {
+ context.stroke();
+ }
+ if (fillColor !== undefined) {
+ context.fill();
+ }
+ if (this.targetX !== undefined && this.targetY !== undefined &&
+ context.isPointInPath(this.targetX, this.targetY)) {
+ this.currentTargetShapeId = shapeid;
+ }
+ },
+
+ _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
+ var context = this._getContext(lineColor, fillColor, lineWidth);
+ context.beginPath();
+ context.arc(x, y, radius, 0, 2 * Math.PI, false);
+ if (this.targetX !== undefined && this.targetY !== undefined &&
+ context.isPointInPath(this.targetX, this.targetY)) {
+ this.currentTargetShapeId = shapeid;
+ }
+ if (lineColor !== undefined) {
+ context.stroke();
+ }
+ if (fillColor !== undefined) {
+ context.fill();
+ }
+ },
+
+ _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
+ var context = this._getContext(lineColor, fillColor);
+ context.beginPath();
+ context.moveTo(x, y);
+ context.arc(x, y, radius, startAngle, endAngle, false);
+ context.lineTo(x, y);
+ context.closePath();
+ if (lineColor !== undefined) {
+ context.stroke();
+ }
+ if (fillColor) {
+ context.fill();
+ }
+ if (this.targetX !== undefined && this.targetY !== undefined &&
+ context.isPointInPath(this.targetX, this.targetY)) {
+ this.currentTargetShapeId = shapeid;
+ }
+ },
+
+ _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
+ return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);
+ },
+
+ appendShape: function (shape) {
+ this.shapes[shape.id] = shape;
+ this.shapeseq.push(shape.id);
+ this.lastShapeId = shape.id;
+ return shape.id;
+ },
+
+ replaceWithShape: function (shapeid, shape) {
+ var shapeseq = this.shapeseq,
+ i;
+ this.shapes[shape.id] = shape;
+ for (i = shapeseq.length; i--;) {
+ if (shapeseq[i] == shapeid) {
+ shapeseq[i] = shape.id;
+ }
+ }
+ delete this.shapes[shapeid];
+ },
+
+ replaceWithShapes: function (shapeids, shapes) {
+ var shapeseq = this.shapeseq,
+ shapemap = {},
+ sid, i, first;
+
+ for (i = shapeids.length; i--;) {
+ shapemap[shapeids[i]] = true;
+ }
+ for (i = shapeseq.length; i--;) {
+ sid = shapeseq[i];
+ if (shapemap[sid]) {
+ shapeseq.splice(i, 1);
+ delete this.shapes[sid];
+ first = i;
+ }
+ }
+ for (i = shapes.length; i--;) {
+ shapeseq.splice(first, 0, shapes[i].id);
+ this.shapes[shapes[i].id] = shapes[i];
+ }
+
+ },
+
+ insertAfterShape: function (shapeid, shape) {
+ var shapeseq = this.shapeseq,
+ i;
+ for (i = shapeseq.length; i--;) {
+ if (shapeseq[i] === shapeid) {
+ shapeseq.splice(i + 1, 0, shape.id);
+ this.shapes[shape.id] = shape;
+ return;
+ }
+ }
+ },
+
+ removeShapeId: function (shapeid) {
+ var shapeseq = this.shapeseq,
+ i;
+ for (i = shapeseq.length; i--;) {
+ if (shapeseq[i] === shapeid) {
+ shapeseq.splice(i, 1);
+ break;
+ }
+ }
+ delete this.shapes[shapeid];
+ },
+
+ getShapeAt: function (el, x, y) {
+ this.targetX = x;
+ this.targetY = y;
+ this.render();
+ return this.currentTargetShapeId;
+ },
+
+ render: function () {
+ var shapeseq = this.shapeseq,
+ shapes = this.shapes,
+ shapeCount = shapeseq.length,
+ context = this._getContext(),
+ shapeid, shape, i;
+ context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
+ for (i = 0; i < shapeCount; i++) {
+ shapeid = shapeseq[i];
+ shape = shapes[shapeid];
+ this['_draw' + shape.type].apply(this, shape.args);
+ }
+ if (!this.interact) {
+ // not interactive so no need to keep the shapes array
+ this.shapes = {};
+ this.shapeseq = [];
+ }
+ }
+
+ });
+
+ VCanvas_vml = createClass(VCanvas_base, {
+ init: function (width, height, target) {
+ var groupel;
+ VCanvas_vml._super.init.call(this, width, height, target);
+ if (target[0]) {
+ target = target[0];
+ }
+ $.data(target, '_jqs_vcanvas', this);
+ this.canvas = document.createElement('span');
+ $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});
+ this._insert(this.canvas, target);
+ this._calculatePixelDims(width, height, this.canvas);
+ this.canvas.width = this.pixelWidth;
+ this.canvas.height = this.pixelHeight;
+ groupel = '';
+ this.canvas.insertAdjacentHTML('beforeEnd', groupel);
+ this.group = $(this.canvas).children()[0];
+ this.rendered = false;
+ this.prerender = '';
+ },
+
+ _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
+ var vpath = [],
+ initial, stroke, fill, closed, vel, plen, i;
+ for (i = 0, plen = path.length; i < plen; i++) {
+ vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);
+ }
+ initial = vpath.splice(0, 1);
+ lineWidth = lineWidth === undefined ? 1 : lineWidth;
+ stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
+ fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
+ closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';
+ vel = '' +
+ ' ';
+ return vel;
+ },
+
+ _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
+ var stroke, fill, vel;
+ x -= radius;
+ y -= radius;
+ stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
+ fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
+ vel = '';
+ return vel;
+
+ },
+
+ _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
+ var vpath, startx, starty, endx, endy, stroke, fill, vel;
+ if (startAngle === endAngle) {
+ return ''; // VML seems to have problem when start angle equals end angle.
+ }
+ if ((endAngle - startAngle) === (2 * Math.PI)) {
+ startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0
+ endAngle = (2 * Math.PI);
+ }
+
+ startx = x + Math.round(Math.cos(startAngle) * radius);
+ starty = y + Math.round(Math.sin(startAngle) * radius);
+ endx = x + Math.round(Math.cos(endAngle) * radius);
+ endy = y + Math.round(Math.sin(endAngle) * radius);
+
+ if (startx === endx && starty === endy) {
+ if ((endAngle - startAngle) < Math.PI) {
+ // Prevent very small slices from being mistaken as a whole pie
+ return '';
+ }
+ // essentially going to be the entire circle, so ignore startAngle
+ startx = endx = x + radius;
+ starty = endy = y;
+ }
+
+ if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {
+ return '';
+ }
+
+ vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];
+ stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" ';
+ fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
+ vel = '' +
+ ' ';
+ return vel;
+ },
+
+ _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
+ return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);
+ },
+
+ reset: function () {
+ this.group.innerHTML = '';
+ },
+
+ appendShape: function (shape) {
+ var vel = this['_draw' + shape.type].apply(this, shape.args);
+ if (this.rendered) {
+ this.group.insertAdjacentHTML('beforeEnd', vel);
+ } else {
+ this.prerender += vel;
+ }
+ this.lastShapeId = shape.id;
+ return shape.id;
+ },
+
+ replaceWithShape: function (shapeid, shape) {
+ var existing = $('#jqsshape' + shapeid),
+ vel = this['_draw' + shape.type].apply(this, shape.args);
+ existing[0].outerHTML = vel;
+ },
+
+ replaceWithShapes: function (shapeids, shapes) {
+ // replace the first shapeid with all the new shapes then toast the remaining old shapes
+ var existing = $('#jqsshape' + shapeids[0]),
+ replace = '',
+ slen = shapes.length,
+ i;
+ for (i = 0; i < slen; i++) {
+ replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);
+ }
+ existing[0].outerHTML = replace;
+ for (i = 1; i < shapeids.length; i++) {
+ $('#jqsshape' + shapeids[i]).remove();
+ }
+ },
+
+ insertAfterShape: function (shapeid, shape) {
+ var existing = $('#jqsshape' + shapeid),
+ vel = this['_draw' + shape.type].apply(this, shape.args);
+ existing[0].insertAdjacentHTML('afterEnd', vel);
+ },
+
+ removeShapeId: function (shapeid) {
+ var existing = $('#jqsshape' + shapeid);
+ this.group.removeChild(existing[0]);
+ },
+
+ getShapeAt: function (el, x, y) {
+ var shapeid = el.id.substr(8);
+ return shapeid;
+ },
+
+ render: function () {
+ if (!this.rendered) {
+ // batch the intial render into a single repaint
+ this.group.innerHTML = this.prerender;
+ this.rendered = true;
+ }
+ }
+ });
+
+}));
diff --git a/addons/web/static/lib/justgage/justgage.1.0.1.js b/addons/web/static/lib/justgage/justgage.1.0.1.js
new file mode 100644
index 00000000000..24540beb293
--- /dev/null
+++ b/addons/web/static/lib/justgage/justgage.1.0.1.js
@@ -0,0 +1,946 @@
+/**
+ * JustGage - this is work-in-progress, unreleased, unofficial code, so it might not work top-notch :)
+ * Check http://www.justgage.com for official releases
+ * Licensed under MIT.
+ * @author Bojan Djuricic (@Toorshia)
+ *
+ * LATEST UPDATES
+
+ * -----------------------------
+ * April 18, 2013.
+ * -----------------------------
+ * parentNode - use this instead of id, to attach gauge to node which is outside of DOM tree - https://github.com/toorshia/justgage/issues/48
+ * width - force gauge width
+ * height - force gauge height
+
+ * -----------------------------
+ * April 17, 2013.
+ * -----------------------------
+ * fix - https://github.com/toorshia/justgage/issues/49
+
+ * -----------------------------
+ * April 01, 2013.
+ * -----------------------------
+ * fix - https://github.com/toorshia/justgage/issues/46
+
+ * -----------------------------
+ * March 26, 2013.
+ * -----------------------------
+ * customSectors - define specific color for value range (0-10 : red, 10-30 : blue etc.)
+
+ * -----------------------------
+ * March 23, 2013.
+ * -----------------------------
+ * counter - option to animate value in counting fashion
+ * fix - https://github.com/toorshia/justgage/issues/45
+
+ * -----------------------------
+ * March 13, 2013.
+ * -----------------------------
+ * refresh method - added optional 'max' parameter to use when you need to update max value
+
+ * -----------------------------
+ * February 26, 2013.
+ * -----------------------------
+ * decimals - option to define/limit number of decimals when not using humanFriendly or customRenderer to display value
+ * fixed a missing parameters bug when calling generateShadow() for IE < 9
+
+ * -----------------------------
+ * December 31, 2012.
+ * -----------------------------
+ * fixed text y-position for hidden divs - workaround for Raphael 'dy' bug - https://github.com/DmitryBaranovskiy/raphael/issues/491
+ * 'show' parameters, like showMinMax are now 'hide' because I am lame developer - please update these in your setups
+ * Min and Max labels are now auto-off when in donut mode
+ * Start angle in donut mode is now 90
+ * donutStartAngle - option to define start angle for donut
+
+ * -----------------------------
+ * November 25, 2012.
+ * -----------------------------
+ * Option to define custom rendering function for displayed value
+
+ * -----------------------------
+ * November 19, 2012.
+ * -----------------------------
+ * Config.value is now updated after gauge refresh
+
+ * -----------------------------
+ * November 13, 2012.
+ * -----------------------------
+ * Donut display mode added
+ * Option to hide value label
+ * Option to enable responsive gauge size
+ * Removed default title attribute
+ * Option to accept min and max defined as string values
+ * Option to configure value symbol
+ * Fixed bad aspect ratio calculations
+ * Option to configure minimum font size for all texts
+ * Option to show shorthand big numbers (human friendly)
+ */
+
+ JustGage = function(config) {
+
+ // if (!config.id) {alert("Missing id parameter for gauge!"); return false;}
+ // if (!document.getElementById(config.id)) {alert("No element with id: \""+config.id+"\" found!"); return false;}
+
+ var obj = this;
+
+ // configurable parameters
+ obj.config =
+ {
+ // id : string
+ // this is container element id
+ id : config.id,
+
+ // parentNode : node object
+ // this is container element
+ parentNode : (config.parentNode) ? config.parentNode : null,
+
+ // width : int
+ // gauge width
+ width : (config.width) ? config.width : null,
+
+ // height : int
+ // gauge height
+ height : (config.height) ? config.height : null,
+
+ // title : string
+ // gauge title
+ title : (config.title) ? config.title : "",
+
+ // titleFontColor : string
+ // color of gauge title
+ titleFontColor : (config.titleFontColor) ? config.titleFontColor : "#999999",
+
+ // value : int
+ // value gauge is showing
+ value : (config.value) ? config.value : 0,
+
+ // valueFontColor : string
+ // color of label showing current value
+ valueFontColor : (config.valueFontColor) ? config.valueFontColor : "#010101",
+
+ // symbol : string
+ // special symbol to show next to value
+ symbol : (config.symbol) ? config.symbol : "",
+
+ // min : int
+ // min value
+ min : (config.min !== undefined) ? parseFloat(config.min) : 0,
+
+ // max : int
+ // max value
+ max : (config.max !== undefined) ? parseFloat(config.max) : 100,
+
+ // humanFriendlyDecimal : int
+ // number of decimal places for our human friendly number to contain
+ humanFriendlyDecimal : (config.humanFriendlyDecimal) ? config.humanFriendlyDecimal : 0,
+
+ // textRenderer: func
+ // function applied before rendering text
+ textRenderer : (config.textRenderer) ? config.textRenderer : null,
+
+ // gaugeWidthScale : float
+ // width of the gauge element
+ gaugeWidthScale : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0,
+
+ // gaugeColor : string
+ // background color of gauge element
+ gaugeColor : (config.gaugeColor) ? config.gaugeColor : "#edebeb",
+
+ // label : string
+ // text to show below value
+ label : (config.label) ? config.label : "",
+
+ // labelFontColor : string
+ // color of label showing label under value
+ labelFontColor : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3",
+
+ // shadowOpacity : int
+ // 0 ~ 1
+ shadowOpacity : (config.shadowOpacity) ? config.shadowOpacity : 0.2,
+
+ // shadowSize: int
+ // inner shadow size
+ shadowSize : (config.shadowSize) ? config.shadowSize : 5,
+
+ // shadowVerticalOffset : int
+ // how much shadow is offset from top
+ shadowVerticalOffset : (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3,
+
+ // levelColors : string[]
+ // colors of indicator, from lower to upper, in RGB format
+ levelColors : (config.levelColors) ? config.levelColors : [
+ "#a9d70b",
+ "#f9c802",
+ "#ff0000"
+ ],
+
+ // startAnimationTime : int
+ // length of initial animation
+ startAnimationTime : (config.startAnimationTime) ? config.startAnimationTime : 700,
+
+ // startAnimationType : string
+ // type of initial animation (linear, >, <, <>, bounce)
+ startAnimationType : (config.startAnimationType) ? config.startAnimationType : ">",
+
+ // refreshAnimationTime : int
+ // length of refresh animation
+ refreshAnimationTime : (config.refreshAnimationTime) ? config.refreshAnimationTime : 700,
+
+ // refreshAnimationType : string
+ // type of refresh animation (linear, >, <, <>, bounce)
+ refreshAnimationType : (config.refreshAnimationType) ? config.refreshAnimationType : ">",
+
+ // donutStartAngle : int
+ // angle to start from when in donut mode
+ donutStartAngle : (config.donutStartAngle) ? config.donutStartAngle : 90,
+
+ // valueMinFontSize : int
+ // absolute minimum font size for the value
+ valueMinFontSize : config.valueMinFontSize || 16,
+
+ // titleMinFontSize
+ // absolute minimum font size for the title
+ titleMinFontSize : config.titleMinFontSize || 10,
+
+ // labelMinFontSize
+ // absolute minimum font size for the label
+ labelMinFontSize : config.labelMinFontSize || 10,
+
+ // minLabelMinFontSize
+ // absolute minimum font size for the minimum label
+ minLabelMinFontSize : config.minLabelMinFontSize || 10,
+
+ // maxLabelMinFontSize
+ // absolute minimum font size for the maximum label
+ maxLabelMinFontSize : config.maxLabelMinFontSize || 10,
+
+ // hideValue : bool
+ // hide value text
+ hideValue : (config.hideValue) ? config.hideValue : false,
+
+ // hideMinMax : bool
+ // hide min and max values
+ hideMinMax : (config.hideMinMax) ? config.hideMinMax : false,
+
+ // hideInnerShadow : bool
+ // hide inner shadow
+ hideInnerShadow : (config.hideInnerShadow) ? config.hideInnerShadow : false,
+
+ // humanFriendly : bool
+ // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)
+ humanFriendly : (config.humanFriendly) ? config.humanFriendly : false,
+
+ // noGradient : bool
+ // whether to use gradual color change for value, or sector-based
+ noGradient : (config.noGradient) ? config.noGradient : false,
+
+ // donut : bool
+ // show full donut gauge
+ donut : (config.donut) ? config.donut : false,
+
+ // relativeGaugeSize : bool
+ // whether gauge size should follow changes in container element size
+ relativeGaugeSize : (config.relativeGaugeSize) ? config.relativeGaugeSize : false,
+
+ // counter : bool
+ // animate level number change
+ counter : (config.counter) ? config.counter : false,
+
+ // decimals : int
+ // number of digits after floating point
+ decimals : (config.decimals) ? config.decimals : 0,
+
+ // customSectors : [] of objects
+ // number of digits after floating point
+ customSectors : (config.customSectors) ? config.customSectors : []
+ };
+
+ // variables
+ var
+ canvasW,
+ canvasH,
+ widgetW,
+ widgetH,
+ aspect,
+ dx,
+ dy,
+ titleFontSize,
+ titleX,
+ titleY,
+ valueFontSize,
+ valueX,
+ valueY,
+ labelFontSize,
+ labelX,
+ labelY,
+ minFontSize,
+ minX,
+ minY,
+ maxFontSize,
+ maxX,
+ maxY;
+
+ // overflow values
+ if (obj.config.value > obj.config.max) obj.config.value = obj.config.max;
+ if (obj.config.value < obj.config.min) obj.config.value = obj.config.min;
+ obj.originalValue = config.value;
+
+ // create canvas
+ if (obj.config.id !== null && (document.getElementById(obj.config.id)) !== null) {
+ obj.canvas = Raphael(obj.config.id, "100%", "100%");
+ } else if (obj.config.parentNode !== null) {
+ obj.canvas = Raphael(obj.config.parentNode, "100%", "100%");
+ }
+
+ if (obj.config.relativeGaugeSize === true) {
+ obj.canvas.setViewBox(0, 0, 200, 150, true);
+ }
+
+ // canvas dimensions
+ if (obj.config.relativeGaugeSize === true) {
+ canvasW = 200;
+ canvasH = 150;
+ } else if (obj.config.width !== null && obj.config.height !== null) {
+ canvasW = obj.config.width;
+ canvasH = obj.config.height;
+ } else if (obj.config.parentNode !== null) {
+ obj.canvas.setViewBox(0, 0, 200, 150, true);
+ canvasW = 200;
+ canvasH = 150;
+ } else {
+ canvasW = getStyle(document.getElementById(obj.config.id), "width").slice(0, -2) * 1;
+ canvasH = getStyle(document.getElementById(obj.config.id), "height").slice(0, -2) * 1;
+ }
+
+ // widget dimensions
+ if (obj.config.donut === true) {
+
+ // DONUT *******************************
+
+ // width more than height
+ if(canvasW > canvasH) {
+ widgetH = canvasH;
+ widgetW = widgetH;
+ // width less than height
+ } else if (canvasW < canvasH) {
+ widgetW = canvasW;
+ widgetH = widgetW;
+ // if height don't fit, rescale both
+ if(widgetH > canvasH) {
+ aspect = widgetH / canvasH;
+ widgetH = widgetH / aspect;
+ widgetW = widgetH / aspect;
+ }
+ // equal
+ } else {
+ widgetW = canvasW;
+ widgetH = widgetW;
+ }
+
+ // delta
+ dx = (canvasW - widgetW)/2;
+ dy = (canvasH - widgetH)/2;
+
+ // title
+ titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10;
+ titleX = dx + widgetW / 2;
+ titleY = dy + widgetH / 11;
+
+ // value
+ valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18;
+ valueX = dx + widgetW / 2;
+ if(obj.config.label !== '') {
+ valueY = dy + widgetH / 1.85;
+ } else {
+ valueY = dy + widgetH / 1.7;
+ }
+
+ // label
+ labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
+ labelX = dx + widgetW / 2;
+ labelY = valueY + labelFontSize;
+
+ // min
+ minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
+ minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;
+ minY = labelY;
+
+ // max
+ maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
+ maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;
+ maxY = labelY;
+
+ } else {
+ // HALF *******************************
+
+ // width more than height
+ if(canvasW > canvasH) {
+ widgetH = canvasH;
+ widgetW = widgetH * 1.25;
+ //if width doesn't fit, rescale both
+ if(widgetW > canvasW) {
+ aspect = widgetW / canvasW;
+ widgetW = widgetW / aspect;
+ widgetH = widgetH / aspect;
+ }
+ // width less than height
+ } else if (canvasW < canvasH) {
+ widgetW = canvasW;
+ widgetH = widgetW / 1.25;
+ // if height don't fit, rescale both
+ if(widgetH > canvasH) {
+ aspect = widgetH / canvasH;
+ widgetH = widgetH / aspect;
+ widgetW = widgetH / aspect;
+ }
+ // equal
+ } else {
+ widgetW = canvasW;
+ widgetH = widgetW * 0.75;
+ }
+
+ // delta
+ dx = (canvasW - widgetW)/2;
+ dy = (canvasH - widgetH)/2;
+
+ // title
+ titleFontSize = ((widgetH / 8) > obj.config.titleMinFontSize) ? (widgetH / 10) : obj.config.titleMinFontSize;
+ titleX = dx + widgetW / 2;
+ titleY = dy + widgetH / 6.4;
+
+ // value
+ valueFontSize = ((widgetH / 6.5) > obj.config.valueMinFontSize) ? (widgetH / 6.5) : obj.config.valueMinFontSize;
+ valueX = dx + widgetW / 2;
+ valueY = dy + widgetH / 1.275;
+
+ // label
+ labelFontSize = ((widgetH / 16) > obj.config.labelMinFontSize) ? (widgetH / 16) : obj.config.labelMinFontSize;
+ labelX = dx + widgetW / 2;
+ labelY = valueY + valueFontSize / 2 + 5;
+
+ // min
+ minFontSize = ((widgetH / 16) > obj.config.minLabelMinFontSize) ? (widgetH / 16) : obj.config.minLabelMinFontSize;
+ minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;
+ minY = labelY;
+
+ // max
+ maxFontSize = ((widgetH / 16) > obj.config.maxLabelMinFontSize) ? (widgetH / 16) : obj.config.maxLabelMinFontSize;
+ maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2 ;
+ maxY = labelY;
+ }
+
+ // parameters
+ obj.params = {
+ canvasW : canvasW,
+ canvasH : canvasH,
+ widgetW : widgetW,
+ widgetH : widgetH,
+ dx : dx,
+ dy : dy,
+ titleFontSize : titleFontSize,
+ titleX : titleX,
+ titleY : titleY,
+ valueFontSize : valueFontSize,
+ valueX : valueX,
+ valueY : valueY,
+ labelFontSize : labelFontSize,
+ labelX : labelX,
+ labelY : labelY,
+ minFontSize : minFontSize,
+ minX : minX,
+ minY : minY,
+ maxFontSize : maxFontSize,
+ maxX : maxX,
+ maxY : maxY
+ };
+
+ // var clear
+ canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null
+
+ // pki - custom attribute for generating gauge paths
+ obj.canvas.customAttributes.pki = function (value, min, max, w, h, dx, dy, gws, donut) {
+
+ var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path;
+
+ if (donut) {
+ alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
+ Ro = w / 2 - w / 7;
+ Ri = Ro - w / 6.666666666666667 * gws;
+
+ Cx = w / 2 + dx;
+ Cy = h / 1.95 + dy;
+
+ Xo = w / 2 + dx + Ro * Math.cos(alpha);
+ Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
+ Xi = w / 2 + dx + Ri * Math.cos(alpha);
+ Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
+
+ path += "M" + (Cx - Ri) + "," + Cy + " ";
+ path += "L" + (Cx - Ro) + "," + Cy + " ";
+ if (value > ((max - min) / 2)) {
+ path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " ";
+ }
+ path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
+ path += "L" + Xi + "," + Yi + " ";
+ if (value > ((max - min) / 2)) {
+ path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " ";
+ }
+ path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
+ path += "Z ";
+
+ return { path: path };
+
+ } else {
+ alpha = (1 - (value - min) / (max - min)) * Math.PI;
+ Ro = w / 2 - w / 10;
+ Ri = Ro - w / 6.666666666666667 * gws;
+
+ Cx = w / 2 + dx;
+ Cy = h / 1.25 + dy;
+
+ Xo = w / 2 + dx + Ro * Math.cos(alpha);
+ Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha);
+ Xi = w / 2 + dx + Ri * Math.cos(alpha);
+ Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha);
+
+ path += "M" + (Cx - Ri) + "," + Cy + " ";
+ path += "L" + (Cx - Ro) + "," + Cy + " ";
+ path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
+ path += "L" + Xi + "," + Yi + " ";
+ path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
+ path += "Z ";
+
+ return { path: path };
+ }
+
+ // var clear
+ alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null;
+ };
+
+ // gauge
+ obj.gauge = obj.canvas.path().attr({
+ "stroke": "none",
+ "fill": obj.config.gaugeColor,
+ pki: [
+ obj.config.max,
+ obj.config.min,
+ obj.config.max,
+ obj.params.widgetW,
+ obj.params.widgetH,
+ obj.params.dx,
+ obj.params.dy,
+ obj.config.gaugeWidthScale,
+ obj.config.donut
+ ]
+ });
+
+ // level
+ obj.level = obj.canvas.path().attr({
+ "stroke": "none",
+ "fill": getColor(obj.config.value, (obj.config.value - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors),
+ pki: [
+ obj.config.min,
+ obj.config.min,
+ obj.config.max,
+ obj.params.widgetW,
+ obj.params.widgetH,
+ obj.params.dx,
+ obj.params.dy,
+ obj.config.gaugeWidthScale,
+ obj.config.donut
+ ]
+ });
+ if(obj.config.donut) {
+ obj.level.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW/2 + obj.params.dx) + ", " + (obj.params.widgetH/1.95 + obj.params.dy));
+ }
+
+ // title
+ obj.txtTitle = obj.canvas.text(obj.params.titleX, obj.params.titleY, obj.config.title);
+ obj.txtTitle.attr({
+ "font-size":obj.params.titleFontSize,
+ "font-weight":"bold",
+ "font-family":"Arial",
+ "fill":obj.config.titleFontColor,
+ "fill-opacity":"1"
+ });
+ setDy(obj.txtTitle, obj.params.titleFontSize, obj.params.titleY);
+
+ // value
+ obj.txtValue = obj.canvas.text(obj.params.valueX, obj.params.valueY, 0);
+ obj.txtValue.attr({
+ "font-size":obj.params.valueFontSize,
+ "font-weight":"bold",
+ "font-family":"Arial",
+ "fill":obj.config.valueFontColor,
+ "fill-opacity":"0"
+ });
+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+
+ // label
+ obj.txtLabel = obj.canvas.text(obj.params.labelX, obj.params.labelY, obj.config.label);
+ obj.txtLabel.attr({
+ "font-size":obj.params.labelFontSize,
+ "font-weight":"normal",
+ "font-family":"Arial",
+ "fill":obj.config.labelFontColor,
+ "fill-opacity":"0"
+ });
+ setDy(obj.txtLabel, obj.params.labelFontSize, obj.params.labelY);
+
+ // min
+ obj.txtMinimum = obj.config.min;
+ if( obj.config.humanFriendly ) obj.txtMinimum = humanFriendlyNumber( obj.config.min, obj.config.humanFriendlyDecimal );
+ obj.txtMin = obj.canvas.text(obj.params.minX, obj.params.minY, obj.txtMinimum);
+ obj.txtMin.attr({
+ "font-size":obj.params.minFontSize,
+ "font-weight":"normal",
+ "font-family":"Arial",
+ "fill":obj.config.labelFontColor,
+ "fill-opacity": (obj.config.hideMinMax || obj.config.donut)? "0" : "1"
+ });
+ setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);
+
+ // max
+ obj.txtMaximum = obj.config.max;
+ if( obj.config.humanFriendly ) obj.txtMaximum = humanFriendlyNumber( obj.config.max, obj.config.humanFriendlyDecimal );
+ obj.txtMax = obj.canvas.text(obj.params.maxX, obj.params.maxY, obj.txtMaximum);
+ obj.txtMax.attr({
+ "font-size":obj.params.maxFontSize,
+ "font-weight":"normal",
+ "font-family":"Arial",
+ "fill":obj.config.labelFontColor,
+ "fill-opacity": (obj.config.hideMinMax || obj.config.donut)? "0" : "1"
+ });
+ setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
+
+ var defs = obj.canvas.canvas.childNodes[1];
+ var svg = "http://www.w3.org/2000/svg";
+
+ if (ie < 9) {
+ onCreateElementNsReady(function() {
+ obj.generateShadow(svg, defs);
+ });
+ } else {
+ obj.generateShadow(svg, defs);
+ }
+
+ // var clear
+ defs, svg = null;
+
+ // set value to display
+ if(obj.config.textRenderer) {
+ obj.originalValue = obj.config.textRenderer(obj.originalValue);
+ } else if(obj.config.humanFriendly) {
+ obj.originalValue = humanFriendlyNumber( obj.originalValue, obj.config.humanFriendlyDecimal ) + obj.config.symbol;
+ } else {
+ obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;
+ }
+
+ if(obj.config.counter === true) {
+ //on each animation frame
+ eve.on("raphael.anim.frame." + (obj.level.id), function() {
+ var currentValue = obj.level.attr("pki");
+ if(obj.config.textRenderer) {
+ obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue[0])));
+ } else if(obj.config.humanFriendly) {
+ obj.txtValue.attr("text", humanFriendlyNumber( Math.floor(currentValue[0]), obj.config.humanFriendlyDecimal ) + obj.config.symbol);
+ } else {
+ obj.txtValue.attr("text", (currentValue[0] * 1).toFixed(obj.config.decimals) + obj.config.symbol);
+ }
+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+ currentValue = null;
+ });
+ //on animation end
+ eve.on("raphael.anim.finish." + (obj.level.id), function() {
+ obj.txtValue.attr({"text" : obj.originalValue});
+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+ });
+ } else {
+ //on animation start
+ eve.on("raphael.anim.start." + (obj.level.id), function() {
+ obj.txtValue.attr({"text" : obj.originalValue});
+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+ });
+ }
+
+ // animate gauge level, value & label
+ obj.level.animate({
+ pki: [
+ obj.config.value,
+ obj.config.min,
+ obj.config.max,
+ obj.params.widgetW,
+ obj.params.widgetH,
+ obj.params.dx,
+ obj.params.dy,
+ obj.config.gaugeWidthScale,
+ obj.config.donut
+ ]
+ }, obj.config.startAnimationTime, obj.config.startAnimationType);
+ obj.txtValue.animate({"fill-opacity":(obj.config.hideValue)?"0":"1"}, obj.config.startAnimationTime, obj.config.startAnimationType);
+ obj.txtLabel.animate({"fill-opacity":"1"}, obj.config.startAnimationTime, obj.config.startAnimationType);
+};
+
+/** Refresh gauge level */
+JustGage.prototype.refresh = function(val, max) {
+
+ var obj = this;
+ var displayVal, color, max = max || null;
+
+ // set new max
+ if(max !== null) {
+ obj.config.max = max;
+
+ obj.txtMaximum = obj.config.max;
+ if( obj.config.humanFriendly ) obj.txtMaximum = humanFriendlyNumber( obj.config.max, obj.config.humanFriendlyDecimal );
+ obj.txtMax.attr({"text" : obj.txtMaximum});
+ setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
+ }
+
+ // overflow values
+ displayVal = val;
+ if ((val * 1) > (obj.config.max * 1)) {val = (obj.config.max * 1);}
+ if ((val * 1) < (obj.config.min * 1)) {val = (obj.config.min * 1);}
+
+ color = getColor(val, (val - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors);
+
+ if(obj.config.textRenderer) {
+ displayVal = obj.config.textRenderer(displayVal);
+ } else if( obj.config.humanFriendly ) {
+ displayVal = humanFriendlyNumber( displayVal, obj.config.humanFriendlyDecimal ) + obj.config.symbol;
+ } else {
+ displayVal = (displayVal * 1).toFixed(obj.config.decimals) + obj.config.symbol;
+ }
+ obj.originalValue = displayVal;
+ obj.config.value = val * 1;
+
+ if(!obj.config.counter) {
+ obj.txtValue.attr({"text":displayVal});
+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
+ }
+
+ obj.level.animate({
+ pki: [
+ obj.config.value,
+ obj.config.min,
+ obj.config.max,
+ obj.params.widgetW,
+ obj.params.widgetH,
+ obj.params.dx,
+ obj.params.dy,
+ obj.config.gaugeWidthScale,
+ obj.config.donut
+ ],
+ "fill":color
+ }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType);
+
+ // var clear
+ obj, displayVal, color, max = null;
+};
+
+/** Generate shadow */
+JustGage.prototype.generateShadow = function(svg, defs) {
+
+ var obj = this;
+ var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;
+
+ // FILTER
+ gaussFilter = document.createElementNS(svg,"filter");
+ gaussFilter.setAttribute("id","inner-shadow");
+ defs.appendChild(gaussFilter);
+
+ // offset
+ feOffset = document.createElementNS(svg,"feOffset");
+ feOffset.setAttribute("dx", 0);
+ feOffset.setAttribute("dy", obj.config.shadowVerticalOffset);
+ gaussFilter.appendChild(feOffset);
+
+ // blur
+ feGaussianBlur = document.createElementNS(svg,"feGaussianBlur");
+ feGaussianBlur.setAttribute("result","offset-blur");
+ feGaussianBlur.setAttribute("stdDeviation", obj.config.shadowSize);
+ gaussFilter.appendChild(feGaussianBlur);
+
+ // composite 1
+ feComposite1 = document.createElementNS(svg,"feComposite");
+ feComposite1.setAttribute("operator","out");
+ feComposite1.setAttribute("in", "SourceGraphic");
+ feComposite1.setAttribute("in2","offset-blur");
+ feComposite1.setAttribute("result","inverse");
+ gaussFilter.appendChild(feComposite1);
+
+ // flood
+ feFlood = document.createElementNS(svg,"feFlood");
+ feFlood.setAttribute("flood-color","black");
+ feFlood.setAttribute("flood-opacity", obj.config.shadowOpacity);
+ feFlood.setAttribute("result","color");
+ gaussFilter.appendChild(feFlood);
+
+ // composite 2
+ feComposite2 = document.createElementNS(svg,"feComposite");
+ feComposite2.setAttribute("operator","in");
+ feComposite2.setAttribute("in", "color");
+ feComposite2.setAttribute("in2","inverse");
+ feComposite2.setAttribute("result","shadow");
+ gaussFilter.appendChild(feComposite2);
+
+ // composite 3
+ feComposite3 = document.createElementNS(svg,"feComposite");
+ feComposite3.setAttribute("operator","over");
+ feComposite3.setAttribute("in", "shadow");
+ feComposite3.setAttribute("in2","SourceGraphic");
+ gaussFilter.appendChild(feComposite3);
+
+ // set shadow
+ if (!obj.config.hideInnerShadow) {
+ obj.canvas.canvas.childNodes[2].setAttribute("filter", "url(#inner-shadow)");
+ obj.canvas.canvas.childNodes[3].setAttribute("filter", "url(#inner-shadow)");
+ }
+
+ // var clear
+ gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;
+
+};
+
+/** Get color for value */
+function getColor(val, pct, col, noGradient, custSec) {
+
+ var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color;
+ var noGradient = noGradient || custSec.length > 0;
+
+ if(custSec.length > 0) {
+ for(var i = 0; i < custSec.length; i++) {
+ if(val > custSec[i].lo && val <= custSec[i].hi) {
+ return custSec[i].color;
+ }
+ }
+ }
+
+ no = col.length;
+ if (no === 1) return col[0];
+ inc = (noGradient) ? (1 / no) : (1 / (no - 1));
+ colors = [];
+ for (var i = 0; i < col.length; i++) {
+ percentage = (noGradient) ? (inc * (i + 1)) : (inc * i);
+ rval = parseInt((cutHex(col[i])).substring(0,2),16);
+ gval = parseInt((cutHex(col[i])).substring(2,4),16);
+ bval = parseInt((cutHex(col[i])).substring(4,6),16);
+ colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval } };
+ }
+
+ if(pct === 0) {
+ return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';
+ }
+
+ for (var j = 0; j < colors.length; j++) {
+ if (pct <= colors[j].pct) {
+ if (noGradient) {
+ return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')';
+ } else {
+ lower = colors[j - 1];
+ upper = colors[j];
+ range = upper.pct - lower.pct;
+ rangePct = (pct - lower.pct) / range;
+ pctLower = 1 - rangePct;
+ pctUpper = rangePct;
+ color = {
+ r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
+ g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
+ b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
+ };
+ return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
+ }
+ }
+ }
+
+}
+
+/** Fix Raphael display:none tspan dy attribute bug */
+function setDy(elem, fontSize, txtYpos) {
+ if (!ie || ie > 9) {
+ elem.node.firstChild.attributes.dy.value = 0;
+ }
+}
+
+/** Random integer */
+function getRandomInt (min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+/** Cut hex */
+function cutHex(str) {
+ return (str.charAt(0)=="#") ? str.substring(1,7):str;
+}
+
+/** Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */
+function humanFriendlyNumber( n, d ) {
+ var p, d2, i, s;
+
+ p = Math.pow;
+ d2 = p(10, d);
+ i = 7;
+ while( i ) {
+ s = p(10,i--*3);
+ if( s <= n ) {
+ n = Math.round(n*d2/s)/d2+"KMGTPE"[i];
+ }
+ }
+ return n;
+}
+
+/** Get style */
+function getStyle(oElm, strCssRule){
+ var strValue = "";
+ if(document.defaultView && document.defaultView.getComputedStyle){
+ strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
+ }
+ else if(oElm.currentStyle){
+ strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
+ return p1.toUpperCase();
+ });
+ strValue = oElm.currentStyle[strCssRule];
+ }
+ return strValue;
+}
+
+/** Create Element NS Ready */
+function onCreateElementNsReady(func) {
+ if (document.createElementNS !== undefined) {
+ func();
+ } else {
+ setTimeout(function() { onCreateElementNsReady(func); }, 100);
+ }
+}
+
+/** Get IE version */
+// ----------------------------------------------------------
+// A short snippet for detecting versions of IE in JavaScript
+// without resorting to user-agent sniffing
+// ----------------------------------------------------------
+// If you're not in IE (or IE version is less than 5) then:
+// ie === undefined
+// If you're in IE (>=5) then you can determine which version:
+// ie === 7; // IE7
+// Thus, to detect IE:
+// if (ie) {}
+// And to detect the version:
+// ie === 6 // IE6
+// ie > 7 // IE8, IE9 ...
+// ie < 9 // Anything less than IE9
+// ----------------------------------------------------------
+// UPDATE: Now using Live NodeList idea from @jdalton
+var ie = (function(){
+
+ var undef,
+ v = 3,
+ div = document.createElement('div'),
+ all = div.getElementsByTagName('i');
+
+ while (
+ div.innerHTML = '',
+ all[0]
+ );
+ return v > 4 ? v : undef;
+}());
\ No newline at end of file
diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js
index 52e975322e5..44db469cd89 100644
--- a/addons/web/static/lib/qweb/qweb2.js
+++ b/addons/web/static/lib/qweb/qweb2.js
@@ -384,6 +384,7 @@ QWeb2.Engine = (function() {
this.compiled_templates[template] = tcompiled;
return this.render(template, dict);
} else {
+ console.log(this.compiled_templates, template);
return this.tools.exception("Template '" + template + "' not found");
}
},
diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js
index 6f47f9c128d..aff3673f687 100644
--- a/addons/web_kanban/static/src/js/kanban.js
+++ b/addons/web_kanban/static/src/js/kanban.js
@@ -1231,6 +1231,148 @@ instance.web_kanban.AbstractField = instance.web.Widget.extend(instance.web_kanb
});
instance.web_kanban.fields_registry = new instance.web.Registry({});
+
+
+/**
+ * Kanban widgets: Sparkline
+ *
+ */
+
+instance.web_kanban.SparklineBarWidget = instance.web_kanban.AbstractField.extend({
+ className: "oe_sparkline_bar",
+ start: function() {
+ var self = this;
+ var title = this.$node.html() || this.field.string;
+ setTimeout(function () {
+ var value = _.pluck(self.field.value, 'value');
+ var tooltips = _.pluck(self.field.value, 'tooltip');
+ var sparkline_options = _.extend({
+ type: 'bar',
+ barWidth: 5,
+ height: '50px',
+ barWidth: '10px',
+ barSpacing: '5px',
+ barColor: '#96d854',
+ tooltipFormat: '{{offset:offset}} {{value}}',
+ tooltipValueLookups: {
+ 'offset': tooltips
+ }
+ }, this.options);
+ self.$el.sparkline(value, sparkline_options);
+ self.$el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'});
+ }, 0);
+ },
+});
+
+instance.web_kanban.fields_registry.add("sparkline_bar", "instance.web_kanban.SparklineBarWidget");
+
+
+/**
+ * Kanban widgets: GaugeWidget
+ *
+ */
+
+instance.web_kanban.GaugeWidget = instance.web_kanban.AbstractField.extend({
+ className: "oe_gauge",
+ start: function() {
+ var self = this;
+ var max = 100;
+ if (this.options.max_field) {
+ max = this.getParent().record[this.options.max_field].raw_value;
+ }
+ var label = this.options.label || "";
+ if (this.options.label_field) {
+ label = this.getParent().record[this.options.label_field].raw_value;
+ }
+ var val = this.field.value;
+ var value = _.isArray(val) && val.length ? val[val.length-1]['value'] : val;
+ var title = this.$node.html() || this.field.string;
+ console.log(value, title, max);
+ this.gage = new JustGage({
+ parentNode: this.$el[0],
+ value: value,
+ title: title,
+ min: 0,
+ max: max,
+ relativeGaugeSize: true,
+ humanFriendly: true,
+ label: label,
+ levelColors: [
+ "#ff0000",
+ "#f9c802",
+ "#a9d70b"
+ ]
+ });
+
+ var flag_open = false;
+ if (self.options.action_change) {
+ var $svg = self.$el.find('svg');
+ var css = {
+ 'text-align': 'center',
+ 'position': 'absolute',
+ 'width': self.$el.outerWidth() + 'px',
+ 'top': (self.$el.outerHeight()/2-5) + 'px'
+ };
+
+ self.$el.click(function (event) {
+ event.stopPropagation();
+ flag_open = false;
+ if (!parent.view.is_action_enabled('edit')) {
+ return;
+ }
+ if (!self.$el.find(".oe_justgage_edit").size()) {
+ $div = $('');
+ $div.css(css);
+ $input = $('').val(value);
+ $input.css({
+ 'text-align': 'center',
+ 'margin': 'auto',
+ 'width': ($svg.outerWidth()-40) + 'px'
+ });
+ $div.append($input);
+ self.$el.prepend($div)
+ $input.focus()
+ .keydown(function (event) {
+ event.stopPropagation();
+ if (event.keyCode == 13 || event.keyCode == 9) {
+ if ($input.val() != value) {
+ parent.view.dataset.call(self.options.action_change, [parent.id, $input.val()]).then(function () {
+ parent.do_reload();
+ });
+ } else {
+ $div.remove();
+ }
+ }
+ })
+ .click(function (event) {
+ event.stopPropagation();
+ flag_open = false;
+ })
+ .blur(function (event) {
+ if(!flag_open) {
+ self.$el.find(".oe_justgage_edit").remove();
+ } else {
+ flag_open = false;
+ setTimeout(function () {$input.focus();}, 0);
+ }
+ });
+ }
+ }).mousedown(function () {
+ flag_open = true;
+ });
+
+ if (!+value) {
+ $svg.fadeTo(0, 0.3);
+ $div = $('').text(_t("Click to change value"));
+ $div.css(css);
+ self.$el.append($div);
+ }
+ }
+ },
+});
+
+instance.web_kanban.fields_registry.add("gauge", "instance.web_kanban.GaugeWidget");
+
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
From 903ca548c7ec709d905b7b1ff20c93678dcab2e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?=
Date: Tue, 3 Sep 2013 16:59:36 +0200
Subject: [PATCH 121/256] [IMP] mass_mailing: added kanban views for
mass_mailing, implemented segment for campaigns. Moved sparkline and gauge
widgets to web.
Segment: template, date, mails.
Campaign: grouped statistics.
bzr revid: tde@openerp.com-20130903145936-nwy250w1suxmtbl3
---
addons/crm/__openerp__.py | 1 -
.../static/lib/sparkline/jquery.sparkline.js | 3047 -----------------
addons/crm/static/src/js/crm_case_section.js | 23 -
addons/mail/mail_mail.py | 31 +-
addons/mail/mail_thread.py | 3 +-
addons/mail/tests/__init__.py | 3 +-
addons/mass_mailing/__openerp__.py | 6 +
addons/mass_mailing/mail_mail.py | 10 +-
addons/mass_mailing/mail_mail_view.xml | 1 +
addons/mass_mailing/mass_mailing.py | 174 +-
addons/mass_mailing/mass_mailing_demo.xml | 81 +
addons/mass_mailing/mass_mailing_view.xml | 175 +-
.../static/src/css/mass_mailing.css | 54 +
addons/sale_crm/__openerp__.py | 1 -
addons/sale_crm/sale_crm_view.xml | 4 +-
addons/sale_crm/static/lib/justgage.js | 883 -----
addons/sale_crm/static/src/js/sale_crm.js | 108 -
17 files changed, 495 insertions(+), 4110 deletions(-)
delete mode 100644 addons/crm/static/lib/sparkline/jquery.sparkline.js
create mode 100644 addons/mass_mailing/mass_mailing_demo.xml
create mode 100644 addons/mass_mailing/static/src/css/mass_mailing.css
delete mode 100644 addons/sale_crm/static/lib/justgage.js
delete mode 100644 addons/sale_crm/static/src/js/sale_crm.js
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index 1b4ba191b82..ae9da7ade08 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -121,7 +121,6 @@ Dashboard for CRM will include:
'static/src/css/crm.css'
],
'js': [
- 'static/lib/sparkline/jquery.sparkline.js',
'static/src/js/crm_case_section.js',
],
'installable': True,
diff --git a/addons/crm/static/lib/sparkline/jquery.sparkline.js b/addons/crm/static/lib/sparkline/jquery.sparkline.js
deleted file mode 100644
index c003923e03b..00000000000
--- a/addons/crm/static/lib/sparkline/jquery.sparkline.js
+++ /dev/null
@@ -1,3047 +0,0 @@
-/**
-*
-* jquery.sparkline.js
-*
-* v2.1.1
-* (c) Splunk, Inc
-* Contact: Gareth Watts (gareth@splunk.com)
-* http://omnipotent.net/jquery.sparkline/
-*
-* Generates inline sparkline charts from data supplied either to the method
-* or inline in HTML
-*
-* Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
-* (Firefox 2.0+, Safari, Opera, etc)
-*
-* License: New BSD License
-*
-* Copyright (c) 2012, Splunk Inc.
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without modification,
-* are permitted provided that the following conditions are met:
-*
-* * Redistributions of source code must retain the above copyright notice,
-* this list of conditions and the following disclaimer.
-* * Redistributions in binary form must reproduce the above copyright notice,
-* this list of conditions and the following disclaimer in the documentation
-* and/or other materials provided with the distribution.
-* * Neither the name of Splunk Inc nor the names of its contributors may
-* be used to endorse or promote products derived from this software without
-* specific prior written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
-* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
-* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*
-*
-* Usage:
-* $(selector).sparkline(values, options)
-*
-* If values is undefined or set to 'html' then the data values are read from the specified tag:
-*
Sparkline: 1,4,6,6,8,5,3,5
-* $('.sparkline').sparkline();
-* There must be no spaces in the enclosed data set
-*
-* Otherwise values must be an array of numbers or null values
-*
Sparkline: This text replaced if the browser is compatible
-* $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])
-* $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])
-*
-* Values can also be specified in an HTML comment, or as a values attribute:
-*
Sparkline:
-*
Sparkline:
-* $('.sparkline').sparkline();
-*
-* For line charts, x values can also be specified:
-*
Sparkline: 1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5
-* $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])
-*
-* By default, options should be passed in as teh second argument to the sparkline function:
-* $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})
-*
-* Options can also be set by passing them on the tag itself. This feature is disabled by default though
-* as there's a slight performance overhead:
-* $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})
-*
Sparkline: loading
-* Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix)
-*
-* Supported options:
-* lineColor - Color of the line used for the chart
-* fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart
-* width - Width of the chart - Defaults to 3 times the number of values in pixels
-* height - Height of the chart - Defaults to the height of the containing element
-* chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied
-* chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied
-* chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax
-* chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied
-* chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied
-* composite - If true then don't erase any existing chart attached to the tag, but draw
-* another chart over the top - Note that width and height are ignored if an
-* existing chart is detected.
-* tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
-* enableTagOptions - Whether to check tags for sparkline options
-* tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
-* disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
-* hidden dom element, avoding a browser reflow
-* disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
-* making the plugin perform much like it did in 1.x
-* disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
-* disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
-* defaults to false (highlights enabled)
-* highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
-* tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
-* tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
-* tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
-* tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
-* tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip
-* callback is given arguments of (sparkline, options, fields)
-* tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
-* tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries)
-* to control the format of the tooltip
-* tooltipPrefix - A string to prepend to each field displayed in a tooltip
-* tooltipSuffix - A string to append to each field displayed in a tooltip
-* tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
-* tooltipValueLookups - An object or range map to map field values to tooltip strings
-* (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
-* numberFormatter - Optional callback for formatting numbers in tooltips
-* numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
-* numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
-* numberDigitGroupCount - Number of digits between group separator - Defaults to 3
-*
-* There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
-* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
-* line - Line chart. Options:
-* spotColor - Set to '' to not end each line in a circular spot
-* minSpotColor - If set, color of spot at minimum value
-* maxSpotColor - If set, color of spot at maximum value
-* spotRadius - Radius in pixels
-* lineWidth - Width of line in pixels
-* normalRangeMin
-* normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
-* or expected range of values
-* normalRangeColor - Color to use for the above bar
-* drawNormalOnTop - Draw the normal range above the chart fill color if true
-* defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
-* highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
-* highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
-* valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map
-*
-* bar - Bar chart. Options:
-* barColor - Color of bars for postive values
-* negBarColor - Color of bars for negative values
-* zeroColor - Color of bars with zero values
-* nullColor - Color of bars with null values - Defaults to omitting the bar entirely
-* barWidth - Width of bars in pixels
-* colorMap - Optional mappnig of values to colors to override the *BarColor values above
-* can be an Array of values to control the color of individual bars or a range map
-* to specify colors for individual ranges of values
-* barSpacing - Gap between bars in pixels
-* zeroAxis - Centers the y-axis around zero if true
-*
-* tristate - Charts values of win (>0), lose (<0) or draw (=0)
-* posBarColor - Color of win values
-* negBarColor - Color of lose values
-* zeroBarColor - Color of draw values
-* barWidth - Width of bars in pixels
-* barSpacing - Gap between bars in pixels
-* colorMap - Optional mappnig of values to colors to override the *BarColor values above
-* can be an Array of values to control the color of individual bars or a range map
-* to specify colors for individual ranges of values
-*
-* discrete - Options:
-* lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
-* thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
-* thresholdColor
-*
-* bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
-* options:
-* targetColor - The color of the vertical target marker
-* targetWidth - The width of the target marker in pixels
-* performanceColor - The color of the performance measure horizontal bar
-* rangeColors - Colors to use for each qualitative range background color
-*
-* pie - Pie chart. Options:
-* sliceColors - An array of colors to use for pie slices
-* offset - Angle in degrees to offset the first slice - Try -90 or +90
-* borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
-* borderColor - Color to use for the pie chart border - Defaults to #000
-*
-* box - Box plot. Options:
-* raw - Set to true to supply pre-computed plot points as values
-* values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
-* When set to false you can supply any number of values and the box plot will
-* be computed for you. Default is false.
-* showOutliers - Set to true (default) to display outliers as circles
-* outlierIQR - Interquartile range used to determine outliers. Default 1.5
-* boxLineColor - Outline color of the box
-* boxFillColor - Fill color for the box
-* whiskerColor - Line color used for whiskers
-* outlierLineColor - Outline color of outlier circles
-* outlierFillColor - Fill color of the outlier circles
-* spotRadius - Radius of outlier circles
-* medianColor - Line color of the median line
-* target - Draw a target cross hair at the supplied value (default undefined)
-*
-*
-*
-* Examples:
-* $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
-* $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
-* $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
-* $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
-* $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
-* $('#pie').sparkline([1,1,2], { type:'pie' });
-*/
-
-/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
-
-(function(factory) {
- if(typeof define === 'function' && define.amd) {
- define(['jquery'], factory);
- }
- else {
- factory(jQuery);
- }
-}
-(function($) {
- 'use strict';
-
- var UNSET_OPTION = {},
- getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
- remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
- MouseHandler, Tooltip, barHighlightMixin,
- line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,
- VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;
-
- /**
- * Default configuration settings
- */
- getDefaults = function () {
- return {
- // Settings common to most/all chart types
- common: {
- type: 'line',
- lineColor: '#00f',
- fillColor: '#cdf',
- defaultPixelsPerValue: 3,
- width: 'auto',
- height: 'auto',
- composite: false,
- tagValuesAttribute: 'values',
- tagOptionsPrefix: 'spark',
- enableTagOptions: false,
- enableHighlight: true,
- highlightLighten: 1.4,
- tooltipSkipNull: true,
- tooltipPrefix: '',
- tooltipSuffix: '',
- disableHiddenCheck: false,
- numberFormatter: false,
- numberDigitGroupCount: 3,
- numberDigitGroupSep: ',',
- numberDecimalMark: '.',
- disableTooltips: false,
- disableInteraction: false
- },
- // Defaults for line charts
- line: {
- spotColor: '#f80',
- highlightSpotColor: '#5f5',
- highlightLineColor: '#f22',
- spotRadius: 1.5,
- minSpotColor: '#f80',
- maxSpotColor: '#f80',
- lineWidth: 1,
- normalRangeMin: undefined,
- normalRangeMax: undefined,
- normalRangeColor: '#ccc',
- drawNormalOnTop: false,
- chartRangeMin: undefined,
- chartRangeMax: undefined,
- chartRangeMinX: undefined,
- chartRangeMaxX: undefined,
- tooltipFormat: new SPFormat('● {{prefix}}{{y}}{{suffix}}')
- },
- // Defaults for bar charts
- bar: {
- barColor: '#3366cc',
- negBarColor: '#f44',
- stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
- '#dd4477', '#0099c6', '#990099'],
- zeroColor: undefined,
- nullColor: undefined,
- zeroAxis: true,
- barWidth: 4,
- barSpacing: 1,
- chartRangeMax: undefined,
- chartRangeMin: undefined,
- chartRangeClip: false,
- colorMap: undefined,
- tooltipFormat: new SPFormat('● {{prefix}}{{value}}{{suffix}}')
- },
- // Defaults for tristate charts
- tristate: {
- barWidth: 4,
- barSpacing: 1,
- posBarColor: '#6f6',
- negBarColor: '#f44',
- zeroBarColor: '#999',
- colorMap: {},
- tooltipFormat: new SPFormat('● {{value:map}}'),
- tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
- },
- // Defaults for discrete charts
- discrete: {
- lineHeight: 'auto',
- thresholdColor: undefined,
- thresholdValue: 0,
- chartRangeMax: undefined,
- chartRangeMin: undefined,
- chartRangeClip: false,
- tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')
- },
- // Defaults for bullet charts
- bullet: {
- targetColor: '#f33',
- targetWidth: 3, // width of the target bar in pixels
- performanceColor: '#33f',
- rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],
- base: undefined, // set this to a number to change the base start number
- tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),
- tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }
- },
- // Defaults for pie charts
- pie: {
- offset: 0,
- sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
- '#dd4477', '#0099c6', '#990099'],
- borderWidth: 0,
- borderColor: '#000',
- tooltipFormat: new SPFormat('● {{value}} ({{percent.1}}%)')
- },
- // Defaults for box plots
- box: {
- raw: false,
- boxLineColor: '#000',
- boxFillColor: '#cdf',
- whiskerColor: '#000',
- outlierLineColor: '#333',
- outlierFillColor: '#fff',
- medianColor: '#f00',
- showOutliers: true,
- outlierIQR: 1.5,
- spotRadius: 1.5,
- target: undefined,
- targetColor: '#4a2',
- chartRangeMax: undefined,
- chartRangeMin: undefined,
- tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),
- tooltipFormatFieldlistKey: 'field',
- tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',
- uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',
- lw: 'Left Whisker', rw: 'Right Whisker'} }
- }
- };
- };
-
- // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
- defaultStyles = '.jqstooltip { ' +
- 'position: absolute;' +
- 'left: 0px;' +
- 'top: 0px;' +
- 'visibility: hidden;' +
- 'background: rgb(0, 0, 0) transparent;' +
- 'background-color: rgba(0,0,0,0.6);' +
- 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
- '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
- 'color: white;' +
- 'font: 10px arial, san serif;' +
- 'text-align: left;' +
- 'white-space: nowrap;' +
- 'padding: 5px;' +
- 'border: 1px solid white;' +
- 'z-index: 10000;' +
- '}' +
- '.jqsfield { ' +
- 'color: white;' +
- 'font: 10px arial, san serif;' +
- 'text-align: left;' +
- '}';
-
- /**
- * Utilities
- */
-
- createClass = function (/* [baseclass, [mixin, ...]], definition */) {
- var Class, args;
- Class = function () {
- this.init.apply(this, arguments);
- };
- if (arguments.length > 1) {
- if (arguments[0]) {
- Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);
- Class._super = arguments[0].prototype;
- } else {
- Class.prototype = arguments[arguments.length - 1];
- }
- if (arguments.length > 2) {
- args = Array.prototype.slice.call(arguments, 1, -1);
- args.unshift(Class.prototype);
- $.extend.apply($, args);
- }
- } else {
- Class.prototype = arguments[0];
- }
- Class.prototype.cls = Class;
- return Class;
- };
-
- /**
- * Wraps a format string for tooltips
- * {{x}}
- * {{x.2}
- * {{x:months}}
- */
- $.SPFormatClass = SPFormat = createClass({
- fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
- precre: /(\w+)\.(\d+)/,
-
- init: function (format, fclass) {
- this.format = format;
- this.fclass = fclass;
- },
-
- render: function (fieldset, lookups, options) {
- var self = this,
- fields = fieldset,
- match, token, lookupkey, fieldvalue, prec;
- return this.format.replace(this.fre, function () {
- var lookup;
- token = arguments[1];
- lookupkey = arguments[3];
- match = self.precre.exec(token);
- if (match) {
- prec = match[2];
- token = match[1];
- } else {
- prec = false;
- }
- fieldvalue = fields[token];
- if (fieldvalue === undefined) {
- return '';
- }
- if (lookupkey && lookups && lookups[lookupkey]) {
- lookup = lookups[lookupkey];
- if (lookup.get) { // RangeMap
- return lookups[lookupkey].get(fieldvalue) || fieldvalue;
- } else {
- return lookups[lookupkey][fieldvalue] || fieldvalue;
- }
- }
- if (isNumber(fieldvalue)) {
- if (options.get('numberFormatter')) {
- fieldvalue = options.get('numberFormatter')(fieldvalue);
- } else {
- fieldvalue = formatNumber(fieldvalue, prec,
- options.get('numberDigitGroupCount'),
- options.get('numberDigitGroupSep'),
- options.get('numberDecimalMark'));
- }
- }
- return fieldvalue;
- });
- }
- });
-
- // convience method to avoid needing the new operator
- $.spformat = function(format, fclass) {
- return new SPFormat(format, fclass);
- };
-
- clipval = function (val, min, max) {
- if (val < min) {
- return min;
- }
- if (val > max) {
- return max;
- }
- return val;
- };
-
- quartile = function (values, q) {
- var vl;
- if (q === 2) {
- vl = Math.floor(values.length / 2);
- return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;
- } else {
- if (values.length % 2 ) { // odd
- vl = (values.length * q + q) / 4;
- return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
- } else { //even
- vl = (values.length * q + 2) / 4;
- return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
-
- }
- }
- };
-
- normalizeValue = function (val) {
- var nf;
- switch (val) {
- case 'undefined':
- val = undefined;
- break;
- case 'null':
- val = null;
- break;
- case 'true':
- val = true;
- break;
- case 'false':
- val = false;
- break;
- default:
- nf = parseFloat(val);
- if (val == nf) {
- val = nf;
- }
- }
- return val;
- };
-
- normalizeValues = function (vals) {
- var i, result = [];
- for (i = vals.length; i--;) {
- result[i] = normalizeValue(vals[i]);
- }
- return result;
- };
-
- remove = function (vals, filter) {
- var i, vl, result = [];
- for (i = 0, vl = vals.length; i < vl; i++) {
- if (vals[i] !== filter) {
- result.push(vals[i]);
- }
- }
- return result;
- };
-
- isNumber = function (num) {
- return !isNaN(parseFloat(num)) && isFinite(num);
- };
-
- formatNumber = function (num, prec, groupsize, groupsep, decsep) {
- var p, i;
- num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');
- p = (p = $.inArray('.', num)) < 0 ? num.length : p;
- if (p < num.length) {
- num[p] = decsep;
- }
- for (i = p - groupsize; i > 0; i -= groupsize) {
- num.splice(i, 0, groupsep);
- }
- return num.join('');
- };
-
- // determine if all values of an array match a value
- // returns true if the array is empty
- all = function (val, arr, ignoreNull) {
- var i;
- for (i = arr.length; i--; ) {
- if (ignoreNull && arr[i] === null) continue;
- if (arr[i] !== val) {
- return false;
- }
- }
- return true;
- };
-
- // sums the numeric values in an array, ignoring other values
- sum = function (vals) {
- var total = 0, i;
- for (i = vals.length; i--;) {
- total += typeof vals[i] === 'number' ? vals[i] : 0;
- }
- return total;
- };
-
- ensureArray = function (val) {
- return $.isArray(val) ? val : [val];
- };
-
- // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
- addCSS = function(css) {
- var tag;
- //if ('\v' == 'v') /* ie only */ {
- if (document.createStyleSheet) {
- document.createStyleSheet().cssText = css;
- } else {
- tag = document.createElement('style');
- tag.type = 'text/css';
- document.getElementsByTagName('head')[0].appendChild(tag);
- tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;
- }
- };
-
- // Provide a cross-browser interface to a few simple drawing primitives
- $.fn.simpledraw = function (width, height, useExisting, interact) {
- var target, mhandler;
- if (useExisting && (target = this.data('_jqs_vcanvas'))) {
- return target;
- }
- if (width === undefined) {
- width = $(this).innerWidth();
- }
- if (height === undefined) {
- height = $(this).innerHeight();
- }
- if ($.fn.sparkline.hasCanvas) {
- target = new VCanvas_canvas(width, height, this, interact);
- } else if ($.fn.sparkline.hasVML) {
- target = new VCanvas_vml(width, height, this);
- } else {
- return false;
- }
- mhandler = $(this).data('_jqs_mhandler');
- if (mhandler) {
- mhandler.registerCanvas(target);
- }
- return target;
- };
-
- $.fn.cleardraw = function () {
- var target = this.data('_jqs_vcanvas');
- if (target) {
- target.reset();
- }
- };
-
- $.RangeMapClass = RangeMap = createClass({
- init: function (map) {
- var key, range, rangelist = [];
- for (key in map) {
- if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {
- range = key.split(':');
- range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);
- range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);
- range[2] = map[key];
- rangelist.push(range);
- }
- }
- this.map = map;
- this.rangelist = rangelist || false;
- },
-
- get: function (value) {
- var rangelist = this.rangelist,
- i, range, result;
- if ((result = this.map[value]) !== undefined) {
- return result;
- }
- if (rangelist) {
- for (i = rangelist.length; i--;) {
- range = rangelist[i];
- if (range[0] <= value && range[1] >= value) {
- return range[2];
- }
- }
- }
- return undefined;
- }
- });
-
- // Convenience function
- $.range_map = function(map) {
- return new RangeMap(map);
- };
-
- MouseHandler = createClass({
- init: function (el, options) {
- var $el = $(el);
- this.$el = $el;
- this.options = options;
- this.currentPageX = 0;
- this.currentPageY = 0;
- this.el = el;
- this.splist = [];
- this.tooltip = null;
- this.over = false;
- this.displayTooltips = !options.get('disableTooltips');
- this.highlightEnabled = !options.get('disableHighlight');
- },
-
- registerSparkline: function (sp) {
- this.splist.push(sp);
- if (this.over) {
- this.updateDisplay();
- }
- },
-
- registerCanvas: function (canvas) {
- var $canvas = $(canvas.canvas);
- this.canvas = canvas;
- this.$canvas = $canvas;
- $canvas.mouseenter($.proxy(this.mouseenter, this));
- $canvas.mouseleave($.proxy(this.mouseleave, this));
- $canvas.click($.proxy(this.mouseclick, this));
- },
-
- reset: function (removeTooltip) {
- this.splist = [];
- if (this.tooltip && removeTooltip) {
- this.tooltip.remove();
- this.tooltip = undefined;
- }
- },
-
- mouseclick: function (e) {
- var clickEvent = $.Event('sparklineClick');
- clickEvent.originalEvent = e;
- clickEvent.sparklines = this.splist;
- this.$el.trigger(clickEvent);
- },
-
- mouseenter: function (e) {
- $(document.body).unbind('mousemove.jqs');
- $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
- this.over = true;
- this.currentPageX = e.pageX;
- this.currentPageY = e.pageY;
- this.currentEl = e.target;
- if (!this.tooltip && this.displayTooltips) {
- this.tooltip = new Tooltip(this.options);
- this.tooltip.updatePosition(e.pageX, e.pageY);
- }
- this.updateDisplay();
- },
-
- mouseleave: function () {
- $(document.body).unbind('mousemove.jqs');
- var splist = this.splist,
- spcount = splist.length,
- needsRefresh = false,
- sp, i;
- this.over = false;
- this.currentEl = null;
-
- if (this.tooltip) {
- this.tooltip.remove();
- this.tooltip = null;
- }
-
- for (i = 0; i < spcount; i++) {
- sp = splist[i];
- if (sp.clearRegionHighlight()) {
- needsRefresh = true;
- }
- }
-
- if (needsRefresh) {
- this.canvas.render();
- }
- },
-
- mousemove: function (e) {
- this.currentPageX = e.pageX;
- this.currentPageY = e.pageY;
- this.currentEl = e.target;
- if (this.tooltip) {
- this.tooltip.updatePosition(e.pageX, e.pageY);
- }
- this.updateDisplay();
- },
-
- updateDisplay: function () {
- var splist = this.splist,
- spcount = splist.length,
- needsRefresh = false,
- offset = this.$canvas.offset(),
- localX = this.currentPageX - offset.left,
- localY = this.currentPageY - offset.top,
- tooltiphtml, sp, i, result, changeEvent;
- if (!this.over) {
- return;
- }
- for (i = 0; i < spcount; i++) {
- sp = splist[i];
- result = sp.setRegionHighlight(this.currentEl, localX, localY);
- if (result) {
- needsRefresh = true;
- }
- }
- if (needsRefresh) {
- changeEvent = $.Event('sparklineRegionChange');
- changeEvent.sparklines = this.splist;
- this.$el.trigger(changeEvent);
- if (this.tooltip) {
- tooltiphtml = '';
- for (i = 0; i < spcount; i++) {
- sp = splist[i];
- tooltiphtml += sp.getCurrentRegionTooltip();
- }
- this.tooltip.setContent(tooltiphtml);
- }
- if (!this.disableHighlight) {
- this.canvas.render();
- }
- }
- if (result === null) {
- this.mouseleave();
- }
- }
- });
-
-
- Tooltip = createClass({
- sizeStyle: 'position: static !important;' +
- 'display: block !important;' +
- 'visibility: hidden !important;' +
- 'float: left !important;',
-
- init: function (options) {
- var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
- sizetipStyle = this.sizeStyle,
- offset;
- this.container = options.get('tooltipContainer') || document.body;
- this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
- this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
- // remove any previous lingering tooltip
- $('#jqssizetip').remove();
- $('#jqstooltip').remove();
- this.sizetip = $('', {
- id: 'jqssizetip',
- style: sizetipStyle,
- 'class': tooltipClassname
- });
- this.tooltip = $('', {
- id: 'jqstooltip',
- 'class': tooltipClassname
- }).appendTo(this.container);
- // account for the container's location
- offset = this.tooltip.offset();
- this.offsetLeft = offset.left;
- this.offsetTop = offset.top;
- this.hidden = true;
- $(window).unbind('resize.jqs scroll.jqs');
- $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
- this.updateWindowDims();
- },
-
- updateWindowDims: function () {
- this.scrollTop = $(window).scrollTop();
- this.scrollLeft = $(window).scrollLeft();
- this.scrollRight = this.scrollLeft + $(window).width();
- this.updatePosition();
- },
-
- getSize: function (content) {
- this.sizetip.html(content).appendTo(this.container);
- this.width = this.sizetip.width() + 1;
- this.height = this.sizetip.height();
- this.sizetip.remove();
- },
-
- setContent: function (content) {
- if (!content) {
- this.tooltip.css('visibility', 'hidden');
- this.hidden = true;
- return;
- }
- this.getSize(content);
- this.tooltip.html(content)
- .css({
- 'width': this.width,
- 'height': this.height,
- 'visibility': 'visible'
- });
- if (this.hidden) {
- this.hidden = false;
- this.updatePosition();
- }
- },
-
- updatePosition: function (x, y) {
- if (x === undefined) {
- if (this.mousex === undefined) {
- return;
- }
- x = this.mousex - this.offsetLeft;
- y = this.mousey - this.offsetTop;
-
- } else {
- this.mousex = x = x - this.offsetLeft;
- this.mousey = y = y - this.offsetTop;
- }
- if (!this.height || !this.width || this.hidden) {
- return;
- }
-
- y -= this.height + this.tooltipOffsetY;
- x += this.tooltipOffsetX;
-
- if (y < this.scrollTop) {
- y = this.scrollTop;
- }
- if (x < this.scrollLeft) {
- x = this.scrollLeft;
- } else if (x + this.width > this.scrollRight) {
- x = this.scrollRight - this.width;
- }
-
- this.tooltip.css({
- 'left': x,
- 'top': y
- });
- },
-
- remove: function () {
- this.tooltip.remove();
- this.sizetip.remove();
- this.sizetip = this.tooltip = undefined;
- $(window).unbind('resize.jqs scroll.jqs');
- }
- });
-
- initStyles = function() {
- addCSS(defaultStyles);
- };
-
- $(initStyles);
-
- pending = [];
- $.fn.sparkline = function (userValues, userOptions) {
- return this.each(function () {
- var options = new $.fn.sparkline.options(this, userOptions),
- $this = $(this),
- render, i;
- render = function () {
- var values, width, height, tmp, mhandler, sp, vals;
- if (userValues === 'html' || userValues === undefined) {
- vals = this.getAttribute(options.get('tagValuesAttribute'));
- if (vals === undefined || vals === null) {
- vals = $this.html();
- }
- values = vals.replace(/(^\s*\s*$)|\s+/g, '').split(',');
- } else {
- values = userValues;
- }
-
- width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');
- if (options.get('height') === 'auto') {
- if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {
- // must be a better way to get the line height
- tmp = document.createElement('span');
- tmp.innerHTML = 'a';
- $this.html(tmp);
- height = $(tmp).innerHeight() || $(tmp).height();
- $(tmp).remove();
- tmp = null;
- }
- } else {
- height = options.get('height');
- }
-
- if (!options.get('disableInteraction')) {
- mhandler = $.data(this, '_jqs_mhandler');
- if (!mhandler) {
- mhandler = new MouseHandler(this, options);
- $.data(this, '_jqs_mhandler', mhandler);
- } else if (!options.get('composite')) {
- mhandler.reset();
- }
- } else {
- mhandler = false;
- }
-
- if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {
- if (!$.data(this, '_jqs_errnotify')) {
- alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
- $.data(this, '_jqs_errnotify', true);
- }
- return;
- }
-
- sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);
-
- sp.render();
-
- if (mhandler) {
- mhandler.registerSparkline(sp);
- }
- };
- // jQuery 1.3.0 completely changed the meaning of :hidden :-/
- if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || ($.fn.jquery < '1.3.0' && $(this).parents().is(':hidden')) || !$(this).parents('body').length) {
- if (!options.get('composite') && $.data(this, '_jqs_pending')) {
- // remove any existing references to the element
- for (i = pending.length; i; i--) {
- if (pending[i - 1][0] == this) {
- pending.splice(i - 1, 1);
- }
- }
- }
- pending.push([this, render]);
- $.data(this, '_jqs_pending', true);
- } else {
- render.call(this);
- }
- });
- };
-
- $.fn.sparkline.defaults = getDefaults();
-
-
- $.sparkline_display_visible = function () {
- var el, i, pl;
- var done = [];
- for (i = 0, pl = pending.length; i < pl; i++) {
- el = pending[i][0];
- if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
- pending[i][1].call(el);
- $.data(pending[i][0], '_jqs_pending', false);
- done.push(i);
- } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {
- // element has been inserted and removed from the DOM
- // If it was not yet inserted into the dom then the .data request
- // will return true.
- // removing from the dom causes the data to be removed.
- $.data(pending[i][0], '_jqs_pending', false);
- done.push(i);
- }
- }
- for (i = done.length; i; i--) {
- pending.splice(done[i - 1], 1);
- }
- };
-
-
- /**
- * User option handler
- */
- $.fn.sparkline.options = createClass({
- init: function (tag, userOptions) {
- var extendedOptions, defaults, base, tagOptionType;
- this.userOptions = userOptions = userOptions || {};
- this.tag = tag;
- this.tagValCache = {};
- defaults = $.fn.sparkline.defaults;
- base = defaults.common;
- this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
-
- tagOptionType = this.getTagSetting('type');
- if (tagOptionType === UNSET_OPTION) {
- extendedOptions = defaults[userOptions.type || base.type];
- } else {
- extendedOptions = defaults[tagOptionType];
- }
- this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
- },
-
-
- getTagSetting: function (key) {
- var prefix = this.tagOptionsPrefix,
- val, i, pairs, keyval;
- if (prefix === false || prefix === undefined) {
- return UNSET_OPTION;
- }
- if (this.tagValCache.hasOwnProperty(key)) {
- val = this.tagValCache.key;
- } else {
- val = this.tag.getAttribute(prefix + key);
- if (val === undefined || val === null) {
- val = UNSET_OPTION;
- } else if (val.substr(0, 1) === '[') {
- val = val.substr(1, val.length - 2).split(',');
- for (i = val.length; i--;) {
- val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
- }
- } else if (val.substr(0, 1) === '{') {
- pairs = val.substr(1, val.length - 2).split(',');
- val = {};
- for (i = pairs.length; i--;) {
- keyval = pairs[i].split(':', 2);
- val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
- }
- } else {
- val = normalizeValue(val);
- }
- this.tagValCache.key = val;
- }
- return val;
- },
-
- get: function (key, defaultval) {
- var tagOption = this.getTagSetting(key),
- result;
- if (tagOption !== UNSET_OPTION) {
- return tagOption;
- }
- return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
- }
- });
-
-
- $.fn.sparkline._base = createClass({
- disabled: false,
-
- init: function (el, values, options, width, height) {
- this.el = el;
- this.$el = $(el);
- this.values = values;
- this.options = options;
- this.width = width;
- this.height = height;
- this.currentRegion = undefined;
- },
-
- /**
- * Setup the canvas
- */
- initTarget: function () {
- var interactive = !this.options.get('disableInteraction');
- if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
- this.disabled = true;
- } else {
- this.canvasWidth = this.target.pixelWidth;
- this.canvasHeight = this.target.pixelHeight;
- }
- },
-
- /**
- * Actually render the chart to the canvas
- */
- render: function () {
- if (this.disabled) {
- this.el.innerHTML = '';
- return false;
- }
- return true;
- },
-
- /**
- * Return a region id for a given x/y co-ordinate
- */
- getRegion: function (x, y) {
- },
-
- /**
- * Highlight an item based on the moused-over x,y co-ordinate
- */
- setRegionHighlight: function (el, x, y) {
- var currentRegion = this.currentRegion,
- highlightEnabled = !this.options.get('disableHighlight'),
- newRegion;
- if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {
- return null;
- }
- newRegion = this.getRegion(el, x, y);
- if (currentRegion !== newRegion) {
- if (currentRegion !== undefined && highlightEnabled) {
- this.removeHighlight();
- }
- this.currentRegion = newRegion;
- if (newRegion !== undefined && highlightEnabled) {
- this.renderHighlight();
- }
- return true;
- }
- return false;
- },
-
- /**
- * Reset any currently highlighted item
- */
- clearRegionHighlight: function () {
- if (this.currentRegion !== undefined) {
- this.removeHighlight();
- this.currentRegion = undefined;
- return true;
- }
- return false;
- },
-
- renderHighlight: function () {
- this.changeHighlight(true);
- },
-
- removeHighlight: function () {
- this.changeHighlight(false);
- },
-
- changeHighlight: function (highlight) {},
-
- /**
- * Fetch the HTML to display as a tooltip
- */
- getCurrentRegionTooltip: function () {
- var options = this.options,
- header = '',
- entries = [],
- fields, formats, formatlen, fclass, text, i,
- showFields, showFieldsKey, newFields, fv,
- formatter, format, fieldlen, j;
- if (this.currentRegion === undefined) {
- return '';
- }
- fields = this.getCurrentRegionFields();
- formatter = options.get('tooltipFormatter');
- if (formatter) {
- return formatter(this, options, fields);
- }
- if (options.get('tooltipChartTitle')) {
- header += '
' + options.get('tooltipChartTitle') + '
\n';
- }
- formats = this.options.get('tooltipFormat');
- if (!formats) {
- return '';
- }
- if (!$.isArray(formats)) {
- formats = [formats];
- }
- if (!$.isArray(fields)) {
- fields = [fields];
- }
- showFields = this.options.get('tooltipFormatFieldlist');
- showFieldsKey = this.options.get('tooltipFormatFieldlistKey');
- if (showFields && showFieldsKey) {
- // user-selected ordering of fields
- newFields = [];
- for (i = fields.length; i--;) {
- fv = fields[i][showFieldsKey];
- if ((j = $.inArray(fv, showFields)) != -1) {
- newFields[j] = fields[i];
- }
- }
- fields = newFields;
- }
- formatlen = formats.length;
- fieldlen = fields.length;
- for (i = 0; i < formatlen; i++) {
- format = formats[i];
- if (typeof format === 'string') {
- format = new SPFormat(format);
- }
- fclass = format.fclass || 'jqsfield';
- for (j = 0; j < fieldlen; j++) {
- if (!fields[j].isNull || !options.get('tooltipSkipNull')) {
- $.extend(fields[j], {
- prefix: options.get('tooltipPrefix'),
- suffix: options.get('tooltipSuffix')
- });
- text = format.render(fields[j], options.get('tooltipValueLookups'), options);
- entries.push('
' + text + '
');
- }
- }
- }
- if (entries.length) {
- return header + entries.join('\n');
- }
- return '';
- },
-
- getCurrentRegionFields: function () {},
-
- calcHighlightColor: function (color, options) {
- var highlightColor = options.get('highlightColor'),
- lighten = options.get('highlightLighten'),
- parse, mult, rgbnew, i;
- if (highlightColor) {
- return highlightColor;
- }
- if (lighten) {
- // extract RGB values
- parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
- if (parse) {
- rgbnew = [];
- mult = color.length === 4 ? 16 : 1;
- for (i = 0; i < 3; i++) {
- rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
- }
- return 'rgb(' + rgbnew.join(',') + ')';
- }
-
- }
- return color;
- }
-
- });
-
- barHighlightMixin = {
- changeHighlight: function (highlight) {
- var currentRegion = this.currentRegion,
- target = this.target,
- shapeids = this.regionShapes[currentRegion],
- newShapes;
- // will be null if the region value was null
- if (shapeids) {
- newShapes = this.renderRegion(currentRegion, highlight);
- if ($.isArray(newShapes) || $.isArray(shapeids)) {
- target.replaceWithShapes(shapeids, newShapes);
- this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {
- return newShape.id;
- });
- } else {
- target.replaceWithShape(shapeids, newShapes);
- this.regionShapes[currentRegion] = newShapes.id;
- }
- }
- },
-
- render: function () {
- var values = this.values,
- target = this.target,
- regionShapes = this.regionShapes,
- shapes, ids, i, j;
-
- if (!this.cls._super.render.call(this)) {
- return;
- }
- for (i = values.length; i--;) {
- shapes = this.renderRegion(i);
- if (shapes) {
- if ($.isArray(shapes)) {
- ids = [];
- for (j = shapes.length; j--;) {
- shapes[j].append();
- ids.push(shapes[j].id);
- }
- regionShapes[i] = ids;
- } else {
- shapes.append();
- regionShapes[i] = shapes.id; // store just the shapeid
- }
- } else {
- // null value
- regionShapes[i] = null;
- }
- }
- target.render();
- }
- };
-
- /**
- * Line charts
- */
- $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {
- type: 'line',
-
- init: function (el, values, options, width, height) {
- line._super.init.call(this, el, values, options, width, height);
- this.vertices = [];
- this.regionMap = [];
- this.xvalues = [];
- this.yvalues = [];
- this.yminmax = [];
- this.hightlightSpotId = null;
- this.lastShapeId = null;
- this.initTarget();
- },
-
- getRegion: function (el, x, y) {
- var i,
- regionMap = this.regionMap; // maps regions to value positions
- for (i = regionMap.length; i--;) {
- if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {
- return regionMap[i][2];
- }
- }
- return undefined;
- },
-
- getCurrentRegionFields: function () {
- var currentRegion = this.currentRegion;
- return {
- isNull: this.yvalues[currentRegion] === null,
- x: this.xvalues[currentRegion],
- y: this.yvalues[currentRegion],
- color: this.options.get('lineColor'),
- fillColor: this.options.get('fillColor'),
- offset: currentRegion
- };
- },
-
- renderHighlight: function () {
- var currentRegion = this.currentRegion,
- target = this.target,
- vertex = this.vertices[currentRegion],
- options = this.options,
- spotRadius = options.get('spotRadius'),
- highlightSpotColor = options.get('highlightSpotColor'),
- highlightLineColor = options.get('highlightLineColor'),
- highlightSpot, highlightLine;
-
- if (!vertex) {
- return;
- }
- if (spotRadius && highlightSpotColor) {
- highlightSpot = target.drawCircle(vertex[0], vertex[1],
- spotRadius, undefined, highlightSpotColor);
- this.highlightSpotId = highlightSpot.id;
- target.insertAfterShape(this.lastShapeId, highlightSpot);
- }
- if (highlightLineColor) {
- highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],
- this.canvasTop + this.canvasHeight, highlightLineColor);
- this.highlightLineId = highlightLine.id;
- target.insertAfterShape(this.lastShapeId, highlightLine);
- }
- },
-
- removeHighlight: function () {
- var target = this.target;
- if (this.highlightSpotId) {
- target.removeShapeId(this.highlightSpotId);
- this.highlightSpotId = null;
- }
- if (this.highlightLineId) {
- target.removeShapeId(this.highlightLineId);
- this.highlightLineId = null;
- }
- },
-
- scanValues: function () {
- var values = this.values,
- valcount = values.length,
- xvalues = this.xvalues,
- yvalues = this.yvalues,
- yminmax = this.yminmax,
- i, val, isStr, isArray, sp;
- for (i = 0; i < valcount; i++) {
- val = values[i];
- isStr = typeof(values[i]) === 'string';
- isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;
- sp = isStr && values[i].split(':');
- if (isStr && sp.length === 2) { // x:y
- xvalues.push(Number(sp[0]));
- yvalues.push(Number(sp[1]));
- yminmax.push(Number(sp[1]));
- } else if (isArray) {
- xvalues.push(val[0]);
- yvalues.push(val[1]);
- yminmax.push(val[1]);
- } else {
- xvalues.push(i);
- if (values[i] === null || values[i] === 'null') {
- yvalues.push(null);
- } else {
- yvalues.push(Number(val));
- yminmax.push(Number(val));
- }
- }
- }
- if (this.options.get('xvalues')) {
- xvalues = this.options.get('xvalues');
- }
-
- this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);
- this.miny = this.minyorg = Math.min.apply(Math, yminmax);
-
- this.maxx = Math.max.apply(Math, xvalues);
- this.minx = Math.min.apply(Math, xvalues);
-
- this.xvalues = xvalues;
- this.yvalues = yvalues;
- this.yminmax = yminmax;
-
- },
-
- processRangeOptions: function () {
- var options = this.options,
- normalRangeMin = options.get('normalRangeMin'),
- normalRangeMax = options.get('normalRangeMax');
-
- if (normalRangeMin !== undefined) {
- if (normalRangeMin < this.miny) {
- this.miny = normalRangeMin;
- }
- if (normalRangeMax > this.maxy) {
- this.maxy = normalRangeMax;
- }
- }
- if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {
- this.miny = options.get('chartRangeMin');
- }
- if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {
- this.maxy = options.get('chartRangeMax');
- }
- if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {
- this.minx = options.get('chartRangeMinX');
- }
- if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {
- this.maxx = options.get('chartRangeMaxX');
- }
-
- },
-
- drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {
- var normalRangeMin = this.options.get('normalRangeMin'),
- normalRangeMax = this.options.get('normalRangeMax'),
- ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),
- height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);
- this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();
- },
-
- render: function () {
- var options = this.options,
- target = this.target,
- canvasWidth = this.canvasWidth,
- canvasHeight = this.canvasHeight,
- vertices = this.vertices,
- spotRadius = options.get('spotRadius'),
- regionMap = this.regionMap,
- rangex, rangey, yvallast,
- canvasTop, canvasLeft,
- vertex, path, paths, x, y, xnext, xpos, xposnext,
- last, next, yvalcount, lineShapes, fillShapes, plen,
- valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;
-
- if (!line._super.render.call(this)) {
- return;
- }
-
- this.scanValues();
- this.processRangeOptions();
-
- xvalues = this.xvalues;
- yvalues = this.yvalues;
-
- if (!this.yminmax.length || this.yvalues.length < 2) {
- // empty or all null valuess
- return;
- }
-
- canvasTop = canvasLeft = 0;
-
- rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;
- rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;
- yvallast = this.yvalues.length - 1;
-
- if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {
- spotRadius = 0;
- }
- if (spotRadius) {
- // adjust the canvas size as required so that spots will fit
- hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction');
- if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {
- canvasHeight -= Math.ceil(spotRadius);
- }
- if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {
- canvasHeight -= Math.ceil(spotRadius);
- canvasTop += Math.ceil(spotRadius);
- }
- if (hlSpotsEnabled ||
- ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {
- canvasLeft += Math.ceil(spotRadius);
- canvasWidth -= Math.ceil(spotRadius);
- }
- if (hlSpotsEnabled || options.get('spotColor') ||
- (options.get('minSpotColor') || options.get('maxSpotColor') &&
- (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {
- canvasWidth -= Math.ceil(spotRadius);
- }
- }
-
-
- canvasHeight--;
-
- if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {
- this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
- }
-
- path = [];
- paths = [path];
- last = next = null;
- yvalcount = yvalues.length;
- for (i = 0; i < yvalcount; i++) {
- x = xvalues[i];
- xnext = xvalues[i + 1];
- y = yvalues[i];
- xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));
- xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;
- next = xpos + ((xposnext - xpos) / 2);
- regionMap[i] = [last || 0, next, i];
- last = next;
- if (y === null) {
- if (i) {
- if (yvalues[i - 1] !== null) {
- path = [];
- paths.push(path);
- }
- vertices.push(null);
- }
- } else {
- if (y < this.miny) {
- y = this.miny;
- }
- if (y > this.maxy) {
- y = this.maxy;
- }
- if (!path.length) {
- // previous value was null
- path.push([xpos, canvasTop + canvasHeight]);
- }
- vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];
- path.push(vertex);
- vertices.push(vertex);
- }
- }
-
- lineShapes = [];
- fillShapes = [];
- plen = paths.length;
- for (i = 0; i < plen; i++) {
- path = paths[i];
- if (path.length) {
- if (options.get('fillColor')) {
- path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);
- fillShapes.push(path.slice(0));
- path.pop();
- }
- // if there's only a single point in this path, then we want to display it
- // as a vertical line which means we keep path[0] as is
- if (path.length > 2) {
- // else we want the first value
- path[0] = [path[0][0], path[1][1]];
- }
- lineShapes.push(path);
- }
- }
-
- // draw the fill first, then optionally the normal range, then the line on top of that
- plen = fillShapes.length;
- for (i = 0; i < plen; i++) {
- target.drawShape(fillShapes[i],
- options.get('fillColor'), options.get('fillColor')).append();
- }
-
- if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {
- this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
- }
-
- plen = lineShapes.length;
- for (i = 0; i < plen; i++) {
- target.drawShape(lineShapes[i], options.get('lineColor'), undefined,
- options.get('lineWidth')).append();
- }
-
- if (spotRadius && options.get('valueSpots')) {
- valueSpots = options.get('valueSpots');
- if (valueSpots.get === undefined) {
- valueSpots = new RangeMap(valueSpots);
- }
- for (i = 0; i < yvalcount; i++) {
- color = valueSpots.get(yvalues[i]);
- if (color) {
- target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),
- canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),
- spotRadius, undefined,
- color).append();
- }
- }
-
- }
- if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) {
- target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),
- canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),
- spotRadius, undefined,
- options.get('spotColor')).append();
- }
- if (this.maxy !== this.minyorg) {
- if (spotRadius && options.get('minSpotColor')) {
- x = xvalues[$.inArray(this.minyorg, yvalues)];
- target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
- canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),
- spotRadius, undefined,
- options.get('minSpotColor')).append();
- }
- if (spotRadius && options.get('maxSpotColor')) {
- x = xvalues[$.inArray(this.maxyorg, yvalues)];
- target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
- canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),
- spotRadius, undefined,
- options.get('maxSpotColor')).append();
- }
- }
-
- this.lastShapeId = target.getLastShapeId();
- this.canvasTop = canvasTop;
- target.render();
- }
- });
-
- /**
- * Bar charts
- */
- $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {
- type: 'bar',
-
- init: function (el, values, options, width, height) {
- var barWidth = parseInt(options.get('barWidth'), 10),
- barSpacing = parseInt(options.get('barSpacing'), 10),
- chartRangeMin = options.get('chartRangeMin'),
- chartRangeMax = options.get('chartRangeMax'),
- chartRangeClip = options.get('chartRangeClip'),
- stackMin = Infinity,
- stackMax = -Infinity,
- isStackString, groupMin, groupMax, stackRanges,
- numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,
- stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;
- bar._super.init.call(this, el, values, options, width, height);
-
- // scan values to determine whether to stack bars
- for (i = 0, vlen = values.length; i < vlen; i++) {
- val = values[i];
- isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;
- if (isStackString || $.isArray(val)) {
- stacked = true;
- if (isStackString) {
- val = values[i] = normalizeValues(val.split(':'));
- }
- val = remove(val, null); // min/max will treat null as zero
- groupMin = Math.min.apply(Math, val);
- groupMax = Math.max.apply(Math, val);
- if (groupMin < stackMin) {
- stackMin = groupMin;
- }
- if (groupMax > stackMax) {
- stackMax = groupMax;
- }
- }
- }
-
- this.stacked = stacked;
- this.regionShapes = {};
- this.barWidth = barWidth;
- this.barSpacing = barSpacing;
- this.totalBarWidth = barWidth + barSpacing;
- this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
-
- this.initTarget();
-
- if (chartRangeClip) {
- clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;
- clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;
- }
-
- numValues = [];
- stackRanges = stacked ? [] : numValues;
- var stackTotals = [];
- var stackRangesNeg = [];
- for (i = 0, vlen = values.length; i < vlen; i++) {
- if (stacked) {
- vlist = values[i];
- values[i] = svals = [];
- stackTotals[i] = 0;
- stackRanges[i] = stackRangesNeg[i] = 0;
- for (j = 0, slen = vlist.length; j < slen; j++) {
- val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];
- if (val !== null) {
- if (val > 0) {
- stackTotals[i] += val;
- }
- if (stackMin < 0 && stackMax > 0) {
- if (val < 0) {
- stackRangesNeg[i] += Math.abs(val);
- } else {
- stackRanges[i] += val;
- }
- } else {
- stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));
- }
- numValues.push(val);
- }
- }
- } else {
- val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];
- val = values[i] = normalizeValue(val);
- if (val !== null) {
- numValues.push(val);
- }
- }
- }
- this.max = max = Math.max.apply(Math, numValues);
- this.min = min = Math.min.apply(Math, numValues);
- this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;
- this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;
-
- if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {
- min = options.get('chartRangeMin');
- }
- if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {
- max = options.get('chartRangeMax');
- }
-
- this.zeroAxis = zeroAxis = options.get('zeroAxis', true);
- if (min <= 0 && max >= 0 && zeroAxis) {
- xaxisOffset = 0;
- } else if (zeroAxis == false) {
- xaxisOffset = min;
- } else if (min > 0) {
- xaxisOffset = min;
- } else {
- xaxisOffset = max;
- }
- this.xaxisOffset = xaxisOffset;
-
- range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;
-
- // as we plot zero/min values a single pixel line, we add a pixel to all other
- // values - Reduce the effective canvas size to suit
- this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;
-
- if (min < xaxisOffset) {
- yMaxCalc = (stacked && max >= 0) ? stackMax : max;
- yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;
- if (yoffset !== Math.ceil(yoffset)) {
- this.canvasHeightEf -= 2;
- yoffset = Math.ceil(yoffset);
- }
- } else {
- yoffset = this.canvasHeight;
- }
- this.yoffset = yoffset;
-
- if ($.isArray(options.get('colorMap'))) {
- this.colorMapByIndex = options.get('colorMap');
- this.colorMapByValue = null;
- } else {
- this.colorMapByIndex = null;
- this.colorMapByValue = options.get('colorMap');
- if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
- this.colorMapByValue = new RangeMap(this.colorMapByValue);
- }
- }
-
- this.range = range;
- },
-
- getRegion: function (el, x, y) {
- var result = Math.floor(x / this.totalBarWidth);
- return (result < 0 || result >= this.values.length) ? undefined : result;
- },
-
- getCurrentRegionFields: function () {
- var currentRegion = this.currentRegion,
- values = ensureArray(this.values[currentRegion]),
- result = [],
- value, i;
- for (i = values.length; i--;) {
- value = values[i];
- result.push({
- isNull: value === null,
- value: value,
- color: this.calcColor(i, value, currentRegion),
- offset: currentRegion
- });
- }
- return result;
- },
-
- calcColor: function (stacknum, value, valuenum) {
- var colorMapByIndex = this.colorMapByIndex,
- colorMapByValue = this.colorMapByValue,
- options = this.options,
- color, newColor;
- if (this.stacked) {
- color = options.get('stackedBarColor');
- } else {
- color = (value < 0) ? options.get('negBarColor') : options.get('barColor');
- }
- if (value === 0 && options.get('zeroColor') !== undefined) {
- color = options.get('zeroColor');
- }
- if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
- color = newColor;
- } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
- color = colorMapByIndex[valuenum];
- }
- return $.isArray(color) ? color[stacknum % color.length] : color;
- },
-
- /**
- * Render bar(s) for a region
- */
- renderRegion: function (valuenum, highlight) {
- var vals = this.values[valuenum],
- options = this.options,
- xaxisOffset = this.xaxisOffset,
- result = [],
- range = this.range,
- stacked = this.stacked,
- target = this.target,
- x = valuenum * this.totalBarWidth,
- canvasHeightEf = this.canvasHeightEf,
- yoffset = this.yoffset,
- y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;
-
- vals = $.isArray(vals) ? vals : [vals];
- valcount = vals.length;
- val = vals[0];
- isNull = all(null, vals);
- allMin = all(xaxisOffset, vals, true);
-
- if (isNull) {
- if (options.get('nullColor')) {
- color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);
- y = (yoffset > 0) ? yoffset - 1 : yoffset;
- return target.drawRect(x, y, this.barWidth - 1, 0, color, color);
- } else {
- return undefined;
- }
- }
- yoffsetNeg = yoffset;
- for (i = 0; i < valcount; i++) {
- val = vals[i];
-
- if (stacked && val === xaxisOffset) {
- if (!allMin || minPlotted) {
- continue;
- }
- minPlotted = true;
- }
-
- if (range > 0) {
- height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;
- } else {
- height = 1;
- }
- if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {
- y = yoffsetNeg;
- yoffsetNeg += height;
- } else {
- y = yoffset - height;
- yoffset -= height;
- }
- color = this.calcColor(i, val, valuenum);
- if (highlight) {
- color = this.calcHighlightColor(color, options);
- }
- result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));
- }
- if (result.length === 1) {
- return result[0];
- }
- return result;
- }
- });
-
- /**
- * Tristate charts
- */
- $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {
- type: 'tristate',
-
- init: function (el, values, options, width, height) {
- var barWidth = parseInt(options.get('barWidth'), 10),
- barSpacing = parseInt(options.get('barSpacing'), 10);
- tristate._super.init.call(this, el, values, options, width, height);
-
- this.regionShapes = {};
- this.barWidth = barWidth;
- this.barSpacing = barSpacing;
- this.totalBarWidth = barWidth + barSpacing;
- this.values = $.map(values, Number);
- this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
-
- if ($.isArray(options.get('colorMap'))) {
- this.colorMapByIndex = options.get('colorMap');
- this.colorMapByValue = null;
- } else {
- this.colorMapByIndex = null;
- this.colorMapByValue = options.get('colorMap');
- if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
- this.colorMapByValue = new RangeMap(this.colorMapByValue);
- }
- }
- this.initTarget();
- },
-
- getRegion: function (el, x, y) {
- return Math.floor(x / this.totalBarWidth);
- },
-
- getCurrentRegionFields: function () {
- var currentRegion = this.currentRegion;
- return {
- isNull: this.values[currentRegion] === undefined,
- value: this.values[currentRegion],
- color: this.calcColor(this.values[currentRegion], currentRegion),
- offset: currentRegion
- };
- },
-
- calcColor: function (value, valuenum) {
- var values = this.values,
- options = this.options,
- colorMapByIndex = this.colorMapByIndex,
- colorMapByValue = this.colorMapByValue,
- color, newColor;
-
- if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
- color = newColor;
- } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
- color = colorMapByIndex[valuenum];
- } else if (values[valuenum] < 0) {
- color = options.get('negBarColor');
- } else if (values[valuenum] > 0) {
- color = options.get('posBarColor');
- } else {
- color = options.get('zeroBarColor');
- }
- return color;
- },
-
- renderRegion: function (valuenum, highlight) {
- var values = this.values,
- options = this.options,
- target = this.target,
- canvasHeight, height, halfHeight,
- x, y, color;
-
- canvasHeight = target.pixelHeight;
- halfHeight = Math.round(canvasHeight / 2);
-
- x = valuenum * this.totalBarWidth;
- if (values[valuenum] < 0) {
- y = halfHeight;
- height = halfHeight - 1;
- } else if (values[valuenum] > 0) {
- y = 0;
- height = halfHeight - 1;
- } else {
- y = halfHeight - 1;
- height = 2;
- }
- color = this.calcColor(values[valuenum], valuenum);
- if (color === null) {
- return;
- }
- if (highlight) {
- color = this.calcHighlightColor(color, options);
- }
- return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);
- }
- });
-
- /**
- * Discrete charts
- */
- $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {
- type: 'discrete',
-
- init: function (el, values, options, width, height) {
- discrete._super.init.call(this, el, values, options, width, height);
-
- this.regionShapes = {};
- this.values = values = $.map(values, Number);
- this.min = Math.min.apply(Math, values);
- this.max = Math.max.apply(Math, values);
- this.range = this.max - this.min;
- this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;
- this.interval = Math.floor(width / values.length);
- this.itemWidth = width / values.length;
- if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {
- this.min = options.get('chartRangeMin');
- }
- if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {
- this.max = options.get('chartRangeMax');
- }
- this.initTarget();
- if (this.target) {
- this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');
- }
- },
-
- getRegion: function (el, x, y) {
- return Math.floor(x / this.itemWidth);
- },
-
- getCurrentRegionFields: function () {
- var currentRegion = this.currentRegion;
- return {
- isNull: this.values[currentRegion] === undefined,
- value: this.values[currentRegion],
- offset: currentRegion
- };
- },
-
- renderRegion: function (valuenum, highlight) {
- var values = this.values,
- options = this.options,
- min = this.min,
- max = this.max,
- range = this.range,
- interval = this.interval,
- target = this.target,
- canvasHeight = this.canvasHeight,
- lineHeight = this.lineHeight,
- pheight = canvasHeight - lineHeight,
- ytop, val, color, x;
-
- val = clipval(values[valuenum], min, max);
- x = valuenum * interval;
- ytop = Math.round(pheight - pheight * ((val - min) / range));
- color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');
- if (highlight) {
- color = this.calcHighlightColor(color, options);
- }
- return target.drawLine(x, ytop, x, ytop + lineHeight, color);
- }
- });
-
- /**
- * Bullet charts
- */
- $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {
- type: 'bullet',
-
- init: function (el, values, options, width, height) {
- var min, max, vals;
- bullet._super.init.call(this, el, values, options, width, height);
-
- // values: target, performance, range1, range2, range3
- this.values = values = normalizeValues(values);
- // target or performance could be null
- vals = values.slice();
- vals[0] = vals[0] === null ? vals[2] : vals[0];
- vals[1] = values[1] === null ? vals[2] : vals[1];
- min = Math.min.apply(Math, values);
- max = Math.max.apply(Math, values);
- if (options.get('base') === undefined) {
- min = min < 0 ? min : 0;
- } else {
- min = options.get('base');
- }
- this.min = min;
- this.max = max;
- this.range = max - min;
- this.shapes = {};
- this.valueShapes = {};
- this.regiondata = {};
- this.width = width = options.get('width') === 'auto' ? '4.0em' : width;
- this.target = this.$el.simpledraw(width, height, options.get('composite'));
- if (!values.length) {
- this.disabled = true;
- }
- this.initTarget();
- },
-
- getRegion: function (el, x, y) {
- var shapeid = this.target.getShapeAt(el, x, y);
- return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
- },
-
- getCurrentRegionFields: function () {
- var currentRegion = this.currentRegion;
- return {
- fieldkey: currentRegion.substr(0, 1),
- value: this.values[currentRegion.substr(1)],
- region: currentRegion
- };
- },
-
- changeHighlight: function (highlight) {
- var currentRegion = this.currentRegion,
- shapeid = this.valueShapes[currentRegion],
- shape;
- delete this.shapes[shapeid];
- switch (currentRegion.substr(0, 1)) {
- case 'r':
- shape = this.renderRange(currentRegion.substr(1), highlight);
- break;
- case 'p':
- shape = this.renderPerformance(highlight);
- break;
- case 't':
- shape = this.renderTarget(highlight);
- break;
- }
- this.valueShapes[currentRegion] = shape.id;
- this.shapes[shape.id] = currentRegion;
- this.target.replaceWithShape(shapeid, shape);
- },
-
- renderRange: function (rn, highlight) {
- var rangeval = this.values[rn],
- rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),
- color = this.options.get('rangeColors')[rn - 2];
- if (highlight) {
- color = this.calcHighlightColor(color, this.options);
- }
- return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);
- },
-
- renderPerformance: function (highlight) {
- var perfval = this.values[1],
- perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),
- color = this.options.get('performanceColor');
- if (highlight) {
- color = this.calcHighlightColor(color, this.options);
- }
- return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,
- Math.round(this.canvasHeight * 0.4) - 1, color, color);
- },
-
- renderTarget: function (highlight) {
- var targetval = this.values[0],
- x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),
- targettop = Math.round(this.canvasHeight * 0.10),
- targetheight = this.canvasHeight - (targettop * 2),
- color = this.options.get('targetColor');
- if (highlight) {
- color = this.calcHighlightColor(color, this.options);
- }
- return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);
- },
-
- render: function () {
- var vlen = this.values.length,
- target = this.target,
- i, shape;
- if (!bullet._super.render.call(this)) {
- return;
- }
- for (i = 2; i < vlen; i++) {
- shape = this.renderRange(i).append();
- this.shapes[shape.id] = 'r' + i;
- this.valueShapes['r' + i] = shape.id;
- }
- if (this.values[1] !== null) {
- shape = this.renderPerformance().append();
- this.shapes[shape.id] = 'p1';
- this.valueShapes.p1 = shape.id;
- }
- if (this.values[0] !== null) {
- shape = this.renderTarget().append();
- this.shapes[shape.id] = 't0';
- this.valueShapes.t0 = shape.id;
- }
- target.render();
- }
- });
-
- /**
- * Pie charts
- */
- $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {
- type: 'pie',
-
- init: function (el, values, options, width, height) {
- var total = 0, i;
-
- pie._super.init.call(this, el, values, options, width, height);
-
- this.shapes = {}; // map shape ids to value offsets
- this.valueShapes = {}; // maps value offsets to shape ids
- this.values = values = $.map(values, Number);
-
- if (options.get('width') === 'auto') {
- this.width = this.height;
- }
-
- if (values.length > 0) {
- for (i = values.length; i--;) {
- total += values[i];
- }
- }
- this.total = total;
- this.initTarget();
- this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);
- },
-
- getRegion: function (el, x, y) {
- var shapeid = this.target.getShapeAt(el, x, y);
- return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
- },
-
- getCurrentRegionFields: function () {
- var currentRegion = this.currentRegion;
- return {
- isNull: this.values[currentRegion] === undefined,
- value: this.values[currentRegion],
- percent: this.values[currentRegion] / this.total * 100,
- color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],
- offset: currentRegion
- };
- },
-
- changeHighlight: function (highlight) {
- var currentRegion = this.currentRegion,
- newslice = this.renderSlice(currentRegion, highlight),
- shapeid = this.valueShapes[currentRegion];
- delete this.shapes[shapeid];
- this.target.replaceWithShape(shapeid, newslice);
- this.valueShapes[currentRegion] = newslice.id;
- this.shapes[newslice.id] = currentRegion;
- },
-
- renderSlice: function (valuenum, highlight) {
- var target = this.target,
- options = this.options,
- radius = this.radius,
- borderWidth = options.get('borderWidth'),
- offset = options.get('offset'),
- circle = 2 * Math.PI,
- values = this.values,
- total = this.total,
- next = offset ? (2*Math.PI)*(offset/360) : 0,
- start, end, i, vlen, color;
-
- vlen = values.length;
- for (i = 0; i < vlen; i++) {
- start = next;
- end = next;
- if (total > 0) { // avoid divide by zero
- end = next + (circle * (values[i] / total));
- }
- if (valuenum === i) {
- color = options.get('sliceColors')[i % options.get('sliceColors').length];
- if (highlight) {
- color = this.calcHighlightColor(color, options);
- }
-
- return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);
- }
- next = end;
- }
- },
-
- render: function () {
- var target = this.target,
- values = this.values,
- options = this.options,
- radius = this.radius,
- borderWidth = options.get('borderWidth'),
- shape, i;
-
- if (!pie._super.render.call(this)) {
- return;
- }
- if (borderWidth) {
- target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),
- options.get('borderColor'), undefined, borderWidth).append();
- }
- for (i = values.length; i--;) {
- if (values[i]) { // don't render zero values
- shape = this.renderSlice(i).append();
- this.valueShapes[i] = shape.id; // store just the shapeid
- this.shapes[shape.id] = i;
- }
- }
- target.render();
- }
- });
-
- /**
- * Box plots
- */
- $.fn.sparkline.box = box = createClass($.fn.sparkline._base, {
- type: 'box',
-
- init: function (el, values, options, width, height) {
- box._super.init.call(this, el, values, options, width, height);
- this.values = $.map(values, Number);
- this.width = options.get('width') === 'auto' ? '4.0em' : width;
- this.initTarget();
- if (!this.values.length) {
- this.disabled = 1;
- }
- },
-
- /**
- * Simulate a single region
- */
- getRegion: function () {
- return 1;
- },
-
- getCurrentRegionFields: function () {
- var result = [
- { field: 'lq', value: this.quartiles[0] },
- { field: 'med', value: this.quartiles[1] },
- { field: 'uq', value: this.quartiles[2] }
- ];
- if (this.loutlier !== undefined) {
- result.push({ field: 'lo', value: this.loutlier});
- }
- if (this.routlier !== undefined) {
- result.push({ field: 'ro', value: this.routlier});
- }
- if (this.lwhisker !== undefined) {
- result.push({ field: 'lw', value: this.lwhisker});
- }
- if (this.rwhisker !== undefined) {
- result.push({ field: 'rw', value: this.rwhisker});
- }
- return result;
- },
-
- render: function () {
- var target = this.target,
- values = this.values,
- vlen = values.length,
- options = this.options,
- canvasWidth = this.canvasWidth,
- canvasHeight = this.canvasHeight,
- minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),
- maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),
- canvasLeft = 0,
- lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,
- size, unitSize;
-
- if (!box._super.render.call(this)) {
- return;
- }
-
- if (options.get('raw')) {
- if (options.get('showOutliers') && values.length > 5) {
- loutlier = values[0];
- lwhisker = values[1];
- q1 = values[2];
- q2 = values[3];
- q3 = values[4];
- rwhisker = values[5];
- routlier = values[6];
- } else {
- lwhisker = values[0];
- q1 = values[1];
- q2 = values[2];
- q3 = values[3];
- rwhisker = values[4];
- }
- } else {
- values.sort(function (a, b) { return a - b; });
- q1 = quartile(values, 1);
- q2 = quartile(values, 2);
- q3 = quartile(values, 3);
- iqr = q3 - q1;
- if (options.get('showOutliers')) {
- lwhisker = rwhisker = undefined;
- for (i = 0; i < vlen; i++) {
- if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {
- lwhisker = values[i];
- }
- if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {
- rwhisker = values[i];
- }
- }
- loutlier = values[0];
- routlier = values[vlen - 1];
- } else {
- lwhisker = values[0];
- rwhisker = values[vlen - 1];
- }
- }
- this.quartiles = [q1, q2, q3];
- this.lwhisker = lwhisker;
- this.rwhisker = rwhisker;
- this.loutlier = loutlier;
- this.routlier = routlier;
-
- unitSize = canvasWidth / (maxValue - minValue + 1);
- if (options.get('showOutliers')) {
- canvasLeft = Math.ceil(options.get('spotRadius'));
- canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));
- unitSize = canvasWidth / (maxValue - minValue + 1);
- if (loutlier < lwhisker) {
- target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,
- canvasHeight / 2,
- options.get('spotRadius'),
- options.get('outlierLineColor'),
- options.get('outlierFillColor')).append();
- }
- if (routlier > rwhisker) {
- target.drawCircle((routlier - minValue) * unitSize + canvasLeft,
- canvasHeight / 2,
- options.get('spotRadius'),
- options.get('outlierLineColor'),
- options.get('outlierFillColor')).append();
- }
- }
-
- // box
- target.drawRect(
- Math.round((q1 - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight * 0.1),
- Math.round((q3 - q1) * unitSize),
- Math.round(canvasHeight * 0.8),
- options.get('boxLineColor'),
- options.get('boxFillColor')).append();
- // left whisker
- target.drawLine(
- Math.round((lwhisker - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight / 2),
- Math.round((q1 - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight / 2),
- options.get('lineColor')).append();
- target.drawLine(
- Math.round((lwhisker - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight / 4),
- Math.round((lwhisker - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight - canvasHeight / 4),
- options.get('whiskerColor')).append();
- // right whisker
- target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight / 2),
- Math.round((q3 - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight / 2),
- options.get('lineColor')).append();
- target.drawLine(
- Math.round((rwhisker - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight / 4),
- Math.round((rwhisker - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight - canvasHeight / 4),
- options.get('whiskerColor')).append();
- // median line
- target.drawLine(
- Math.round((q2 - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight * 0.1),
- Math.round((q2 - minValue) * unitSize + canvasLeft),
- Math.round(canvasHeight * 0.9),
- options.get('medianColor')).append();
- if (options.get('target')) {
- size = Math.ceil(options.get('spotRadius'));
- target.drawLine(
- Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
- Math.round((canvasHeight / 2) - size),
- Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
- Math.round((canvasHeight / 2) + size),
- options.get('targetColor')).append();
- target.drawLine(
- Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),
- Math.round(canvasHeight / 2),
- Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),
- Math.round(canvasHeight / 2),
- options.get('targetColor')).append();
- }
- target.render();
- }
- });
-
- // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
- // This is accessible as $(foo).simpledraw()
-
- // Detect browser renderer support
- (function() {
- if (document.namespaces && !document.namespaces.v) {
- $.fn.sparkline.hasVML = true;
- document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
- } else {
- $.fn.sparkline.hasVML = false;
- }
-
- var el = document.createElement('canvas');
- $.fn.sparkline.hasCanvas = !!(el.getContext && el.getContext('2d'));
-
- })()
-
- VShape = createClass({
- init: function (target, id, type, args) {
- this.target = target;
- this.id = id;
- this.type = type;
- this.args = args;
- },
- append: function () {
- this.target.appendShape(this);
- return this;
- }
- });
-
- VCanvas_base = createClass({
- _pxregex: /(\d+)(px)?\s*$/i,
-
- init: function (width, height, target) {
- if (!width) {
- return;
- }
- this.width = width;
- this.height = height;
- this.target = target;
- this.lastShapeId = null;
- if (target[0]) {
- target = target[0];
- }
- $.data(target, '_jqs_vcanvas', this);
- },
-
- drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {
- return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);
- },
-
- drawShape: function (path, lineColor, fillColor, lineWidth) {
- return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);
- },
-
- drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {
- return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);
- },
-
- drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {
- return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);
- },
-
- drawRect: function (x, y, width, height, lineColor, fillColor) {
- return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);
- },
-
- getElement: function () {
- return this.canvas;
- },
-
- /**
- * Return the most recently inserted shape id
- */
- getLastShapeId: function () {
- return this.lastShapeId;
- },
-
- /**
- * Clear and reset the canvas
- */
- reset: function () {
- alert('reset not implemented');
- },
-
- _insert: function (el, target) {
- $(target).html(el);
- },
-
- /**
- * Calculate the pixel dimensions of the canvas
- */
- _calculatePixelDims: function (width, height, canvas) {
- // XXX This should probably be a configurable option
- var match;
- match = this._pxregex.exec(height);
- if (match) {
- this.pixelHeight = match[1];
- } else {
- this.pixelHeight = $(canvas).height();
- }
- match = this._pxregex.exec(width);
- if (match) {
- this.pixelWidth = match[1];
- } else {
- this.pixelWidth = $(canvas).width();
- }
- },
-
- /**
- * Generate a shape object and id for later rendering
- */
- _genShape: function (shapetype, shapeargs) {
- var id = shapeCount++;
- shapeargs.unshift(id);
- return new VShape(this, id, shapetype, shapeargs);
- },
-
- /**
- * Add a shape to the end of the render queue
- */
- appendShape: function (shape) {
- alert('appendShape not implemented');
- },
-
- /**
- * Replace one shape with another
- */
- replaceWithShape: function (shapeid, shape) {
- alert('replaceWithShape not implemented');
- },
-
- /**
- * Insert one shape after another in the render queue
- */
- insertAfterShape: function (shapeid, shape) {
- alert('insertAfterShape not implemented');
- },
-
- /**
- * Remove a shape from the queue
- */
- removeShapeId: function (shapeid) {
- alert('removeShapeId not implemented');
- },
-
- /**
- * Find a shape at the specified x/y co-ordinates
- */
- getShapeAt: function (el, x, y) {
- alert('getShapeAt not implemented');
- },
-
- /**
- * Render all queued shapes onto the canvas
- */
- render: function () {
- alert('render not implemented');
- }
- });
-
- VCanvas_canvas = createClass(VCanvas_base, {
- init: function (width, height, target, interact) {
- VCanvas_canvas._super.init.call(this, width, height, target);
- this.canvas = document.createElement('canvas');
- if (target[0]) {
- target = target[0];
- }
- $.data(target, '_jqs_vcanvas', this);
- $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });
- this._insert(this.canvas, target);
- this._calculatePixelDims(width, height, this.canvas);
- this.canvas.width = this.pixelWidth;
- this.canvas.height = this.pixelHeight;
- this.interact = interact;
- this.shapes = {};
- this.shapeseq = [];
- this.currentTargetShapeId = undefined;
- $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});
- },
-
- _getContext: function (lineColor, fillColor, lineWidth) {
- var context = this.canvas.getContext('2d');
- if (lineColor !== undefined) {
- context.strokeStyle = lineColor;
- }
- context.lineWidth = lineWidth === undefined ? 1 : lineWidth;
- if (fillColor !== undefined) {
- context.fillStyle = fillColor;
- }
- return context;
- },
-
- reset: function () {
- var context = this._getContext();
- context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
- this.shapes = {};
- this.shapeseq = [];
- this.currentTargetShapeId = undefined;
- },
-
- _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
- var context = this._getContext(lineColor, fillColor, lineWidth),
- i, plen;
- context.beginPath();
- context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);
- for (i = 1, plen = path.length; i < plen; i++) {
- context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
- }
- if (lineColor !== undefined) {
- context.stroke();
- }
- if (fillColor !== undefined) {
- context.fill();
- }
- if (this.targetX !== undefined && this.targetY !== undefined &&
- context.isPointInPath(this.targetX, this.targetY)) {
- this.currentTargetShapeId = shapeid;
- }
- },
-
- _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
- var context = this._getContext(lineColor, fillColor, lineWidth);
- context.beginPath();
- context.arc(x, y, radius, 0, 2 * Math.PI, false);
- if (this.targetX !== undefined && this.targetY !== undefined &&
- context.isPointInPath(this.targetX, this.targetY)) {
- this.currentTargetShapeId = shapeid;
- }
- if (lineColor !== undefined) {
- context.stroke();
- }
- if (fillColor !== undefined) {
- context.fill();
- }
- },
-
- _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
- var context = this._getContext(lineColor, fillColor);
- context.beginPath();
- context.moveTo(x, y);
- context.arc(x, y, radius, startAngle, endAngle, false);
- context.lineTo(x, y);
- context.closePath();
- if (lineColor !== undefined) {
- context.stroke();
- }
- if (fillColor) {
- context.fill();
- }
- if (this.targetX !== undefined && this.targetY !== undefined &&
- context.isPointInPath(this.targetX, this.targetY)) {
- this.currentTargetShapeId = shapeid;
- }
- },
-
- _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
- return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);
- },
-
- appendShape: function (shape) {
- this.shapes[shape.id] = shape;
- this.shapeseq.push(shape.id);
- this.lastShapeId = shape.id;
- return shape.id;
- },
-
- replaceWithShape: function (shapeid, shape) {
- var shapeseq = this.shapeseq,
- i;
- this.shapes[shape.id] = shape;
- for (i = shapeseq.length; i--;) {
- if (shapeseq[i] == shapeid) {
- shapeseq[i] = shape.id;
- }
- }
- delete this.shapes[shapeid];
- },
-
- replaceWithShapes: function (shapeids, shapes) {
- var shapeseq = this.shapeseq,
- shapemap = {},
- sid, i, first;
-
- for (i = shapeids.length; i--;) {
- shapemap[shapeids[i]] = true;
- }
- for (i = shapeseq.length; i--;) {
- sid = shapeseq[i];
- if (shapemap[sid]) {
- shapeseq.splice(i, 1);
- delete this.shapes[sid];
- first = i;
- }
- }
- for (i = shapes.length; i--;) {
- shapeseq.splice(first, 0, shapes[i].id);
- this.shapes[shapes[i].id] = shapes[i];
- }
-
- },
-
- insertAfterShape: function (shapeid, shape) {
- var shapeseq = this.shapeseq,
- i;
- for (i = shapeseq.length; i--;) {
- if (shapeseq[i] === shapeid) {
- shapeseq.splice(i + 1, 0, shape.id);
- this.shapes[shape.id] = shape;
- return;
- }
- }
- },
-
- removeShapeId: function (shapeid) {
- var shapeseq = this.shapeseq,
- i;
- for (i = shapeseq.length; i--;) {
- if (shapeseq[i] === shapeid) {
- shapeseq.splice(i, 1);
- break;
- }
- }
- delete this.shapes[shapeid];
- },
-
- getShapeAt: function (el, x, y) {
- this.targetX = x;
- this.targetY = y;
- this.render();
- return this.currentTargetShapeId;
- },
-
- render: function () {
- var shapeseq = this.shapeseq,
- shapes = this.shapes,
- shapeCount = shapeseq.length,
- context = this._getContext(),
- shapeid, shape, i;
- context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
- for (i = 0; i < shapeCount; i++) {
- shapeid = shapeseq[i];
- shape = shapes[shapeid];
- this['_draw' + shape.type].apply(this, shape.args);
- }
- if (!this.interact) {
- // not interactive so no need to keep the shapes array
- this.shapes = {};
- this.shapeseq = [];
- }
- }
-
- });
-
- VCanvas_vml = createClass(VCanvas_base, {
- init: function (width, height, target) {
- var groupel;
- VCanvas_vml._super.init.call(this, width, height, target);
- if (target[0]) {
- target = target[0];
- }
- $.data(target, '_jqs_vcanvas', this);
- this.canvas = document.createElement('span');
- $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});
- this._insert(this.canvas, target);
- this._calculatePixelDims(width, height, this.canvas);
- this.canvas.width = this.pixelWidth;
- this.canvas.height = this.pixelHeight;
- groupel = '';
- this.canvas.insertAdjacentHTML('beforeEnd', groupel);
- this.group = $(this.canvas).children()[0];
- this.rendered = false;
- this.prerender = '';
- },
-
- _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
- var vpath = [],
- initial, stroke, fill, closed, vel, plen, i;
- for (i = 0, plen = path.length; i < plen; i++) {
- vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);
- }
- initial = vpath.splice(0, 1);
- lineWidth = lineWidth === undefined ? 1 : lineWidth;
- stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
- fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
- closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';
- vel = '' +
- ' ';
- return vel;
- },
-
- _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
- var stroke, fill, vel;
- x -= radius;
- y -= radius;
- stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
- fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
- vel = '';
- return vel;
-
- },
-
- _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
- var vpath, startx, starty, endx, endy, stroke, fill, vel;
- if (startAngle === endAngle) {
- return ''; // VML seems to have problem when start angle equals end angle.
- }
- if ((endAngle - startAngle) === (2 * Math.PI)) {
- startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0
- endAngle = (2 * Math.PI);
- }
-
- startx = x + Math.round(Math.cos(startAngle) * radius);
- starty = y + Math.round(Math.sin(startAngle) * radius);
- endx = x + Math.round(Math.cos(endAngle) * radius);
- endy = y + Math.round(Math.sin(endAngle) * radius);
-
- if (startx === endx && starty === endy) {
- if ((endAngle - startAngle) < Math.PI) {
- // Prevent very small slices from being mistaken as a whole pie
- return '';
- }
- // essentially going to be the entire circle, so ignore startAngle
- startx = endx = x + radius;
- starty = endy = y;
- }
-
- if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {
- return '';
- }
-
- vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];
- stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" ';
- fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
- vel = '' +
- ' ';
- return vel;
- },
-
- _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
- return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);
- },
-
- reset: function () {
- this.group.innerHTML = '';
- },
-
- appendShape: function (shape) {
- var vel = this['_draw' + shape.type].apply(this, shape.args);
- if (this.rendered) {
- this.group.insertAdjacentHTML('beforeEnd', vel);
- } else {
- this.prerender += vel;
- }
- this.lastShapeId = shape.id;
- return shape.id;
- },
-
- replaceWithShape: function (shapeid, shape) {
- var existing = $('#jqsshape' + shapeid),
- vel = this['_draw' + shape.type].apply(this, shape.args);
- existing[0].outerHTML = vel;
- },
-
- replaceWithShapes: function (shapeids, shapes) {
- // replace the first shapeid with all the new shapes then toast the remaining old shapes
- var existing = $('#jqsshape' + shapeids[0]),
- replace = '',
- slen = shapes.length,
- i;
- for (i = 0; i < slen; i++) {
- replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);
- }
- existing[0].outerHTML = replace;
- for (i = 1; i < shapeids.length; i++) {
- $('#jqsshape' + shapeids[i]).remove();
- }
- },
-
- insertAfterShape: function (shapeid, shape) {
- var existing = $('#jqsshape' + shapeid),
- vel = this['_draw' + shape.type].apply(this, shape.args);
- existing[0].insertAdjacentHTML('afterEnd', vel);
- },
-
- removeShapeId: function (shapeid) {
- var existing = $('#jqsshape' + shapeid);
- this.group.removeChild(existing[0]);
- },
-
- getShapeAt: function (el, x, y) {
- var shapeid = el.id.substr(8);
- return shapeid;
- },
-
- render: function () {
- if (!this.rendered) {
- // batch the intial render into a single repaint
- this.group.innerHTML = this.prerender;
- this.rendered = true;
- }
- }
- });
-
-}));
diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js
index 2b4ca741d75..e027313d6dc 100644
--- a/addons/crm/static/src/js/crm_case_section.js
+++ b/addons/crm/static/src/js/crm_case_section.js
@@ -8,27 +8,4 @@ openerp.crm = function(openerp) {
}
},
});
-
- openerp.crm.SparklineBarWidget = openerp.web_kanban.AbstractField.extend({
- className: "oe_sparkline_bar",
- start: function() {
- var self = this;
- var title = this.$node.html();
- setTimeout(function () {
- var value = _.pluck(self.field.value, 'value');
- var tooltips = _.pluck(self.field.value, 'tooltip');
- self.$el.sparkline(value, {
- type: 'bar',
- barWidth: 5,
- tooltipFormat: '{{offset:offset}} {{value}}',
- tooltipValueLookups: {
- 'offset': tooltips
- },
- });
- self.$el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'});
- }, 0);
- },
- });
- openerp.web_kanban.fields_registry.add("sparkline_bar", "openerp.crm.SparklineBarWidget");
-
};
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index e5cb2b56eb3..73fb148c0ed 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -63,12 +63,16 @@ class mail_mail(osv.Model):
'notification': fields.boolean('Is Notification',
help='Mail has been created to notify people of an existing mail.message'),
# Bounce and tracking
- 'opened': fields.integer(
+ 'opened': fields.datetime(
'Opened',
- help='Number of times this email has been seen, using the OpenERP tracking.'),
- 'replied': fields.integer(
- 'Reply Received',
- help='If checked, a reply to this email has been received.'),
+ help='Date when this email has been opened for the first time.'),
+ 'replied': fields.datetime(
+ 'Replied',
+ help='Date when this email has been replied for the first time.'),
+ 'bounced': fields.datetime(
+ 'Bounced',
+ help='Date when this email has bounced.'
+ ),
}
_defaults = {
@@ -103,15 +107,24 @@ class mail_mail(osv.Model):
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
def set_opened(self, cr, uid, ids, context=None):
- """ Increment opened counter """
+ """ Set as opened """
for mail in self.browse(cr, uid, ids, context=context):
- self.write(cr, uid, [mail.id], {'opened': (mail.opened + 1)}, context=context)
+ if not mail.opened:
+ self.write(cr, uid, [mail.id], {'opened': fields.datetime.now()}, context=context)
return True
def set_replied(self, cr, uid, ids, context=None):
- """ Increment replied counter """
+ """ Set as replied """
for mail in self.browse(cr, uid, ids, context=context):
- self.write(cr, uid, [mail.id], {'replied': (mail.replied + 1)}, context=context)
+ if not mail.replied:
+ self.write(cr, uid, [mail.id], {'replied': fields.datetime.now()}, context=context)
+ return True
+
+ def set_bounced(self, cr, uid, ids, context=None):
+ """ Set as bounced """
+ for mail in self.browse(cr, uid, ids, context=context):
+ if not mail.bounced:
+ self.write(cr, uid, [mail.id], {'bounced': fields.datetime.now()}, context=context)
return True
def process_email_queue(self, cr, uid, ids=None, context=None):
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 9943114a07a..9a5d3e3580d 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -1426,8 +1426,7 @@ class mail_thread(osv.AbstractModel):
# update original mail_mail if exists
if type == 'email':
mail_mail_ids = self.pool['mail.mail'].search(cr, SUPERUSER_ID, [('mail_message_id', '=', parent_id)], context=context)
- for mail in self.pool['mail.mail'].browse(cr, SUPERUSER_ID, mail_mail_ids, context=context):
- self.pool['mail.mail'].write(cr, SUPERUSER_ID, [mail.id], {'replied': mail.replied + 1}, context=context)
+ self.pool['mail.mail'].set_replied(cr, SUPERUSER_ID, mail_mail_ids, context=context)
message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context)
# avoid loops when finding ancestors
diff --git a/addons/mail/tests/__init__.py b/addons/mail/tests/__init__.py
index 242beb60bf1..ff075d73d73 100644
--- a/addons/mail/tests/__init__.py
+++ b/addons/mail/tests/__init__.py
@@ -19,9 +19,10 @@
#
##############################################################################
-from . import test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
+from . import test_mail_mail, test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
checks = [
+ # test_mail_mail,
test_mail_group,
test_mail_message,
test_mail_features,
diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py
index e6fbb42e886..9bb5b746f63 100644
--- a/addons/mass_mailing/__openerp__.py
+++ b/addons/mass_mailing/__openerp__.py
@@ -29,10 +29,16 @@
'description': """TODO""",
'data': [
'mass_mailing_view.xml',
+ 'mass_mailing_demo.xml',
'mail_mail_view.xml',
'wizard/mail_compose_message_view.xml',
'security/ir.model.access.csv',
],
+ 'js': [],
+ 'qweb': [],
+ 'css': [
+ 'static/src/css/mass_mailing.css'
+ ],
'demo': [],
'installable': True,
'auto_install': False,
diff --git a/addons/mass_mailing/mail_mail.py b/addons/mass_mailing/mail_mail.py
index d7f46446b56..529c6c73e7b 100644
--- a/addons/mass_mailing/mail_mail.py
+++ b/addons/mass_mailing/mail_mail.py
@@ -28,8 +28,14 @@ class MailMail(osv.Model):
_inherit = ['mail.mail']
_columns = {
- 'mass_mailing_campaign_id': fields.many2one(
- 'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
+ 'mass_mailing_segment_id': fields.many2one(
+ 'mail.mass_mailing.segment', 'Mass Mailing Segment',
ondelete='set null',
),
+ 'mass_mailing_campaign_id': fields.related(
+ 'mass_mailing_segment_id', 'mass_mailing_campaign_id',
+ type='many2one', ondelete='set null',
+ relation='mail.mass_mailing.campaign',
+ store=True, readonly=True,
+ ),
}
diff --git a/addons/mass_mailing/mail_mail_view.xml b/addons/mass_mailing/mail_mail_view.xml
index dd66aede66f..957ee0f6f93 100644
--- a/addons/mass_mailing/mail_mail_view.xml
+++ b/addons/mass_mailing/mail_mail_view.xml
@@ -10,6 +10,7 @@
+
diff --git a/addons/mass_mailing/mass_mailing.py b/addons/mass_mailing/mass_mailing.py
index 91392759b0e..f37f86e385a 100644
--- a/addons/mass_mailing/mass_mailing.py
+++ b/addons/mass_mailing/mass_mailing.py
@@ -19,6 +19,10 @@
#
##############################################################################
+from datetime import date, datetime
+from dateutil import relativedelta
+
+from openerp import tools
from openerp.osv import osv, fields
@@ -32,33 +36,42 @@ class MassMailingCampaign(osv.Model):
""" Compute statistics of the mass mailing campaign """
results = dict.fromkeys(ids, False)
for campaign in self.browse(cr, uid, ids, context=context):
- if not campaign.mail_ids:
- results[campaign.id] = {
- 'sent': 0,
- 'opened_ratio': 0.0,
- 'replied_ratio': 0.0,
- 'bounce_ratio': 0.0,
- }
- continue
results[campaign.id] = {
'sent': len(campaign.mail_ids),
- 'opened_ratio': len([mail for mail in campaign.mail_ids if mail.opened]) * 1.0 / len(campaign.mail_ids),
- 'replied_ratio': len([mail for mail in campaign.mail_ids if mail.replied]) * 1.0 / len(campaign.mail_ids),
- 'bounce_ratio': 0.0,
+ 'opened': len([mail for mail in campaign.mail_ids if mail.opened]),
+ 'replied': len([mail for mail in campaign.mail_ids if mail.replied]),
+ 'bounced': len([mail for mail in campaign.mail_ids if mail.bounced]),
}
return results
+ def _get_segment_kanban_ids(self, cr, uid, ids, name, arg, context=None):
+ results = dict.fromkeys(ids, '')
+ for campaign in self.browse(cr, uid, ids, context=context):
+ segment_results = []
+ for segment in campaign.segment_ids:
+ segment_object = {}
+ for attr in ['name', 'sent', 'opened', 'replied', 'bounced']:
+ segment_object[attr] = getattr(segment, attr)
+ segment_results.append(segment_object)
+ results[campaign.id] = segment_results
+ return results
+
_columns = {
'name': fields.char(
'Campaign Name', required=True,
),
- 'template_id': fields.many2one(
- 'email.template', 'Email Template',
- ondelete='set null',
+ 'segment_ids': fields.one2many(
+ 'mail.mass_mailing.segment', 'mass_mailing_campaign_id',
+ 'Segments',
+ ),
+ 'segment_kanban_ids': fields.function(
+ _get_segment_kanban_ids,
+ type='text', string='Segments (kanban data)',
+ help='This field has for purpose to gather data about segment to display them in kanban view as nested kanban views is not possible currently',
),
'mail_ids': fields.one2many(
'mail.mail', 'mass_mailing_campaign_id',
- 'Send Emails',
+ 'Sent Emails',
),
# stat fields
'sent': fields.function(
@@ -66,19 +79,132 @@ class MassMailingCampaign(osv.Model):
string='Sent Emails',
type='integer', multi='_get_statistics'
),
- 'opened_ratio': fields.function(
+ 'opened': fields.function(
_get_statistics,
- string='Opened Ratio',
- type='float', multi='_get_statistics',
+ string='Opened',
+ type='integer', multi='_get_statistics',
),
- 'replied_ratio': fields.function(
+ 'replied': fields.function(
_get_statistics,
- string='Replied Ratio',
- type='float', multi='_get_statistics'
+ string='Replied',
+ type='integer', multi='_get_statistics'
),
- 'bounce_ratio': fields.function(
+ 'bounced': fields.function(
_get_statistics,
- string='Bounce Ratio',
- type='float', multi='_get_statistics'
+ string='Bounced',
+ type='integer', multi='_get_statistics'
+ ),
+ }
+
+
+class MassMailingSegment(osv.Model):
+ """ TODO """
+ _name = 'mail.mass_mailing.segment'
+ _description = 'Segment of a mass mailing campaign'
+ # number of periods for tracking mail_mail statistics
+ _period_number = 6
+
+ def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
+ """ Generic method to generate data for bar chart values using SparklineBarWidget.
+ This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
+
+ :param obj: the target model (i.e. crm_lead)
+ :param domain: the domain applied to the read_group
+ :param list read_fields: the list of fields to read in the read_group
+ :param str value_field: the field used to compute the value of the bar slice
+ :param str groupby_field: the fields used to group
+
+ :return list section_result: a list of dicts: [
+ { 'value': (int) bar_column_value,
+ 'tootip': (str) bar_column_tooltip,
+ }
+ ]
+ """
+ month_begin = date.today().replace(day=1)
+ section_result = [{'value': 0,
+ 'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
+ } for i in range(self._period_number - 1, -1, -1)]
+ group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
+ print group_obj
+ for group in group_obj:
+ group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
+ month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
+ section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
+ return section_result
+
+ def _get_monthly_statistics(self, cr, uid, ids, field_name, arg, context=None):
+ """ TODO
+ """
+ obj = self.pool.get('mail.mail')
+ res = dict.fromkeys(ids, False)
+ month_begin = date.today().replace(day=1)
+ groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ for id in ids:
+ res[id] = dict()
+ domain = [('mass_mailing_segment_id', '=', id), ('opened', '>=', groupby_begin)]
+ res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened', context=context)
+ domain = [('mass_mailing_segment_id', '=', id), ('replied', '>=', groupby_begin)]
+ res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied', context=context)
+ return res
+
+ def _get_statistics(self, cr, uid, ids, name, arg, context=None):
+ """ Compute statistics of the mass mailing campaign """
+ results = dict.fromkeys(ids, False)
+ for segment in self.browse(cr, uid, ids, context=context):
+ results[segment.id] = {
+ 'sent': len(segment.mail_ids),
+ 'opened': len([mail for mail in segment.mail_ids if mail.opened]),
+ 'replied': len([mail for mail in segment.mail_ids if mail.replied]),
+ 'bounced': len([mail for mail in segment.mail_ids if mail.bounced]),
+ }
+ return results
+
+ _columns = {
+ 'name': fields.char('Name', required=True),
+ 'mass_mailing_campaign_id': fields.many2one(
+ 'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
+ ondelete='cascade',
+ ),
+ 'template_id': fields.many2one(
+ 'email.template', 'Email Template',
+ ondelete='set null',
+ ),
+ 'domain': fields.char('Domain'),
+ 'date': fields.datetime('Date'),
+ # mail_mail data
+ 'mail_ids': fields.one2many(
+ 'mail.mail', 'mass_mailing_segment_id',
+ 'Send Emails',
+ ),
+ 'sent': fields.function(
+ _get_statistics,
+ string='Sent Emails',
+ type='integer', multi='_get_statistics'
+ ),
+ 'opened': fields.function(
+ _get_statistics,
+ string='Opened',
+ type='integer', multi='_get_statistics',
+ ),
+ 'replied': fields.function(
+ _get_statistics,
+ string='Replied',
+ type='integer', multi='_get_statistics'
+ ),
+ 'bounced': fields.function(
+ _get_statistics,
+ string='Bounce',
+ type='integer', multi='_get_statistics'
+ ),
+ # monthly ratio
+ 'opened_monthly': fields.function(
+ _get_monthly_statistics,
+ string='Sent Emails',
+ type='char', multi='_get_monthly_statistics',
+ ),
+ 'replied_monthly': fields.function(
+ _get_monthly_statistics,
+ string='Replied',
+ type='char', multi='_get_monthly_statistics',
),
}
diff --git a/addons/mass_mailing/mass_mailing_demo.xml b/addons/mass_mailing/mass_mailing_demo.xml
new file mode 100644
index 00000000000..3e04baf7cef
--- /dev/null
+++ b/addons/mass_mailing/mass_mailing_demo.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ Partner Newsletter 1
+
+
+ ${object.id}
+ Hello]]>
+
+
+ Partner Newsletter 2
+
+
+ ${object.id}
+ Hello]]>
+
+
+
+ Partners Newsletter
+
+
+
+ First Newsletter
+
+
+
+
+
+ Second Newsletter
+
+
+
+
+
+
+
+
+
+ sent
+
+
+
+
+
+ sent
+
+
+
+
+ sent
+
+
+
+ sent
+
+
+
+
+ sent
+
+
+
+
+
+ sent
+
+
+
+
+ sent
+
+
+
+ sent
+
+
+
+
diff --git a/addons/mass_mailing/mass_mailing_view.xml b/addons/mass_mailing/mass_mailing_view.xml
index bc28827343d..b847a89db53 100644
--- a/addons/mass_mailing/mass_mailing_view.xml
+++ b/addons/mass_mailing/mass_mailing_view.xml
@@ -3,7 +3,7 @@
-
+ mail.mass_mailing.campaign.treemail.mass_mailing.campaign10
@@ -14,7 +14,7 @@
-
+ mail.mass_mailing.campaign.formmail.mass_mailing.campaign
@@ -22,27 +22,188 @@
-
-
-
-
+
+
+
+
+
+
+ mail.mass_mailing.campaign.kanban
+ mail.mass_mailing.campaign
+
+
+
+
+
+
+