summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2015-08-14 18:35:33 +0200
committerChristophe Simonis <chs@odoo.com>2015-08-15 16:57:35 +0200
commit73e652818b812670b7d6ce3da727de1809da531c (patch)
treefcc9f2ae92e5d1318dbddf423d330114cff68896
parent9979939cd160a234270426f644c3bb2fdda8b543 (diff)
parent8d95f6dcdf6e451f32c247bcabd5ce024a158ebe (diff)
[MERGE] forward port of branch saas-6 up to 8d95f6d
-rw-r--r--addons/account_analytic_plans/account_analytic_plans_view.xml2
-rw-r--r--addons/auth_oauth/auth_oauth_data.xml4
-rw-r--r--addons/auth_oauth/res_users.py7
-rw-r--r--addons/hr_evaluation/hr_evaluation_view.xml1
-rw-r--r--addons/hr_holidays/hr_holidays_view.xml13
-rw-r--r--addons/hr_timesheet_invoice/hr_timesheet_invoice.py5
-rw-r--r--addons/hw_escpos/controllers/main.py8
-rw-r--r--addons/hw_posbox_homepage/controllers/main.py8
-rw-r--r--addons/hw_posbox_upgrade/controllers/main.py41
-rw-r--r--addons/hw_proxy/controllers/main.py18
-rw-r--r--addons/hw_scale/controllers/main.py24
-rw-r--r--addons/hw_scanner/controllers/main.py6
-rw-r--r--addons/l10n_cn/account_chart_small_business_template.xml75
-rw-r--r--addons/l10n_cn/account_chart_template.xml8
-rw-r--r--addons/l10n_cn/account_chart_type.xml55
-rw-r--r--addons/mass_mailing/models/mail_mail.py6
-rw-r--r--addons/mass_mailing/models/mass_mailing.py4
-rw-r--r--addons/mass_mailing/views/editor_field_html.xml4
-rw-r--r--addons/mrp_repair/wizard/make_invoice.py2
-rw-r--r--addons/point_of_sale/tools/posbox/.gitignore3
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_after_init/etc/fstab4
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS10
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_after_init/etc/network/interfaces3
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local25
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_after_init/etc/setup_ramdisks.sh25
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo72
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh68
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/etc/ld.so.preload1
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.conf32
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.d/odoo5
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/etc/rc.local22
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/90-qemu.rules3
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/led_status.sh19
-rw-r--r--addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/odoo.conf6
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/posbox_update.sh6
-rwxr-xr-xaddons/point_of_sale/tools/posbox/posbox_create_image.sh79
-rwxr-xr-xaddons/point_of_sale/tools/posbox/posbox_download_images.sh5
-rw-r--r--addons/purchase/purchase.py10
-rw-r--r--addons/purchase/stock.py2
-rw-r--r--addons/purchase_requisition/purchase_requisition.py48
-rw-r--r--addons/report/models/report.py9
-rw-r--r--addons/sale/wizard/sale_make_invoice_advance.py2
-rw-r--r--addons/sale_contract/models/sale_contract.py2
-rw-r--r--addons/sale_stock/sale_stock_view.xml1
-rw-r--r--addons/stock/stock.py14
-rw-r--r--addons/stock/tests/__init__.py1
-rw-r--r--addons/stock/tests/test_resupply.py51
-rw-r--r--addons/stock/views/report_stockpicking_operations.xml2
-rw-r--r--addons/stock_account/stock.py2
-rw-r--r--addons/survey/security/survey_security.xml2
-rw-r--r--addons/survey/survey.py7
-rw-r--r--addons/web/static/src/css/ie.css3
-rw-r--r--addons/web/static/src/js/control_panel.js2
-rw-r--r--addons/web_editor/models/ir_ui_view.py9
-rw-r--r--addons/web_editor/static/src/js/rte.summernote.js2
-rw-r--r--addons/website/controllers/main.py2
-rw-r--r--addons/website/data/data.xml4
-rw-r--r--addons/website/static/src/js/website.js5
-rw-r--r--addons/website/static/src/js/website.snippets.editor.js29
-rw-r--r--addons/website/tests/test_views.py10
-rw-r--r--addons/website/views/website_templates.xml7
-rw-r--r--addons/website_crm/data/website_crm_data.xml2
-rw-r--r--addons/website_hr/data/website_hr_data.xml2
-rw-r--r--addons/website_sale/controllers/main.py2
-rw-r--r--addons/website_sale/static/src/js/website_sale.js2
-rw-r--r--doc/cla/corporate/hbee.md21
-rw-r--r--doc/cla/corporate/rossa.md14
-rw-r--r--doc/cla/corporate/willow-it.md18
-rw-r--r--doc/cla/individual/cysnake4713.md11
-rw-r--r--openerp/addons/base/ir/ir_ui_view.py10
-rw-r--r--openerp/addons/base/res/res_users.py40
-rw-r--r--openerp/addons/base/tests/test_search.py15
-rw-r--r--openerp/models.py122
-rw-r--r--openerp/osv/fields.py3
-rw-r--r--openerp/tests/common.py1
75 files changed, 926 insertions, 242 deletions
diff --git a/addons/account_analytic_plans/account_analytic_plans_view.xml b/addons/account_analytic_plans/account_analytic_plans_view.xml
index 1870b894849..d6110b6faa2 100644
--- a/addons/account_analytic_plans/account_analytic_plans_view.xml
+++ b/addons/account_analytic_plans/account_analytic_plans_view.xml
@@ -70,7 +70,7 @@
<group col="4">
<field name="name"/>
<field name="code"/>
- <field name="plan_id"/>
+ <field name="plan_id" required="1"/>
<field name="journal_id"/>
</group>
<field name="account_ids">
diff --git a/addons/auth_oauth/auth_oauth_data.xml b/addons/auth_oauth/auth_oauth_data.xml
index ff5187f18f1..3b71b56be1f 100644
--- a/addons/auth_oauth/auth_oauth_data.xml
+++ b/addons/auth_oauth/auth_oauth_data.xml
@@ -14,8 +14,8 @@
<record id="provider_facebook" model="auth.oauth.provider">
<field name="name">Facebook Graph</field>
<field name="auth_endpoint">https://www.facebook.com/dialog/oauth</field>
- <field name="scope"></field>
- <field name="validation_endpoint">https://graph.facebook.com/me/permissions</field>
+ <field name="scope">public_profile,email</field>
+ <field name="validation_endpoint">https://graph.facebook.com/me</field>
<field name="data_endpoint"></field>
<field name="css_class">fa fa-facebook-square</field>
<field name="body">Log in with facebook</field>
diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py
index f2c6be389f1..cada7d51a26 100644
--- a/addons/auth_oauth/res_users.py
+++ b/addons/auth_oauth/res_users.py
@@ -101,7 +101,12 @@ class res_users(osv.Model):
validation = self._auth_oauth_validate(cr, uid, provider, access_token)
# required check
if not validation.get('user_id'):
- raise openerp.exceptions.AccessDenied()
+ # Workaround: facebook does not send 'user_id' in Open Graph Api
+ if validation.get('id'):
+ validation['user_id'] = validation['id']
+ else:
+ raise openerp.exceptions.AccessDenied()
+
# retrieve and sign in user
login = self._auth_oauth_signin(cr, uid, provider, validation, params, context=context)
if not login:
diff --git a/addons/hr_evaluation/hr_evaluation_view.xml b/addons/hr_evaluation/hr_evaluation_view.xml
index a80241a15dd..c737cc29039 100644
--- a/addons/hr_evaluation/hr_evaluation_view.xml
+++ b/addons/hr_evaluation/hr_evaluation_view.xml
@@ -342,7 +342,6 @@
<filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
<group expand="0" string="Group By">
<filter string="Interviewer" domain="[]" context="{'group_by':'user_id'}"/>
- <filter string="Survey" domain="[]" context="{'group_by':'survey_id'}"/>
<filter string="Status" name="group_state" domain="[]" context="{'group_by':'state'}"/>
</group>
</search>
diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml
index 4272b471656..43d861d026f 100644
--- a/addons/hr_holidays/hr_holidays_view.xml
+++ b/addons/hr_holidays/hr_holidays_view.xml
@@ -1,3 +1,4 @@
+<?xml version='1.0' encoding='UTF-8' ?>
<openerp>
<data>
@@ -24,6 +25,8 @@
<filter domain="[('state','in',('confirm','validate1'))]" string="To Approve" name="approve"/>
<filter string="Approved Leaves" domain="[('state', '=', 'validate')]" name="validated"/>
<separator/>
+ <filter name="active_types" string="Active Types" domain="[('holiday_status_id.active', '=', True)]" help="Filters only on allocations and requests that belong to an holiday type that is 'active' (active field is True)"/>
+ <separator/>
<filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="My Department Leaves" domain="[('department_id.manager_id.user_id', '=', uid)]" help="My Department Leaves"/>
@@ -31,9 +34,17 @@
<separator/>
<filter string="To Report in Payslip" name="gray" domain="[('payslip_status', '=', False)]" groups="base.group_hr_manager"/>
<separator/>
- <filter name="year" string="Year" domain="[('holiday_status_id.active','=',True)]" help="Filters only on allocations and requests that belong to an holiday type that is 'active' (active field is True)"/>
<separator/>
<filter string="To Do" name="todo" domain="[('payslip_status', '=', False), ('holiday_status_id.active', '=', True)]"/>
+ <separator/>
+ <filter name="year" string="Year"
+ domain="['|','&amp;',('date_to', '&lt;=', time.strftime('%%Y-12-31')),
+ ('date_to', '&gt;=', time.strftime('%%Y-01-01')),
+ '&amp;',('date_from', '&lt;=', time.strftime('%%Y-12-31')),
+ ('date_from', '&gt;=', time.strftime('%%Y-01-01'))]"
+ help="Current Year Leaves"/>
+ <separator/>
+ <filter string="My Requests" name="my_leaves" domain="[('user_id', '=', uid)]" help="My Leave Requests"/>
<field name="employee_id"/>
<field name="department_id"/>
<field name="holiday_status_id"/>
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index 4be9e3a1de3..7dffc8c042b 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -210,6 +210,7 @@ class account_analytic_line(osv.osv):
def invoice_cost_create(self, cr, uid, ids, data=None, context=None):
invoice_obj = self.pool.get('account.invoice')
invoice_line_obj = self.pool.get('account.invoice.line')
+ analytic_line_obj = self.pool.get('account.analytic.line')
invoices = []
if context is None:
context = {}
@@ -279,13 +280,15 @@ class account_analytic_line(osv.osv):
analytic_line.to_invoice.id,
analytic_line.account_id,
analytic_line.journal_id.type)
+ # We want to retrieve the data in the partner language for the invoice creation
+ analytic_line = analytic_line_obj.browse(cr, uid , [line.id for line in analytic_line], context=invoice_context)
invoice_lines_grouping.setdefault(key, []).append(analytic_line)
# finally creates the invoice line
for (product_id, uom, user_id, factor_id, account, journal_type), lines_to_invoice in invoice_lines_grouping.items():
curr_invoice_line = self._prepare_cost_invoice_line(cr, uid, last_invoice,
product_id, uom, user_id, factor_id, account, lines_to_invoice,
- journal_type, data, context=context)
+ journal_type, data, context=invoice_context)
invoice_line_obj.create(cr, uid, curr_invoice_line, context=context)
self.write(cr, uid, [l.id for l in analytic_line_ids], {'invoice_id': last_invoice}, context=context)
diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py
index c57238bd659..083e5cd5557 100644
--- a/addons/hw_escpos/controllers/main.py
+++ b/addons/hw_escpos/controllers/main.py
@@ -40,6 +40,10 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
+# workaround https://bugs.launchpad.net/openobject-server/+bug/947231
+# related to http://bugs.python.org/issue7980
+from datetime import datetime
+datetime.strptime('2012-01-01', '%Y-%m-%d')
class EscposDriver(Thread):
def __init__(self):
@@ -146,7 +150,7 @@ class EscposDriver(Thread):
_logger.warning('ESC/POS Device Disconnected: '+message)
def run(self):
-
+ printer = None
if not escpos:
_logger.error('ESC/POS cannot initialize, please verify system dependencies.')
return
@@ -192,7 +196,7 @@ class EscposDriver(Thread):
errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n'
_logger.error(errmsg);
finally:
- if error:
+ if error:
self.queue.put((timestamp, task, data))
if printer:
printer.close()
diff --git a/addons/hw_posbox_homepage/controllers/main.py b/addons/hw_posbox_homepage/controllers/main.py
index 25e33c409fa..a021986bee7 100644
--- a/addons/hw_posbox_homepage/controllers/main.py
+++ b/addons/hw_posbox_homepage/controllers/main.py
@@ -36,16 +36,16 @@ index_template = """
</p>
<p>
For more information on how to setup the Point of Sale with
- the PosBox, please refer to <a href='/hw_proxy/static/doc/manual.pdf'>the manual</a>
+ the PosBox, please refer to <a href='/hw_proxy/static/doc/manual.pdf'>the manual</a>.
</p>
<p>
To see the status of the connected hardware, please refer
- to the <a href='/hw_proxy/status'>hardware status page</a>
+ to the <a href='/hw_proxy/status'>hardware status page</a>.
</p>
<p>
- The PosBox software installed on this posbox is <b>version 6</b>,
+ The PosBox software installed on this posbox is <b>version 13</b>,
the posbox version number is independent from Odoo. You can upgrade
- the software on the <a href='/hw_proxy/upgrade/'>upgrade page</a>
+ the software on the <a href='/hw_proxy/upgrade/'>upgrade page</a>.
</p>
<p>For any other question, please contact the Odoo support at <a href='mailto:support@odoo.com'>support@odoo.com</a>
</p>
diff --git a/addons/hw_posbox_upgrade/controllers/main.py b/addons/hw_posbox_upgrade/controllers/main.py
index 0947fbc8564..5ed958d020e 100644
--- a/addons/hw_posbox_upgrade/controllers/main.py
+++ b/addons/hw_posbox_upgrade/controllers/main.py
@@ -29,17 +29,8 @@ upgrade_template = """
$.ajax({
url:'/hw_proxy/perform_upgrade/'
}).then(function(status){
- $('#upgrade').html('Upgrade Successful<br \\>Click to Restart the PosBox');
+ $('#upgrade').html('Upgrade successful, restarting the posbox...');
$('#upgrade').off('click');
- $('#upgrade').click(function(){
- $.ajax({ url:'/hw_proxy/perform_restart' })
- $('#upgrade').text('Restarting');
- $('#upgrade').off('click');
- setTimeout(function(){
- window.location = '/'
- },30*1000);
- });
-
},function(){
$('#upgrade').text('Upgrade Failed');
});
@@ -73,9 +64,11 @@ upgrade_template = """
<body>
<h1>PosBox Software Upgrade</h1>
<p>
- This tool will help you perform an upgrade of the PosBox's software.
+ This tool will help you perform an upgrade of the PosBox's software over the
+ internet.
+ <p></p>
However the preferred method to upgrade the posbox is to flash the sd-card with
- the <a href='http://nightly.openerp.com/trunk/posbox/'>latest image</a>. The upgrade
+ the <a href='http://nightly.odoo.com/trunk/posbox/'>latest image</a>. The upgrade
procedure is explained into to the <a href='/hw_proxy/static/doc/manual.pdf'>PosBox manual</a>
</p>
<p>
@@ -93,7 +86,6 @@ class PosboxUpgrader(hw_proxy.Proxy):
def __init__(self):
super(PosboxUpgrader,self).__init__()
self.upgrading = threading.Lock()
- self.last_upgrade = 0
@http.route('/hw_proxy/upgrade', type='http', auth='none', )
def upgrade(self):
@@ -102,25 +94,8 @@ class PosboxUpgrader(hw_proxy.Proxy):
@http.route('/hw_proxy/perform_upgrade', type='http', auth='none')
def perform_upgrade(self):
self.upgrading.acquire()
- if time.time() - self.last_upgrade < 30:
- self.upgrading.release()
- return 'UPTODATE'
- else:
- os.system('/bin/bash /home/pi/openerp/update.sh')
- self.last_upgrade = time.time()
- self.upgrading.release()
- return 'SUCCESS'
-
- @http.route('/hw_proxy/perform_restart', type='http', auth='none')
- def perform_restart(self):
- self.upgrading.acquire()
- if time.time() - self.last_upgrade < 30:
- self.upgrading.release()
- return 'RESTARTED'
- else:
- os.system('/bin/bash /home/pi/openerp/restart.sh')
- self.last_upgrade = time.time()
- self.upgrading.release()
- return 'SUCCESS'
+ os.system('/home/pi/posbox_update.sh')
+ self.upgrading.release()
+ return 'SUCCESS'
diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py
index 337874cf67a..eb95527efcd 100644
--- a/addons/hw_proxy/controllers/main.py
+++ b/addons/hw_proxy/controllers/main.py
@@ -17,6 +17,14 @@ _logger = logging.getLogger(__name__)
from openerp import http
from openerp.http import request
+# Those are the builtin raspberry pi USB modules, they should
+# not appear in the list of connected devices.
+BANNED_DEVICES = set([
+ "0424:9514", # Standard Microsystem Corp. Builtin Ethernet module
+ "1d6b:0002", # Linux Foundation 2.0 root hub
+ "0424:ec00", # Standard Microsystem Corp. Other Builtin Ethernet module
+])
+
# drivers modules must add to drivers an object with a get_status() method
# so that 'status' can return the status of all active drivers
@@ -88,10 +96,18 @@ class Proxy(http.Controller):
<p>The list of connected USB devices as seen by the posbox</p>
"""
devices = commands.getoutput("lsusb").split('\n')
+ count = 0
resp += "<div class='devices'>\n"
for device in devices:
device_name = device[device.find('ID')+2:]
- resp+= "<div class='device' data-device='"+device+"'>"+device_name+"</div>\n"
+ device_id = device_name.split()[0]
+ if not (device_id in BANNED_DEVICES):
+ resp+= "<div class='device' data-device='"+device+"'>"+device_name+"</div>\n"
+ count += 1
+
+ if count == 0:
+ resp += "<div class='device'>No USB Device Found</div>"
+
resp += "</div>\n"
resp += """
<h2>Add New Printer</h2>
diff --git a/addons/hw_scale/controllers/main.py b/addons/hw_scale/controllers/main.py
index 44a031f5566..2f9e0271dd5 100644
--- a/addons/hw_scale/controllers/main.py
+++ b/addons/hw_scale/controllers/main.py
@@ -44,6 +44,11 @@ class Scale(Thread):
if status == self.status['status']:
if message != None and message != self.status['messages'][-1]:
self.status['messages'].append(message)
+
+ if status == 'error' and message:
+ _logger.error('Scale Error: '+message)
+ elif status == 'disconnected' and message:
+ _logger.warning('Disconnected Scale: '+message)
else:
self.status['status'] = status
if message:
@@ -51,13 +56,16 @@ class Scale(Thread):
else:
self.status['messages'] = []
- if status == 'error' and message:
- _logger.error('Scale Error: '+message)
- elif status == 'disconnected' and message:
- _logger.info('Disconnected Scale: %s', message)
+ if status == 'error' and message:
+ _logger.error('Scale Error: '+message)
+ elif status == 'disconnected' and message:
+ _logger.info('Disconnected Scale: %s', message)
def get_device(self):
try:
+ if not os.path.exists(self.input_dir):
+ self.set_status('disconnected','Scale Not Found')
+ return None
devices = [ device for device in listdir(self.input_dir)]
scales = [ device for device in devices if ('mettler' in device.lower()) or ('toledo' in device.lower()) ]
if len(scales) > 0:
@@ -69,8 +77,8 @@ class Scale(Thread):
stopbits = serial.STOPBITS_ONE,
parity = serial.PARITY_EVEN,
#xonxoff = serial.XON,
- timeout = 0.01,
- writeTimeout= 0.01)
+ timeout = 0.02,
+ writeTimeout= 0.02)
else:
self.set_status('disconnected','Scale Not Found')
return None
@@ -95,7 +103,7 @@ class Scale(Thread):
if self.device:
try:
self.device.write('W')
- time.sleep(0.1)
+ time.sleep(0.2)
answer = []
while True:
@@ -171,7 +179,7 @@ class Scale(Thread):
while True:
if self.device:
self.read_weight()
- time.sleep(0.05)
+ time.sleep(0.15)
else:
with self.scalelock:
self.device = self.get_device()
diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py
index 6e53c37006f..8e5e8f6af18 100644
--- a/addons/hw_scanner/controllers/main.py
+++ b/addons/hw_scanner/controllers/main.py
@@ -111,6 +111,8 @@ class Scanner(Thread):
def get_device(self):
try:
+ if not evdev:
+ return None
devices = [ device for device in listdir(self.input_dir)]
keyboards = [ device for device in devices if ('kbd' in device) and ('keyboard' not in device.lower())]
scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())]
@@ -133,6 +135,7 @@ class Scanner(Thread):
been returned before. This is necessary to catch barcodes scanned while the POS is
busy reading another barcode
"""
+
self.lockedstart()
while True:
@@ -164,10 +167,13 @@ class Scanner(Thread):
try:
device.ungrab()
except Exception as e:
+ device = None
self.set_status('error',str(e))
else:
time.sleep(5) # wait until a suitable device is plugged
device = self.get_device()
+ if not device:
+ continue
try:
device.grab()
diff --git a/addons/l10n_cn/account_chart_small_business_template.xml b/addons/l10n_cn/account_chart_small_business_template.xml
index 43e0431d20c..0d6abf5c222 100644
--- a/addons/l10n_cn/account_chart_small_business_template.xml
+++ b/addons/l10n_cn/account_chart_small_business_template.xml
@@ -4,7 +4,7 @@
<!--
Copyright (C) 2012-2012 南京盈通 ccdos@intoerp.com <small business chart>
-
+
会计科目表模板( 小企业会计准则2011)
科目表依据:
@@ -13,19 +13,24 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
-->
<data noupdate="True">
-
+
<!-- 科目表模板-->
<record id="cn_small_business_root" model="account.account.template">
- <field name="name">会计科目</field>
+ <field name="name">2011会计科目</field>
<field name="code">0</field>
<field name="user_type_id" ref="user_type_all"/>
</record>
<!-- 资产类 -->
+ <record id="cn_small_business_asset" model="account.account.template">
+ <field name="name">资产类</field>
+ <field name="code">1</field>
+ <field name="user_type_id" ref="user_type_all"/>
+ </record>
<record id="small_business_chart1001" model="account.account.template">
<field name="code">1001</field>
<field eval="False" name="reconcile"/>
- <field name="name">现金</field>
+ <field name="name">库存现金</field>
<field name="user_type_id" ref="user_type_bs1"/>
</record>
<record id="small_business_chart1002" model="account.account.template">
@@ -64,7 +69,7 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="code">1123</field>
<field eval="True" name="reconcile"/>
<field name="name">预付账款</field>
- <field name="user_type_id" ref="user_type_bs6"/>
+ <field name="user_type_id" ref="user_type_bs8"/>
</record>
<record id="small_business_chart1131" model="account.account.template">
<field name="code">1131</field>
@@ -187,21 +192,21 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="code">1606</field>
<field eval="False" name="reconcile"/>
<field name="name">固定资产清理</field>
- <field name="user_type_id" ref="user_type_bs39"/>
+ <field name="user_type_id" ref="user_type_bs46"/>
</record>
<record id="small_business_chart1621" model="account.account.template">
<field name="code">1621</field>
<field eval="False" name="reconcile"/>
<field name="name">生产性生物资产</field>
- <field name="user_type_id" ref="user_type_bs39"/>
+ <field name="user_type_id" ref="user_type_bs47"/>
</record>
- <record id="small_business_chart1602" model="account.account.template">
+ <record id="small_business_chart1622" model="account.account.template">
<field name="code">1622</field>
<field eval="False" name="reconcile"/>
<field name="name">生产性生物资产累计折旧</field>
- <field name="user_type_id" ref="user_type_bs40"/>
+ <field name="user_type_id" ref="user_type_bs47"/>
</record>
<record id="small_business_chart1701" model="account.account.template">
@@ -230,11 +235,16 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="code">1901</field>
<field eval="False" name="reconcile"/>
<field name="name">待处理财产损溢</field>
- <field name="user_type_id" ref="user_type_bs44"/>
+ <field name="user_type_id" ref="user_type_bs53"/>
</record>
-
-
+
+
<!-- 二、负债类 -->
+ <record id="cn_small_business_liability" model="account.account.template">
+ <field name="name">负债类</field>
+ <field name="code">2</field>
+ <field name="user_type_id" ref="user_type_all"/>
+ </record>
<record id="small_business_chart2001" model="account.account.template">
<field name="code">2001</field>
<field eval="False" name="reconcile"/>
@@ -277,27 +287,27 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="code">2231</field>
<field eval="False" name="reconcile"/>
<field name="name">应付利息</field>
- <field name="user_type_id" ref="user_type_bs74"/>
+ <field name="user_type_id" ref="user_type_bs174"/>
</record>
<record id="small_business_chart2232" model="account.account.template">
<field name="code">2232</field>
<field eval="False" name="reconcile"/>
<field name="name">应付利润</field>
- <field name="user_type_id" ref="user_type_bs74"/>
+ <field name="user_type_id" ref="user_type_bs175"/>
</record>
<record id="small_business_chart2241" model="account.account.template">
<field name="code">2241</field>
<field eval="True" name="reconcile"/>
<field name="name">其他应付款</field>
- <field name="user_type_id" ref="user_type_bs70"/>
+ <field name="user_type_id" ref="user_type_bs73"/>
</record>
<record id="small_business_chart2401" model="account.account.template">
<field name="code">2401</field>
<field eval="False" name="reconcile"/>
<field name="name">递延收益</field>
- <field name="user_type_id" ref="user_type_all"/>
+ <field name="user_type_id" ref="user_type_bs104"/>
</record>
<record id="small_business_chart2501" model="account.account.template">
<field name="code">2501</field>
@@ -315,6 +325,11 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<!-- 所有者权益类 -->
+ <record id="cn_small_business_right" model="account.account.template">
+ <field name="name">所有者权益类</field>
+ <field name="code">3</field>
+ <field name="user_type_id" ref="user_type_all"/>
+ </record>
<record id="small_business_chart3001" model="account.account.template">
<field name="code">3001</field>
<field eval="False" name="reconcile"/>
@@ -337,7 +352,7 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="code">3103</field>
<field eval="False" name="reconcile"/>
<field name="name">本年利润</field>
- <field name="user_type_id" ref="user_type_bs121"/>
+ <field name="user_type_id" ref="user_type_bs120"/>
</record>
<record id="small_business_chart3104" model="account.account.template">
<field name="code">3104</field>
@@ -347,42 +362,52 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
</record>
<!-- 成本类 -->
+ <record id="cn_small_business_cost" model="account.account.template">
+ <field name="name">成本类</field>
+ <field name="code">4</field>
+ <field name="user_type_id" ref="user_type_all"/>
+ </record>
<record id="small_business_chart4001" model="account.account.template">
<field name="code">4001</field>
<field eval="False" name="reconcile"/>
<field name="name">生产成本</field>
- <field name="user_type_id" ref="user_type_all"/>
+ <field name="user_type_id" ref="user_type_cost2"/>
</record>
<record id="small_business_chart4101" model="account.account.template">
<field name="code">4101</field>
<field eval="False" name="reconcile"/>
<field name="name">制造费用</field>
- <field name="user_type_id" ref="user_type_all"/>
+ <field name="user_type_id" ref="user_type_cost2"/>
</record>
<record id="small_business_chart4301" model="account.account.template">
<field name="code">4301</field>
<field eval="False" name="reconcile"/>
<field name="name">研发支出</field>
- <field name="user_type_id" ref="user_type_all"/>
+ <field name="user_type_id" ref="user_type_cost3"/>
</record>
<record id="small_business_chart4401" model="account.account.template">
<field name="code">4401</field>
<field eval="False" name="reconcile"/>
<field name="name">工程施工</field>
- <field name="user_type_id" ref="user_type_all"/>
+ <field name="user_type_id" ref="user_type_cost2"/>
</record>
<record id="small_business_chart4403" model="account.account.template">
<field name="code">4403</field>
<field eval="False" name="reconcile"/>
<field name="name">机械作业</field>
- <field name="user_type_id" ref="user_type_all"/>
+ <field name="user_type_id" ref="user_type_cost2"/>
</record>
- <!-- 损益类 -->
+ <!-- 损益类 -->
+ <record id="cn_small_business_gal" model="account.account.template">
+ <field name="name">损益类</field>
+ <field name="code">5</field>
+ <field name="user_type_id" ref="user_type_all"/>
+ </record>
<record id="small_business_chart5001" model="account.account.template">
<field name="code">5001</field>
<field eval="False" name="reconcile"/>
@@ -393,7 +418,7 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<field name="code">5051</field>
<field eval="False" name="reconcile"/>
<field name="name">其他业务收入</field>
- <field name="user_type_id" ref="user_type_pl23"/>
+ <field name="user_type_id" ref="user_type_pl1"/>
</record>
<record id="small_business_chart5111" model="account.account.template">
@@ -456,7 +481,7 @@ http://kjs.mof.gov.cn/zhengwuxinxi/zhengcefabu/201111/t20111107_605525.html
<record id="small_business_chart5801" model="account.account.template">
<field name="code">5801</field>
<field eval="False" name="reconcile"/>
- <field name="name">所得税</field>
+ <field name="name">所得税费用</field>
<field name="user_type_id" ref="user_type_pl29"/>
</record>
diff --git a/addons/l10n_cn/account_chart_template.xml b/addons/l10n_cn/account_chart_template.xml
index b061bd311ee..eeac49a3bdd 100644
--- a/addons/l10n_cn/account_chart_template.xml
+++ b/addons/l10n_cn/account_chart_template.xml
@@ -21,7 +21,7 @@
<record id="chart1001" model="account.account.template">
<field name="code">1001</field>
<field eval="False" name="reconcile"/>
- <field name="name">现金</field>
+ <field name="name">库存现金</field>
<field name="user_type_id" ref="user_type_bs1"/>
</record>
<record id="chart1002" model="account.account.template">
@@ -432,6 +432,12 @@
<field name="name">应付利息</field>
<field name="user_type_id" ref="user_type_bs74"/>
</record>
+ <record id="chart2241" model="account.account.template">
+ <field name="code">2241</field>
+ <field eval="True" name="reconcile"/>
+ <field name="name">其他应付款</field>
+ <field name="user_typed_id" ref="user_type_bs73"/>
+ </record>
<record id="chart2401" model="account.account.template">
<field name="code">2401</field>
<field eval="True" name="reconcile"/>
diff --git a/addons/l10n_cn/account_chart_type.xml b/addons/l10n_cn/account_chart_type.xml
index 49900d84f7b..ae694220c73 100644
--- a/addons/l10n_cn/account_chart_type.xml
+++ b/addons/l10n_cn/account_chart_type.xml
@@ -15,7 +15,7 @@
</record>
<!-- 利润表科目 -->
<record id="user_type_pl1" model="account.account.type">
- <field name="name">一、主营业务收入</field>
+ <field name="name">一、营业收入</field>
<field name="report_type">income</field>
</record>
<record id="user_type_pl21" model="account.account.type">
@@ -23,7 +23,7 @@
<field name="report_type">income</field>
</record>
<record id="user_type_pl3" model="account.account.type">
- <field name="name">减:主营业务成本</field>
+ <field name="name">减:营业成本</field>
<field name="report_type">expense</field>
</record>
<record id="user_type_pl5" model="account.account.type">
@@ -35,7 +35,7 @@
<field name="report_type">income</field>
</record>
<record id="user_type_pl11" model="account.account.type">
- <field name="name">减:营业费用</field>
+ <field name="name"> 销售费用</field>
<field name="report_type">expense</field>
</record>
<record id="user_type_pl13" model="account.account.type">
@@ -87,6 +87,10 @@
<field name="name">应收账款</field>
<field name="report_type">asset</field>
</record>
+ <record id="user_type_bs8" model="account.account.type">
+ <field name="name">预付账款</field>
+ <field name="report_type">asset</field>
+ </record>
<record id="user_type_bs7" model="account.account.type">
<field name="name">其他应收款</field>
<field name="report_type">asset</field>
@@ -135,6 +139,10 @@
<field name="name">固定资产清理</field>
<field name="report_type">asset</field>
</record>
+ <record id="user_type_bs47" model="account.account.type">
+ <field name="name">生产性生物资产</field>
+ <field name="report_type">asset</field>
+ </record>
<record id="user_type_bs51" model="account.account.type">
<field name="name">无形资产</field>
<field name="report_type">asset</field>
@@ -143,6 +151,10 @@
<field name="name">长期待摊费用</field>
<field name="report_type">asset</field>
</record>
+ <record id="user_type_bs53" model="account.account.type">
+ <field name="name">待处理财产损溢-无结转</field>
+ <field name="report_type">asset</field>
+ </record>
<record id="user_type_bs61" model="account.account.type">
<field name="name">递延税款借项</field>
<field name="report_type">asset</field>
@@ -159,12 +171,25 @@
<field name="name">应付账款</field>
<field name="report_type">liability</field>
</record>
+ <record id="user_type_bs73" model="account.account.type">
+ <field name="name">其他应付款</field>
+ <field name="report_type">liability</field>
+ </record>
<record id="user_type_bs71" model="account.account.type">
<field name="name">预收账款</field>
<field name="report_type">liability</field>
</record>
<record id="user_type_bs72" model="account.account.type">
- <field name="name">应付工资</field>
+ <field name="name">应付职工薪酬</field>
+ <field name="report_type">liability</field>
+ </record>
+ <record id="user_type_bs174" model="account.account.type">
+ <field name="name">应付利息</field>
+ <field name="report_type">liability</field>
+ </record>
+ <record id="user_type_bs175" model="account.account.type">
+ <field name="name">应付利润</field>
+
<field name="report_type">liability</field>
</record>
<record id="user_type_bs74" model="account.account.type">
@@ -191,6 +216,11 @@
<field name="name">长期应付款</field>
<field name="report_type">liability</field>
</record>
+ <record id="user_type_bs104" model="account.account.type">
+ <field name="name">递延收益</field>
+ <field name="report_type">liability</field>
+ </record>
+
<record id="user_type_bs115" model="account.account.type">
<field name="name">实收资本</field>
<field name="report_type">liability</field>
@@ -203,9 +233,26 @@
<field name="name">盈余公积</field>
<field name="report_type">liability</field>
</record>
+ <record id="user_type_bs120" model="account.account.type">
+ <field name="name">本年利润</field>
+
+ <field name="report_type">liability</field>
+ </record>
<record id="user_type_bs121" model="account.account.type">
<field name="name">未分配利润</field>
<field name="report_type">liability</field>
</record>
+ <record id="user_type_cost1" model="account.account.type">
+ <field name="name">成本类-余额结转</field>
+ <field name="report_type">none</field>
+ </record>
+ <record id="user_type_cost2" model="account.account.type">
+ <field name="name">成本-无结转</field>
+ <field name="report_type">none</field>
+ </record>
+ <record id="user_type_cost3" model="account.account.type">
+ <field name="name">开发支出</field>
+ <field name="report_type">none</field>
+ </record>
</data>
</openerp>
diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py
index 8ac5dea8113..0e8e94c94c2 100644
--- a/addons/mass_mailing/models/mail_mail.py
+++ b/addons/mass_mailing/models/mail_mail.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import urllib
import urlparse
import re
+import werkzeug.urls
from openerp import tools
from openerp import SUPERUSER_ID
@@ -41,7 +41,7 @@ class MailMail(osv.Model):
track_url = urlparse.urljoin(
base_url, 'mail/track/%(mail_id)s/blank.gif?%(params)s' % {
'mail_id': mail.id,
- 'params': urllib.urlencode({'db': cr.dbname})
+ 'params': werkzeug.url_encode({'db': cr.dbname})
}
)
return '<img src="%s" alt=""/>' % track_url
@@ -51,7 +51,7 @@ class MailMail(osv.Model):
url = urlparse.urljoin(
base_url, 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
'mailing_id': mail.mailing_id.id,
- 'params': urllib.urlencode({'db': cr.dbname, 'res_id': mail.res_id, 'email': email_to})
+ 'params': werkzeug.url_encode({'db': cr.dbname, 'res_id': mail.res_id, 'email': email_to})
}
)
return url
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index 443abcb123c..89ac9cb0244 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -52,8 +52,8 @@ class MassMailingList(osv.Model):
}
def _get_default_popup_content(self, cr, uid, context=None):
- return """<div class="o_popup_modal_header text-center">
- <h3 class="o_popup_modal_title mt8">Odoo Presents</h3>
+ return """<div class="modal-header text-center">
+ <h3 class="modal-title mt8">Odoo Presents</h3>
</div>
<div class="o_popup_message">
<font>7</font>
diff --git a/addons/mass_mailing/views/editor_field_html.xml b/addons/mass_mailing/views/editor_field_html.xml
index de7c8316062..4e28ab8647a 100644
--- a/addons/mass_mailing/views/editor_field_html.xml
+++ b/addons/mass_mailing/views/editor_field_html.xml
@@ -69,8 +69,8 @@
<div class="o_editable" t-att-data-oe-model="model" t-att-data-oe-field="field" t-att-data-oe-id="res_id" data-oe-type="html">
<!-- Can be removed once default values are ok for the editor -->
<t t-raw="content">
- <div class="o_popup_modal_header text-center">
- <h3 class="o_popup_modal_title mt8">Odoo Presents</h3>
+ <div class="modal-header text-center">
+ <h3 class="modal-title mt8">Odoo Presents</h3>
</div>
<div class="o_popup_message">
<font>7</font>
diff --git a/addons/mrp_repair/wizard/make_invoice.py b/addons/mrp_repair/wizard/make_invoice.py
index 4abfcb9c2d8..72241c4fa72 100644
--- a/addons/mrp_repair/wizard/make_invoice.py
+++ b/addons/mrp_repair/wizard/make_invoice.py
@@ -36,7 +36,7 @@ class make_invoice(osv.osv_memory):
form_res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
form_id = form_res and form_res[1] or False
tree_res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_tree')
- tree_id = tree_res and tree_res[1] or Fals
+ tree_id = tree_res and tree_res[1] or False
return {
'domain': [('id','in', newinv.values())],
diff --git a/addons/point_of_sale/tools/posbox/.gitignore b/addons/point_of_sale/tools/posbox/.gitignore
new file mode 100644
index 00000000000..b26eafa34d1
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/.gitignore
@@ -0,0 +1,3 @@
+kernel-qemu
+posbox.img
+raspbian.img \ No newline at end of file
diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/fstab b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/fstab
new file mode 100644
index 00000000000..28898fc8ea4
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/fstab
@@ -0,0 +1,4 @@
+proc /proc proc defaults 0 0
+/dev/mmcblk0p1 /boot vfat defaults 0 2
+/dev/mmcblk0p2 / ext4 defaults,noatime,ro 0 1
+# a swapfile is not a swap partition, so no using swapon|off from here on, use dphys-swapfile swap[on|off] for that
diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS
new file mode 100755
index 00000000000..6380e949834
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/init.d/rcS
@@ -0,0 +1,10 @@
+#! /bin/sh
+#
+# rcS
+#
+# Call all S??* scripts in /etc/rcS.d/ in numerical/alphabetical order
+#
+
+/etc/setup_ramdisks.sh
+
+exec /etc/init.d/rc S
diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/network/interfaces b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/network/interfaces
new file mode 100644
index 00000000000..95b6f07524a
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/network/interfaces
@@ -0,0 +1,3 @@
+# needed for postgresql
+auto lo
+iface lo inet loopback \ No newline at end of file
diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local
new file mode 100755
index 00000000000..065b6a05f77
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/rc.local
@@ -0,0 +1,25 @@
+#!/bin/sh -e
+#
+# rc.local
+#
+# This script is executed at the end of each multiuser runlevel.
+# Make sure that the script will "exit 0" on success or any other
+# value on error.
+#
+# In order to enable or disable this script just change the execution
+# bits.
+#
+# By default this script does nothing.
+
+# Print the IP address
+_IP=$(hostname -I) || true
+if [ "$_IP" ]; then
+ printf "My IP address is %s\n" "$_IP"
+fi
+
+mkdir -p /var/run/odoo
+chown pi:pi /var/run/odoo
+
+/home/pi/led_status.sh &
+
+exit 0
diff --git a/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/setup_ramdisks.sh b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/setup_ramdisks.sh
new file mode 100755
index 00000000000..47e11d1bdd3
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_after_init/etc/setup_ramdisks.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -o errexit
+set -o nounset
+set -o pipefail
+# set -o xtrace
+
+create_ramdisk () {
+ ORIGINAL="${1}"
+ RAMDISK="${ORIGINAL}_ram"
+ SIZE="${2}"
+ echo "Creating ramdisk for ${1} of size ${SIZE}..."
+
+ mount -t tmpfs -o size="${SIZE}" tmpfs "${RAMDISK}"
+ rsync -a --exclude="swap" --exclude="apt" --exclude="dpkg" "${ORIGINAL}/" "${RAMDISK}/"
+ mount --bind "${RAMDISK}" "${ORIGINAL}"
+}
+
+# check /proc/cmdline
+
+# bind mount / so that we can get to the real /var and /etc
+mount --bind / /root_bypass_ramdisks
+
+echo "Creating ramdisks..."
+create_ramdisk "/var" "128M"
+create_ramdisk "/etc" "16M"
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
new file mode 100755
index 00000000000..9bdaa86c584
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
@@ -0,0 +1,72 @@
+#!/bin/bash
+### BEGIN INIT INFO
+# Provides: odoo.py
+# Required-Start: $remote_fs $syslog postgresql
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Start odoo daemon at boot time
+# Description: Enable service provided by daemon.
+# X-Interactive: true
+### END INIT INFO
+## more info: http://wiki.debian.org/LSBInitScripts
+
+. /lib/lsb/init-functions
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
+DAEMON=/home/pi/odoo/odoo.py
+NAME=odoo
+DESC=odoo
+CONFIG=/home/pi/odoo.conf
+LOGFILE=/var/log/odoo/odoo-server.log
+PIDFILE=/var/run/${NAME}.pid
+USER=pi
+
+test -x $DAEMON || exit 0
+set -e
+
+function _start() {
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos
+}
+
+function _stop() {
+ start-stop-daemon --stop --quiet --pidfile $PIDFILE --oknodo --retry 3
+ rm -f $PIDFILE
+}
+
+function _status() {
+ start-stop-daemon --status --quiet --pidfile $PIDFILE
+ return $?
+}
+
+
+case "$1" in
+ start)
+ echo -n "Starting $DESC: "
+ _start
+ echo "ok"
+ ;;
+ stop)
+ echo -n "Stopping $DESC: "
+ _stop
+ echo "ok"
+ ;;
+ restart|force-reload)
+ echo -n "Restarting $DESC: "
+ _stop
+ sleep 1
+ _start
+ echo "ok"
+ ;;
+ status)
+ echo -n "Status of $DESC: "
+ _status && echo "running" || echo "stopped"
+ ;;
+ *)
+ N=/etc/init.d/$NAME
+ echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
new file mode 100755
index 00000000000..68b64fb94ca
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+set -o errexit
+set -o nounset
+set -o pipefail
+# set -o xtrace
+
+__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
+__base="$(basename ${__file} .sh)"
+
+# Recommends: antiword, graphviz, ghostscript, postgresql, python-gevent, poppler-utils
+export DEBIAN_FRONTEND=noninteractive
+
+# GUI-related packages
+PKGS_TO_DELETE="xserver-xorg-video-fbdev xserver-xorg xinit gstreamer1.0-x gstreamer1.0-omx gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-alsa gstreamer1.0-libav epiphany-browser lxde lxtask menu-xdg gksu xserver-xorg-video-fbturbo xpdf gtk2-engines alsa-utils netsurf-gtk zenity desktop-base lxpolkit weston omxplayer raspberrypi-artwork lightdm gnome-themes-standard-data gnome-icon-theme qt50-snapshot qt50-quick-particle-examples idle python-pygame python-tk idle3 python-serial python-picamera debian-reference-en dillo x2x scratch nuscratch raspberrypi-ui-mods timidity smartsim penguinspuzzle pistore sonic-pi python-pifacecommon python-pifacedigitalio oracle-java8-jdk minecraft-pi python-minecraftpi wolfram-engine raspi-config libgl1-mesa-dri libicu48 pypy-upstream lxde-icon-theme python3"
+INSTALLED_PKGS_TO_DELETE=""
+set +o errexit
+for CURRENT_PKG in $(echo $PKGS_TO_DELETE); do
+ $(dpkg --status $CURRENT_PKG &> /dev/null)
+ if [[ $? -eq 0 ]]; then
+ INSTALLED_PKGS_TO_DELETE="$INSTALLED_PKGS_TO_DELETE $CURRENT_PKG"
+ fi
+done
+set -o errexit
+
+apt-get -y remove --purge ${INSTALLED_PKGS_TO_DELETE}
+
+# Remove automatically installed dependency packages
+apt-get -y autoremove
+
+apt-get update
+apt-get -y dist-upgrade
+
+PKGS_TO_INSTALL="adduser postgresql-client python python-dateutil python-decorator python-docutils python-feedparser python-imaging python-jinja2 python-ldap python-libxslt1 python-lxml python-mako python-mock python-openid python-passlib python-psutil python-psycopg2 python-pybabel python-pychart python-pydot python-pyparsing python-pypdf python-reportlab python-requests python-simplejson python-tz python-unittest2 python-vatnumber python-vobject python-werkzeug python-xlwt python-yaml postgresql python-gevent python-serial python-pip python-dev localepurge vim mc mg"
+
+apt-get -y install ${PKGS_TO_INSTALL}
+
+apt-get clean
+localepurge
+rm -rf /usr/share/doc || true
+rm -rf /home/pi/python_games || true
+
+# python-usb in wheezy is too old
+# the latest pyusb from pip does not work either, usb.core.find() never returns
+# this may be fixed with libusb>2:1.0.11-1, but that's the most recent one in raspbian
+# so we install the latest pyusb that works with this libusb
+pip install pyusb==1.0.0b1
+pip install qrcode
+pip install evdev
+
+usermod -a -G lp pi
+
+sudo -u postgres createuser -s pi
+mkdir /var/log/odoo
+chown pi:pi /var/log/odoo
+
+update-rc.d odoo defaults
+
+# create dirs for ramdisks
+create_ramdisk_dir () {
+ mkdir "${1}_ram"
+}
+
+create_ramdisk_dir "/var"
+create_ramdisk_dir "/etc"
+mkdir /root_bypass_ramdisks
+
+reboot
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/ld.so.preload b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/ld.so.preload
new file mode 100644
index 00000000000..cfa850b8356
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/ld.so.preload
@@ -0,0 +1 @@
+#/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.conf b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.conf
new file mode 100644
index 00000000000..1ac409b252e
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.conf
@@ -0,0 +1,32 @@
+# see "man logrotate" for details
+# rotate log files daily
+daily
+
+# keep 2 days worth of backlogs
+rotate 2
+
+# create new (empty) log files after rotating old ones
+create
+
+# uncomment this if you want your log files compressed
+#compress
+
+# packages drop log rotation information into this directory
+include /etc/logrotate.d
+
+# no packages own wtmp, or btmp -- we'll rotate them here
+/var/log/wtmp {
+ missingok
+ monthly
+ create 0664 root utmp
+ rotate 1
+}
+
+/var/log/btmp {
+ missingok
+ monthly
+ create 0660 root utmp
+ rotate 1
+}
+
+# system-specific logs may be configured here
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.d/odoo b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.d/odoo
new file mode 100644
index 00000000000..060ec54d012
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/logrotate.d/odoo
@@ -0,0 +1,5 @@
+/var/log/odoo/*.log {
+ copytruncate
+ missingok
+ notifempty
+}
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/rc.local b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/rc.local
new file mode 100755
index 00000000000..c55290c3978
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/rc.local
@@ -0,0 +1,22 @@
+#!/bin/sh -e
+#
+# rc.local
+#
+# This script is executed at the end of each multiuser runlevel.
+# Make sure that the script will "exit 0" on success or any other
+# value on error.
+#
+# In order to enable or disable this script just change the execution
+# bits.
+#
+# By default this script does nothing.
+
+# Print the IP address
+_IP=$(hostname -I) || true
+if [ "$_IP" ]; then
+ printf "My IP address is %s\n" "$_IP"
+fi
+
+/etc/init_posbox_image.sh
+
+exit 0
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/90-qemu.rules b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/90-qemu.rules
new file mode 100644
index 00000000000..fadedd206fe
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/udev/rules.d/90-qemu.rules
@@ -0,0 +1,3 @@
+KERNEL=="sda", SYMLINK+="mmcblk0"
+KERNEL=="sda?", SYMLINK+="mmcblk0p%n"
+KERNEL=="sda2", SYMLINK+="root" \ No newline at end of file
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/led_status.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/led_status.sh
new file mode 100755
index 00000000000..8d272d7de2b
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/led_status.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+set_brightness() {
+ echo "${1}" > /sys/class/leds/led0/brightness
+}
+
+check_status_loop() {
+ while true ; do
+ if wget --quiet localhost:8069/hw_proxy/hello -O /dev/null ; then
+ set_brightness 255
+ else
+ set_brightness 0
+ fi
+ sleep 5
+ done
+}
+
+echo none > /sys/class/leds/led0/trigger
+check_status_loop
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/odoo.conf b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/odoo.conf
new file mode 100644
index 00000000000..1bc0a0d3ba5
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/odoo.conf
@@ -0,0 +1,6 @@
+[options]
+data_dir = /var/run/odoo
+log_level = error
+logfile = /var/log/odoo/odoo-server.log
+pidfile = /var/run/odoo/odoo.pid
+server_wide_modules = web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos \ No newline at end of file
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/posbox_update.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/posbox_update.sh
new file mode 100755
index 00000000000..e994a60461f
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/home/pi/posbox_update.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+sudo mount -o remount,rw /
+sudo git --work-tree=/home/pi/odoo/ --git-dir=/home/pi/odoo/.git pull
+sudo mount -o remount,ro /
+(sleep 5 && sudo reboot) &
diff --git a/addons/point_of_sale/tools/posbox/posbox_create_image.sh b/addons/point_of_sale/tools/posbox/posbox_create_image.sh
new file mode 100755
index 00000000000..9e65410f8fe
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/posbox_create_image.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+set -o errexit
+set -o nounset
+set -o pipefail
+# set -o xtrace
+
+if [[ $EUID -ne 0 ]]; then
+ echo "This script must be run as root"
+ exit 1
+fi
+
+file_exists() {
+ [[ -f $1 ]];
+}
+
+__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
+__base="$(basename ${__file} .sh)"
+
+MOUNT_POINT="${__dir}/root_mount"
+OVERWRITE_FILES_BEFORE_INIT_DIR="${__dir}/overwrite_before_init"
+OVERWRITE_FILES_AFTER_INIT_DIR="${__dir}/overwrite_after_init"
+
+if [ ! -f kernel-qemu ] || ! file_exists *raspbian*.img ; then
+ ./posbox_download_images.sh
+fi
+
+cp -a *raspbian*.img posbox.img
+
+CLONE_DIR="${OVERWRITE_FILES_BEFORE_INIT_DIR}/home/pi/odoo"
+mkdir "${CLONE_DIR}"
+git clone -b 8.0 --no-checkout --depth 1 https://github.com/odoo/odoo.git "${CLONE_DIR}"
+cd "${CLONE_DIR}"
+git config core.sparsecheckout true
+echo "addons/web
+addons/web_kanban
+addons/hw_*
+openerp/
+odoo.py" | tee --append .git/info/sparse-checkout > /dev/null
+git read-tree -mu HEAD
+cd "${__dir}"
+
+# rc.local
+LOOP_MAPPER_PATH=$(kpartx -av posbox.img | tail -n 1 | cut -d ' ' -f 3)
+LOOP_MAPPER_PATH="/dev/mapper/${LOOP_MAPPER_PATH}"
+mkdir "${MOUNT_POINT}"
+mount "${LOOP_MAPPER_PATH}" "${MOUNT_POINT}"
+
+# 'overlay' the overwrite directory onto the mounted image filesystem
+cp -a "${OVERWRITE_FILES_BEFORE_INIT_DIR}"/* "${MOUNT_POINT}"
+
+# get rid of the git clone
+rm -rf "${CLONE_DIR}"
+
+# get rid of the mount, we have to remount it anyway because we have
+# to "refresh" the filesystem after qemu modified it
+sleep 2
+umount "${MOUNT_POINT}"
+
+# from http://paulscott.co.za/blog/full-raspberry-pi-raspbian-emulation-with-qemu/
+# ssh pi@localhost -p10022
+QEMU_OPTS=(-kernel kernel-qemu -cpu arm1176 -m 256 -M versatilepb -no-reboot -serial stdio -append 'root=/dev/sda2 panic=1 rootfstype=ext4 rw' -hda posbox.img -net user,hostfwd=tcp::10022-:22,hostfwd=tcp::18069-:8069 -net nic)
+if [ -z ${DISPLAY:-} ] ; then
+ QEMU_OPTS+=(-nographic)
+fi
+qemu-system-arm "${QEMU_OPTS[@]}"
+
+mount "${LOOP_MAPPER_PATH}" "${MOUNT_POINT}"
+cp -av "${OVERWRITE_FILES_AFTER_INIT_DIR}"/* "${MOUNT_POINT}"
+
+# cleanup
+sleep 2
+umount "${MOUNT_POINT}"
+rm -r "${MOUNT_POINT}"
+
+echo "Running zerofree..."
+zerofree -v "${LOOP_MAPPER_PATH}" || true
+
+kpartx -d posbox.img
diff --git a/addons/point_of_sale/tools/posbox/posbox_download_images.sh b/addons/point_of_sale/tools/posbox/posbox_download_images.sh
new file mode 100755
index 00000000000..a138e44bfab
--- /dev/null
+++ b/addons/point_of_sale/tools/posbox/posbox_download_images.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+wget 'http://downloads.raspberrypi.org/raspbian_latest' -O raspbian.img.zip
+unzip raspbian.img.zip
+wget 'https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/kernel-qemu'
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 3085f221c09..243822b9715 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -1399,13 +1399,15 @@ class procurement_order(osv.osv):
pricelist_id = partner.property_product_pricelist_purchase.id
prices_qty = []
+ qty = {}
+
for procurement in procurements:
seller_qty = procurement.product_id.seller_qty if procurement.location_id.usage != 'customer' else 0.0
uom_id = procurement.product_id.uom_po_id.id
- qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
+ qty[procurement.product_id.id] = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
if seller_qty:
- qty = max(qty, seller_qty)
- prices_qty += [(procurement.product_id, qty, partner)]
+ qty[procurement.product_id.id] = max(qty[procurement.product_id.id], seller_qty)
+ prices_qty += [(procurement.product_id, qty[procurement.product_id.id], partner)]
prices = pricelist_obj.price_get_multi(cr, uid, [pricelist_id], prices_qty)
#Passing partner_id to context for purchase order line integrity of Line name
@@ -1430,7 +1432,7 @@ class procurement_order(osv.osv):
values = {
'name': name,
- 'product_qty': qty,
+ 'product_qty': qty[procurement.product_id.id],
'product_id': procurement.product_id.id,
'product_uom': procurement.product_id.uom_po_id.id,
'price_unit': price or 0.0,
diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py
index eee573c8e53..3397a99f37d 100644
--- a/addons/purchase/stock.py
+++ b/addons/purchase/stock.py
@@ -126,7 +126,7 @@ class stock_move(osv.osv):
pricelist_obj = self.pool.get("product.pricelist")
pricelist = partner.property_product_pricelist_purchase.id
price = pricelist_obj.price_get(cr, uid, [pricelist],
- move.product_id.id, move.product_uom_qty, partner, {
+ move.product_id.id, move.product_uom_qty, partner.id, {
'uom': move.product_uom.id,
'date': move.date,
})[pricelist]
diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py
index 87f16bb4060..976ab5ea9c6 100644
--- a/addons/purchase_requisition/purchase_requisition.py
+++ b/addons/purchase_requisition/purchase_requisition.py
@@ -379,28 +379,36 @@ class procurement_order(osv.osv):
'requisition_id': fields.many2one('purchase.requisition', 'Latest Requisition')
}
- def _run(self, cr, uid, procurement, context=None):
+ def make_po(self, cr, uid, ids, context=None):
requisition_obj = self.pool.get('purchase.requisition')
warehouse_obj = self.pool.get('stock.warehouse')
- if procurement.rule_id and procurement.rule_id.action == 'buy' and procurement.product_id.purchase_requisition:
- warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id)], context=context)
- requisition_id = requisition_obj.create(cr, uid, {
- 'origin': procurement.origin,
- 'date_end': procurement.date_planned,
- 'warehouse_id': warehouse_id and warehouse_id[0] or False,
- 'company_id': procurement.company_id.id,
- 'procurement_id': procurement.id,
- 'picking_type_id': procurement.rule_id.picking_type_id.id,
- 'line_ids': [(0, 0, {
- 'product_id': procurement.product_id.id,
- 'product_uom_id': procurement.product_uom.id,
- 'product_qty': procurement.product_qty
-
- })],
- })
- self.message_post(cr, uid, [procurement.id], body=_("Purchase Requisition created"), context=context)
- return self.write(cr, uid, [procurement.id], {'requisition_id': requisition_id}, context=context)
- return super(procurement_order, self)._run(cr, uid, procurement, context=context)
+ req_ids = []
+ res = {}
+ for procurement in self.browse(cr, uid, ids, context=context):
+ res[procurement.id] = False
+ if procurement.product_id.purchase_requisition:
+ warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id)], context=context)
+ requisition_id = requisition_obj.create(cr, uid, {
+ 'origin': procurement.origin,
+ 'date_end': procurement.date_planned,
+ 'warehouse_id': warehouse_id and warehouse_id[0] or False,
+ 'company_id': procurement.company_id.id,
+ 'procurement_id': procurement.id,
+ 'picking_type_id': procurement.rule_id.picking_type_id.id,
+ 'line_ids': [(0, 0, {
+ 'product_id': procurement.product_id.id,
+ 'product_uom_id': procurement.product_uom.id,
+ 'product_qty': procurement.product_qty
+ })],
+ })
+ self.message_post(cr, uid, [procurement.id], body=_("Purchase Requisition created"), context=context)
+ self.write(cr, uid, [procurement.id], {'requisition_id': requisition_id}, context=context)
+ req_ids += [procurement.id]
+ res[procurement.id] = True
+ set_others = set(ids) - set(req_ids)
+ if set_others:
+ res.update(super(procurement_order, self).make_po(cr, uid, list(set_others), context=context))
+ return res
def _check(self, cr, uid, procurement, context=None):
if procurement.rule_id and procurement.rule_id.action == 'buy' and procurement.product_id.purchase_requisition:
diff --git a/addons/report/models/report.py b/addons/report/models/report.py
index 97e5e28fbc3..c86ed372260 100644
--- a/addons/report/models/report.py
+++ b/addons/report/models/report.py
@@ -331,7 +331,7 @@ class Report(osv.Model):
"""
command_args = []
if wkhtmltopdf_state == "ok":
- command_args.extend(['--viewport-size', '1024x768'])
+ command_args.extend(['--viewport-size', landscape and '1024x1280' or '1280x1024'])
# Passing the cookie to wkhtmltopdf in order to resolve internal links.
try:
@@ -354,7 +354,7 @@ class Report(osv.Model):
del command_args[index]
del command_args[index]
command_args.extend(['--orientation', 'landscape'])
- elif landscape and not '--orientation' in command_args:
+ elif landscape and '--orientation' not in command_args:
command_args.extend(['--orientation', 'landscape'])
# Execute WKhtmltopdf
@@ -403,7 +403,7 @@ class Report(osv.Model):
if process.returncode not in [0, 1]:
raise UserError(_('Wkhtmltopdf failed (error code: %s). '
- 'Message: %s') % (str(process.returncode), err))
+ 'Message: %s') % (str(process.returncode), err))
# Save the pdf in attachment if marked
if reporthtml[0] is not False and save_in_attachment.get(reporthtml[0]):
@@ -418,8 +418,7 @@ class Report(osv.Model):
try:
self.pool['ir.attachment'].create(cr, uid, attachment)
except AccessError:
- _logger.info("Cannot save PDF report %r as attachment",
- attachment['name'])
+ _logger.info("Cannot save PDF report %r as attachment", attachment['name'])
else:
_logger.info('The PDF document %s is now saved in the database',
attachment['name'])
diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py
index 4910d5e8c01..add69e5839a 100644
--- a/addons/sale/wizard/sale_make_invoice_advance.py
+++ b/addons/sale/wizard/sale_make_invoice_advance.py
@@ -128,7 +128,7 @@ class sale_advance_payment_inv(osv.osv_memory):
'partner_id': sale.partner_invoice_id.id,
'invoice_line_ids': [(0, 0, inv_line_values)],
'currency_id': sale.pricelist_id.currency_id.id,
- 'comment': '',
+ 'comment': sale.note,
'payment_term_id': sale.payment_term_id.id,
'fiscal_position_id': sale.fiscal_position_id.id or sale.partner_id.property_account_position_id.id,
'team_id': sale.team_id.id,
diff --git a/addons/sale_contract/models/sale_contract.py b/addons/sale_contract/models/sale_contract.py
index 6fa1ddf9114..7fcd9f51bc5 100644
--- a/addons/sale_contract/models/sale_contract.py
+++ b/addons/sale_contract/models/sale_contract.py
@@ -280,7 +280,7 @@ class account_analytic_account(osv.osv):
inv_ids = []
for account in self.browse(cr, uid, ids, context=context):
res[account.id] = 0.0
- line_ids = lines_obj.search(cr, uid, [('account_id','=', account.id), ('invoice_id','!=',False), ('to_invoice','!=', False), ('journal_id.type', '=', 'general'), ('invoice_id.type', 'in', ['out_invoice', 'out_refund'])], context=context)
+ line_ids = lines_obj.search(cr, uid, [('account_id','=', account.id), ('invoice_id','!=',False), ('invoice_id.state', 'not in', ['draft', 'cancel']), ('to_invoice','!=', False), ('journal_id.type', '=', 'general'), ('invoice_id.type', 'in', ['out_invoice', 'out_refund'])], context=context)
for line in lines_obj.browse(cr, uid, line_ids, context=context):
if line.invoice_id not in inv_ids:
inv_ids.append(line.invoice_id)
diff --git a/addons/sale_stock/sale_stock_view.xml b/addons/sale_stock/sale_stock_view.xml
index 553a6bfec02..3f72d3f1c10 100644
--- a/addons/sale_stock/sale_stock_view.xml
+++ b/addons/sale_stock/sale_stock_view.xml
@@ -45,6 +45,7 @@
parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position_id,
False, parent.warehouse_id, context)
</attribute>
+ <attribute name="context">{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':False}</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/tree//field[@name='product_id']" position="attributes">
<attribute name="on_change">product_id_change_with_wh(
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 15d06f2d3af..480835dffb6 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -1061,7 +1061,8 @@ class stock_picking(models.Model):
move_obj = self.pool.get("stock.move")
move_obj.write(cr, uid, backorder_move_ids, {'picking_id': backorder_id}, context=context)
- self.write(cr, uid, [picking.id], {'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
+ if not picking.date_done:
+ self.write(cr, uid, [picking.id], {'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
self.action_confirm(cr, uid, [backorder_id], context=context)
self.action_assign(cr, uid, [backorder_id], context=context)
return backorder_id
@@ -1726,12 +1727,14 @@ class stock_move(osv.osv):
settings_obj = self.pool.get('stock.config.settings')
uom_obj = self.pool.get('product.uom')
res = dict.fromkeys(ids, '')
+ precision = self.pool['decimal.precision'].precision_get(cr, uid, 'Product Unit of Measure')
for move in self.browse(cr, uid, ids, context=context):
if move.state in ('draft', 'done', 'cancel') or move.location_id.usage != 'internal':
res[move.id] = '' # 'not applicable' or 'n/a' could work too
continue
total_available = min(move.product_qty, move.reserved_availability + move.availability)
- total_available = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, total_available, move.product_uom, context=context)
+ total_available = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, total_available, move.product_uom, round=False, context=context)
+ total_available = float_round(total_available, precision_digits=precision)
info = str(total_available)
#look in the settings if we need to display the UoM name or not
config_ids = settings_obj.search(cr, uid, [], limit=1, order='id DESC', context=context)
@@ -1742,7 +1745,8 @@ class stock_move(osv.osv):
if move.reserved_availability:
if move.reserved_availability != total_available:
#some of the available quantity is assigned and some are available but not reserved
- reserved_available = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, move.reserved_availability, move.product_uom, context=context)
+ reserved_available = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, move.reserved_availability, move.product_uom, round=False, context=context)
+ reserved_available = float_round(reserved_available, precision_digits=precision)
info += _(' (%s reserved)') % str(reserved_available)
else:
#all available quantity is assigned
@@ -3076,7 +3080,7 @@ class stock_warehouse(osv.osv):
'crossdock_route_id': fields.many2one('stock.location.route', 'Crossdock Route'),
'reception_route_id': fields.many2one('stock.location.route', 'Receipt Route'),
'delivery_route_id': fields.many2one('stock.location.route', 'Delivery Route'),
- 'resupply_from_wh': fields.boolean('Resupply From Other Warehouses'),
+ 'resupply_from_wh': fields.boolean('Resupply From Other Warehouses', help='Unused field'),
'resupply_wh_ids': fields.many2many('stock.warehouse', 'stock_wh_resupply_table', 'supplied_wh_id', 'supplier_wh_id', 'Resupply Warehouses'),
'resupply_route_ids': fields.one2many('stock.location.route', 'supplied_wh_id', 'Resupply Routes',
help="Routes will be created for these resupply warehouses and you can select them on products and product categories"),
@@ -3136,7 +3140,7 @@ class stock_warehouse(osv.osv):
pull_obj.create(cr, uid, vals=pull_rule, context=context)
#if the warehouse is also set as default resupply method, assign this route automatically to the warehouse
if default_resupply_wh and default_resupply_wh.id == wh.id:
- self.write(cr, uid, [warehouse.id], {'route_ids': [(4, inter_wh_route_id)]}, context=context)
+ self.write(cr, uid, [warehouse.id, wh.id], {'route_ids': [(4, inter_wh_route_id)]}, context=context)
_defaults = {
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c),
diff --git a/addons/stock/tests/__init__.py b/addons/stock/tests/__init__.py
index 4c20f7a769a..c372f3c8fb5 100644
--- a/addons/stock/tests/__init__.py
+++ b/addons/stock/tests/__init__.py
@@ -2,3 +2,4 @@
from . import test_stock_flow
from . import test_owner_available
+from . import test_resupply
diff --git a/addons/stock/tests/test_resupply.py b/addons/stock/tests/test_resupply.py
new file mode 100644
index 00000000000..79ecbf456b7
--- /dev/null
+++ b/addons/stock/tests/test_resupply.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+from openerp.addons.stock.tests.common import TestStockCommon
+from openerp.tools import mute_logger, float_round
+
+
+class TestResupply(TestStockCommon):
+ def setUp(self):
+ super(TestResupply, self).setUp()
+ self.Warehouse = self.env['stock.warehouse']
+ # create 2 WH, BIG and SMALL
+ # SMALL resupplies from BIG
+ self.bigwh = self.Warehouse.create({'name': 'BIG', 'code': 'B'})
+ self.smallwh = self.Warehouse.create({'name': 'SMALL', 'code': 'S',
+ 'default_resupply_wh_id': self.bigwh.id,
+ 'resupply_wh_ids': [(6, 0, [self.bigwh.id])],
+ })
+ # minimum stock rule for Product A on SMALL
+ Orderpoint = self.env['stock.warehouse.orderpoint']
+ Orderpoint.create({'warehouse_id': self.smallwh.id,
+ 'location_id': self.smallwh.lot_stock_id.id,
+ 'product_id': self.productA.id,
+ 'product_min_qty': 100,
+ 'product_max_qty': 200,
+ 'product_uom': self.uom_unit.id,
+ })
+ # create some stock on BIG
+ Wiz = self.env['stock.change.product.qty']
+ wiz = Wiz.create({'product_id': self.productA.id,
+ 'new_quantity': 1000,
+ 'location_id': self.bigwh.lot_stock_id.id,
+ })
+ wiz.change_product_qty()
+
+ def test_resupply_from_wh(self):
+ sched = self.env['procurement.order']
+ sched.run_scheduler()
+ # we generated 2 procurements for product A: one on small wh and the
+ # other one on the transit location
+ procs = sched.search([('product_id', '=', self.productA.id)])
+ self.assertEqual(len(procs), 2)
+ proc1 = sched.search([('product_id', '=', self.productA.id),
+ ('warehouse_id', '=', self.smallwh.id)])
+ self.assertEqual(proc1.state, 'running')
+ proc2 = sched.search([('product_id', '=', self.productA.id),
+ ('warehouse_id', '=', self.bigwh.id)])
+ self.assertEqual(proc2.location_id.usage, 'transit')
+ self.assertNotEqual(proc2.state, 'exception')
+ proc2.run()
+ self.assertEqual(proc2.state, 'running')
+ self.assertTrue(proc2.rule_id)
diff --git a/addons/stock/views/report_stockpicking_operations.xml b/addons/stock/views/report_stockpicking_operations.xml
index 7350302fffd..641d26199ba 100644
--- a/addons/stock/views/report_stockpicking_operations.xml
+++ b/addons/stock/views/report_stockpicking_operations.xml
@@ -107,7 +107,7 @@
<img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', pack_operation.lot_id.name, 600, 100)" style="width:300px;height:50px"/>
</span>
<span t-if="pack_operation.product_id and not pack_operation.lot_id and pack_operation.product_id.barcode">
- <img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Barcode', pack_operation.product_id.barcode, 600, 100)" style="width:300px;height:50px"/>
+ <img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('EAN13', pack_operation.product_id.barcode, 600, 100)" style="width:300px;height:50px"/>
</span>
<span t-if="pack_operation.package_id and not pack_operation.product_id">
<img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', pack_operation.package_id.name, 600, 100)" style="width:300px;height:50px"/>
diff --git a/addons/stock_account/stock.py b/addons/stock_account/stock.py
index 6e3e81be447..e4903326670 100644
--- a/addons/stock_account/stock.py
+++ b/addons/stock_account/stock.py
@@ -214,7 +214,7 @@ class stock_picking(osv.osv):
@return: object of the partner to invoice
"""
return picking.partner_id and picking.partner_id.id
-
+
def action_invoice_create(self, cr, uid, ids, journal_id, group=False, type='out_invoice', context=None):
""" Creates invoice based on the invoice state selected for picking.
@param journal_id: Id of journal
diff --git a/addons/survey/security/survey_security.xml b/addons/survey/security/survey_security.xml
index 07989d79181..afaf592686b 100644
--- a/addons/survey/security/survey_security.xml
+++ b/addons/survey/security/survey_security.xml
@@ -52,7 +52,7 @@
<record id="survey_input_users_access" model="ir.rule">
<field name="name">Access to user_input for regular users</field>
<field name="model_id" ref="survey.model_survey_user_input"/>
- <field name="domain_force">[('create_uid', '=', user.id)]</field>
+ <field name="domain_force">['|', ('create_uid', '=', user.id), ('partner_id', '=', user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_survey_user'))]"/>
<field eval="0" name="perm_unlink"/>
<field eval="0" name="perm_write"/>
diff --git a/addons/survey/survey.py b/addons/survey/survey.py
index 40733f97309..16a0592dde8 100644
--- a/addons/survey/survey.py
+++ b/addons/survey/survey.py
@@ -3,6 +3,7 @@
from openerp.osv import fields, osv
from openerp.tools.translate import _
+from openerp import SUPERUSER_ID
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DF
from openerp.addons.website.models.website import slug
from urlparse import urljoin
@@ -1140,7 +1141,7 @@ class survey_user_input_line(osv.Model):
('question_id', '=', question.id)],
context=context)
if old_uil:
- self.unlink(cr, uid, old_uil, context=context)
+ self.unlink(cr, SUPERUSER_ID, old_uil, context=context)
if answer_tag in post and post[answer_tag].strip() != '':
vals.update({'answer_type': 'suggestion', 'value_suggested': post[answer_tag]})
@@ -1171,7 +1172,7 @@ class survey_user_input_line(osv.Model):
('question_id', '=', question.id)],
context=context)
if old_uil:
- self.unlink(cr, uid, old_uil, context=context)
+ self.unlink(cr, SUPERUSER_ID, old_uil, context=context)
ca = dict_keys_startswith(post, answer_tag)
comment_answer = ca.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
@@ -1202,7 +1203,7 @@ class survey_user_input_line(osv.Model):
('question_id', '=', question.id)],
context=context)
if old_uil:
- self.unlink(cr, uid, old_uil, context=context)
+ self.unlink(cr, SUPERUSER_ID, old_uil, context=context)
no_answers = True
ca = dict_keys_startswith(post, answer_tag)
diff --git a/addons/web/static/src/css/ie.css b/addons/web/static/src/css/ie.css
index f628ed1213d..a547120033e 100644
--- a/addons/web/static/src/css/ie.css
+++ b/addons/web/static/src/css/ie.css
@@ -24,9 +24,6 @@
}
/** web_kanban */
-:root .openerp .oe_kanban_view .oe_kanban_group_title_vertical {
- writing-mode: lr-tb;
-}
.openerp .oe_kanban_column_cards {
min-height: 80px;
}
diff --git a/addons/web/static/src/js/control_panel.js b/addons/web/static/src/js/control_panel.js
index b2d603b53bf..5e2cc36a6a1 100644
--- a/addons/web/static/src/js/control_panel.js
+++ b/addons/web/static/src/js/control_panel.js
@@ -189,7 +189,7 @@ var ControlPanel = Widget.extend({
function make_breadcrumb (bc, is_last) {
var $bc = $('<li>')
- .append(is_last ? bc.title : $('<a>').html(bc.title))
+ .append(is_last ? _.escape(bc.title) : $('<a>').text(bc.title))
.toggleClass('active', is_last);
if (!is_last) {
$bc.click(function () {
diff --git a/addons/web_editor/models/ir_ui_view.py b/addons/web_editor/models/ir_ui_view.py
index dd7501c44f9..d0d18b5ee41 100644
--- a/addons/web_editor/models/ir_ui_view.py
+++ b/addons/web_editor/models/ir_ui_view.py
@@ -5,6 +5,7 @@ from openerp.exceptions import AccessError
from openerp.osv import osv
from lxml import etree, html
from openerp import api
+from openerp.tools import html_escape
class view(osv.osv):
@@ -73,6 +74,14 @@ class view(osv.osv):
# ensure there's only one match
[root] = arch.xpath(section_xpath)
+ # html text need to be escaped for xml storage
+ def escape_node(node):
+ node.text = node.text and html_escape(node.text)
+ node.tail = node.tail and html_escape(node.tail)
+ escape_node(replacement)
+ for descendant in replacement.iterdescendants():
+ escape_node(descendant)
+
root.text = replacement.text
root.tail = replacement.tail
# replace all children
diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js
index 5b6b6d593e9..467f0042090 100644
--- a/addons/web_editor/static/src/js/rte.summernote.js
+++ b/addons/web_editor/static/src/js/rte.summernote.js
@@ -214,6 +214,8 @@ eventHandler.popover.button.update = function ($container, oStyle) {
$container.find('button[data-event="resizefa"][data-value="1"]').toggleClass("active", !$container.find('.active[data-event="resizefa"]').length);
$container.find('button[data-event="imageShape"][data-value="fa-spin"]').toggleClass("active", $(oStyle.image).hasClass("fa-spin"));
+ $container.find('button[data-event="imageShape"][data-value="shadow"]').toggleClass("active", $(oStyle.image).hasClass("shadow"));
+ $container.find('.btn-group:has(button[data-event="imageShape"])').removeClass("hidden");
} else {
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 4a56edd3c53..c304ec1b04a 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -186,7 +186,7 @@ class Website(openerp.addons.web.controllers.main.Home):
request.registry['website.menu'].create(
request.cr, request.uid, {
'name': path,
- 'url': "/page/" + xml_id,
+ 'url': "/page/" + xml_id[8:],
'parent_id': request.website.menu_id.id,
'website_id': request.website.id,
}, context=request.context)
diff --git a/addons/website/data/data.xml b/addons/website/data/data.xml
index 8ba468c4b45..f30068b72e9 100644
--- a/addons/website/data/data.xml
+++ b/addons/website/data/data.xml
@@ -24,7 +24,7 @@
<record id="menu_contactus" model="website.menu">
<field name="name">Contact us</field>
- <field name="url">/page/website.contactus</field>
+ <field name="url">/page/contactus</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">60</field>
<field name="website_id" ref="default_website"/>
@@ -79,7 +79,7 @@
</record>
<record id="action_website_homepage" model="ir.actions.act_url">
<field name="name">Website Homepage</field>
- <field name="url">/page/website.homepage</field>
+ <field name="url">/page/homepage</field>
<field name="target">self</field>
</record>
diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js
index 6d656aa3936..f049593e8d7 100644
--- a/addons/website/static/src/js/website.js
+++ b/addons/website/static/src/js/website.js
@@ -113,8 +113,7 @@ var prompt = function (options, _qweb) {
field.focus();
dialog.on('click', '.btn-primary', function () {
def.resolve(field.val(), field, dialog);
- dialog.remove();
- $('.modal-backdrop').remove();
+ dialog.modal('hide').remove();
});
});
dialog.on('hidden.bs.modal', function () {
@@ -208,7 +207,7 @@ base.ready().then(function () {
var redirect = {
lang: self.data('lang'),
url: encodeURIComponent(self.attr('href').replace(/[&?]edit_translations[^&?]+/, '')),
- hash: location.hash
+ hash: encodeURIComponent(location.hash)
};
location.href = _.str.sprintf("/website/lang/%(lang)s?r=%(url)s%(hash)s", redirect);
});
diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js
index 86d0717d979..9cc40247b89 100644
--- a/addons/website/static/src/js/website.snippets.editor.js
+++ b/addons/website/static/src/js/website.snippets.editor.js
@@ -81,17 +81,24 @@ options.registry.slider = options.Class.extend({
this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>');
// clone the best candidate from template to use new features
- var $snippets = this.buildingBlock.$snippets.find('.oe_snippet_body.carousel');
- var point = 0;
- var selection;
- var className = _.compact(this.$target.attr("class").split(" "));
- $snippets.each(function () {
- var len = _.intersection(_.compact(this.className.split(" ")), className).length;
- if (len > point) {
- point = len;
- selection = this;
- }
- });
+ var $snippets = this.BuildingBlock.$snippets;
+ //since saas-6, all snippets must start by s_
+ var selection = this.$target.closest('[class*="s_"');
+ if (_.isUndefined(selection)) {
+ var point = 0;
+ var className = _.compact(this.$target.attr("class").split(" "));
+ $snippets.find('.oe_snippet_body').each(function () {
+ var len = _.intersection(_.compact(this.className.split(" ")), className).length;
+ if (len > point) {
+ point = len;
+ selection = this;
+ }
+ });
+ }
+ else {
+ var s_class = selection.attr('class').split(' ').filter(function(o) { return _.str.startsWith(o, "s_") })[0]
+ selection = $snippets.find("." + s_class);
+ }
var $clone = $(selection).find('.item:first').clone();
// insert
diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py
index 1adb11712ec..c8e7efb9044 100644
--- a/addons/website/tests/test_views.py
+++ b/addons/website/tests/test_views.py
@@ -174,6 +174,16 @@ class TestViewSaving(common.TransactionCase):
)
)
+ def test_save_escaped_text(self):
+ view_id = self.registry('ir.ui.view').create(self.cr, self.uid, {
+ 'arch':'<t>hello world</t>',
+ 'type':'qweb'
+ })
+ view = self.registry('ir.ui.view').browse(self.cr, self.uid, view_id)
+ replacement = 'hello world &amp; &lt;angle brackets&gt;!'
+ view.save(replacement, xpath='/t')
+ self.assertEqual(view.render(), replacement, 'html special characters wrongly escaped')
+
def test_save_only_embedded(self):
Company = self.registry('res.company')
company_id = 1
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index dacd1454bf0..ad5b53ac608 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -59,16 +59,15 @@
<template id="submenu" name="Submenu">
<li t-if="not submenu.child_id" t-att-class="
- ((submenu.url and submenu.url != '/' and request.httprequest.path.startswith(submenu.url)) or
- request.httprequest.path == submenu.url) and 'active'
+ 'active' if submenu.url and submenu.url != '/' and request.httprequest.path == submenu.url.replace('/page/website.', '/page/') else None
">
<a t-att-href="(website.menu_id.child_id[0] == submenu) and '/' or submenu.url" t-ignore="true" t-att-target="'_blank' if submenu.new_window else None">
<span t-field="submenu.name"/>
</a>
</li>
<li t-if="submenu.child_id" t-attf-class="dropdown #{
- ((submenu.url and submenu.url != '/' and [1 for submenu in submenu.child_id if request.httprequest.path.startswith(submenu.url)]) or
- request.httprequest.path == submenu.url) and 'active'
+ (submenu.url and submenu.url != '/' and any([request.httprequest.path == child.url.replace('/page/website.', '/page/') for child in submenu.child_id]) or
+ request.httprequest.path == submenu.url.replace('/page/website.', '/page/')) and 'active'
}">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span t-field="submenu.name"/> <span class="caret" t-ignore="true"></span>
diff --git a/addons/website_crm/data/website_crm_data.xml b/addons/website_crm/data/website_crm_data.xml
index ada2e73e71d..8d6c916fffa 100644
--- a/addons/website_crm/data/website_crm_data.xml
+++ b/addons/website_crm/data/website_crm_data.xml
@@ -5,7 +5,7 @@
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Contact Form</field>
<field name="target">self</field>
- <field name="url">/page/website.contactus</field>
+ <field name="url">/page/contactus</field>
</record>
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_open_website"/>
diff --git a/addons/website_hr/data/website_hr_data.xml b/addons/website_hr/data/website_hr_data.xml
index 161b875556a..5afe68add8f 100644
--- a/addons/website_hr/data/website_hr_data.xml
+++ b/addons/website_hr/data/website_hr_data.xml
@@ -5,7 +5,7 @@
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website About</field>
<field name="target">self</field>
- <field name="url">/page/website.aboutus</field>
+ <field name="url">/page/aboutus</field>
</record>
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_open_website"/>
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 50e49230500..d35ef34f3c1 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -554,7 +554,7 @@ class website_sale(http.Controller):
partner_lang = request.lang if request.lang in [lang.code for lang in request.website.language_ids] else None
- billing_info = {}
+ billing_info = {'customer': True}
if partner_lang:
billing_info['lang'] = partner_lang
billing_info.update(self.checkout_parse('billing', checkout, True))
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 4c160e0018b..67f7a47be35 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -173,7 +173,7 @@ $('.oe_website_sale').each(function () {
return;
}
if (!data.quantity) {
- location.reload();
+ location.reload(true);
return;
}
var $q = $(".my_cart_quantity");
diff --git a/doc/cla/corporate/hbee.md b/doc/cla/corporate/hbee.md
new file mode 100644
index 00000000000..aaebe686d00
--- /dev/null
+++ b/doc/cla/corporate/hbee.md
@@ -0,0 +1,21 @@
+Lithuania, Macedonia, 2015-07-31
+
+HBEE agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Paulius Sladkevičius paulius@hbee.eu https://github.com/komsas
+
+List of contributors:
+
+Darko Nikolovski darko@hbee.eu https://github.com/DarkoNikolovski
+Ignas Karpiejus ignas@hbee.eu https://github.com/ignask
+Karolis Kalantojus karolis@hbee.eu https://github.com/kkarolis
+Kiril Vangelovski kiril@hbee.eu https://github.com/kirca
+Naglis Jonaitis naglis@hbee.eu https://github.com/naglis
+Paulius Sladkevičius paulius@hbee.eu https://github.com/komsas
+Tomas Račys tomas@hbee.eu https://github.com/taezaz
diff --git a/doc/cla/corporate/rossa.md b/doc/cla/corporate/rossa.md
new file mode 100644
index 00000000000..5f274745bd5
--- /dev/null
+++ b/doc/cla/corporate/rossa.md
@@ -0,0 +1,14 @@
+Paraguay, 2015-07-09
+
+Rossa S.A. agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this declaration.
+
+Signed,
+
+Rossa S.A. emaildarossa@gmail.com https://github.com/rossasa
+
+List of contributors:
+
+Marcelo Pickler marcelo@rossa.com.py https://github.com/loxamir
+Alan R. S. Pieske alan@rossa.com.py https://github.com/alanpy
diff --git a/doc/cla/corporate/willow-it.md b/doc/cla/corporate/willow-it.md
new file mode 100644
index 00000000000..9e3480e6cb4
--- /dev/null
+++ b/doc/cla/corporate/willow-it.md
@@ -0,0 +1,18 @@
+Australia, 2015-07-20
+
+Willow IT agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Richard deMeester richard@willowit.com.au https://github.com/richard-willowit
+
+List of contributors:
+
+Richard deMeester richard@willowit.com.au https://github.com/richard-willowit
+Douglas Parker doug@willowit.com.au https://github.com/doug-willowit
+Bhavya Malik bhavya@willowit.com.au https://github.com/bhavya-willowit
+Jon Wilson jon@willowit.com.au https://github.com/jon-willowit
diff --git a/doc/cla/individual/cysnake4713.md b/doc/cla/individual/cysnake4713.md
new file mode 100644
index 00000000000..911c1b318e9
--- /dev/null
+++ b/doc/cla/individual/cysnake4713.md
@@ -0,0 +1,11 @@
+China, 2015-07-09
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Matt Cai 蔡旸 cysnake4713@gmail.com https://github.com/cysnake4713
diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py
index 2d5678a0a37..97b5fbe17fe 100644
--- a/openerp/addons/base/ir/ir_ui_view.py
+++ b/openerp/addons/base/ir/ir_ui_view.py
@@ -746,7 +746,7 @@ class view(osv.osv):
orm.transfer_field_to_modifiers(field, modifiers)
elif node.tag in ('form', 'tree'):
- result = Model.view_header_get(cr, user, False, node.tag, context)
+ result = Model.view_header_get(cr, user, False, node.tag, context=context)
if result:
node.set('string', result)
in_tree_view = node.tag == 'tree'
@@ -845,17 +845,17 @@ class view(osv.osv):
if node.tag == 'diagram':
if node.getchildren()[0].tag == 'node':
node_model = self.pool[node.getchildren()[0].get('object')]
- node_fields = node_model.fields_get(cr, user, None, context)
+ node_fields = node_model.fields_get(cr, user, None, context=context)
fields.update(node_fields)
if not node.get("create") and \
not node_model.check_access_rights(cr, user, 'create', raise_exception=False) or \
not context.get("create", True):
node.set("create", 'false')
if node.getchildren()[1].tag == 'arrow':
- arrow_fields = self.pool[node.getchildren()[1].get('object')].fields_get(cr, user, None, context)
+ arrow_fields = self.pool[node.getchildren()[1].get('object')].fields_get(cr, user, None, context=context)
fields.update(arrow_fields)
else:
- fields = Model.fields_get(cr, user, None, context)
+ fields = Model.fields_get(cr, user, None, context=context)
node = self.add_on_change(cr, user, model, node)
fields_def = self.postprocess(cr, user, model, node, view_id, False, fields, context=context)
@@ -1006,7 +1006,7 @@ class view(osv.osv):
def get_trans(text):
if not text or not text.strip():
return None
- text = h.unescape(text.strip())
+ text = text.strip()
if len(text) < 2 or (text.startswith('<!') and text.endswith('>')):
return None
return translate_func(text)
diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py
index ce9c4cdd2a1..abd0afdf5bd 100644
--- a/openerp/addons/base/res/res_users.py
+++ b/openerp/addons/base/res/res_users.py
@@ -126,12 +126,13 @@ class res_users(osv.osv):
avatar, ... The user model is now dedicated to technical data.
"""
__admin_ids = {}
- _uid_cache = {}
+ __uid_cache = {}
_inherits = {
'res.partner': 'partner_id',
}
_name = "res.users"
_description = 'Users'
+ _order = 'name, login'
def _set_new_password(self, cr, uid, id, name, value, args, context=None):
if value is False:
@@ -311,6 +312,24 @@ class res_users(osv.osv):
return result
+ def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
+ if uid != SUPERUSER_ID:
+ groupby_fields = set([groupby] if isinstance(groupby, basestring) else groupby)
+ if groupby_fields.intersection(USER_PRIVATE_FIELDS):
+ raise openerp.exceptions.AccessError('Invalid groupby')
+ return super(res_users, self).read_group(
+ cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby, lazy=lazy)
+
+ def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
+ if user != SUPERUSER_ID and args:
+ domain_terms = [term for term in args if isinstance(term, (tuple, list))]
+ domain_fields = set(left for (left, op, right) in domain_terms)
+ if domain_fields.intersection(USER_PRIVATE_FIELDS):
+ raise openerp.exceptions.AccessError('Invalid search criterion')
+ return super(res_users, self)._search(
+ cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count,
+ access_rights_uid=access_rights_uid)
+
def create(self, cr, uid, vals, context=None):
user_id = super(res_users, self).create(cr, uid, vals, context=context)
user = self.browse(cr, uid, user_id, context=context)
@@ -353,10 +372,10 @@ class res_users(osv.osv):
clear = partial(self.pool['ir.rule'].clear_cache, cr)
map(clear, ids)
db = cr.dbname
- if db in self._uid_cache:
+ if db in self.__uid_cache:
for id in ids:
- if id in self._uid_cache[db]:
- del self._uid_cache[db][id]
+ if id in self.__uid_cache[db]:
+ del self.__uid_cache[db][id]
self.context_get.clear_cache(self)
self.has_group.clear_cache(self)
return res
@@ -365,10 +384,10 @@ class res_users(osv.osv):
if 1 in ids:
raise UserError(_('You can not remove the admin user as it is used internally for resources created by Odoo (updates, module installation, ...)'))
db = cr.dbname
- if db in self._uid_cache:
+ if db in self.__uid_cache:
for id in ids:
- if id in self._uid_cache[db]:
- del self._uid_cache[db][id]
+ if id in self.__uid_cache[db]:
+ del self.__uid_cache[db][id]
return super(res_users, self).unlink(cr, uid, ids, context=context)
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
@@ -504,15 +523,12 @@ class res_users(osv.osv):
if not passwd:
# empty passwords disallowed for obvious security reasons
raise openerp.exceptions.AccessDenied()
- if self._uid_cache.get(db, {}).get(uid) == passwd:
+ if self.__uid_cache.setdefault(db, {}).get(uid) == passwd:
return
cr = self.pool.cursor()
try:
self.check_credentials(cr, uid, passwd)
- if self._uid_cache.has_key(db):
- self._uid_cache[db][uid] = passwd
- else:
- self._uid_cache[db] = {uid:passwd}
+ self.__uid_cache[db][uid] = passwd
finally:
cr.close()
diff --git a/openerp/addons/base/tests/test_search.py b/openerp/addons/base/tests/test_search.py
index df8b317cb52..bd9d5089356 100644
--- a/openerp/addons/base/tests/test_search.py
+++ b/openerp/addons/base/tests/test_search.py
@@ -106,6 +106,21 @@ class test_search(common.TransactionCase):
test_user_ids = filter(lambda x: x in expected_ids, user_ids)
self.assertEqual(test_user_ids, expected_ids, 'search on res_users did not provide expected ids or expected order')
+ def test_11_indirect_inherits_m2order(self):
+ registry, cr, uid = self.registry, self.cr, self.uid
+ Cron = registry('ir.cron')
+ Users = registry('res.users')
+
+ user_ids = {}
+ cron_ids = {}
+ for u in 'BAC':
+ user_ids[u] = Users.create(cr, uid, {'name': u, 'login': u})
+ cron_ids[u] = Cron.create(cr, uid, {'name': u, 'user_id': user_ids[u]})
+
+ ids = Cron.search(cr, uid, [('id', 'in', cron_ids.values())], order='user_id')
+ expected_ids = [cron_ids[l] for l in 'ABC']
+ self.assertEqual(ids, expected_ids)
+
if __name__ == '__main__':
unittest2.main()
diff --git a/openerp/models.py b/openerp/models.py
index 73fbc47aae7..a57e5588564 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -1881,7 +1881,7 @@ class BaseModel(object):
gb_function = split[1] if len(split) == 2 else None
temporal = field_type in ('date', 'datetime')
tz_convert = field_type == 'datetime' and self._context.get('tz') in pytz.all_timezones
- qualified_field = self._inherits_join_calc(split[0], query)
+ qualified_field = self._inherits_join_calc(self._table, split[0], query)
if temporal:
display_formats = {
# Careful with week/year formats:
@@ -2041,7 +2041,7 @@ class BaseModel(object):
field_formatter = lambda f: (
self._fields[f].group_operator or 'sum',
- self._inherits_join_calc(cr, uid, f, query, context=context),
+ self._inherits_join_calc(cr, uid, self._table, f, query, context=context),
f,
)
select_terms = ["%s(%s) AS %s" % field_formatter(f) for f in aggregated_fields]
@@ -2118,17 +2118,18 @@ class BaseModel(object):
return parent_alias
@api.model
- def _inherits_join_calc(self, field, query):
+ def _inherits_join_calc(self, alias, field, query):
"""
Adds missing table select and join clause(s) to ``query`` for reaching
the field coming from an '_inherits' parent table (no duplicates).
+ :param alias: name of the initial SQL alias
:param field: name of inherited field to reach
:param query: query object on which the JOIN should be added
:return: qualified name of field, to be used in SELECT clause
"""
# INVARIANT: alias is the SQL alias of model._table in query
- model, alias = self, self._table
+ model = self
while field in model._inherit_fields and field not in model._columns:
# retrieve the parent model where field is inherited from
parent_model_name = model._inherit_fields[field][0]
@@ -3263,7 +3264,7 @@ class BaseModel(object):
# the query may involve several tables: we need fully-qualified names
def qualify(field):
col = field.name
- res = self._inherits_join_calc(field.name, query)
+ res = self._inherits_join_calc(self._table, field.name, query)
if field.type == 'binary' and (context.get('bin_size') or context.get('bin_size_' + col)):
# PG 9.2 introduces conflicting pg_size_pretty(numeric) -> need ::cast
res = 'pg_size_pretty(length(%s)::bigint)' % res
@@ -4522,7 +4523,7 @@ class BaseModel(object):
for inherited_model in self._inherits:
rule_where_clause, rule_where_clause_params, rule_tables = rule_obj.domain_get(cr, uid, inherited_model, mode, context=context)
apply_rule(rule_where_clause, rule_where_clause_params, rule_tables,
- parent_model=inherited_model)
+ parent_model=inherited_model)
@api.model
def _generate_translated_field(self, table_alias, field, query):
@@ -4546,7 +4547,7 @@ class BaseModel(object):
return '"%s"."%s"' % (table_alias, field)
@api.model
- def _generate_m2o_order_by(self, order_field, query):
+ def _generate_m2o_order_by(self, alias, order_field, query, reverse_direction):
"""
Add possibly missing JOIN to ``query`` and generate the ORDER BY clause for m2o fields,
either native m2o fields or function/related fields that are stored, including
@@ -4556,10 +4557,10 @@ class BaseModel(object):
"""
if order_field not in self._columns and order_field in self._inherit_fields:
# also add missing joins for reaching the table containing the m2o field
- qualified_field = self._inherits_join_calc(order_field, query)
+ qualified_field = self._inherits_join_calc(alias, order_field, query)
order_field_column = self._inherit_fields[order_field][2]
else:
- qualified_field = '"%s"."%s"' % (self._table, order_field)
+ qualified_field = '"%s"."%s"' % (alias, order_field)
order_field_column = self._columns[order_field]
assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
@@ -4570,24 +4571,68 @@ class BaseModel(object):
return
# figure out the applicable order_by for the m2o
- dest_model = self.pool[order_field_column._obj]
+ dest_model = self.env[order_field_column._obj]
m2o_order = dest_model._order
if not regex_order.match(m2o_order):
# _order is complex, can't use it here, so we default to _rec_name
m2o_order = dest_model._rec_name
- else:
- # extract the field names, to be able to qualify them and add desc/asc
- m2o_order_list = []
- for order_part in m2o_order.split(","):
- m2o_order_list.append(order_part.strip().split(" ", 1)[0].strip())
- m2o_order = m2o_order_list
# Join the dest m2o table if it's not joined yet. We use [LEFT] OUTER join here
# as we don't want to exclude results that have NULL values for the m2o
src_table, src_field = qualified_field.replace('"', '').split('.', 1)
dst_alias, dst_alias_statement = query.add_join((src_table, dest_model._table, src_field, 'id', src_field), implicit=False, outer=True)
- qualify = lambda field: '"%s"."%s"' % (dst_alias, field)
- return map(qualify, m2o_order) if isinstance(m2o_order, list) else qualify(m2o_order)
+ return dest_model._generate_order_by_inner(dst_alias, m2o_order, query,
+ reverse_direction=reverse_direction)
+
+ @api.model
+ def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False):
+ order_by_elements = []
+ self._check_qorder(order_spec)
+ for order_part in order_spec.split(','):
+ order_split = order_part.strip().split(' ')
+ order_field = order_split[0].strip()
+ order_direction = order_split[1].strip().upper() if len(order_split) == 2 else ''
+ if reverse_direction:
+ order_direction = 'ASC' if order_direction == 'DESC' else 'DESC'
+ do_reverse = order_direction == 'DESC'
+ order_column = None
+ inner_clauses = []
+ add_dir = False
+ if order_field == 'id':
+ order_by_elements.append('"%s"."%s" %s' % (alias, order_field, order_direction))
+ elif order_field in self._columns:
+ order_column = self._columns[order_field]
+ if order_column._classic_read:
+ if order_column.translate and not callable(order_column.translate):
+ inner_clauses = [self._generate_translated_field(alias, order_field, query)]
+ else:
+ inner_clauses = ['"%s"."%s"' % (alias, order_field)]
+ add_dir = True
+ elif order_column._type == 'many2one':
+ inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse)
+ else:
+ continue # ignore non-readable or "non-joinable" fields
+ elif order_field in self._inherit_fields:
+ parent_obj = self.pool[self._inherit_fields[order_field][3]]
+ order_column = parent_obj._columns[order_field]
+ if order_column._classic_read:
+ inner_clauses = [self._inherits_join_calc(alias, order_field, query)]
+ add_dir = True
+ elif order_column._type == 'many2one':
+ inner_clauses = self._generate_m2o_order_by(alias, order_field, query, do_reverse)
+ else:
+ continue # ignore non-readable or "non-joinable" fields
+ else:
+ raise ValueError(_("Sorting field %s not found on model %s") % (order_field, self._name))
+ if order_column and order_column._type == 'boolean':
+ inner_clauses = ["COALESCE(%s, false)" % inner_clauses[0]]
+
+ for clause in inner_clauses:
+ if add_dir:
+ order_by_elements.append("%s %s" % (clause, order_direction))
+ else:
+ order_by_elements.append(clause)
+ return order_by_elements
@api.model
def _generate_order_by(self, order_spec, query):
@@ -4600,46 +4645,7 @@ class BaseModel(object):
order_by_clause = ''
order_spec = order_spec or self._order
if order_spec:
- order_by_elements = []
- self._check_qorder(order_spec)
- for order_part in order_spec.split(','):
- order_split = order_part.strip().split(' ')
- order_field = order_split[0].strip()
- order_direction = order_split[1].strip() if len(order_split) == 2 else ''
- order_column = None
- inner_clause = None
- if order_field == 'id':
- order_by_elements.append('"%s"."%s" %s' % (self._table, order_field, order_direction))
- elif order_field in self._columns:
- order_column = self._columns[order_field]
- if order_column._classic_read:
- if order_column.translate and not callable(order_column.translate):
- inner_clause = self._generate_translated_field(self._table, order_field, query)
- else:
- inner_clause = '"%s"."%s"' % (self._table, order_field)
- elif order_column._type == 'many2one':
- inner_clause = self._generate_m2o_order_by(order_field, query)
- else:
- continue # ignore non-readable or "non-joinable" fields
- elif order_field in self._inherit_fields:
- parent_obj = self.pool[self._inherit_fields[order_field][3]]
- order_column = parent_obj._columns[order_field]
- if order_column._classic_read:
- inner_clause = self._inherits_join_calc(order_field, query)
- elif order_column._type == 'many2one':
- inner_clause = self._generate_m2o_order_by(order_field, query)
- else:
- continue # ignore non-readable or "non-joinable" fields
- else:
- raise ValueError(_("Sorting field %s not found on model %s") % ( order_field, self._name))
- if order_column and order_column._type == 'boolean':
- inner_clause = "COALESCE(%s, false)" % inner_clause
- if inner_clause:
- if isinstance(inner_clause, list):
- for clause in inner_clause:
- order_by_elements.append("%s %s" % (clause, order_direction))
- else:
- order_by_elements.append("%s %s" % (inner_clause, order_direction))
+ order_by_elements = self._generate_order_by_inner(self._table, order_spec, query)
if order_by_elements:
order_by_clause = ",".join(order_by_elements)
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index 04707788109..5640e97e7de 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -979,12 +979,11 @@ class many2many(_column):
wquery = obj._where_calc(cr, user, domain, context=context)
obj._apply_ir_rules(cr, user, wquery, 'read', context=context)
+ order_by = obj._generate_order_by(cr, user, None, wquery, context=context)
from_c, where_c, where_params = wquery.get_sql()
if where_c:
where_c = ' AND ' + where_c
- order_by = ' ORDER BY "%s".%s' %(obj._table, obj._order.split(',')[0])
-
limit_str = ''
if self._limit is not None:
limit_str = ' LIMIT %d' % self._limit
diff --git a/openerp/tests/common.py b/openerp/tests/common.py
index 17678cdbe1f..c7ac812d91f 100644
--- a/openerp/tests/common.py
+++ b/openerp/tests/common.py
@@ -344,6 +344,7 @@ class HttpCase(TransactionCase):
phantom.terminate()
phantom.wait()
self._wait_remaining_requests()
+ # we ignore phantomjs return code as we kill it as soon as we have ok
_logger.info("phantom_run execution finished")
def _wait_remaining_requests(self):