[MERGE] merged with trunk addons
bzr revid: pga@tinyerp.com-20140326112709-m1iqkkmjt1xtmio1
This commit is contained in:
commit
473243270b
|
@ -30,7 +30,7 @@ from openerp import SUPERUSER_ID
|
|||
from openerp import tools
|
||||
from openerp.osv import fields, osv, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools.float_utils import float_round
|
||||
from openerp.tools.float_utils import float_round as round
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
|
@ -1937,15 +1937,15 @@ class account_tax(osv.osv):
|
|||
#
|
||||
'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the tax declaration."),
|
||||
'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the tax declaration."),
|
||||
'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
|
||||
'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
|
||||
'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
|
||||
# Same fields for refund invoices
|
||||
|
||||
'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the tax declaration."),
|
||||
'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the tax declaration."),
|
||||
'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1."),
|
||||
'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1."),
|
||||
'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
'description': fields.char('Tax Code'),
|
||||
|
@ -2143,7 +2143,7 @@ class account_tax(osv.osv):
|
|||
tax_compute_precision = precision
|
||||
if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
|
||||
tax_compute_precision += 5
|
||||
totalin = totalex = float_round(price_unit * quantity, precision)
|
||||
totalin = totalex = round(price_unit * quantity, precision)
|
||||
tin = []
|
||||
tex = []
|
||||
for tax in taxes:
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<separator/>
|
||||
<filter string="Draft" icon="terp-document-new" domain="[('state','=','draft')]" help = "Draft Invoices"/>
|
||||
<filter string="Pro-forma" icon="terp-gtk-media-pause" domain="['|', ('state','=','proforma'),('state','=','proforma2')]" help = "Pro-forma Invoices"/>
|
||||
<filter string="Invoiced" name="current" icon="terp-check" domain="[('state','not in', ('draft','cancel'))]" help = "Open and Paid Invoices"/>
|
||||
<filter string="Invoiced" name="current" icon="terp-check" domain="[('state','not in', ('draft','cancel','proforma','proforma2'))]" help = "Open and Paid Invoices"/>
|
||||
<separator/>
|
||||
<filter icon="terp-personal" string="Customer" name="customer" domain="['|', ('type','=','out_invoice'),('type','=','out_refund')]" help="Customer Invoices And Refunds"/>
|
||||
<filter icon="terp-personal" string="Supplier" domain="['|', ('type','=','in_invoice'),('type','=','in_refund')]" help="Supplier Invoices And Refunds"/>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import osv
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from common_report_header import common_report_header
|
||||
|
@ -29,14 +30,12 @@ except ImportError:
|
|||
import xlwt
|
||||
|
||||
|
||||
class tax_report(http.Controller, common_report_header):
|
||||
class tax_report(osv.AbstractModel, common_report_header):
|
||||
_name = 'report.account.report_vat'
|
||||
|
||||
@http.route(['/report/account.report_vat'], type='http', auth='user', website=True, multilang=True)
|
||||
def report_account_tax(self, **data):
|
||||
def render_html(self, cr, uid, ids, data=None, context=None):
|
||||
report_obj = request.registry['report']
|
||||
self.cr, self.uid, self.pool = request.cr, request.uid, request.registry
|
||||
|
||||
data = report_obj.eval_params(data)
|
||||
self.cr, self.uid, self.context = cr, uid, context
|
||||
|
||||
res = {}
|
||||
self.period_ids = []
|
||||
|
@ -54,23 +53,23 @@ class tax_report(http.Controller, common_report_header):
|
|||
'based_on': self._get_basedon(data),
|
||||
'period_from': self.get_start_period(data),
|
||||
'period_to': self.get_end_period(data),
|
||||
'taxlines': self._get_lines(self._get_basedon(data), company_id=data['form']['company_id']),
|
||||
'taxlines': self._get_lines(self._get_basedon(data), company_id=data['form']['company_id'], cr=cr, uid=uid),
|
||||
}
|
||||
return request.registry['report'].render(self.cr, self.uid, [], 'account.report_vat', docargs)
|
||||
return report_obj.render(self.cr, self.uid, [], 'account.report_vat', docargs, context=context)
|
||||
|
||||
def _get_basedon(self, form):
|
||||
return form['form']['based_on']
|
||||
|
||||
def _get_lines(self, based_on, company_id=False, parent=False, level=0, context=None):
|
||||
def _get_lines(self, based_on, company_id=False, parent=False, level=0, context=None, cr=None, uid=None):
|
||||
period_list = self.period_ids
|
||||
res = self._get_codes(based_on, company_id, parent, level, period_list, context=context)
|
||||
res = self._get_codes(based_on, company_id, parent, level, period_list, cr=cr, uid=uid, context=context)
|
||||
if period_list:
|
||||
res = self._add_codes(based_on, res, period_list, context=context)
|
||||
else:
|
||||
self.cr.execute ("select id from account_fiscalyear")
|
||||
fy = self.cr.fetchall()
|
||||
self.cr.execute ("select id from account_period where fiscalyear_id = %s",(fy[0][0],))
|
||||
periods = self.cr.fetchall()
|
||||
cr.execute ("select id from account_fiscalyear")
|
||||
fy = cr.fetchall()
|
||||
cr.execute ("select id from account_period where fiscalyear_id = %s",(fy[0][0],))
|
||||
periods = cr.fetchall()
|
||||
for p in periods:
|
||||
period_list.append(p[0])
|
||||
res = self._add_codes(based_on, res, period_list, context=context)
|
||||
|
@ -90,7 +89,7 @@ class tax_report(http.Controller, common_report_header):
|
|||
}
|
||||
|
||||
top_result.append(res_dict)
|
||||
res_general = self._get_general(res[i][1].id, period_list, company_id, based_on, context=context)
|
||||
res_general = self._get_general(res[i][1].id, period_list, company_id, based_on, cr=cr, uid=uid, context=context)
|
||||
ind_general = 0
|
||||
while ind_general < len(res_general):
|
||||
res_general[ind_general]['type'] = 2
|
||||
|
@ -101,14 +100,14 @@ class tax_report(http.Controller, common_report_header):
|
|||
i+=1
|
||||
return top_result
|
||||
|
||||
def _get_general(self, tax_code_id, period_list, company_id, based_on, context=None):
|
||||
def _get_general(self, tax_code_id, period_list, company_id, based_on, cr=None, uid=None, context=None):
|
||||
if not self.display_detail:
|
||||
return []
|
||||
res = []
|
||||
obj_account = self.pool.get('account.account')
|
||||
periods_ids = tuple(period_list)
|
||||
if based_on == 'payments':
|
||||
self.cr.execute('SELECT SUM(line.tax_amount) AS tax_amount, \
|
||||
cr.execute('SELECT SUM(line.tax_amount) AS tax_amount, \
|
||||
SUM(line.debit) AS debit, \
|
||||
SUM(line.credit) AS credit, \
|
||||
COUNT(*) AS count, \
|
||||
|
@ -132,7 +131,7 @@ class tax_report(http.Controller, common_report_header):
|
|||
company_id, periods_ids, 'paid',))
|
||||
|
||||
else:
|
||||
self.cr.execute('SELECT SUM(line.tax_amount) AS tax_amount, \
|
||||
cr.execute('SELECT SUM(line.tax_amount) AS tax_amount, \
|
||||
SUM(line.debit) AS debit, \
|
||||
SUM(line.credit) AS credit, \
|
||||
COUNT(*) AS count, \
|
||||
|
@ -149,23 +148,21 @@ class tax_report(http.Controller, common_report_header):
|
|||
AND account.active \
|
||||
GROUP BY account.id,account.name,account.code', ('draft', tax_code_id,
|
||||
company_id, periods_ids,))
|
||||
res = self.cr.dictfetchall()
|
||||
res = cr.dictfetchall()
|
||||
|
||||
i = 0
|
||||
while i<len(res):
|
||||
res[i]['account'] = obj_account.browse(self.cr, self.uid, res[i]['account_id'], context=context)
|
||||
res[i]['account'] = obj_account.browse(cr, uid, res[i]['account_id'], context=context)
|
||||
i+=1
|
||||
return res
|
||||
|
||||
def _get_codes(self, based_on, company_id, parent=False, level=0, period_list=None, context=None):
|
||||
def _get_codes(self, based_on, company_id, parent=False, level=0, period_list=None, cr=None, uid=None, context=None):
|
||||
obj_tc = self.pool.get('account.tax.code')
|
||||
ids = obj_tc.search(self.cr, self.uid, [('parent_id','=',parent),('company_id','=',company_id)], order='sequence', context=context)
|
||||
|
||||
ids = obj_tc.search(cr, uid, [('parent_id', '=', parent), ('company_id', '=', company_id)], order='sequence', context=context)
|
||||
res = []
|
||||
for code in obj_tc.browse(self.cr, self.uid, ids, {'based_on': based_on}):
|
||||
for code in obj_tc.browse(cr, uid, ids, {'based_on': based_on}):
|
||||
res.append(('.'*2*level, code))
|
||||
|
||||
res += self._get_codes(based_on, company_id, code.id, level+1, context=context)
|
||||
res += self._get_codes(based_on, company_id, code.id, level+1, cr=cr, uid=uid, context=context)
|
||||
return res
|
||||
|
||||
def _add_codes(self, based_on, account_list=None, period_list=None, context=None):
|
||||
|
@ -187,9 +184,6 @@ class tax_report(http.Controller, common_report_header):
|
|||
res.append((account[0], code))
|
||||
return res
|
||||
|
||||
def _get_currency(self, form, context=None):
|
||||
return self.pool.get('res.company').browse(self.cr, self.uid, form['company_id'], context=context).currency_id.name
|
||||
|
||||
def sort_result(self, accounts, context=None):
|
||||
result_accounts = []
|
||||
ind=0
|
||||
|
@ -206,7 +200,8 @@ class tax_report(http.Controller, common_report_header):
|
|||
bcl_rup_ind = ind - 1
|
||||
|
||||
while (bcl_current_level >= int(accounts[bcl_rup_ind]['level']) and bcl_rup_ind >= 0 ):
|
||||
res_tot = { 'code': accounts[bcl_rup_ind]['code'],
|
||||
res_tot = {
|
||||
'code': accounts[bcl_rup_ind]['code'],
|
||||
'name': '',
|
||||
'debit': 0,
|
||||
'credit': 0,
|
||||
|
@ -229,25 +224,23 @@ class tax_report(http.Controller, common_report_header):
|
|||
|
||||
return result_accounts
|
||||
|
||||
|
||||
class tax_report_xls(http.Controller):
|
||||
|
||||
@http.route(['/report/account.report_vat_xls'], type='http', auth='user', website=True, multilang=True)
|
||||
def report_account_tax_xls(self, **data):
|
||||
report_obj = request.registry['report']
|
||||
self.cr, self.uid, self.pool = request.cr, request.uid, request.registry
|
||||
|
||||
data = report_obj.eval_params(data)
|
||||
# Very ugly lines, only for the proof of concept of 'controller' report
|
||||
taxreport_obj = request.registry['report.account.report_vat']
|
||||
from openerp.addons.report.controllers.main import ReportController
|
||||
eval_params = ReportController()._eval_params
|
||||
|
||||
res = {}
|
||||
self.period_ids = []
|
||||
period_obj = self.pool.get('account.period')
|
||||
self.display_detail = data['form']['display_detail']
|
||||
res['periods'] = ''
|
||||
res['fiscalyear'] = data['form'].get('fiscalyear_id', False)
|
||||
cr, uid = request.cr, request.uid
|
||||
data = eval_params(data)
|
||||
data = {'form': data}
|
||||
|
||||
if data['form'].get('period_from', False) and data['form'].get('period_to', False):
|
||||
self.period_ids = period_obj.build_ctx_periods(self.cr, self.uid, data['form']['period_from'], data['form']['period_to'])
|
||||
|
||||
content = ''
|
||||
lines = self._get_lines(self._get_basedon(data), company_id=data['form']['company_id'])
|
||||
taxreport_obj.render_html(cr, uid, [], data=data)
|
||||
lines = taxreport_obj._get_lines(taxreport_obj._get_basedon(data), company_id=data['form']['company_id'], cr=cr, uid=uid)
|
||||
|
||||
if lines:
|
||||
xls = StringIO.StringIO()
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<newline/>
|
||||
<field name="result_selection"/>
|
||||
<field name="direction_selection"/>
|
||||
<field name="target_move"/>
|
||||
</group>
|
||||
<field name="journal_ids" required="0" invisible="1"/>
|
||||
<footer>
|
||||
|
|
|
@ -41,6 +41,23 @@ class account_voucher(osv.osv):
|
|||
'number': fields.char('Number', size=32),
|
||||
}
|
||||
|
||||
def _amount_to_text(self, cr, uid, amount, currency_id, context=None):
|
||||
# Currency complete name is not available in res.currency model
|
||||
# Exceptions done here (EUR, USD, BRL) cover 75% of cases
|
||||
# For other currencies, display the currency code
|
||||
currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
|
||||
if currency.name.upper() == 'EUR':
|
||||
currency_name = 'Euro'
|
||||
elif currency.name.upper() == 'USD':
|
||||
currency_name = 'Dollars'
|
||||
elif currency.name.upper() == 'BRL':
|
||||
currency_name = 'reais'
|
||||
else:
|
||||
currency_name = currency.name
|
||||
#TODO : generic amount_to_text is not ready yet, otherwise language (and country) and currency can be passed
|
||||
#amount_in_word = amount_to_text(amount, context=context)
|
||||
return amount_to_text(amount, currency=currency_name)
|
||||
|
||||
def onchange_amount(self, cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=None):
|
||||
""" Inherited - add amount_in_word and allow_check_writting in returned value dictionary """
|
||||
if not context:
|
||||
|
@ -48,22 +65,7 @@ class account_voucher(osv.osv):
|
|||
default = super(account_voucher, self).onchange_amount(cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=context)
|
||||
if 'value' in default:
|
||||
amount = 'amount' in default['value'] and default['value']['amount'] or amount
|
||||
|
||||
# Currency complete name is not available in res.currency model
|
||||
# Exceptions done here (EUR, USD, BRL) cover 75% of cases
|
||||
# For other currencies, display the currency code
|
||||
currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
|
||||
if currency.name.upper() == 'EUR':
|
||||
currency_name = 'Euro'
|
||||
elif currency.name.upper() == 'USD':
|
||||
currency_name = 'Dollars'
|
||||
elif currency.name.upper() == 'BRL':
|
||||
currency_name = 'reais'
|
||||
else:
|
||||
currency_name = currency.name
|
||||
#TODO : generic amount_to_text is not ready yet, otherwise language (and country) and currency can be passed
|
||||
#amount_in_word = amount_to_text(amount, context=context)
|
||||
amount_in_word = amount_to_text(amount, currency=currency_name)
|
||||
amount_in_word = self._amount_to_text(cr, uid, amount, currency_id, context=context)
|
||||
default['value'].update({'amount_in_word':amount_in_word})
|
||||
if journal_id:
|
||||
allow_check_writing = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context).allow_check_writing
|
||||
|
@ -92,6 +94,19 @@ class account_voucher(osv.osv):
|
|||
},
|
||||
'nodestroy': True
|
||||
}
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if vals.get('amount') and vals.get('journal_id') and 'amount_in_word' not in vals:
|
||||
vals['amount_in_word'] = self._amount_to_text(cr, uid, vals['amount'], vals.get('currency_id') or \
|
||||
self.pool['account.journal'].browse(cr, uid, vals['journal_id'], context=context).currency.id or \
|
||||
self.pool['res.company'].browse(cr, uid, vals['company_id']).currency_id.id, context=context)
|
||||
return super(account_voucher, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if vals.get('amount') and vals.get('journal_id') and 'amount_in_word' not in vals:
|
||||
vals['amount_in_word'] = self._amount_to_text(cr, uid, vals['amount'], vals.get('currency_id') or \
|
||||
self.pool['account.journal'].browse(cr, uid, vals['journal_id'], context=context).currency.id or \
|
||||
self.pool['res.company'].browse(cr, uid, vals['company_id']).currency_id.id, context=context)
|
||||
return super(account_voucher, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
|
||||
"""
|
||||
|
|
|
@ -463,14 +463,12 @@ def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_val
|
|||
# if at least one modification has been found
|
||||
for model_id, resource_id in lines:
|
||||
line_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, model_id).model
|
||||
name = pool.get(line_model).name_get(cr, uid, [resource_id])[0][1]
|
||||
|
||||
vals = {
|
||||
'method': method,
|
||||
'object_id': model_id,
|
||||
'user_id': uid,
|
||||
'res_id': resource_id,
|
||||
'name': name,
|
||||
}
|
||||
if (model_id, resource_id) not in old_values and method not in ('copy', 'read'):
|
||||
# the resource was not existing so we are forcing the method to 'create'
|
||||
|
@ -481,7 +479,11 @@ def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_val
|
|||
# the resource is not existing anymore so we are forcing the method to 'unlink'
|
||||
# (because it could also come with the value 'write' if we are deleting the
|
||||
# record through a one2many field)
|
||||
name = old_values[(model_id, resource_id)]['value'].get('name',False)
|
||||
vals.update({'method': 'unlink'})
|
||||
else :
|
||||
name = pool[line_model].name_get(cr, uid, [resource_id])[0][1]
|
||||
vals.update({'name': name})
|
||||
# create the audittrail log in super admin mode, only if a change has been detected
|
||||
if lines[(model_id, resource_id)]:
|
||||
log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals)
|
||||
|
|
|
@ -78,6 +78,9 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
|||
@http.route()
|
||||
def web_login(self, *args, **kw):
|
||||
ensure_db()
|
||||
if request.httprequest.method == 'GET' and request.session.uid and request.params.get('redirect'):
|
||||
# Redirect if already logged in and redirect param is present
|
||||
return http.redirect_with_hash(request.params.get('redirect'))
|
||||
providers = self.list_providers()
|
||||
|
||||
response = super(OAuthLogin, self).web_login(*args, **kw)
|
||||
|
|
|
@ -37,6 +37,9 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
|
|||
ensure_db()
|
||||
response = super(AuthSignupHome, self).web_login(*args, **kw)
|
||||
response.qcontext.update(self.get_auth_signup_config())
|
||||
if request.httprequest.method == 'GET' and request.session.uid and request.params.get('redirect'):
|
||||
# Redirect if already logged in and redirect param is present
|
||||
return http.redirect_with_hash(request.params.get('redirect'))
|
||||
return response
|
||||
|
||||
@http.route('/web/signup', type='http', auth='public', website=True, multilang=True)
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
##############################################################################
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
from urllib import urlencode
|
||||
from urlparse import urljoin
|
||||
import werkzeug
|
||||
|
||||
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
|
||||
from openerp.osv import osv, fields
|
||||
|
@ -53,7 +53,7 @@ class res_partner(osv.Model):
|
|||
(not partner.signup_expiration or dt <= partner.signup_expiration)
|
||||
return res
|
||||
|
||||
def _get_signup_url_for_action(self, cr, uid, ids, action='login', view_type=None, menu_id=None, res_id=None, model=None, context=None):
|
||||
def _get_signup_url_for_action(self, cr, uid, ids, action=None, view_type=None, menu_id=None, res_id=None, model=None, context=None):
|
||||
""" generate a signup url for the given partner ids and action, possibly overriding
|
||||
the url state components (menu_id, id, view_type) """
|
||||
if context is None:
|
||||
|
@ -81,6 +81,8 @@ class res_partner(osv.Model):
|
|||
continue # no signup token, no user, thus no signup url!
|
||||
|
||||
fragment = dict()
|
||||
if action:
|
||||
fragment['action'] = action
|
||||
if view_type:
|
||||
fragment['view_type'] = view_type
|
||||
if menu_id:
|
||||
|
@ -90,7 +92,10 @@ class res_partner(osv.Model):
|
|||
if res_id:
|
||||
fragment['id'] = res_id
|
||||
|
||||
res[partner.id] = urljoin(base_url, "/web/%s?%s#%s" % (route, urlencode(query), urlencode(fragment)))
|
||||
if fragment:
|
||||
query['redirect'] = '/web#' + werkzeug.url_encode(fragment)
|
||||
|
||||
res[partner.id] = urljoin(base_url, "/web/%s?%s" % (route, werkzeug.url_encode(query)))
|
||||
|
||||
return res
|
||||
|
||||
|
|
|
@ -456,7 +456,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
bFound = False
|
||||
LastFound = False
|
||||
for one_date in self.pool.get('calendar.event').get_recurrent_date_by_event(cr, uid, curEvent, context=context):
|
||||
in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S')
|
||||
in_date_format = one_date.replace(tzinfo=None)
|
||||
LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, cron_interval, notif=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
|
@ -490,7 +490,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
bFound = False
|
||||
LastFound = False
|
||||
for one_date in self.pool.get("calendar.event").get_recurrent_date_by_event(cr, uid, curEvent, context=context):
|
||||
in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S')
|
||||
in_date_format = one_date.replace(tzinfo=None)
|
||||
LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner.calendar_last_notif_ack, mail=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
|
@ -680,19 +680,23 @@ class calendar_event(osv.Model):
|
|||
return [d.astimezone(pytz.UTC) for d in rset1]
|
||||
|
||||
def _get_recurrency_end_date(self, data, context=None):
|
||||
if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')):
|
||||
data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S')
|
||||
if data.get('rrule_type') in ('daily', unicode('count')):
|
||||
rel_date = relativedelta(days=data.get('count') + 1)
|
||||
elif data.get('rrule_type') in ('weekly', unicode('weekly')):
|
||||
rel_date = relativedelta(days=(data.get('count') + 1) * 7)
|
||||
elif data.get('rrule_type') in ('monthly', unicode('monthly')):
|
||||
rel_date = relativedelta(months=data.get('count') + 1)
|
||||
elif data.get('rrule_type') in ('yearly', unicode('yearly')):
|
||||
rel_date = relativedelta(years=data.get('count') + 1)
|
||||
end_date = data_date_deadline + rel_date
|
||||
else:
|
||||
end_date = data.get('end_date')
|
||||
if not data.get('recurrency'):
|
||||
return False
|
||||
|
||||
end_type = data.get('end_type')
|
||||
end_date = data.get('end_date')
|
||||
|
||||
if end_type == 'count' and all(data.get(key) for key in ['count', 'rrule_type', 'date_deadline']):
|
||||
count = data['count'] + 1
|
||||
delay, mult = {
|
||||
'daily': ('days', 1),
|
||||
'weekly': ('days', 7),
|
||||
'monthly': ('months', 1),
|
||||
'yearly': ('years', 1),
|
||||
}[data['rrule_type']]
|
||||
|
||||
deadline = datetime.strptime(data['date_deadline'], tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
return deadline + relativedelta(**{delay: count * mult})
|
||||
return end_date
|
||||
|
||||
def _find_my_attendee(self, cr, uid, meeting_ids, context=None):
|
||||
|
@ -774,7 +778,11 @@ class calendar_event(osv.Model):
|
|||
result[event] = ""
|
||||
return result
|
||||
|
||||
# retro compatibility function
|
||||
def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None):
|
||||
return self._set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=context)
|
||||
|
||||
def _set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=None):
|
||||
if not isinstance(ids, list):
|
||||
ids = [ids]
|
||||
data = self._get_empty_rrule_data()
|
||||
|
@ -812,7 +820,7 @@ class calendar_event(osv.Model):
|
|||
'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
|
||||
'location': fields.char('Location', help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}),
|
||||
'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}),
|
||||
'rrule': fields.function(_get_rulestring, type='char', fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
|
||||
'rrule': fields.function(_get_rulestring, type='char', fnct_inv=_set_rulestring, store=True, string='Recurrent Rule'),
|
||||
'rrule_type': fields.selection([('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'), ('yearly', 'Year(s)')], 'Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval"),
|
||||
'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
|
||||
'recurrent_id': fields.integer('Recurrent ID'),
|
||||
|
@ -1169,7 +1177,7 @@ class calendar_event(osv.Model):
|
|||
#repeat monthly by nweekday ((weekday, weeknumber), )
|
||||
if r._bynweekday:
|
||||
data['week_list'] = day_list[r._bynweekday[0][0]].upper()
|
||||
data['byday'] = r._bynweekday[0][1]
|
||||
data['byday'] = str(r._bynweekday[0][1])
|
||||
data['month_by'] = 'day'
|
||||
data['rrule_type'] = 'monthly'
|
||||
|
||||
|
@ -1423,7 +1431,7 @@ class calendar_event(osv.Model):
|
|||
# set end_date for calendar searching
|
||||
if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \
|
||||
(values.get('rrule_type') or values.get('count') or values.get('date') or values.get('date_deadline')):
|
||||
for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context):
|
||||
for data in self.read(cr, uid, ids, ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context):
|
||||
end_date = self._get_recurrency_end_date(data, context=context)
|
||||
super(calendar_event, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context)
|
||||
|
||||
|
@ -1454,11 +1462,12 @@ class calendar_event(osv.Model):
|
|||
if not 'user_id' in vals: # Else bug with quick_create when we are filter on an other user
|
||||
vals['user_id'] = uid
|
||||
|
||||
if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \
|
||||
(vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')):
|
||||
vals['end_date'] = self._get_recurrency_end_date(vals, context=context)
|
||||
|
||||
res = super(calendar_event, self).create(cr, uid, vals, context=context)
|
||||
|
||||
data = self.read(cr, uid, [res], ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context)[0]
|
||||
end_date = self._get_recurrency_end_date(data, context=context)
|
||||
self.write(cr, uid, [res], {'end_date': end_date}, context=context)
|
||||
|
||||
self.create_attendees(cr, uid, [res], context=context)
|
||||
return res
|
||||
|
||||
|
|
|
@ -65,3 +65,20 @@
|
|||
-
|
||||
!python {model: calendar.event}: |
|
||||
self.write(cr, uid, [ref("calendar_event_alldaytestevent0")], {'alarm_ids': [(6,0,[ref("res_alarm_daybeforeeventstarts0")])]})
|
||||
-
|
||||
I create a recuring rule for my event
|
||||
-
|
||||
!record {model: calendar.event, id: calendar.event_sprintreview1}:
|
||||
name: Begin of month meeting
|
||||
date: !eval time.strftime('%Y-%m-%d 12:00:00')
|
||||
recurrency: true
|
||||
rrule: FREQ=MONTHLY;INTERVAL=1;COUNT=12;BYDAY=1MO
|
||||
-
|
||||
I check that the attributes are set correctly
|
||||
-
|
||||
!assert {model: calendar.event, id: calendar.event_sprintreview1}:
|
||||
- rrule_type == 'monthly'
|
||||
- count == 12
|
||||
- month_by == 'day'
|
||||
- byday == '1'
|
||||
- week_list == 'MO'
|
||||
|
|
|
@ -313,7 +313,10 @@ class crm_lead(format_address, osv.osv):
|
|||
stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context)
|
||||
if not stage.on_change:
|
||||
return {'value': {}}
|
||||
return {'value': {'probability': stage.probability}}
|
||||
vals = {'probability': stage.probability}
|
||||
if stage.probability >= 100 or (stage.probability == 0 and stage.sequence > 1):
|
||||
vals['date_closed'] = fields.datetime.now()
|
||||
return {'value': vals}
|
||||
|
||||
def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
|
||||
values = {}
|
||||
|
@ -407,7 +410,7 @@ class crm_lead(format_address, osv.osv):
|
|||
'probability = 0 %, select "Change Probability Automatically".\n'
|
||||
'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
|
||||
for stage_id, lead_ids in stages_leads.items():
|
||||
self.write(cr, uid, lead_ids, {'stage_id': stage_id, 'date_closed': fields.datetime.now()}, context=context)
|
||||
self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context)
|
||||
return True
|
||||
|
||||
def case_mark_won(self, cr, uid, ids, context=None):
|
||||
|
@ -428,7 +431,7 @@ class crm_lead(format_address, osv.osv):
|
|||
'probability = 100 % and select "Change Probability Automatically".\n'
|
||||
'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
|
||||
for stage_id, lead_ids in stages_leads.items():
|
||||
self.write(cr, uid, lead_ids, {'stage_id': stage_id, 'date_closed': fields.datetime.now()}, context=context)
|
||||
self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context)
|
||||
return True
|
||||
|
||||
def case_escalate(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -95,7 +95,6 @@
|
|||
<form string="Leads Form" version="7.0">
|
||||
<header>
|
||||
<button name="%(crm.action_crm_lead2opportunity_partner)d" string="Convert to Opportunity" type="action"
|
||||
attrs="{'invisible': [('probability', '=', 100)]}"
|
||||
help="Convert to Opportunity" class="oe_highlight"/>
|
||||
<field name="stage_id" widget="statusbar" clickable="True"
|
||||
domain="['&', '|', ('case_default', '=', True), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
Salesman create a mass convert wizard and convert all the leads.
|
||||
-
|
||||
!python {model: crm.lead2opportunity.partner.mass}: |
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01"), 'no_force_assignation': False})
|
||||
id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department'), 'deduplicate': False}, context=context)
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01")})
|
||||
id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department'), 'deduplicate': False, 'force_assignation': True}, context=context)
|
||||
self.mass_convert(cr, uid, [id], context=context)
|
||||
-
|
||||
The leads should now be opps with a salesman and a salesteam. Also, salesmen should have been assigned following a round-robin method.
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import email_split
|
||||
import re
|
||||
|
||||
class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
|
@ -41,21 +42,25 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
def onchange_action(self, cr, uid, ids, action, context=None):
|
||||
return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}}
|
||||
|
||||
def _get_duplicated_leads(self, cr, uid, partner_id, email, context=None):
|
||||
def _get_duplicated_leads(self, cr, uid, partner_id, email, include_lost=False, context=None):
|
||||
"""
|
||||
Search for opportunities that have the same partner and that arent done or cancelled
|
||||
"""
|
||||
lead_obj = self.pool.get('crm.lead')
|
||||
results = []
|
||||
emails = set(email_split(email) + [email])
|
||||
final_stage_domain = [('stage_id.probability', '<', 100), '|', ('stage_id.probability', '>', 0), ('stage_id.sequence', '<=', 1)]
|
||||
partner_match_domain = []
|
||||
for email in emails:
|
||||
partner_match_domain.append(('email_from', '=ilike', email))
|
||||
if partner_id:
|
||||
# Search for opportunities that have the same partner and that arent done or cancelled
|
||||
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')])
|
||||
for id in ids:
|
||||
results.append(id)
|
||||
email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
|
||||
if email:
|
||||
ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')])
|
||||
for id in ids:
|
||||
results.append(id)
|
||||
return list(set(results))
|
||||
|
||||
partner_match_domain.append(('partner_id', '=', partner_id))
|
||||
partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain
|
||||
if not partner_match_domain:
|
||||
return []
|
||||
domain = partner_match_domain
|
||||
if not include_lost:
|
||||
domain += final_stage_domain
|
||||
return lead_obj.search(cr, uid, domain)
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
"""
|
||||
|
@ -73,7 +78,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)
|
||||
email = lead.partner_id and lead.partner_id.email or lead.email_from
|
||||
|
||||
tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email))
|
||||
tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email, include_lost=True, context=context))
|
||||
tomerge = list(set(tomerge))
|
||||
|
||||
if 'action' in fields:
|
||||
|
@ -126,7 +131,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
leads = lead.browse(cr, uid, lead_ids, context=context)
|
||||
for lead_id in leads:
|
||||
partner_id = self._create_partner(cr, uid, lead_id.id, data.action, lead_id.partner_id.id, context=context)
|
||||
res = lead.convert_opportunity(cr, uid, [lead_id.id], partner_id, [], team_id, context=context)
|
||||
res = lead.convert_opportunity(cr, uid, [lead_id.id], partner_id, [], False, context=context)
|
||||
user_ids = vals.get('user_ids', False)
|
||||
if context.get('no_force_assignation'):
|
||||
leads_to_allocate = [lead_id.id for lead_id in leads if not lead_id.user_id]
|
||||
|
@ -195,8 +200,7 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
|||
('each_exist_or_create', 'Use existing partner or create'),
|
||||
('nothing', 'Do not link to a customer')
|
||||
], 'Related Customer', required=True),
|
||||
# Uncomment me in trunk
|
||||
# 'force_assignation': fields.boolean('Force assignation', help='If unchecked, this will leave the salesman of duplicated opportunities'),
|
||||
'force_assignation': fields.boolean('Force assignation', help='If unchecked, this will leave the salesman of duplicated opportunities'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
|
@ -272,10 +276,7 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
|||
active_ids = active_ids.difference(merged_lead_ids)
|
||||
active_ids = active_ids.union(remaining_lead_ids)
|
||||
ctx['active_ids'] = list(active_ids)
|
||||
# Remove me in trunk
|
||||
ctx['no_force_assignation'] = ctx.get('no_force_assignation', True)
|
||||
# Uncomment me in trunk
|
||||
# ctx['no_force_assignation'] = not data.force_assignation
|
||||
ctx['no_force_assignation'] = not data.force_assignation
|
||||
return self.action_apply(cr, uid, ids, context=ctx)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<div class="oe_title">
|
||||
<h3>
|
||||
<span class="oe_grey">( </span>
|
||||
<field name="partner_latitude" nolabel="1" readonly="1" class="oe_inline"/>
|
||||
<field name="partner_latitude" nolabel="1" class="oe_inline"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','<=',0)]}">N </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','>=',0)]}">S </span>
|
||||
<field name="partner_longitude" class="oe_inline" readonly="1" nolabel="1"/>
|
||||
<field name="partner_longitude" class="oe_inline" nolabel="1"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','<=',0)]}">E </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','>=',0)]}">W </span>
|
||||
<span class="oe_grey">) </span>
|
||||
|
@ -88,10 +88,10 @@
|
|||
<div>
|
||||
<h3>
|
||||
<span class="oe_grey">( </span>
|
||||
<field name="partner_latitude" nolabel="1" readonly="1" class="oe_inline"/>
|
||||
<field name="partner_latitude" nolabel="1" class="oe_inline"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','<=',0)]}">N </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','>=',0)]}">S </span>
|
||||
<field name="partner_longitude" class="oe_inline" readonly="1" nolabel="1"/>
|
||||
<field name="partner_longitude" class="oe_inline" nolabel="1"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','<=',0)]}">E </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','>=',0)]}">W </span>
|
||||
<span class="oe_grey">) </span>
|
||||
|
|
|
@ -449,7 +449,7 @@ class email_template(osv.osv):
|
|||
ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, [res_id], context)[res_id] # take 0 ?
|
||||
|
||||
if report.report_type in ['qweb-html', 'qweb-pdf']:
|
||||
result, format = self.pool['report'].get_pdf(report, res_id, context=ctx), 'pdf'
|
||||
result, format = self.pool['report'].get_pdf(cr, uid, [res_id], report_service, context=ctx), 'pdf'
|
||||
else:
|
||||
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
|
||||
|
||||
|
|
|
@ -0,0 +1,963 @@
|
|||
# Amharic translation for openobject-addons
|
||||
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
|
||||
"PO-Revision-Date: 2014-03-18 13:39+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Amharic <am@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-19 05:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,name:hr.process_node_openerpuser0
|
||||
msgid "Openerp user"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_timesheet_sheet:0
|
||||
msgid "Allow timesheets validation by managers"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,requirements:0
|
||||
msgid "Requirements"
|
||||
msgstr "አስፈላጊ"
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,name:hr.process_transition_contactofemployee0
|
||||
msgid "Link the employee to information"
|
||||
msgstr "የሰራተኞች መረጃ ማገናኘት"
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,sinid:0
|
||||
msgid "SIN No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_board_hr
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_dashboard
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_main
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_reporting
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_root
|
||||
#: model:ir.ui.menu,name:hr.menu_human_resources_configuration
|
||||
msgid "Human Resources"
|
||||
msgstr "የሰው ሀይል አስተዳደር"
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,image_medium:0
|
||||
msgid ""
|
||||
"Medium-sized photo of the employee. It is automatically resized as a "
|
||||
"128x128px image, with aspect ratio preserved. Use this field in form views "
|
||||
"or some kanban views."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Time Tracking"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: view:hr.job:0
|
||||
msgid "Group By..."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.view_department_form_installer
|
||||
msgid "Create Your Departments"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,no_of_employee:0
|
||||
msgid "Number of employees currently occupying this job position."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_evaluation:0
|
||||
msgid "Organize employees periodic evaluation"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,department_id:0
|
||||
#: view:hr.job:0
|
||||
#: field:hr.job,department_id:0
|
||||
#: model:ir.model,name:hr.model_hr_department
|
||||
msgid "Department"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,work_email:0
|
||||
msgid "Work Email"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,image:0
|
||||
msgid ""
|
||||
"This field holds the image used as photo for the employee, limited to "
|
||||
"1024x1024px."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_holidays:0
|
||||
msgid "This installs the module hr_holidays."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "Jobs"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "In Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_unread:0
|
||||
msgid "Unread Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,company_id:0
|
||||
#: view:hr.employee:0
|
||||
#: view:hr.job:0
|
||||
#: field:hr.job,company_id:0
|
||||
msgid "Company"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,no_of_recruitment:0
|
||||
msgid "Expected in Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:res.users,employee_ids:0
|
||||
msgid "Related employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: constraint:hr.employee.category:0
|
||||
msgid "Error! You cannot create recursive Categories."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_recruitment:0
|
||||
msgid "This installs the module hr_recruitment."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Birth"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_view_categ_form
|
||||
#: model:ir.ui.menu,name:hr.menu_view_employee_category_form
|
||||
msgid "Employee Tags"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "Launch Recruitement"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,name:hr.process_transition_employeeuser0
|
||||
msgid "Link a user to an employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,parent_id:0
|
||||
msgid "Parent Department"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_config
|
||||
msgid "Leaves"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Married"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_ids:0
|
||||
msgid "Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Talent Management"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_timesheet_sheet:0
|
||||
msgid "This installs the module hr_timesheet_sheet."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Mobile:"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Position"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,message_unread:0
|
||||
msgid "If checked new messages require your attention."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,color:0
|
||||
msgid "Color Index"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,note:hr.process_transition_employeeuser0
|
||||
msgid ""
|
||||
"The Related user field on the Employee form allows to link the OpenERP user "
|
||||
"(and her rights) to the employee."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,image_medium:0
|
||||
msgid "Medium-sized photo"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,identification_id:0
|
||||
msgid "Identification No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,gender:0
|
||||
msgid "Female"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_new_config
|
||||
msgid "Attendance"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,work_phone:0
|
||||
msgid "Work Phone"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee.category,child_ids:0
|
||||
msgid "Child Categories"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,description:0
|
||||
#: model:ir.model,name:hr.model_hr_job
|
||||
msgid "Job Description"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,work_location:0
|
||||
msgid "Office Location"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_follower_ids:0
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: model:ir.model,name:hr.model_hr_employee
|
||||
#: model:process.node,name:hr.process_node_employee0
|
||||
msgid "Employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,note:hr.process_node_employeecontact0
|
||||
msgid "Other information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,image_small:0
|
||||
msgid ""
|
||||
"Small-sized photo of the employee. It is automatically resized as a 64x64px "
|
||||
"image, with aspect ratio preserved. Use this field anywhere a small image is "
|
||||
"required."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,birthday:0
|
||||
msgid "Date of Birth"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,no_of_recruitment:0
|
||||
msgid "Number of new employees you expect to recruit."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.client,name:hr.action_client_hr_menu
|
||||
msgid "Open HR Menu"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,message_summary:0
|
||||
msgid ""
|
||||
"Holds the Chatter summary (number of messages, ...). This summary is "
|
||||
"directly in html format in order to be inserted in kanban views."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_account_analytic_analysis:0
|
||||
msgid ""
|
||||
"This installs the module account_analytic_analysis, which will install sales "
|
||||
"management too."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:board.board:0
|
||||
msgid "Human Resources Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,job_id:0
|
||||
#: view:hr.job:0
|
||||
msgid "Job"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,no_of_employee:0
|
||||
msgid "Current Number of Employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,member_ids:0
|
||||
msgid "Members"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_configuration
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,note:hr.process_node_employee0
|
||||
msgid "Employee form and structure"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_expense:0
|
||||
msgid "Manage employees expenses"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Tel:"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Divorced"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee.category,parent_id:0
|
||||
msgid "Parent Category"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
#: model:ir.actions.act_window,name:hr.open_module_tree_department
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_department_tree
|
||||
msgid "Departments"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,name:hr.process_node_employeecontact0
|
||||
msgid "Employee Contact"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.action_hr_job
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to define a new job position.\n"
|
||||
" </p><p>\n"
|
||||
" Job Positions are used to define jobs and their "
|
||||
"requirements.\n"
|
||||
" You can keep track of the number of employees you have per "
|
||||
"job\n"
|
||||
" position and follow the evolution according to what you "
|
||||
"planned\n"
|
||||
" for the future.\n"
|
||||
" </p><p>\n"
|
||||
" You can attach a survey to a job position. It will be used "
|
||||
"in\n"
|
||||
" the recruitment process to evaluate the applicants for this "
|
||||
"job\n"
|
||||
" position.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,gender:0
|
||||
msgid "Male"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid ""
|
||||
"$('.oe_employee_picture').load(function() { if($(this).width() > "
|
||||
"$(this).height()) { $(this).addClass('oe_employee_picture_wide') } });"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_evaluation:0
|
||||
msgid "This installs the module hr_evaluation."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: constraint:hr.employee:0
|
||||
msgid "Error! You cannot create recursive hierarchy of Employee(s)."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_attendance:0
|
||||
msgid "This installs the module hr_attendance."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,image_small:0
|
||||
msgid "Smal-sized photo"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee.category:0
|
||||
#: model:ir.model,name:hr.model_hr_employee_category
|
||||
msgid "Employee Category"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,category_ids:0
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_contract:0
|
||||
msgid "This installs the module hr_contract."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Related User"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee.category,name:0
|
||||
msgid "Category"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "Stop Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_attendance:0
|
||||
msgid "Install attendances feature"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,bank_account_id:0
|
||||
msgid "Employee bank salary account"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,note:0
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_view_employee_tree
|
||||
msgid "Employees Structure"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Contact Information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_holidays:0
|
||||
msgid "Manage holidays, leaves and allocation requests"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,child_ids:0
|
||||
msgid "Child Departments"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: view:hr.job:0
|
||||
#: field:hr.job,state:0
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,otherid:0
|
||||
msgid "Other Id"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.process,name:hr.process_process_employeecontractprocess0
|
||||
msgid "Employee Contract"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Contracts"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,message_ids:0
|
||||
msgid "Messages and communication history"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,ssnid:0
|
||||
msgid "SSN No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_is_follower:0
|
||||
msgid "Is a Follower"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_recruitment:0
|
||||
msgid "Manage the recruitment process"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Human Resources Management"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Install your country's payroll"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,bank_account_id:0
|
||||
msgid "Bank Account Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
msgid "Companies"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_summary:0
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,note:hr.process_transition_contactofemployee0
|
||||
msgid ""
|
||||
"In the Employee form, there are different kind of information like Contact "
|
||||
"information."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.open_view_employee_list_my
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to add a new employee.\n"
|
||||
" </p><p>\n"
|
||||
" With just a quick glance on the OpenERP employee screen, "
|
||||
"you\n"
|
||||
" can easily find all the information you need for each "
|
||||
"person;\n"
|
||||
" contact data, job position, availability, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "HR Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Citizenship & Other Info"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: constraint:hr.department:0
|
||||
msgid "Error! You cannot create recursive departments."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,address_id:0
|
||||
msgid "Working Address"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Public Information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,marital:0
|
||||
msgid "Marital Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.model,name:hr.model_ir_actions_act_window
|
||||
msgid "ir.actions.act_window"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,last_login:0
|
||||
msgid "Latest Connection"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,image:0
|
||||
msgid "Photo"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.open_module_tree_department
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to create a department.\n"
|
||||
" </p><p>\n"
|
||||
" OpenERP's department structure is used to manage all "
|
||||
"documents\n"
|
||||
" related to employees by departments: expenses, timesheets,\n"
|
||||
" leaves and holidays, recruitments, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_timesheet:0
|
||||
msgid "This installs the module hr_timesheet."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,expected_employees:0
|
||||
msgid ""
|
||||
"Expected number of employees for this job position after new recruitment."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.view_department_form_installer
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to define a new department.\n"
|
||||
" </p><p>\n"
|
||||
" Your departments structure is used to manage all documents\n"
|
||||
" related to employees by departments: expenses and "
|
||||
"timesheets,\n"
|
||||
" leaves and holidays, recruitments, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Personal Information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,city:0
|
||||
msgid "City"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,passport_id:0
|
||||
msgid "Passport No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,mobile_phone:0
|
||||
msgid "Work Mobile"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.job,state:0
|
||||
msgid "Recruitement in Progress"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_account_analytic_analysis:0
|
||||
msgid ""
|
||||
"Allow invoicing based on timesheets (the sale application will be installed)"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee.category:0
|
||||
msgid "Employees Categories"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,address_home_id:0
|
||||
msgid "Home Address"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_timesheet:0
|
||||
msgid "Manage timesheets"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_payroll_modules
|
||||
msgid "Payroll"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Single"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,name:0
|
||||
msgid "Job Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "In Position"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_payroll:0
|
||||
msgid "This installs the module hr_payroll."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_contract:0
|
||||
msgid "Record contracts per employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
msgid "department"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,country_id:0
|
||||
msgid "Nationality"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Additional Features"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,notes:0
|
||||
msgid "Notes"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.action2
|
||||
msgid "Subordinate Hierarchy"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,resource_id:0
|
||||
msgid "Resource"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,complete_name:0
|
||||
#: field:hr.employee,name_related:0
|
||||
#: field:hr.employee.category,complete_name:0
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,gender:0
|
||||
msgid "Gender"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee.category,employee_ids:0
|
||||
#: field:hr.job,employee_ids:0
|
||||
#: model:ir.actions.act_window,name:hr.hr_employee_normal_action_tree
|
||||
#: model:ir.actions.act_window,name:hr.open_view_employee_list
|
||||
#: model:ir.actions.act_window,name:hr.open_view_employee_list_my
|
||||
#: model:ir.ui.menu,name:hr.menu_open_view_employee_list_my
|
||||
msgid "Employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,sinid:0
|
||||
msgid "Social Insurance Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,name:0
|
||||
msgid "Department Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_reporting_timesheet
|
||||
msgid "Reports"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_payroll:0
|
||||
msgid "Manage payroll"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
#: model:ir.actions.act_window,name:hr.action_human_resources_configuration
|
||||
msgid "Configure Human Resources"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.job,state:0
|
||||
msgid "No Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,ssnid:0
|
||||
msgid "Social Security Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,note:hr.process_node_openerpuser0
|
||||
msgid "Creation of a OpenERP user"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,login:0
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,expected_employees:0
|
||||
msgid "Total Forecasted Employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,state:0
|
||||
msgid ""
|
||||
"By default 'In position', set it to 'In Recruitment' if recruitment process "
|
||||
"is going on for this job position."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.model,name:hr.model_res_users
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.action_hr_job
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_job
|
||||
msgid "Job Positions"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.open_board_hr
|
||||
msgid ""
|
||||
"<div class=\"oe_empty_custom_dashboard\">\n"
|
||||
" <p>\n"
|
||||
" <b>Human Resources dashboard is empty.</b>\n"
|
||||
" </p><p>\n"
|
||||
" To add your first report into this dashboard, go to any\n"
|
||||
" menu, switch to list or graph view, and click <i>'Add "
|
||||
"to\n"
|
||||
" Dashboard'</i> in the extended search options.\n"
|
||||
" </p><p>\n"
|
||||
" You can filter and group data before inserting into the\n"
|
||||
" dashboard using the search options.\n"
|
||||
" </p>\n"
|
||||
" </div>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,coach_id:0
|
||||
msgid "Coach"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: sql_constraint:hr.job:0
|
||||
msgid "The name of the job position must be unique per company!"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_expense:0
|
||||
msgid "This installs the module hr_expense."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.model,name:hr.model_hr_config_settings
|
||||
msgid "hr.config.settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,manager_id:0
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,parent_id:0
|
||||
msgid "Manager"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Widower"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,child_ids:0
|
||||
msgid "Subordinates"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Apply"
|
||||
msgstr ""
|
|
@ -126,7 +126,7 @@ class mail_group(osv.Model):
|
|||
vals['menu_id'] = menu_id
|
||||
|
||||
# Create group and alias
|
||||
create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name)
|
||||
create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name, mail_create_nolog=True)
|
||||
mail_group_id = super(mail_group, self).create(cr, uid, vals, context=create_context)
|
||||
group = self.browse(cr, uid, mail_group_id, context=context)
|
||||
self.pool.get('mail.alias').write(cr, uid, [group.alias_id.id], {"alias_force_thread_id": mail_group_id, 'alias_parent_thread_id': mail_group_id}, context)
|
||||
|
|
|
@ -161,6 +161,7 @@ class mail_thread(osv.AbstractModel):
|
|||
if res[id]['message_unread_count']:
|
||||
title = res[id]['message_unread_count'] > 1 and _("You have %d unread messages") % res[id]['message_unread_count'] or _("You have one unread message")
|
||||
res[id]['message_summary'] = "<span class='oe_kanban_mail_new' title='%s'><span class='oe_e'>9</span> %d %s</span>" % (title, res[id].pop('message_unread_count'), _("New"))
|
||||
res[id].pop('message_unread_count', None)
|
||||
return res
|
||||
|
||||
def read_followers_data(self, cr, uid, follower_ids, context=None):
|
||||
|
|
|
@ -76,8 +76,8 @@ openerp.mail = function (session) {
|
|||
*/
|
||||
get_text2html: function (text) {
|
||||
return text
|
||||
.replace(/[\n\r]/g,'<br/>')
|
||||
.replace(/((?:https?|ftp):\/\/[\S]+)/g,'<a href="$1">$1</a> ')
|
||||
.replace(/[\n\r]/g,'<br/>')
|
||||
},
|
||||
|
||||
/* Returns the complete domain with "&"
|
||||
|
|
|
@ -19,24 +19,22 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.osv import osv
|
||||
|
||||
|
||||
class bom_structure(http.Controller):
|
||||
class bom_structure(osv.AbstractModel):
|
||||
_name = 'report.mrp.report_mrpbomstructure'
|
||||
|
||||
@http.route(['/report/mrp.report_mrpbomstructure/<docids>'], type='http', auth='user', website=True, multilang=True)
|
||||
def report_mrpbomstructure(self, docids):
|
||||
ids = [int(i) for i in docids.split(',')]
|
||||
ids = list(set(ids))
|
||||
report_obj = request.registry['mrp.bom']
|
||||
docs = report_obj.browse(request.cr, request.uid, ids, context=request.context)
|
||||
def render_html(self, cr, uid, ids, data=None, context=None):
|
||||
mrpbom_obj = self.pool['mrp.bom']
|
||||
report_obj = self.pool['report']
|
||||
docs = mrpbom_obj.browse(cr, uid, ids, context=context)
|
||||
|
||||
docargs = {
|
||||
'docs': docs,
|
||||
'get_children': self.get_children,
|
||||
}
|
||||
return request.registry['report'].render(request.cr, request.uid, [], 'mrp.report_mrpbomstructure', docargs)
|
||||
return report_obj.render(cr, uid, [], 'mrp.report_mrpbomstructure', docargs, context=context)
|
||||
|
||||
def get_children(self, object, level=0):
|
||||
result = []
|
||||
|
|
|
@ -14,6 +14,10 @@ _logger = logging.getLogger(__name__)
|
|||
class pad_common(osv.osv_memory):
|
||||
_name = 'pad.common'
|
||||
|
||||
def pad_is_configured(self, cr, uid, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return bool(user.company_id.pad_server)
|
||||
|
||||
def pad_generate_url(self, cr, uid, context=None):
|
||||
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id;
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
.oe_pad_loading{
|
||||
text-align: center;
|
||||
opacity: 0.75;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.etherpad_readonly ul, .etherpad_readonly ol {
|
||||
|
|
|
@ -1,66 +1,82 @@
|
|||
openerp.pad = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
|
||||
instance.web.form.FieldPad = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeWidgetMixin, {
|
||||
template: 'FieldPad',
|
||||
content: "",
|
||||
init: function() {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this.set("configured", true);
|
||||
this.on("change:configured", this, this.switch_configured);
|
||||
this._configured_deferred = this.view.dataset.call('pad_is_configured').done(function(data) {
|
||||
self.set("configured", !!data);
|
||||
}).fail(function(data, event) {
|
||||
event.preventDefault();
|
||||
self.set("configured", true);
|
||||
});
|
||||
},
|
||||
initialize_content: function() {
|
||||
var self = this;
|
||||
this.switch_configured();
|
||||
this.$('.oe_pad_switch').click(function() {
|
||||
self.$el.toggleClass('oe_pad_fullscreen');
|
||||
self.$el.find('.oe_pad_switch').toggleClass('fa-expand fa-compress');
|
||||
self.view.$el.find('.oe_chatter').toggle();
|
||||
});
|
||||
this._configured_deferred.always(function() {
|
||||
var configured = self.get('configured');
|
||||
self.$(".oe_unconfigured").toggle(!configured);
|
||||
self.$(".oe_configured").toggle(configured);
|
||||
});
|
||||
this.render_value();
|
||||
},
|
||||
switch_configured: function() {
|
||||
this.$(".oe_unconfigured").toggle(! this.get("configured"));
|
||||
this.$(".oe_configured").toggle(this.get("configured"));
|
||||
},
|
||||
render_value: function() {
|
||||
var self = this;
|
||||
if (this.get("configured") && ! this.get("value")) {
|
||||
self.view.dataset.call('pad_generate_url', {
|
||||
context: {
|
||||
model: self.view.model,
|
||||
field_name: self.name,
|
||||
object_id: self.view.datarecord.id
|
||||
},
|
||||
}).done(function(data) {
|
||||
if (! data.url) {
|
||||
self.set("configured", false);
|
||||
var self = this;
|
||||
this._configured_deferred.always(function() {
|
||||
if (! self.get('configured')) {
|
||||
return;
|
||||
};
|
||||
var value = self.get('value');
|
||||
if (self.get('effective_readonly')) {
|
||||
if (_.str.startsWith(value, 'http')) {
|
||||
this.pad_loading_request = self.view.dataset.call('pad_get_content', {url: value}).done(function(data) {
|
||||
self.$('.oe_pad_content').removeClass('oe_pad_loading').html('<div class="oe_pad_readonly"><div>');
|
||||
self.$('.oe_pad_readonly').html(data);
|
||||
}).fail(function() {
|
||||
self.$('.oe_pad_content').text(_t('Unable to load pad'));
|
||||
});
|
||||
} else {
|
||||
self.set("value", data.url);
|
||||
self.$('.oe_pad_content').addClass('oe_pad_loading').show().text(_t("This pad will be initialized on first edit"));
|
||||
}
|
||||
});
|
||||
}
|
||||
this.$('.oe_pad_content').html("");
|
||||
var value = this.get('value');
|
||||
if (this.pad_loading_request) {
|
||||
this.pad_loading_request.abort();
|
||||
}
|
||||
if (_.str.startsWith(value, 'http')) {
|
||||
if (! this.get('effective_readonly')) {
|
||||
var content = '<iframe width="100%" height="100%" frameborder="0" src="' + value + '?showChat=false&userName=' + this.session.username + '"></iframe>';
|
||||
this.$('.oe_pad_content').html(content);
|
||||
this._dirty_flag = true;
|
||||
} else {
|
||||
this.content = '<div class="oe_pad_loading">... Loading pad ...</div>';
|
||||
this.pad_loading_request = $.get(value + '/export/html').done(function(data) {
|
||||
groups = /\<\s*body\s*\>(.*?)\<\s*\/body\s*\>/.exec(data);
|
||||
data = (groups || []).length >= 2 ? groups[1] : '';
|
||||
self.$('.oe_pad_content').html('<div class="oe_pad_readonly"><div>');
|
||||
self.$('.oe_pad_readonly').html(data);
|
||||
}).fail(function() {
|
||||
self.$('.oe_pad_content').text('Unable to load pad');
|
||||
}
|
||||
else {
|
||||
var def = $.when();
|
||||
if (! value || !_.str.startsWith(value, 'http')) {
|
||||
def = self.view.dataset.call('pad_generate_url', {
|
||||
context: {
|
||||
model: self.view.model,
|
||||
field_name: self.name,
|
||||
object_id: self.view.datarecord.id
|
||||
},
|
||||
}).done(function(data) {
|
||||
if (! data.url) {
|
||||
self.set("configured", false);
|
||||
} else {
|
||||
self.set("value", data.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
def.then(function() {
|
||||
value = self.get('value');
|
||||
if (_.str.startsWith(value, 'http')) {
|
||||
var content = '<iframe width="100%" height="100%" frameborder="0" src="' + value + '?showChat=false&userName=' + self.session.username + '"></iframe>';
|
||||
self.$('.oe_pad_content').html(content);
|
||||
self._dirty_flag = true;
|
||||
}
|
||||
else {
|
||||
self.$('.oe_pad_content').text(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -513,7 +513,7 @@ class pos_order(osv.osv):
|
|||
_description = "Point of Sale"
|
||||
_order = "id desc"
|
||||
|
||||
def create_from_ui(self, cr, uid, orders, context=None):
|
||||
def create_from_ui(self, cr, uid, orders, context=None):
|
||||
|
||||
# Keep only new orders
|
||||
submitted_references = [o['data']['name'] for o in orders]
|
||||
|
|
|
@ -19,5 +19,11 @@
|
|||
<field name="global" eval="True" />
|
||||
<field name="domain_force">[('company_id', '=', user.company_id.id)]</field>
|
||||
</record>
|
||||
<record id="rule_pos_config_multi_company" model="ir.rule">
|
||||
<field name="name">Point Of Sale Config</field>
|
||||
<field name="model_id" ref="model_pos_config" />
|
||||
<field name="global" eval="True" />
|
||||
<field name="domain_force">[('warehouse_id.company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<separator string="Select your Point of Sale" colspan="4" attrs="{'invisible' : [('show_config', '=', False)]}" />
|
||||
<group attrs="{'invisible' : [('show_config', '=', False)]}">
|
||||
<field name="pos_config_id" on_change="on_change_config(pos_config_id)"
|
||||
widget="selection" domain="[('state','=','active')]"
|
||||
options="{'no_create': True}" domain="[('state','=','active')]"
|
||||
class="oe_inline"/>
|
||||
<field name="pos_state" invisible="1" />
|
||||
</group>
|
||||
|
|
|
@ -39,7 +39,7 @@ class mail_mail(osv.Model):
|
|||
if partner and not partner.user_ids:
|
||||
contex_signup = dict(context, signup_valid=True)
|
||||
signup_url = partner_obj._get_signup_url_for_action(cr, SUPERUSER_ID, [partner.id],
|
||||
action='login', model=mail.model, res_id=mail.res_id,
|
||||
model=mail.model, res_id=mail.res_id,
|
||||
context=contex_signup)[partner.id]
|
||||
return _("""<span class='oe_mail_footer_access'><small>Access your messages and documents through <a style='color:inherit' href="%s">our Customer Portal</a></small></span>""") % signup_url
|
||||
else:
|
||||
|
|
|
@ -35,14 +35,14 @@ class mail_message(osv.Model):
|
|||
"""
|
||||
if uid == SUPERUSER_ID:
|
||||
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
||||
context=context, count=False, access_rights_uid=access_rights_uid)
|
||||
context=context, count=count, access_rights_uid=access_rights_uid)
|
||||
group_ids = self.pool.get('res.users').browse(cr, uid, uid, context=context).groups_id
|
||||
group_user_id = self.pool.get("ir.model.data").get_object_reference(cr, uid, 'base', 'group_user')[1]
|
||||
if group_user_id not in [group.id for group in group_ids]:
|
||||
args = [('subtype_id', '!=', False)] + list(args)
|
||||
|
||||
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
||||
context=context, count=False, access_rights_uid=access_rights_uid)
|
||||
context=context, count=count, access_rights_uid=access_rights_uid)
|
||||
|
||||
def check_access_rule(self, cr, uid, ids, operation, context=None):
|
||||
""" Add Access rules of mail.message for non-employee user:
|
||||
|
|
|
@ -30,10 +30,3 @@ class portal(osv.osv):
|
|||
_columns = {
|
||||
'is_portal': fields.boolean('Portal', help="If checked, this group is usable as a portal."),
|
||||
}
|
||||
|
||||
class res_users(osv.Model):
|
||||
_inherit = 'res.users'
|
||||
def _signup_create_user(self, cr, uid, values, context=None):
|
||||
values['share'] = True
|
||||
return super(res_users, self)._signup_create_user(cr, uid, values, context=context)
|
||||
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
<record id="base.group_portal" model="res.groups">
|
||||
<field name="is_portal" eval="True"/>
|
||||
</record>
|
||||
<record id="auth_signup.default_template_user" model="res.users">
|
||||
<field name="share" eval="True"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
Mr Demo Portal</field>
|
||||
<!-- Avoid auto-including this user in any default group -->
|
||||
<field name="groups_id" eval="[(5,)]"/>
|
||||
<field name="share" eval="True" />
|
||||
</record>
|
||||
|
||||
<!-- Add the demo user to the portal (and therefore to the portal member group) -->
|
||||
|
|
|
@ -134,7 +134,8 @@ class test_portal(TestMail):
|
|||
'invite: subject of invitation email is incorrect')
|
||||
self.assertIn('Administrator invited you to follow Discussion group document: Pigs', sent_email.get('body'),
|
||||
'invite: body of invitation email is incorrect')
|
||||
self.assertTrue(partner_carine.signup_url in sent_email.get('body'),
|
||||
invite_url = partner_carine._get_signup_url_for_action(model='mail.group', res_id=self.group_pigs_id)[partner_carine.id]
|
||||
self.assertTrue(invite_url in sent_email.get('body'),
|
||||
'invite: body of invitation email does not contain signup url')
|
||||
|
||||
def test_20_notification_url(self):
|
||||
|
|
|
@ -209,7 +209,6 @@ class wizard_user(osv.osv_memory):
|
|||
'login': extract_email(wizard_user.email),
|
||||
'partner_id': wizard_user.partner_id.id,
|
||||
'groups_id': [(6, 0, [])],
|
||||
'share': True,
|
||||
}
|
||||
user_id = res_users.create(cr, uid, values, context=create_context)
|
||||
return res_users.browse(cr, uid, user_id, context)
|
||||
|
|
|
@ -115,11 +115,29 @@ class product_pricelist(osv.osv):
|
|||
if name and operator == '=' and not args:
|
||||
# search on the name of the pricelist and its currency, opposite of name_get(),
|
||||
# Used by the magic context filter in the product search view.
|
||||
query_args = {'name': name, 'limit': limit}
|
||||
query_args = {'name': name, 'limit': limit, 'lang': (context or {}).get('lang') or 'en_US'}
|
||||
query = """SELECT p.id
|
||||
FROM product_pricelist p JOIN
|
||||
res_currency c ON (p.currency_id = c.id)
|
||||
WHERE p.name || ' (' || c.name || ')' = %(name)s
|
||||
FROM ((
|
||||
SELECT pr.id, pr.name
|
||||
FROM product_pricelist pr JOIN
|
||||
res_currency cur ON
|
||||
(pr.currency_id = cur.id)
|
||||
WHERE pr.name || ' (' || cur.name || ')' = %(name)s
|
||||
)
|
||||
UNION (
|
||||
SELECT tr.res_id as id, tr.value as name
|
||||
FROM ir_translation tr JOIN
|
||||
product_pricelist pr ON (
|
||||
pr.id = tr.res_id AND
|
||||
tr.type = 'model' AND
|
||||
tr.name = 'product.pricelist,name' AND
|
||||
tr.lang = %(lang)s
|
||||
) JOIN
|
||||
res_currency cur ON
|
||||
(pr.currency_id = cur.id)
|
||||
WHERE tr.value || ' (' || cur.name || ')' = %(name)s
|
||||
)
|
||||
) p
|
||||
ORDER BY p.name"""
|
||||
if limit:
|
||||
query += " LIMIT %(limit)s"
|
||||
|
|
|
@ -527,12 +527,13 @@ class product_product(osv.osv):
|
|||
cr, uid, pricelist, operator='=', context=context, limit=1)
|
||||
pricelist = pricelist_ids[0][0] if pricelist_ids else pricelist
|
||||
|
||||
products = self.browse(cr, uid, ids, context=context)
|
||||
qtys = map(lambda x: (x, quantity, partner), products)
|
||||
pl = plobj.browse(cr, uid, pricelist, context=context)
|
||||
price = plobj._price_get_multi(cr,uid, pl, qtys, context=context)
|
||||
for id in ids:
|
||||
res[id] = price.get(id, 0.0)
|
||||
if isinstance(pricelist, (int, long)):
|
||||
products = self.browse(cr, uid, ids, context=context)
|
||||
qtys = map(lambda x: (x, quantity, partner), products)
|
||||
pl = plobj.browse(cr, uid, pricelist, context=context)
|
||||
price = plobj._price_get_multi(cr,uid, pl, qtys, context=context)
|
||||
for id in ids:
|
||||
res[id] = price.get(id, 0.0)
|
||||
for id in ids:
|
||||
res.setdefault(id, 0.0)
|
||||
return res
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
# Amharic translation for openobject-addons
|
||||
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:06+0000\n"
|
||||
"PO-Revision-Date: 2014-03-18 08:01+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Amharic <am@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-19 05:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
#: field:product.product,turnover:0
|
||||
msgid "Turnover"
|
||||
msgstr "ተመላሽ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,expected_margin_rate:0
|
||||
msgid "Expected Margin (%)"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.margin,from_date:0
|
||||
msgid "From"
|
||||
msgstr "ከ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,total_cost:0
|
||||
msgid ""
|
||||
"Sum of Multiplication of Invoice price and quantity of Supplier Invoices "
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.margin,to_date:0
|
||||
msgid "To"
|
||||
msgstr "ለ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,total_margin:0
|
||||
msgid "Turnover - Standard price"
|
||||
msgstr "የተመላሽ መደበኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,total_margin_rate:0
|
||||
msgid "Total Margin Rate(%)"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: selection:product.margin,invoice_state:0
|
||||
#: selection:product.product,invoice_state:0
|
||||
msgid "Draft, Open and Paid"
|
||||
msgstr "ያልተከፈለና የተከፈለ ደረሰኞች"
|
||||
|
||||
#. module: product_margin
|
||||
#: code:addons/product_margin/wizard/product_margin.py:73
|
||||
#: model:ir.actions.act_window,name:product_margin.product_margin_act_window
|
||||
#: model:ir.ui.menu,name:product_margin.menu_action_product_margin
|
||||
#: view:product.product:0
|
||||
#, python-format
|
||||
msgid "Product Margins"
|
||||
msgstr "የእቃው አይነት በአንድ መጠን ሲጨምር"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,purchase_avg_price:0
|
||||
#: field:product.product,sale_avg_price:0
|
||||
msgid "Avg. Unit Price"
|
||||
msgstr "የእቃዎች መካከለኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,sale_num_invoiced:0
|
||||
msgid "# Invoiced in Sale"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Catalog Price"
|
||||
msgstr "ቅናሽ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: selection:product.margin,invoice_state:0
|
||||
#: selection:product.product,invoice_state:0
|
||||
msgid "Paid"
|
||||
msgstr "ተከፈል"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
#: field:product.product,sales_gap:0
|
||||
msgid "Sales Gap"
|
||||
msgstr "የሽያጭ ክፍተት"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sales_gap:0
|
||||
msgid "Expected Sale - Turn Over"
|
||||
msgstr "ሊሸጥ የሚችል እቃ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,sale_expected:0
|
||||
msgid "Expected Sale"
|
||||
msgstr "ሊሸጥ የሚችል እቃ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Standard Price"
|
||||
msgstr "የእቃው መደበኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,purchase_num_invoiced:0
|
||||
msgid "Sum of Quantity in Supplier Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,date_to:0
|
||||
msgid "Margin Date To"
|
||||
msgstr "የእቃው መጠን የጨመረበት ቀን"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Analysis Criteria"
|
||||
msgstr "የመመዘኛ መስፈርት"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
#: field:product.product,total_cost:0
|
||||
msgid "Total Cost"
|
||||
msgstr "አጠቃላይ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,normal_cost:0
|
||||
msgid "Sum of Multiplication of Cost price and quantity of Supplier Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,expected_margin:0
|
||||
msgid "Expected Margin"
|
||||
msgstr "የሚጠበቅ ጭማሪ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "#Purchased"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,expected_margin_rate:0
|
||||
msgid "Expected margin * 100 / Expected Sale"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sale_avg_price:0
|
||||
msgid "Avg. Price in Customer Invoices."
|
||||
msgstr "የመካከለኛ ዋጋ ለገዢዎች"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,purchase_avg_price:0
|
||||
msgid "Avg. Price in Supplier Invoices "
|
||||
msgstr "የመካከለኛ ዋጋ አቅራቢዎች "
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.margin,invoice_state:0
|
||||
#: field:product.product,invoice_state:0
|
||||
msgid "Invoice State"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,purchase_gap:0
|
||||
msgid "Normal Cost - Total Cost"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sale_expected:0
|
||||
msgid ""
|
||||
"Sum of Multiplication of Sale Catalog price and quantity of Customer Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,total_margin:0
|
||||
msgid "Total Margin"
|
||||
msgstr "የሁሉም ዋጋ በአንድ መጠን ሲጨምር"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,date_from:0
|
||||
msgid "Margin Date From"
|
||||
msgstr "እቃው ከጨመረበት ቀን ጀምሮ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,turnover:0
|
||||
msgid ""
|
||||
"Sum of Multiplication of Invoice price and quantity of Customer Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,normal_cost:0
|
||||
msgid "Normal Cost"
|
||||
msgstr "መደበኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Purchases"
|
||||
msgstr "ግዢዎች"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,purchase_num_invoiced:0
|
||||
msgid "# Invoiced in Purchase"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,expected_margin:0
|
||||
msgid "Expected Sale - Normal Cost"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "Properties categories"
|
||||
msgstr "በአይነታቸው መከፍፈል"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,total_margin_rate:0
|
||||
msgid "Total margin * 100 / Turnover"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "Open Margins"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: selection:product.margin,invoice_state:0
|
||||
#: selection:product.product,invoice_state:0
|
||||
msgid "Open and Paid"
|
||||
msgstr "የተከፈተና የትከፈል"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Sales"
|
||||
msgstr "ሽያጭ"
|
||||
|
||||
#. module: product_margin
|
||||
#: model:ir.model,name:product_margin.model_product_product
|
||||
msgid "Product"
|
||||
msgstr "ውጤት"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "General Information"
|
||||
msgstr "አጠቃላይ መርጃ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,purchase_gap:0
|
||||
msgid "Purchase Gap"
|
||||
msgstr "የግዢ ክፍተት"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "Cancel"
|
||||
msgstr "መሰረዝ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Margins"
|
||||
msgstr "በአንድ መጠን ሲጨምር"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sale_num_invoiced:0
|
||||
msgid "Sum of Quantity in Customer Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "or"
|
||||
msgstr "ወይም"
|
||||
|
||||
#. module: product_margin
|
||||
#: model:ir.model,name:product_margin.model_product_margin
|
||||
msgid "Product Margin"
|
||||
msgstr "የእቃው መጨመር"
|
|
@ -745,7 +745,7 @@ class task(osv.osv):
|
|||
|
||||
_columns = {
|
||||
'active': fields.function(_is_template, store=True, string='Not a Template Task', type='boolean', help="This field is computed automatically and have the same behavior than the boolean 'active' field: if the task is linked to a template or unactivated project, it will be hidden unless specifically asked."),
|
||||
'name': fields.char('Task Summary', size=128, required=True, select=True),
|
||||
'name': fields.char('Task Summary', track_visibility='onchange', size=128, required=True, select=True),
|
||||
'description': fields.text('Description'),
|
||||
'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Important'), ('0','Very important')], 'Priority', select=True),
|
||||
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
|
||||
|
|
|
@ -446,12 +446,7 @@ class purchase_order(osv.osv):
|
|||
'''
|
||||
assert len(ids) == 1, 'This option should only be used for a single id at a time'
|
||||
self.signal_send_rfq(cr, uid, ids)
|
||||
datas = {
|
||||
'model': 'purchase.order',
|
||||
'ids': ids,
|
||||
'form': self.read(cr, uid, ids[0], context=context),
|
||||
}
|
||||
return {'type': 'ir.actions.report.xml', 'report_name': 'purchase.quotation', 'datas': datas, 'nodestroy': True}
|
||||
return self.pool['report'].get_action(cr, uid, ids, 'purchase.report_purchasequotation', context=context)
|
||||
|
||||
#TODO: implement messages system
|
||||
def wkf_confirm_order(self, cr, uid, ids, context=None):
|
||||
|
@ -634,10 +629,9 @@ class purchase_order(osv.osv):
|
|||
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
|
||||
'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
|
||||
'date': self.date_to_datetime(cr, uid, order.date_order, context),
|
||||
'partner_id': order.dest_address_id.id or order.partner_id.id,
|
||||
'partner_id': order.partner_id.id,
|
||||
'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
|
||||
'type': 'in',
|
||||
'partner_id': order.dest_address_id.id or order.partner_id.id,
|
||||
'purchase_id': order.id,
|
||||
'company_id': order.company_id.id,
|
||||
'move_lines' : [],
|
||||
|
|
|
@ -19,459 +19,62 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv.osv import except_osv
|
||||
from openerp.addons.web import http
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.web.http import request
|
||||
import openerp.tools.config as config
|
||||
from openerp.addons.web.http import Controller, route, request
|
||||
|
||||
import time
|
||||
import base64
|
||||
import logging
|
||||
import tempfile
|
||||
import lxml.html
|
||||
import subprocess
|
||||
import simplejson
|
||||
try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
import psutil
|
||||
import signal
|
||||
import os
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
|
||||
import urlparse
|
||||
from werkzeug import exceptions
|
||||
from werkzeug.test import Client
|
||||
from werkzeug.wrappers import BaseResponse
|
||||
from werkzeug.datastructures import Headers
|
||||
from reportlab.graphics.barcode import createBarcodeDrawing
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
try:
|
||||
from pyPdf import PdfFileWriter, PdfFileReader
|
||||
except ImportError:
|
||||
PdfFileWriter = PdfFileReader = None
|
||||
class ReportController(Controller):
|
||||
|
||||
class Report(http.Controller):
|
||||
|
||||
@http.route(['/report/<reportname>/<docids>'], type='http', auth='user', website=True, multilang=True)
|
||||
def report_html(self, reportname, docids, **kwargs):
|
||||
"""This is the generic route for QWeb reports. It is used for reports
|
||||
which do not need to preprocess the data (i.e. reports that just display
|
||||
fields of a record).
|
||||
|
||||
It is given a ~fully qualified report name, for instance 'account.report_invoice'.
|
||||
Based on it, we know the module concerned and the name of the template. With the
|
||||
name of the template, we will make a search on the ir.actions.reports.xml table and
|
||||
get the record associated to finally know the model this template refers to.
|
||||
|
||||
There is a way to declare the report (in module_report(s).xml) that you must respect:
|
||||
id="action_report_model"
|
||||
model="module.model" # To know which model the report refers to
|
||||
string="Invoices"
|
||||
report_type="qweb-pdf" # or qweb-html
|
||||
name="module.template_name"
|
||||
file="module.template_name"
|
||||
|
||||
If you don't want your report listed under the print button, just add
|
||||
'menu=False'.
|
||||
"""
|
||||
ids = [int(i) for i in docids.split(',')]
|
||||
ids = list(set(ids))
|
||||
report = self._get_report_from_name(reportname)
|
||||
report_obj = request.registry[report.model]
|
||||
docs = report_obj.browse(request.cr, request.uid, ids, context=request.context)
|
||||
|
||||
docargs = {
|
||||
'doc_ids': ids,
|
||||
'doc_model': report.model,
|
||||
'docs': docs,
|
||||
}
|
||||
|
||||
return request.registry['report'].render(request.cr, request.uid, [], report.report_name,
|
||||
docargs, context=request.context)
|
||||
|
||||
@http.route(['/report/pdf/<path:path>'], type='http', auth="user", website=True)
|
||||
def report_pdf(self, path=None, landscape=False, **post):
|
||||
"""Route converting any reports to pdf. It will get the html-rendered report, extract
|
||||
header, page and footer in order to prepare minimal html pages that will be further passed
|
||||
to wkhtmltopdf.
|
||||
|
||||
:param path: URL of the report (e.g. /report/account.report_invoice/1)
|
||||
:returns: a response with 'application/pdf' headers and the pdf as content
|
||||
"""
|
||||
#------------------------------------------------------
|
||||
# Generic reports controller
|
||||
#------------------------------------------------------
|
||||
@route('/report/<reportname>/<docids>', type='http', auth='user', website=True, multilang=True)
|
||||
def report_html(self, reportname, docids):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
docids = self._eval_params(docids)
|
||||
return request.registry['report'].get_html(cr, uid, docids, reportname, context=context)
|
||||
|
||||
# Get the report we are working on.
|
||||
# Pattern is /report/module.reportname(?a=1)
|
||||
reportname_in_path = path.split('/')[1].split('?')[0]
|
||||
report = self._get_report_from_name(reportname_in_path)
|
||||
|
||||
# Check attachment_use field. If set to true and an existing pdf is already saved, load
|
||||
# this one now. If not, mark save it.
|
||||
save_in_attachment = {}
|
||||
|
||||
if report.attachment_use is True:
|
||||
# Get the record ids we are working on.
|
||||
path_ids = [int(i) for i in path.split('/')[2].split('?')[0].split(',')]
|
||||
|
||||
save_in_attachment['model'] = report.model
|
||||
save_in_attachment['loaded_documents'] = {}
|
||||
|
||||
for path_id in path_ids:
|
||||
obj = request.registry[report.model].browse(cr, uid, path_id)
|
||||
filename = eval(report.attachment, {'object': obj, 'time': time})
|
||||
|
||||
if filename is False: # May be false if, for instance, the record is in draft state
|
||||
continue
|
||||
else:
|
||||
alreadyindb = [('datas_fname', '=', filename),
|
||||
('res_model', '=', report.model),
|
||||
('res_id', '=', path_id)]
|
||||
|
||||
attach_ids = request.registry['ir.attachment'].search(cr, uid, alreadyindb)
|
||||
if attach_ids:
|
||||
# Add the loaded pdf in the loaded_documents list
|
||||
pdf = request.registry['ir.attachment'].browse(cr, uid, attach_ids[0]).datas
|
||||
pdf = base64.decodestring(pdf)
|
||||
save_in_attachment['loaded_documents'][path_id] = pdf
|
||||
_logger.info('The PDF document %s was loaded from the database' % filename)
|
||||
else:
|
||||
# Mark current document to be saved
|
||||
save_in_attachment[path_id] = filename
|
||||
|
||||
# Get the paperformat associated to the report. If there is not, get the one associated to
|
||||
# the company.
|
||||
if not report.paperformat_id:
|
||||
user = request.registry['res.users'].browse(cr, uid, uid, context=context)
|
||||
paperformat = user.company_id.paperformat_id
|
||||
else:
|
||||
paperformat = report.paperformat_id
|
||||
|
||||
# Get the html report.
|
||||
html = self._get_url_content('/' + path, post)[0]
|
||||
subst = self._get_url_content('/report/static/src/js/subst.js')[0] # Used in age numbering
|
||||
css = '' # Local css
|
||||
|
||||
headerhtml = []
|
||||
contenthtml = []
|
||||
footerhtml = []
|
||||
base_url = request.registry['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
|
||||
|
||||
minimalhtml = """
|
||||
<base href="{3}">
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 0;">
|
||||
<head>
|
||||
<link href="/report/static/src/css/reset.min.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/bootstrap/css/bootstrap.css" rel="stylesheet"/>
|
||||
<link href="/website/static/src/css/website.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
|
||||
|
||||
<style type='text/css'>{0}</style>
|
||||
|
||||
<script type='text/javascript'>{1}</script>
|
||||
</head>
|
||||
<body class="container" onload='subst()'>
|
||||
{2}
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# The retrieved html report must be simplified. We convert it into a xml tree
|
||||
# via lxml in order to extract headers, footers and content.
|
||||
try:
|
||||
root = lxml.html.fromstring(html)
|
||||
|
||||
for node in root.xpath("//html/head/style"):
|
||||
css += node.text
|
||||
|
||||
for node in root.xpath("//div[@class='header']"):
|
||||
body = lxml.html.tostring(node)
|
||||
header = minimalhtml.format(css, subst, body, base_url)
|
||||
headerhtml.append(header)
|
||||
|
||||
for node in root.xpath("//div[@class='footer']"):
|
||||
body = lxml.html.tostring(node)
|
||||
footer = minimalhtml.format(css, subst, body, base_url)
|
||||
footerhtml.append(footer)
|
||||
|
||||
for node in root.xpath("//div[@class='page']"):
|
||||
# Previously, we marked some reports to be saved in attachment via their ids, so we
|
||||
# must set a relation between report ids and report's content. We use the QWeb
|
||||
# branding in order to do so: searching after a node having a data-oe-model
|
||||
# attribute with the value of the current report model and read its oe-id attribute
|
||||
oemodelnode = node.find(".//*[@data-oe-model='" + report.model + "']")
|
||||
if oemodelnode is not None:
|
||||
reportid = oemodelnode.get('data-oe-id', False)
|
||||
if reportid is not False:
|
||||
reportid = int(reportid)
|
||||
else:
|
||||
reportid = False
|
||||
|
||||
body = lxml.html.tostring(node)
|
||||
reportcontent = minimalhtml.format(css, '', body, base_url)
|
||||
contenthtml.append(tuple([reportid, reportcontent]))
|
||||
|
||||
except lxml.etree.XMLSyntaxError:
|
||||
contenthtml = []
|
||||
contenthtml.append(html)
|
||||
save_in_attachment = {} # Don't save this potentially malformed document
|
||||
|
||||
# Get paperformat arguments set in the root html tag. They are prioritized over
|
||||
# paperformat-record arguments.
|
||||
specific_paperformat_args = {}
|
||||
for attribute in root.items():
|
||||
if attribute[0].startswith('data-report-'):
|
||||
specific_paperformat_args[attribute[0]] = attribute[1]
|
||||
|
||||
# Execute wkhtmltopdf process.
|
||||
pdf = self._generate_wkhtml_pdf(headerhtml, footerhtml, contenthtml, landscape,
|
||||
paperformat, specific_paperformat_args, save_in_attachment)
|
||||
|
||||
return self._make_pdf_response(pdf)
|
||||
|
||||
def _get_url_content(self, url, post=None):
|
||||
"""Resolve an internal webpage url and return its content with the help of
|
||||
werkzeug.test.client.
|
||||
|
||||
:param url: string representing the url to resolve
|
||||
:param post: a dict representing the query string
|
||||
:returns: a tuple str(html), int(statuscode)
|
||||
"""
|
||||
# Rebuilding the query string.
|
||||
if post:
|
||||
url += '?'
|
||||
url += '&'.join('%s=%s' % (k, v) for (k, v) in post.iteritems())
|
||||
|
||||
# We have to pass the current headers in order to see the report.
|
||||
reqheaders = Headers(request.httprequest.headers)
|
||||
response = Client(request.httprequest.app, BaseResponse).get(url, headers=reqheaders,
|
||||
follow_redirects=True)
|
||||
content = response.data
|
||||
|
||||
try:
|
||||
content = content.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
|
||||
return tuple([content, response.headers])
|
||||
|
||||
def _generate_wkhtml_pdf(self, headers, footers, bodies, landscape,
|
||||
paperformat, spec_paperformat_args=None, save_in_attachment=None):
|
||||
"""Execute wkhtmltopdf as a subprocess in order to convert html given in input into a pdf
|
||||
document.
|
||||
|
||||
:param header: list of string containing the headers
|
||||
:param footer: list of string containing the footers
|
||||
:param bodies: list of string containing the reports
|
||||
:param landscape: boolean to force the pdf to be rendered under a landscape format
|
||||
:param paperformat: ir.actions.report.paperformat to generate the wkhtmltopf arguments
|
||||
:param specific_paperformat_args: dict of prioritized paperformat arguments
|
||||
:param save_in_attachment: dict of reports to save/load in/from the db
|
||||
:returns: Content of the pdf as a string
|
||||
"""
|
||||
command = ['wkhtmltopdf']
|
||||
tmp_dir = tempfile.gettempdir()
|
||||
|
||||
command_args = []
|
||||
# Passing the cookie in order to resolve URL.
|
||||
command_args.extend(['--cookie', 'session_id', request.httprequest.cookies['session_id']])
|
||||
|
||||
# Display arguments
|
||||
if paperformat:
|
||||
command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args))
|
||||
|
||||
if landscape and '--orientation' in command_args:
|
||||
command_args_copy = list(command_args)
|
||||
for index, elem in enumerate(command_args_copy):
|
||||
if elem == '--orientation':
|
||||
del command_args[index]
|
||||
del command_args[index]
|
||||
command_args.extend(['--orientation', 'landscape'])
|
||||
elif landscape and not '--orientation' in command_args:
|
||||
command_args.extend(['--orientation', 'landscape'])
|
||||
|
||||
pdfdocuments = []
|
||||
# HTML to PDF thanks to WKhtmltopdf
|
||||
for index, reporthtml in enumerate(bodies):
|
||||
command_arg_local = []
|
||||
pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.',
|
||||
mode='w+b')
|
||||
# Directly load the document if we have it
|
||||
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
|
||||
pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0]))
|
||||
pdfreport.seek(0)
|
||||
pdfdocuments.append(pdfreport)
|
||||
continue
|
||||
|
||||
# Header stuff
|
||||
if headers:
|
||||
head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
head_file.write(headers[index])
|
||||
head_file.seek(0)
|
||||
command_arg_local.extend(['--header-html', head_file.name])
|
||||
|
||||
# Footer stuff
|
||||
if footers:
|
||||
foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
foot_file.write(footers[index])
|
||||
foot_file.seek(0)
|
||||
command_arg_local.extend(['--footer-html', foot_file.name])
|
||||
|
||||
# Body stuff
|
||||
content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
content_file.write(reporthtml[1])
|
||||
content_file.seek(0)
|
||||
|
||||
try:
|
||||
# If the server is running with only one worker, increase it to two to be able
|
||||
# to serve the http request from wkhtmltopdf.
|
||||
if config['workers'] == 1:
|
||||
ppid = psutil.Process(os.getpid()).ppid
|
||||
os.kill(ppid, signal.SIGTTIN)
|
||||
|
||||
wkhtmltopdf = command + command_args + command_arg_local
|
||||
wkhtmltopdf += [content_file.name] + [pdfreport.name]
|
||||
|
||||
process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = process.communicate()
|
||||
|
||||
if config['workers'] == 1:
|
||||
os.kill(ppid, signal.SIGTTOU)
|
||||
|
||||
if process.returncode != 0:
|
||||
raise except_osv(_('Report (PDF)'),
|
||||
_('wkhtmltopdf failed with error code = %s. '
|
||||
'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]):
|
||||
attachment = {
|
||||
'name': save_in_attachment.get(reporthtml[0]),
|
||||
'datas': base64.encodestring(pdfreport.read()),
|
||||
'datas_fname': save_in_attachment.get(reporthtml[0]),
|
||||
'res_model': save_in_attachment.get('model'),
|
||||
'res_id': reporthtml[0],
|
||||
}
|
||||
request.registry['ir.attachment'].create(request.cr, request.uid, attachment)
|
||||
_logger.info('The PDF document %s is now saved in the '
|
||||
'database' % attachment['name'])
|
||||
|
||||
pdfreport.seek(0)
|
||||
pdfdocuments.append(pdfreport)
|
||||
|
||||
if headers:
|
||||
head_file.close()
|
||||
if footers:
|
||||
foot_file.close()
|
||||
except:
|
||||
raise
|
||||
|
||||
# Get and return the full pdf
|
||||
if len(pdfdocuments) == 1:
|
||||
content = pdfdocuments[0].read()
|
||||
pdfdocuments[0].close()
|
||||
else:
|
||||
content = self._merge_pdf(pdfdocuments)
|
||||
|
||||
return content
|
||||
|
||||
def _build_wkhtmltopdf_args(self, paperformat, specific_paperformat_args=None):
|
||||
"""Build arguments understandable by wkhtmltopdf from an ir.actions.report.paperformat
|
||||
record.
|
||||
|
||||
:paperformat: ir.actions.report.paperformat record associated to a document
|
||||
:specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments
|
||||
:returns: list of string containing the wkhtmltopdf arguments
|
||||
"""
|
||||
command_args = []
|
||||
if paperformat.format and paperformat.format != 'custom':
|
||||
command_args.extend(['--page-size', paperformat.format])
|
||||
|
||||
if paperformat.page_height and paperformat.page_width and paperformat.format == 'custom':
|
||||
command_args.extend(['--page-width', str(paperformat.page_width) + 'in'])
|
||||
command_args.extend(['--page-height', str(paperformat.page_height) + 'in'])
|
||||
|
||||
if specific_paperformat_args and specific_paperformat_args['data-report-margin-top']:
|
||||
command_args.extend(['--margin-top',
|
||||
str(specific_paperformat_args['data-report-margin-top'])])
|
||||
elif paperformat.margin_top:
|
||||
command_args.extend(['--margin-top', str(paperformat.margin_top)])
|
||||
|
||||
if paperformat.margin_left:
|
||||
command_args.extend(['--margin-left', str(paperformat.margin_left)])
|
||||
if paperformat.margin_bottom:
|
||||
command_args.extend(['--margin-bottom', str(paperformat.margin_bottom)])
|
||||
if paperformat.margin_right:
|
||||
command_args.extend(['--margin-right', str(paperformat.margin_right)])
|
||||
if paperformat.orientation:
|
||||
command_args.extend(['--orientation', str(paperformat.orientation)])
|
||||
if paperformat.header_spacing:
|
||||
command_args.extend(['--header-spacing', str(paperformat.header_spacing)])
|
||||
if paperformat.header_line:
|
||||
command_args.extend(['--header-line'])
|
||||
if paperformat.dpi:
|
||||
command_args.extend(['--dpi', str(paperformat.dpi)])
|
||||
|
||||
return command_args
|
||||
|
||||
def _get_report_from_name(self, report_name):
|
||||
"""Get the first record of ir.actions.report.xml having the argument as value for
|
||||
the field report_name.
|
||||
"""
|
||||
report_obj = request.registry['ir.actions.report.xml']
|
||||
qwebtypes = ['qweb-pdf', 'qweb-html']
|
||||
|
||||
idreport = report_obj.search(request.cr, request.uid,
|
||||
[('report_type', 'in', qwebtypes),
|
||||
('report_name', '=', report_name)])
|
||||
|
||||
report = report_obj.browse(request.cr, request.uid, idreport[0],
|
||||
context=request.context)
|
||||
return report
|
||||
|
||||
def _make_pdf_response(self, pdf):
|
||||
"""Make a request response for a PDF file with correct http headers.
|
||||
|
||||
:param pdf: content of a pdf in a string
|
||||
:returns: request response for a pdf document
|
||||
"""
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'),
|
||||
('Content-Length', len(pdf))]
|
||||
@route('/report/pdf/report/<reportname>/<docids>', type='http', auth="user", website=True)
|
||||
def report_pdf(self, reportname, docids):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
docids = self._eval_params(docids)
|
||||
pdf = request.registry['report'].get_pdf(cr, uid, docids, reportname, context=context)
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
def _merge_pdf(self, documents):
|
||||
"""Merge PDF files into one.
|
||||
#------------------------------------------------------
|
||||
# Particular reports controller
|
||||
#------------------------------------------------------
|
||||
@route('/report/<reportname>', type='http', auth='user', website=True, multilang=True)
|
||||
def report_html_particular(self, reportname, **data):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
report_obj = request.registry['report']
|
||||
data = self._eval_params(data) # Sanitizing
|
||||
return report_obj.get_html(cr, uid, [], reportname, data=data, context=context)
|
||||
|
||||
:param documents: list of pdf files
|
||||
:returns: string containing the merged pdf
|
||||
"""
|
||||
writer = PdfFileWriter()
|
||||
for document in documents:
|
||||
reader = PdfFileReader(file(document.name, "rb"))
|
||||
for page in range(0, reader.getNumPages()):
|
||||
writer.addPage(reader.getPage(page))
|
||||
document.close()
|
||||
merged = StringIO.StringIO()
|
||||
writer.write(merged)
|
||||
merged.seek(0)
|
||||
content = merged.read()
|
||||
merged.close()
|
||||
return content
|
||||
@route('/report/pdf/report/<reportname>', type='http', auth='user', website=True, multilang=True)
|
||||
def report_pdf_particular(self, reportname, **data):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
report_obj = request.registry['report']
|
||||
data = self._eval_params(data) # Sanitizing
|
||||
pdf = report_obj.get_pdf(cr, uid, [], reportname, data=data, context=context)
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
@http.route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="user")
|
||||
def barcode(self, type, value, width=300, height=50):
|
||||
#------------------------------------------------------
|
||||
# Misc. route utils
|
||||
#------------------------------------------------------
|
||||
@route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="user")
|
||||
def report_barcode(self, type, value, width=300, height=50):
|
||||
"""Contoller able to render barcode images thanks to reportlab.
|
||||
Samples:
|
||||
<img t-att-src="'/report/barcode/QR/%s' % o.name"/>
|
||||
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('QR', o.name, 200, 200)"/>
|
||||
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' %
|
||||
('QR', o.name, 200, 200)"/>
|
||||
|
||||
:param type: Accepted types: 'Codabar', 'Code11', 'Code128', 'EAN13', 'EAN8', 'Extended39',
|
||||
'Extended93', 'FIM', 'I2of5', 'MSI', 'POSTNET', 'QR', 'Standard39', 'Standard93',
|
||||
|
@ -488,52 +91,69 @@ class Report(http.Controller):
|
|||
|
||||
return request.make_response(barcode, headers=[('Content-Type', 'image/png')])
|
||||
|
||||
@http.route('/report/download', type='http', auth="user")
|
||||
def report_attachment(self, data, token):
|
||||
@route(['/report/download'], type='http', auth="user", website=True)
|
||||
def report_download(self, data, token):
|
||||
"""This function is used by 'qwebactionmanager.js' in order to trigger the download of
|
||||
a report of any type.
|
||||
a pdf report.
|
||||
|
||||
:param data: a javasscript array JSON.stringified containg report internal url ([0]) and
|
||||
:param data: a javascript array JSON.stringified containg report internal url ([0]) and
|
||||
type [1]
|
||||
:returns: Response with a filetoken cookie and an attachment header
|
||||
"""
|
||||
requestcontent = simplejson.loads(data)
|
||||
url, type = requestcontent[0], requestcontent[1]
|
||||
file, fileheaders = self._get_url_content(url)
|
||||
|
||||
if type == 'qweb-pdf':
|
||||
response = self._make_pdf_response(file)
|
||||
response.headers.add('Content-Disposition', 'attachment; filename=report.pdf;')
|
||||
elif type == 'controller':
|
||||
response = request.make_response(file)
|
||||
response.headers.add('Content-Disposition', fileheaders['Content-Disposition'])
|
||||
response.headers.add('Content-Type', fileheaders['Content-Type'])
|
||||
reportname = url.split('/report/pdf/report/')[1].split('?')[0].split('/')[0]
|
||||
|
||||
if '?' not in url:
|
||||
# Generic report:
|
||||
docids = url.split('/')[-1]
|
||||
response = self.report_pdf(reportname, docids)
|
||||
else:
|
||||
# Particular report:
|
||||
querystring = url.split('?')[1]
|
||||
querystring = dict(urlparse.parse_qsl(querystring))
|
||||
response = self.report_pdf_particular(reportname, **querystring)
|
||||
|
||||
response.headers.add('Content-Disposition', 'attachment; filename=%s.pdf;' % reportname)
|
||||
response.set_cookie('fileToken', token)
|
||||
return response
|
||||
elif type =='controller':
|
||||
from werkzeug.test import Client
|
||||
from werkzeug.wrappers import BaseResponse
|
||||
from werkzeug.datastructures import Headers
|
||||
reqheaders = Headers(request.httprequest.headers)
|
||||
response = Client(request.httprequest.app, BaseResponse).get(url, headers=reqheaders, follow_redirects=True)
|
||||
response.set_cookie('fileToken', token)
|
||||
return response
|
||||
else:
|
||||
return
|
||||
|
||||
response.headers.add('Content-Length', len(file))
|
||||
response.set_cookie('fileToken', token)
|
||||
return response
|
||||
|
||||
@http.route('/report/check_wkhtmltopdf', type='json', auth="user")
|
||||
@route(['/report/check_wkhtmltopdf'], type='json', auth="user")
|
||||
def check_wkhtmltopdf(self):
|
||||
"""Check the presence of wkhtmltopdf and return its version. If wkhtmltopdf
|
||||
cannot be found, return False.
|
||||
return request.registry['report']._check_wkhtmltopdf()
|
||||
|
||||
def _eval_params(self, param):
|
||||
"""Parse a dict generated by the webclient (javascript) into a python dict.
|
||||
"""
|
||||
try:
|
||||
process = subprocess.Popen(['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = process.communicate()
|
||||
if err:
|
||||
raise
|
||||
|
||||
version = out.splitlines()[1].strip()
|
||||
version = version.split(' ')[1]
|
||||
|
||||
if LooseVersion(version) < LooseVersion('0.12.0'):
|
||||
_logger.warning('Upgrade WKHTMLTOPDF to (at least) 0.12.0')
|
||||
return 'upgrade'
|
||||
|
||||
return True
|
||||
except:
|
||||
_logger.error('You need WKHTMLTOPDF to print a pdf version of this report.')
|
||||
return False
|
||||
if isinstance(param, dict):
|
||||
for key, value in param.iteritems():
|
||||
if value.lower() == 'false':
|
||||
param[key] = False
|
||||
elif value.lower() == 'true':
|
||||
param[key] = True
|
||||
elif ',' in value:
|
||||
param[key] = [int(i) for i in value.split(',')]
|
||||
else:
|
||||
try:
|
||||
param[key] = int(value)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
if isinstance(param, (str, unicode)):
|
||||
param = [int(i) for i in param.split(',')]
|
||||
if isinstance(param, list):
|
||||
param = list(set(param))
|
||||
if isinstance(param, int):
|
||||
param = [param]
|
||||
return param
|
||||
|
|
|
@ -19,54 +19,85 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.osv import osv
|
||||
from openerp.osv.fields import float as float_field, function as function_field, datetime as datetime_field
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, config
|
||||
from openerp.osv.fields import float as float_field, function as function_field, datetime as datetime_field
|
||||
|
||||
import os
|
||||
import time
|
||||
import psutil
|
||||
import signal
|
||||
import base64
|
||||
import logging
|
||||
import tempfile
|
||||
import lxml.html
|
||||
import cStringIO
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.wrappers import BaseResponse
|
||||
from werkzeug.test import Client
|
||||
from functools import partial
|
||||
from distutils.version import LooseVersion
|
||||
try:
|
||||
from pyPdf import PdfFileWriter, PdfFileReader
|
||||
except ImportError:
|
||||
PdfFileWriter = PdfFileReader = None
|
||||
|
||||
|
||||
def get_date_length(date_format=DEFAULT_SERVER_DATE_FORMAT):
|
||||
return len((datetime.now()).strftime(date_format))
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class report(osv.Model):
|
||||
"""Check the presence of wkhtmltopdf and return its version."""
|
||||
wkhtmltopdf_state = 'install'
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
['wkhtmltopdf', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
except OSError:
|
||||
_logger.error('You need wkhtmltopdf to print a pdf version of the reports.')
|
||||
else:
|
||||
out, err = process.communicate()
|
||||
version = out.splitlines()[1].strip()
|
||||
version = version.split(' ')[1]
|
||||
if LooseVersion(version) < LooseVersion('0.12.0'):
|
||||
_logger.warning('Upgrade wkhtmltopdf to (at least) 0.12.0')
|
||||
wkhtmltopdf_state = 'upgrade'
|
||||
wkhtmltopdf_state = 'ok'
|
||||
|
||||
|
||||
class Report(osv.Model):
|
||||
_name = "report"
|
||||
_description = "Report"
|
||||
|
||||
public_user = None
|
||||
|
||||
def get_digits(self, obj=None, f=None, dp=None):
|
||||
#--------------------------------------------------------------------------
|
||||
# Extension of ir_ui_view.render with arguments frequently used in reports
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def _get_digits(self, cr, uid, obj=None, f=None, dp=None):
|
||||
d = DEFAULT_DIGITS = 2
|
||||
if dp:
|
||||
decimal_precision_obj = self.pool['decimal.precision']
|
||||
ids = decimal_precision_obj.search(request.cr, request.uid, [('name', '=', dp)])
|
||||
ids = decimal_precision_obj.search(cr, uid, [('name', '=', dp)])
|
||||
if ids:
|
||||
d = decimal_precision_obj.browse(request.cr, request.uid, ids)[0].digits
|
||||
d = decimal_precision_obj.browse(cr, uid, ids)[0].digits
|
||||
elif obj and f:
|
||||
res_digits = getattr(obj._columns[f], 'digits', lambda x: ((16, DEFAULT_DIGITS)))
|
||||
if isinstance(res_digits, tuple):
|
||||
d = res_digits[1]
|
||||
else:
|
||||
d = res_digits(request.cr)[1]
|
||||
d = res_digits(cr)[1]
|
||||
elif (hasattr(obj, '_field') and
|
||||
isinstance(obj._field, (float_field, function_field)) and
|
||||
obj._field.digits):
|
||||
d = obj._field.digits[1] or DEFAULT_DIGITS
|
||||
return d
|
||||
|
||||
def _get_lang_dict(self):
|
||||
def _get_lang_dict(self, cr, uid):
|
||||
pool_lang = self.pool['res.lang']
|
||||
lang = self.localcontext.get('lang', 'en_US') or 'en_US'
|
||||
lang_ids = pool_lang.search(request.cr, request.uid, [('code', '=', lang)])[0]
|
||||
lang_obj = pool_lang.browse(request.cr, request.uid, lang_ids)
|
||||
lang_ids = pool_lang.search(cr, uid, [('code', '=', lang)])[0]
|
||||
lang_obj = pool_lang.browse(cr, uid, lang_ids)
|
||||
lang_dict = {
|
||||
'lang_obj': lang_obj,
|
||||
'date_format': lang_obj.date_format,
|
||||
|
@ -76,7 +107,7 @@ class report(osv.Model):
|
|||
self.default_lang[lang] = self.lang_dict.copy()
|
||||
return True
|
||||
|
||||
def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False, dp=False, currency_obj=False):
|
||||
def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False, dp=False, currency_obj=False, cr=None, uid=None):
|
||||
"""
|
||||
Assuming 'Account' decimal.precision=3:
|
||||
formatLang(value) -> digits=2 (default)
|
||||
|
@ -84,17 +115,20 @@ class report(osv.Model):
|
|||
formatLang(value, dp='Account') -> digits=3
|
||||
formatLang(value, digits=5, dp='Account') -> digits=5
|
||||
"""
|
||||
def get_date_length(date_format=DEFAULT_SERVER_DATE_FORMAT):
|
||||
return len((datetime.now()).strftime(date_format))
|
||||
|
||||
if digits is None:
|
||||
if dp:
|
||||
digits = self.get_digits(dp=dp)
|
||||
digits = self._get_digits(cr, uid, dp=dp)
|
||||
else:
|
||||
digits = self.get_digits(value)
|
||||
digits = self._get_digits(cr, uid, value)
|
||||
|
||||
if isinstance(value, (str, unicode)) and not value:
|
||||
return ''
|
||||
|
||||
if not self.lang_dict_called:
|
||||
self._get_lang_dict()
|
||||
self._get_lang_dict(cr, uid)
|
||||
self.lang_dict_called = True
|
||||
|
||||
if date or date_time:
|
||||
|
@ -117,9 +151,7 @@ class report(osv.Model):
|
|||
date = datetime(*value.timetuple()[:6])
|
||||
if date_time:
|
||||
# Convert datetime values to the expected client/context timezone
|
||||
date = datetime_field.context_timestamp(request.cr, request.uid,
|
||||
timestamp=date,
|
||||
context=self.localcontext)
|
||||
date = datetime_field.context_timestamp(cr, uid, timestamp=date, context=self.localcontext)
|
||||
return date.strftime(date_format.encode('utf-8'))
|
||||
|
||||
res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
|
||||
|
@ -150,7 +182,7 @@ class report(osv.Model):
|
|||
'tz': context.get('tz'),
|
||||
'uid': context.get('uid'),
|
||||
}
|
||||
self._get_lang_dict()
|
||||
self._get_lang_dict(cr, uid)
|
||||
|
||||
view_obj = self.pool['ir.ui.view']
|
||||
|
||||
|
@ -177,51 +209,180 @@ class report(osv.Model):
|
|||
qcontext['o'] = self.pool[model].browse(cr, uid, doc_id, context=ctx)
|
||||
return view_obj.render(cr, uid, template, qcontext, context=ctx)
|
||||
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
|
||||
# Website independance code
|
||||
website = False
|
||||
res_company = current_user.company_id
|
||||
|
||||
try:
|
||||
website = request.website
|
||||
res_company = request.website.company_id
|
||||
except:
|
||||
pass
|
||||
|
||||
values.update({
|
||||
'time': time,
|
||||
'user': current_user,
|
||||
'user_id': current_user.id,
|
||||
'formatLang': self.formatLang,
|
||||
'get_digits': self.get_digits,
|
||||
'formatLang': partial(self.formatLang, cr=cr, uid=uid),
|
||||
'get_digits': self._get_digits,
|
||||
'render_doc': render_doc,
|
||||
'website': website,
|
||||
'res_company': res_company,
|
||||
'editable': True, # Will active inherit_branding
|
||||
'res_company': self.pool['res.users'].browse(cr, uid, uid).company_id,
|
||||
'website': False, # Will be overidden by ir.ui.view if the request has website enabled
|
||||
})
|
||||
|
||||
return view_obj.render(cr, uid, template, values, context=context)
|
||||
|
||||
def get_pdf(self, report, record_id, context=None):
|
||||
"""Used to return the content of a generated PDF.
|
||||
#--------------------------------------------------------------------------
|
||||
# Main reports methods
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
:returns: pdf
|
||||
def get_html(self, cr, uid, ids, report_name, data=None, context=None):
|
||||
"""This method generates and returns html version of a report.
|
||||
"""
|
||||
url = '/report/pdf/report/' + report.report_file + '/' + str(record_id)
|
||||
reqheaders = Headers(request.httprequest.headers)
|
||||
reqheaders.pop('Accept')
|
||||
reqheaders.add('Accept', 'application/pdf')
|
||||
reqheaders.pop('Content-Type')
|
||||
reqheaders.add('Content-Type', 'text/plain')
|
||||
response = Client(request.httprequest.app, BaseResponse).get(url, headers=reqheaders,
|
||||
follow_redirects=True)
|
||||
return response.data
|
||||
# If the report is using a custom model to render its html, we must use it.
|
||||
# Otherwise, fallback on the generic html rendering.
|
||||
try:
|
||||
report_model_name = 'report.%s' % report_name
|
||||
particularreport_obj = self.pool[report_model_name]
|
||||
return particularreport_obj.render_html(cr, uid, ids, data={'form': data}, context=context)
|
||||
except KeyError:
|
||||
report = self._get_report_from_name(cr, uid, report_name)
|
||||
report_obj = self.pool[report.model]
|
||||
docs = report_obj.browse(cr, uid, ids, context=context)
|
||||
docargs = {
|
||||
'doc_ids': ids,
|
||||
'doc_model': report.model,
|
||||
'docs': docs,
|
||||
}
|
||||
return self.render(cr, uid, [], report.report_name, docargs, context=context)
|
||||
|
||||
def get_pdf(self, cr, uid, ids, report_name, html=None, data=None, context=None):
|
||||
"""This method generates and returns pdf version of a report.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if html is None:
|
||||
html = self.get_html(cr, uid, ids, report_name, data=data, context=context)
|
||||
|
||||
html = html.decode('utf-8')
|
||||
|
||||
# Get the ir.actions.report.xml record we are working on.
|
||||
report = self._get_report_from_name(cr, uid, report_name)
|
||||
|
||||
# Check attachment_use field. If set to true and an existing pdf is already saved, load
|
||||
# this one now. Else, mark save it.
|
||||
save_in_attachment = {}
|
||||
|
||||
if report.attachment_use is True:
|
||||
save_in_attachment['model'] = report.model
|
||||
save_in_attachment['loaded_documents'] = {}
|
||||
|
||||
for record_id in ids:
|
||||
obj = self.pool[report.model].browse(cr, uid, record_id)
|
||||
filename = eval(report.attachment, {'object': obj, 'time': time})
|
||||
|
||||
if filename is False: # May be false if, for instance, the record is in draft state
|
||||
continue
|
||||
else:
|
||||
alreadyindb = [('datas_fname', '=', filename),
|
||||
('res_model', '=', report.model),
|
||||
('res_id', '=', record_id)]
|
||||
|
||||
attach_ids = self.pool['ir.attachment'].search(cr, uid, alreadyindb)
|
||||
if attach_ids:
|
||||
# Add the loaded pdf in the loaded_documents list
|
||||
pdf = self.pool['ir.attachment'].browse(cr, uid, attach_ids[0]).datas
|
||||
pdf = base64.decodestring(pdf)
|
||||
save_in_attachment['loaded_documents'][record_id] = pdf
|
||||
_logger.info('The PDF document %s was loaded from the database' % filename)
|
||||
else:
|
||||
# Mark current document to be saved
|
||||
save_in_attachment[id] = filename
|
||||
|
||||
# Get the paperformat associated to the report, otherwise fallback on the company one.
|
||||
if not report.paperformat_id:
|
||||
user = self.pool['res.users'].browse(cr, uid, uid)
|
||||
paperformat = user.company_id.paperformat_id
|
||||
else:
|
||||
paperformat = report.paperformat_id
|
||||
|
||||
# Preparing the minimal html pages
|
||||
#subst = self._get_url_content('/report/static/src/js/subst.js')[0] # Used in age numbering
|
||||
subst = "<script src='/report/static/src/js/subst.js'></script> "
|
||||
css = '' # Will contain local css
|
||||
|
||||
headerhtml = []
|
||||
contenthtml = []
|
||||
footerhtml = []
|
||||
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
|
||||
|
||||
minimalhtml = """
|
||||
<base href="{base_url}">
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 0;">
|
||||
<head>
|
||||
<link href="/report/static/src/css/reset.min.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/bootstrap/css/bootstrap.css" rel="stylesheet"/>
|
||||
<link href="/website/static/src/css/website.css" rel="stylesheet"/>
|
||||
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
|
||||
<style type='text/css'>{css}</style>
|
||||
{subst}
|
||||
</head>
|
||||
<body class="container" onload='subst()'>
|
||||
{body}
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# The retrieved html report must be simplified. We convert it into a xml tree
|
||||
# via lxml in order to extract headers, footers and content.
|
||||
try:
|
||||
root = lxml.html.fromstring(html)
|
||||
|
||||
for node in root.xpath("//html/head/style"):
|
||||
css += node.text
|
||||
|
||||
for node in root.xpath("//div[@class='header']"):
|
||||
body = lxml.html.tostring(node)
|
||||
header = minimalhtml.format(css=css, subst=subst, body=body, base_url=base_url)
|
||||
headerhtml.append(header)
|
||||
|
||||
for node in root.xpath("//div[@class='footer']"):
|
||||
body = lxml.html.tostring(node)
|
||||
footer = minimalhtml.format(css=css, subst=subst, body=body, base_url=base_url)
|
||||
footerhtml.append(footer)
|
||||
|
||||
for node in root.xpath("//div[@class='page']"):
|
||||
# Previously, we marked some reports to be saved in attachment via their ids, so we
|
||||
# must set a relation between report ids and report's content. We use the QWeb
|
||||
# branding in order to do so: searching after a node having a data-oe-model
|
||||
# attribute with the value of the current report model and read its oe-id attribute
|
||||
oemodelnode = node.find(".//*[@data-oe-model='%s']" % report.model)
|
||||
if oemodelnode is not None:
|
||||
reportid = oemodelnode.get('data-oe-id')
|
||||
if reportid:
|
||||
reportid = int(reportid)
|
||||
else:
|
||||
reportid = False
|
||||
|
||||
body = lxml.html.tostring(node)
|
||||
reportcontent = minimalhtml.format(css=css, subst='', body=body, base_url=base_url)
|
||||
contenthtml.append(tuple([reportid, reportcontent]))
|
||||
|
||||
except lxml.etree.XMLSyntaxError:
|
||||
contenthtml = []
|
||||
contenthtml.append(html)
|
||||
save_in_attachment = {} # Don't save this potentially malformed document
|
||||
|
||||
# Get paperformat arguments set in the root html tag. They are prioritized over
|
||||
# paperformat-record arguments.
|
||||
specific_paperformat_args = {}
|
||||
for attribute in root.items():
|
||||
if attribute[0].startswith('data-report-'):
|
||||
specific_paperformat_args[attribute[0]] = attribute[1]
|
||||
|
||||
# Run wkhtmltopdf process
|
||||
pdf = self._generate_wkhtml_pdf(
|
||||
cr, uid, headerhtml, footerhtml, contenthtml, context.get('landscape'),
|
||||
paperformat, specific_paperformat_args, save_in_attachment
|
||||
)
|
||||
return pdf
|
||||
|
||||
def get_action(self, cr, uid, ids, report_name, datas=None, context=None):
|
||||
"""Used to return an action of type ir.actions.report.xml.
|
||||
"""Return an action of type ir.actions.report.xml.
|
||||
|
||||
:param report_name: Name of the template to generate an action for
|
||||
"""
|
||||
# TODO: return the action for the ids passed in args
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
|
@ -249,26 +410,203 @@ class report(osv.Model):
|
|||
|
||||
return action
|
||||
|
||||
def eval_params(self, dict_param):
|
||||
"""Parse a dictionary generated by the webclient (javascript) into a dictionary
|
||||
understandable by a wizard controller (python).
|
||||
"""
|
||||
for key, value in dict_param.iteritems():
|
||||
if value.lower() == 'false':
|
||||
dict_param[key] = False
|
||||
elif value.lower() == 'true':
|
||||
dict_param[key] = True
|
||||
elif ',' in value:
|
||||
dict_param[key] = [int(i) for i in value.split(',')]
|
||||
elif '%2C' in value:
|
||||
dict_param[key] = [int(i) for i in value.split('%2C')]
|
||||
else:
|
||||
try:
|
||||
i = int(value)
|
||||
dict_param[key] = i
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
#--------------------------------------------------------------------------
|
||||
# Report generation helpers
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
data = {}
|
||||
data['form'] = dict_param
|
||||
return data
|
||||
def _check_wkhtmltopdf(self):
|
||||
return wkhtmltopdf_state
|
||||
|
||||
def _generate_wkhtml_pdf(self, cr, uid, headers, footers, bodies, landscape, paperformat, spec_paperformat_args=None, save_in_attachment=None):
|
||||
"""Execute wkhtmltopdf as a subprocess in order to convert html given in input into a pdf
|
||||
document.
|
||||
|
||||
:param header: list of string containing the headers
|
||||
:param footer: list of string containing the footers
|
||||
:param bodies: list of string containing the reports
|
||||
:param landscape: boolean to force the pdf to be rendered under a landscape format
|
||||
:param paperformat: ir.actions.report.paperformat to generate the wkhtmltopf arguments
|
||||
:param specific_paperformat_args: dict of prioritized paperformat arguments
|
||||
:param save_in_attachment: dict of reports to save/load in/from the db
|
||||
:returns: Content of the pdf as a string
|
||||
"""
|
||||
command = ['wkhtmltopdf']
|
||||
command_args = []
|
||||
tmp_dir = tempfile.gettempdir()
|
||||
|
||||
# Passing the cookie to wkhtmltopdf in order to resolve URL.
|
||||
try:
|
||||
from openerp.addons.web.http import request
|
||||
command_args.extend(['--cookie', 'session_id', request.session.sid])
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Display arguments
|
||||
if paperformat:
|
||||
command_args.extend(self._build_wkhtmltopdf_args(paperformat, spec_paperformat_args))
|
||||
|
||||
if landscape and '--orientation' in command_args:
|
||||
command_args_copy = list(command_args)
|
||||
for index, elem in enumerate(command_args_copy):
|
||||
if elem == '--orientation':
|
||||
del command_args[index]
|
||||
del command_args[index]
|
||||
command_args.extend(['--orientation', 'landscape'])
|
||||
elif landscape and not '--orientation' in command_args:
|
||||
command_args.extend(['--orientation', 'landscape'])
|
||||
|
||||
pdfdocuments = []
|
||||
# HTML to PDF thanks to WKhtmltopdf
|
||||
for index, reporthtml in enumerate(bodies):
|
||||
command_arg_local = []
|
||||
pdfreport = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='report.tmp.',
|
||||
mode='w+b')
|
||||
# Directly load the document if we have it
|
||||
if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]):
|
||||
pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0]))
|
||||
pdfreport.flush()
|
||||
pdfdocuments.append(pdfreport)
|
||||
continue
|
||||
|
||||
# Header stuff
|
||||
if headers:
|
||||
head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
head_file.write(headers[index])
|
||||
head_file.flush()
|
||||
command_arg_local.extend(['--header-html', head_file.name])
|
||||
|
||||
# Footer stuff
|
||||
if footers:
|
||||
foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
foot_file.write(footers[index])
|
||||
foot_file.flush()
|
||||
command_arg_local.extend(['--footer-html', foot_file.name])
|
||||
|
||||
# Body stuff
|
||||
content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.',
|
||||
dir=tmp_dir, mode='w+')
|
||||
content_file.write(reporthtml[1])
|
||||
content_file.flush()
|
||||
|
||||
try:
|
||||
# If the server is running with only one worker, ask to create a secund to be able
|
||||
# to serve the http request of wkhtmltopdf subprocess.
|
||||
if config['workers'] == 1:
|
||||
ppid = psutil.Process(os.getpid()).ppid
|
||||
os.kill(ppid, signal.SIGTTIN)
|
||||
|
||||
wkhtmltopdf = command + command_args + command_arg_local
|
||||
wkhtmltopdf += [content_file.name] + [pdfreport.name]
|
||||
|
||||
process = subprocess.Popen(wkhtmltopdf, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = process.communicate()
|
||||
|
||||
if config['workers'] == 1:
|
||||
os.kill(ppid, signal.SIGTTOU)
|
||||
|
||||
if process.returncode != 0:
|
||||
raise osv.except_osv(_('Report (PDF)'),
|
||||
_('wkhtmltopdf failed with error code = %s. '
|
||||
'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]):
|
||||
attachment = {
|
||||
'name': save_in_attachment.get(reporthtml[0]),
|
||||
'datas': base64.encodestring(pdfreport.read()),
|
||||
'datas_fname': save_in_attachment.get(reporthtml[0]),
|
||||
'res_model': save_in_attachment.get('model'),
|
||||
'res_id': reporthtml[0],
|
||||
}
|
||||
self.pool['ir.attachment'].create(cr, uid, attachment)
|
||||
_logger.info('The PDF document %s is now saved in the '
|
||||
'database' % attachment['name'])
|
||||
|
||||
pdfreport.flush()
|
||||
pdfdocuments.append(pdfreport)
|
||||
|
||||
if headers:
|
||||
head_file.close()
|
||||
if footers:
|
||||
foot_file.close()
|
||||
except:
|
||||
raise
|
||||
|
||||
# Get and return the full pdf
|
||||
if len(pdfdocuments) == 1:
|
||||
content = pdfdocuments[0].read()
|
||||
pdfdocuments[0].close()
|
||||
else:
|
||||
content = self._merge_pdf(pdfdocuments)
|
||||
|
||||
return content
|
||||
|
||||
def _get_report_from_name(self, cr, uid, report_name):
|
||||
"""Get the first record of ir.actions.report.xml having the ``report_name`` as value for
|
||||
the field report_name.
|
||||
"""
|
||||
report_obj = self.pool['ir.actions.report.xml']
|
||||
qwebtypes = ['qweb-pdf', 'qweb-html']
|
||||
conditions = [('report_type', 'in', qwebtypes), ('report_name', '=', report_name)]
|
||||
idreport = report_obj.search(cr, uid, conditions)[0]
|
||||
return report_obj.browse(cr, uid, idreport)
|
||||
|
||||
def _build_wkhtmltopdf_args(self, paperformat, specific_paperformat_args=None):
|
||||
"""Build arguments understandable by wkhtmltopdf from a report.paperformat record.
|
||||
|
||||
:paperformat: report.paperformat record
|
||||
:specific_paperformat_args: a dict containing prioritized wkhtmltopdf arguments
|
||||
:returns: list of string representing the wkhtmltopdf arguments
|
||||
"""
|
||||
command_args = []
|
||||
if paperformat.format and paperformat.format != 'custom':
|
||||
command_args.extend(['--page-size', paperformat.format])
|
||||
|
||||
if paperformat.page_height and paperformat.page_width and paperformat.format == 'custom':
|
||||
command_args.extend(['--page-width', str(paperformat.page_width) + 'in'])
|
||||
command_args.extend(['--page-height', str(paperformat.page_height) + 'in'])
|
||||
|
||||
if specific_paperformat_args and specific_paperformat_args['data-report-margin-top']:
|
||||
command_args.extend(['--margin-top',
|
||||
str(specific_paperformat_args['data-report-margin-top'])])
|
||||
elif paperformat.margin_top:
|
||||
command_args.extend(['--margin-top', str(paperformat.margin_top)])
|
||||
|
||||
if paperformat.margin_left:
|
||||
command_args.extend(['--margin-left', str(paperformat.margin_left)])
|
||||
if paperformat.margin_bottom:
|
||||
command_args.extend(['--margin-bottom', str(paperformat.margin_bottom)])
|
||||
if paperformat.margin_right:
|
||||
command_args.extend(['--margin-right', str(paperformat.margin_right)])
|
||||
if paperformat.orientation:
|
||||
command_args.extend(['--orientation', str(paperformat.orientation)])
|
||||
if paperformat.header_spacing:
|
||||
command_args.extend(['--header-spacing', str(paperformat.header_spacing)])
|
||||
if paperformat.header_line:
|
||||
command_args.extend(['--header-line'])
|
||||
if paperformat.dpi:
|
||||
command_args.extend(['--dpi', str(paperformat.dpi)])
|
||||
|
||||
return command_args
|
||||
|
||||
def _merge_pdf(self, documents):
|
||||
"""Merge PDF files into one.
|
||||
|
||||
:param documents: list of pdf files
|
||||
:returns: string containing the merged pdf
|
||||
"""
|
||||
writer = PdfFileWriter()
|
||||
for document in documents:
|
||||
reader = PdfFileReader(file(document.name, "rb"))
|
||||
for page in range(0, reader.getNumPages()):
|
||||
writer.addPage(reader.getPage(page))
|
||||
document.close()
|
||||
merged = cStringIO.StringIO()
|
||||
writer.write(merged)
|
||||
merged.seek(0)
|
||||
content = merged.read()
|
||||
merged.close()
|
||||
return content
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
openerp.report = function(instance) {
|
||||
var wkhtmltopdf_state;
|
||||
|
||||
instance.web.ActionManager = instance.web.ActionManager.extend({
|
||||
ir_actions_report_xml: function(action, options) {
|
||||
|
@ -11,8 +12,7 @@ openerp.report = function(instance) {
|
|||
|
||||
// QWeb reports
|
||||
if ('report_type' in action && (action.report_type == 'qweb-html' || action.report_type == 'qweb-pdf' || action.report_type == 'controller')) {
|
||||
|
||||
var report_url = ''
|
||||
var report_url = '';
|
||||
switch (action.report_type) {
|
||||
case 'qweb-html':
|
||||
report_url = '/report/' + action.report_name;
|
||||
|
@ -49,41 +49,49 @@ openerp.report = function(instance) {
|
|||
}
|
||||
if (action.report_type == 'qweb-html') {
|
||||
// Open the html report in a popup
|
||||
window.open(report_url, '_blank', 'height=768,width=1024');
|
||||
window.open(report_url, '_blank', 'height=900,width=1280');
|
||||
instance.web.unblockUI();
|
||||
return;
|
||||
} else {
|
||||
// Trigger the download of the pdf/custom controller report
|
||||
// Trigger the download of the pdf/controller report
|
||||
var c = openerp.webclient.crashmanager;
|
||||
var response = new Array()
|
||||
response[0] = report_url
|
||||
response[1] = action.report_type
|
||||
var response = new Array();
|
||||
response[0] = report_url;
|
||||
response[1] = action.report_type;
|
||||
|
||||
openerp.session.rpc('/report/check_wkhtmltopdf').then(function (presence) {
|
||||
// Fallback of qweb-pdf if wkhtmltopdf is not installed
|
||||
if (!presence && action.report_type == 'qweb-pdf') {
|
||||
self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
|
||||
system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" _target="blank">\
|
||||
wkhtmltopdf.org</a>'), true);
|
||||
window.open(report_url.substring(12), '_blank', 'height=768,width=1024');
|
||||
instance.web.unblockUI();
|
||||
}
|
||||
else {
|
||||
if (presence == 'upgrade') {
|
||||
self.do_notify(_t('Report'), _t('You should upgrade your version of\
|
||||
Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\
|
||||
support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \
|
||||
target="_blank">wkhtmltopdf.org</a>'), true);
|
||||
if (action.report_type == 'qweb-pdf') {
|
||||
(wkhtmltopdf_state = wkhtmltopdf_state || openerp.session.rpc('/report/check_wkhtmltopdf')).then(function (presence) {
|
||||
// Fallback of qweb-pdf if wkhtmltopdf is not installed
|
||||
if (presence == 'install' && action.report_type == 'qweb-pdf') {
|
||||
self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \
|
||||
system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\
|
||||
wkhtmltopdf.org</a>'), true);
|
||||
window.open(report_url.substring(12), '_blank', 'height=768,width=1024');
|
||||
instance.web.unblockUI();
|
||||
} else {
|
||||
if (presence == 'upgrade') {
|
||||
self.do_notify(_t('Report'), _t('You should upgrade your version of\
|
||||
Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\
|
||||
support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \
|
||||
target="_blank">wkhtmltopdf.org</a>'), true);
|
||||
}
|
||||
self.session.get_file({
|
||||
url: '/report/download',
|
||||
data: {data: JSON.stringify(response)},
|
||||
complete: openerp.web.unblockUI,
|
||||
error: c.rpc_error.bind(c)
|
||||
});
|
||||
}
|
||||
self.session.get_file({
|
||||
url: '/report/download',
|
||||
data: {data: JSON.stringify(response)},
|
||||
complete: openerp.web.unblockUI,
|
||||
error: c.rpc_error.bind(c)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.session.get_file({
|
||||
url: '/report/download',
|
||||
data: {data: JSON.stringify(response)},
|
||||
complete: openerp.web.unblockUI,
|
||||
error: c.rpc_error.bind(c)
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return self._super(action, options);
|
||||
}
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
import logging
|
||||
import openerp
|
||||
import urllib2
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class TestReports(openerp.tests.HttpCase):
|
||||
|
||||
@openerp.tests.common.at_install(False)
|
||||
@openerp.tests.common.post_install(True)
|
||||
class TestReports(openerp.tests.TransactionCase):
|
||||
def test_reports(self):
|
||||
return # commented out until post_install tests are working
|
||||
|
||||
registry, cr, uid = self.registry, self.cr, self.uid
|
||||
r_model = registry('ir.actions.report.xml')
|
||||
domain = [('report_type','like','qweb')]
|
||||
domain = [('report_type', 'like', 'qweb')]
|
||||
for r in r_model.browse(cr, uid, r_model.search(cr, uid, domain)):
|
||||
report_model = 'report.%s' % r.report_name
|
||||
particular_model = registry('ir.model').search(cr, uid, [('model', '=', report_model)])
|
||||
|
||||
# Only test the generic reports here
|
||||
if particular_model:
|
||||
continue
|
||||
|
||||
_logger.info("testing report %s", r.report_name)
|
||||
report_model = registry(r.model)
|
||||
report_model_ids = report_model.search(cr, uid, [], limit=10)
|
||||
if not report_model_ids:
|
||||
_logger.info("no record found skipping report %s", r.report_name)
|
||||
continue
|
||||
if not r.multi:
|
||||
report_model_ids = report_model_ids[:1]
|
||||
url = "/report/%s/%s" % (r.report_name, ','.join(str(i) for i in report_model_ids))
|
||||
_logger.info("testing report %s", url)
|
||||
# TODO sle: uncomment this
|
||||
#content = self.url_open(url)
|
||||
|
||||
# Test report generation
|
||||
registry('report').get_html(cr, uid, report_model_ids, r.report_name)
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
<template id="html_container">
|
||||
<!DOCTYPE html>
|
||||
<html t-att-lang="lang and lang.replace('_', '-')"
|
||||
t-att-data-website-id="website.id if editable else None"
|
||||
t-att-data-editable="'1' if editable else None"
|
||||
t-att-data-translatable="'1' if translatable else None"
|
||||
t-att-data-view-xmlid="xmlid if editable else None"
|
||||
t-att-data-main-object="repr(main_object) if editable else None">
|
||||
<head>
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
class="oe_link"
|
||||
string="Search associated QWeb views"
|
||||
name="associated_view"
|
||||
attrs="{'invisible':[('report_type', 'not in', ['qweb-pdf', 'qweb-html'])]}"
|
||||
/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="content-type"/>
|
||||
|
|
|
@ -112,6 +112,7 @@ def webkit_report_extender(report_name):
|
|||
return fct
|
||||
return fct1
|
||||
|
||||
|
||||
class WebKitParser(report_sxw):
|
||||
"""Custom class that use webkit to render HTML reports
|
||||
Code partially taken from report openoffice. Thanks guys :)
|
||||
|
@ -173,7 +174,7 @@ class WebKitParser(report_sxw):
|
|||
),
|
||||
'w'
|
||||
)
|
||||
head_file.write(header.encode('utf-8'))
|
||||
head_file.write(self._sanitize_html(header.encode('utf-8')))
|
||||
head_file.close()
|
||||
file_to_del.append(head_file.name)
|
||||
command.extend(['--header-html', head_file.name])
|
||||
|
@ -184,7 +185,7 @@ class WebKitParser(report_sxw):
|
|||
),
|
||||
'w'
|
||||
)
|
||||
foot_file.write(footer.encode('utf-8'))
|
||||
foot_file.write(self._sanitize_html(footer.encode('utf-8')))
|
||||
foot_file.close()
|
||||
file_to_del.append(foot_file.name)
|
||||
command.extend(['--footer-html', foot_file.name])
|
||||
|
@ -205,7 +206,7 @@ class WebKitParser(report_sxw):
|
|||
for html in html_list :
|
||||
html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
|
||||
count += 1
|
||||
html_file.write(html.encode('utf-8'))
|
||||
html_file.write(self._sanitize_html(html.encode('utf-8')))
|
||||
html_file.close()
|
||||
file_to_del.append(html_file.name)
|
||||
command.append(html_file.name)
|
||||
|
@ -366,7 +367,6 @@ class WebKitParser(report_sxw):
|
|||
pdf = self.generate_pdf(bin, report_xml, head, foot, htmls)
|
||||
return (pdf, 'pdf')
|
||||
|
||||
|
||||
def create(self, cursor, uid, ids, data, context=None):
|
||||
"""We override the create function in order to handle generator
|
||||
Code taken from report openoffice. Thanks guys :) """
|
||||
|
@ -387,11 +387,18 @@ class WebKitParser(report_sxw):
|
|||
report_xml.report_sxw = None
|
||||
else:
|
||||
return super(WebKitParser, self).create(cursor, uid, ids, data, context)
|
||||
if report_xml.report_type != 'webkit' :
|
||||
if report_xml.report_type != 'webkit':
|
||||
return super(WebKitParser, self).create(cursor, uid, ids, data, context)
|
||||
result = self.create_source_pdf(cursor, uid, ids, data, report_xml, context)
|
||||
if not result:
|
||||
return (False,False)
|
||||
return result
|
||||
|
||||
def _sanitize_html(self, html):
|
||||
"""wkhtmltopdf expects the html page to declare a doctype.
|
||||
"""
|
||||
if html and html[:9].upper() != "<!DOCTYPE":
|
||||
html = "<!DOCTYPE html>\n" + html
|
||||
return html
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -55,11 +55,12 @@ class sale_make_invoice(osv.osv_memory):
|
|||
raise osv.except_osv(_('Warning!'), _("You shouldn't manually invoice the following sale order %s") % (sale_order.name))
|
||||
|
||||
order_obj.action_invoice_create(cr, uid, context.get(('active_ids'), []), data['grouped'], date_invoice=data['invoice_date'])
|
||||
|
||||
for o in order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context):
|
||||
orders = order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context)
|
||||
for o in orders:
|
||||
for i in o.invoice_ids:
|
||||
newinv.append(i.id)
|
||||
|
||||
# Dummy call to workflow, will not create another invoice but bind the new invoice to the subflow
|
||||
order_obj.signal_manual_invoice(cr, uid, [o.id for o in orders if o.order_policy == 'manual'])
|
||||
result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree1')
|
||||
id = result and result[1] or False
|
||||
result = act_obj.read(cr, uid, [id], context=context)[0]
|
||||
|
|
|
@ -38,9 +38,25 @@ class res_groups(osv.osv):
|
|||
class res_users(osv.osv):
|
||||
_name = 'res.users'
|
||||
_inherit = 'res.users'
|
||||
|
||||
def _is_share(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
for user in self.browse(cr, uid, ids, context=context):
|
||||
res[user.id] = not self.has_group(cr, user.id, 'base.group_user')
|
||||
return res
|
||||
|
||||
def _get_users_from_group(self, cr, uid, ids, context=None):
|
||||
result = set()
|
||||
for group in self.pool['res.groups'].browse(cr, uid, ids, context=context):
|
||||
result.update(user.id for user in group.users)
|
||||
return list(result)
|
||||
|
||||
_columns = {
|
||||
'share': fields.boolean('Share User', readonly=True,
|
||||
help="External user with limited access, created only for the purpose of sharing data.")
|
||||
'share': fields.function(_is_share, string='Share User', type='boolean',
|
||||
store={
|
||||
'res.users': (lambda self, cr, uid, ids, c={}: ids, None, 50),
|
||||
'res.groups': (_get_users_from_group, None, 50),
|
||||
}, help="External user with limited access, created only for the purpose of sharing data."),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<data noupdate="1">
|
||||
<record id="base.public_user" model="res.users">
|
||||
<field eval="[(4, ref('group_share_user'))]" name="groups_id"/>
|
||||
<field name="share" eval="True" />
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -258,7 +258,6 @@ class share_wizard(osv.TransientModel):
|
|||
'name': new_user,
|
||||
'email': new_user,
|
||||
'groups_id': [(6,0,[group_id])],
|
||||
'share': True,
|
||||
'company_id': current_user.company_id.id,
|
||||
'company_ids': [(6, 0, [current_user.company_id.id])],
|
||||
}, context)
|
||||
|
@ -276,7 +275,6 @@ class share_wizard(osv.TransientModel):
|
|||
'password': new_pass,
|
||||
'name': new_login,
|
||||
'groups_id': [(6,0,[group_id])],
|
||||
'share': True,
|
||||
'company_id': current_user.company_id.id,
|
||||
'company_ids': [(6, 0, [current_user.company_id.id])],
|
||||
}, context)
|
||||
|
@ -906,7 +904,7 @@ class share_result_line(osv.osv_memory):
|
|||
'login': fields.related('user_id', 'login', string='Login', type='char', size=64, required=True, readonly=True),
|
||||
'password': fields.char('Password', size=64, readonly=True),
|
||||
'share_url': fields.function(_share_url, string='Share URL', type='char', size=512),
|
||||
'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True),
|
||||
'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True, ondelete='cascade'),
|
||||
'newly_created': fields.boolean('Newly created', readonly=True),
|
||||
}
|
||||
_defaults = {
|
||||
|
|
|
@ -2232,14 +2232,14 @@ class stock_move(osv.osv):
|
|||
if move.picking_id:
|
||||
pickings.add(move.picking_id.id)
|
||||
if move.move_dest_id and move.move_dest_id.state == 'waiting':
|
||||
self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'})
|
||||
self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context)
|
||||
if context.get('call_unlink',False) and move.move_dest_id.picking_id:
|
||||
workflow.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
|
||||
self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False})
|
||||
self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context)
|
||||
if not context.get('call_unlink',False):
|
||||
for pick in self.pool.get('stock.picking').browse(cr, uid, list(pickings), context=context):
|
||||
if all(move.state == 'cancel' for move in pick.move_lines):
|
||||
self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'})
|
||||
self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'}, context=context)
|
||||
|
||||
for id in ids:
|
||||
workflow.trg_trigger(uid, 'stock.move', id, cr)
|
||||
|
|
|
@ -792,7 +792,7 @@
|
|||
</group>
|
||||
<notebook>
|
||||
<page string="Products">
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'picking_type': 'internal'}" options='{"reload_on_button": true}'/>
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'picking_type': 'internal'}" options='{"reload_on_button": true}'/>
|
||||
<field name="note" placeholder="Add an internal note..." class="oe_inline"/>
|
||||
</page>
|
||||
<page string="Additional Info">
|
||||
|
@ -926,7 +926,7 @@
|
|||
<field name="partner_id" on_change="onchange_partner_in(partner_id)" string="Customer" domain="[('customer','=',True)]" />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='move_lines']" position="replace">
|
||||
<field name="move_lines" context="{'address_out_id': partner_id, 'picking_type': 'out', 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
<field name="move_lines" context="{'address_out_id': partner_id, 'picking_type': 'out', 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet" position="after">
|
||||
<div class="oe_chatter">
|
||||
|
@ -1053,7 +1053,7 @@
|
|||
<field name="partner_id" on_change="onchange_partner_in(partner_id)" string="Supplier" domain="[('supplier','=',True)]" context="{'default_supplier':1,'default_customer':0}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='move_lines']" position="replace">
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'picking_type': 'in', 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'picking_type': 'in', 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet" position="after">
|
||||
<div class="oe_chatter">
|
||||
|
|
|
@ -33,6 +33,10 @@ class stock_change_product_qty(osv.osv_memory):
|
|||
'prodlot_id': fields.many2one('stock.production.lot', 'Serial Number', domain="[('product_id','=',product_id)]"),
|
||||
'location_id': fields.many2one('stock.location', 'Location', required=True, domain="[('usage', '=', 'internal')]"),
|
||||
}
|
||||
_defaults = {
|
||||
'new_quantity': 1,
|
||||
'product_id': lambda self, cr, uid, ctx: ctx and ctx.get('active_id', False) or False
|
||||
}
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
if context is None: context = {}
|
||||
|
@ -54,20 +58,22 @@ class stock_change_product_qty(osv.osv_memory):
|
|||
@param context: A standard dictionary
|
||||
@return: A dictionary which of fields with values.
|
||||
"""
|
||||
product_id = context and context.get('active_id', False) or False
|
||||
|
||||
res = super(stock_change_product_qty, self).default_get(cr, uid, fields, context=context)
|
||||
|
||||
if 'new_quantity' in fields:
|
||||
res.update({'new_quantity': 1})
|
||||
if 'product_id' in fields:
|
||||
res.update({'product_id': product_id})
|
||||
if 'location_id' in fields:
|
||||
try:
|
||||
model, location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock')
|
||||
self.pool.get('stock.location').check_access_rule(cr, uid, [location_id], 'read', context=context)
|
||||
except (orm.except_orm, ValueError):
|
||||
location_id = False
|
||||
res.update({'location_id': location_id})
|
||||
location_id = res.get('location_id', False)
|
||||
if not location_id:
|
||||
try:
|
||||
model, location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock')
|
||||
except (orm.except_orm, ValueError):
|
||||
pass
|
||||
if location_id:
|
||||
try:
|
||||
self.pool.get('stock.location').check_access_rule(cr, uid, [location_id], 'read', context=context)
|
||||
except (orm.except_orm, ValueError):
|
||||
pass
|
||||
res['location_id'] = location_id
|
||||
return res
|
||||
|
||||
def change_product_qty(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'form_view_ref': False}</field>
|
||||
</record>
|
||||
|
||||
<record id="view_split_in_lots" model="ir.ui.view">
|
||||
|
@ -124,6 +125,7 @@
|
|||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'form_view_ref': False}</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
|
|
@ -37,7 +37,7 @@ e.g. To have an invoice generated automatically periodically:
|
|||
above. Specify the interval information and partner to be invoice.
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': [],
|
||||
'depends': ['base'],
|
||||
'data': ['security/subcription_security.xml', 'security/ir.model.access.csv', 'subscription_view.xml'],
|
||||
'demo': ['subscription_demo.xml',],
|
||||
'installable': True,
|
||||
|
|
|
@ -128,23 +128,14 @@ Thanks,''') % (name, self.pool.get('ir.config_parameter').get_param(cr, uid, 'we
|
|||
for use in exist_user:
|
||||
new_user.append(use.id)
|
||||
for id in survey_ref.browse(cr, uid, survey_ids):
|
||||
report = self.create_report(cr, uid, [id.id], 'report.survey.form', id.title)
|
||||
file = open(get_module_resource('survey', 'report') + id.title +".pdf")
|
||||
file_data = ""
|
||||
while 1:
|
||||
line = file.readline()
|
||||
file_data += line
|
||||
if not line:
|
||||
break
|
||||
file.close()
|
||||
attachments[id.title +".pdf"] = file_data
|
||||
os.remove(get_module_resource('survey', 'report') + id.title +".pdf")
|
||||
result, format = openerp.report.render_report(cr, uid, [id.id], 'survey.form', {}, {})
|
||||
attachments[id.title +".pdf"] = result
|
||||
|
||||
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids):
|
||||
if not partner.email:
|
||||
skipped+= 1
|
||||
continue
|
||||
user = user_ref.search(cr, uid, [('login', "=", partner.email)])
|
||||
user = user_ref.search(cr, uid, [('partner_id', "=", partner.id)])
|
||||
if user:
|
||||
if user[0] not in new_user:
|
||||
new_user.append(user[0])
|
||||
|
@ -185,6 +176,8 @@ Thanks,''') % (name, self.pool.get('ir.config_parameter').get_param(cr, uid, 'we
|
|||
if ans:
|
||||
res_data = {'name': partner.name or _('Unknown'),
|
||||
'login': partner.email,
|
||||
'email': partner.email,
|
||||
'partner_id': partner.id,
|
||||
'password': passwd,
|
||||
'address_id': partner.id,
|
||||
'groups_id': [[6, 0, [group_id]]],
|
||||
|
|
|
@ -299,7 +299,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
#------------------------------------------------------
|
||||
# Helpers
|
||||
#------------------------------------------------------
|
||||
@http.route(['/website/kanban/'], type='http', auth="public", methods=['POST'], website=True)
|
||||
@http.route(['/website/kanban'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def kanban(self, **post):
|
||||
return request.website.kanban_col(**post)
|
||||
|
||||
|
|
|
@ -91,7 +91,12 @@ class ir_http(orm.AbstractModel):
|
|||
assert path is not None
|
||||
except Exception:
|
||||
return self._handle_exception(werkzeug.exceptions.NotFound())
|
||||
if path != request.httprequest.path:
|
||||
|
||||
generated_path = werkzeug.url_unquote_plus(path)
|
||||
current_path = werkzeug.url_unquote_plus(request.httprequest.path)
|
||||
if generated_path != current_path:
|
||||
if request.lang != request.website.default_lang_code:
|
||||
path = '/' + request.lang + path
|
||||
return werkzeug.utils.redirect(path)
|
||||
|
||||
def _handle_exception(self, exception=None, code=500):
|
||||
|
|
|
@ -381,42 +381,8 @@ class RelativeDatetime(orm.AbstractModel):
|
|||
|
||||
class Contact(orm.AbstractModel):
|
||||
_name = 'website.qweb.field.contact'
|
||||
_inherit = ['website.qweb.field', 'website.qweb.field.many2one']
|
||||
_inherit = ['ir.qweb.field.contact', 'website.qweb.field.many2one']
|
||||
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
# FIXME: this behavior is really weird, what if the user wanted to edit the name of the related thingy? Should m2os really be editable without a widget?
|
||||
divs = element.xpath(".//div")
|
||||
for div in divs:
|
||||
if div != divs[0]:
|
||||
div.getparent().remove(div)
|
||||
return super(Contact, self).from_html(cr, uid, model, column, element, context=context)
|
||||
|
||||
def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
|
||||
opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
|
||||
|
||||
if not getattr(record, field_name):
|
||||
return None
|
||||
|
||||
id = getattr(record, field_name).id
|
||||
field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context={"show_address": True})
|
||||
value = werkzeug.utils.escape( field_browse.name_get()[0][1] )
|
||||
|
||||
val = {
|
||||
'name': value.split("\n")[0],
|
||||
'address': werkzeug.utils.escape("\n".join(value.split("\n")[1:])),
|
||||
'phone': field_browse.phone,
|
||||
'mobile': field_browse.mobile,
|
||||
'fax': field_browse.fax,
|
||||
'city': field_browse.city,
|
||||
'country_id': field_browse.country_id and field_browse.country_id.name_get()[0][1],
|
||||
'email': field_browse.email,
|
||||
'fields': opf,
|
||||
'options': options
|
||||
}
|
||||
|
||||
html = self.pool["ir.ui.view"].render(cr, uid, "website.contact", val, engine='website.qweb', context=context).decode('utf8')
|
||||
|
||||
return ir_qweb.HTMLSafe(html)
|
||||
|
||||
def html_to_text(element):
|
||||
""" Converts HTML content with HTML-specified line breaks (br, p, div, ...)
|
||||
|
|
|
@ -168,20 +168,9 @@ class view(osv.osv):
|
|||
arch_no_whitespace = etree.fromstring(
|
||||
etree.tostring(arch, encoding='utf-8'),
|
||||
parser=etree.XMLParser(encoding='utf-8', remove_blank_text=True))
|
||||
arch_pretty_indent_2 = etree.tostring(
|
||||
return etree.tostring(
|
||||
arch_no_whitespace, encoding='unicode', pretty_print=True)
|
||||
|
||||
# pretty_print uses a fixed indent level of 2, we want an indent of 4,
|
||||
# double up leading spaces.
|
||||
def repl(m):
|
||||
indent = len(m.group(0)) / 2
|
||||
return u' ' * 4 * indent
|
||||
# FIXME: If py2.7 only, can use re.M in sub and don't have to do replacement line by line
|
||||
return u'\n'.join(
|
||||
re.sub(ur'^((?: )+)', repl, line)
|
||||
for line in arch_pretty_indent_2.split(u'\n')
|
||||
)
|
||||
|
||||
def save(self, cr, uid, res_id, value, xpath=None, context=None):
|
||||
""" Update a view section. The view section may embed fields to write
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@ class website(osv.osv):
|
|||
pmin = pmax - scope if pmax - scope > 0 else 1
|
||||
|
||||
def get_url(page):
|
||||
_url = "%spage/%s/" % (url, page) if page > 1 else url
|
||||
_url = "%s/page/%s" % (url, page) if page > 1 else url
|
||||
if url_args:
|
||||
_url = "%s?%s" % (_url, werkzeug.url_encode(url_args))
|
||||
return _url
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#website-top-navbar a.btn-link {
|
||||
color: #ad1d28;
|
||||
}
|
||||
.oe_product section {
|
||||
background: rgba(16, 138, 147, 0.75) !important;
|
||||
}
|
||||
.oe_product section .text-info {
|
||||
color: #DFD6F5;
|
||||
}
|
||||
ul.wizard li {
|
||||
background: #debb27 !important;
|
||||
}
|
||||
ul.wizard .chevron:before {
|
||||
border-left: 10px solid #debb27 !important;
|
||||
}
|
||||
ul.wizard .text-primary {
|
||||
color: #ad1d28 !important;
|
||||
}
|
||||
ul.wizard .text-success {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before {
|
||||
color: #79D5DB !important;
|
||||
}
|
||||
.input-group-addon .fa {
|
||||
color: #444;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#website-top-navbar button.btn-primary {
|
||||
color: #fff;
|
||||
border: 2px solid #0061c2;
|
||||
}
|
||||
#website-top-navbar a.btn-link {
|
||||
color: #fff;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
.oe_product section {
|
||||
background: rgba(40, 40, 40, 0.85) !important;
|
||||
}
|
||||
.oe_product section .text-info {
|
||||
color: #BA66E4;
|
||||
}
|
||||
ul.wizard li {
|
||||
background: #222 !important;
|
||||
}
|
||||
ul.wizard .chevron:before {
|
||||
border-left: 10px solid #222 !important;
|
||||
}
|
||||
.popover, .modal-content {
|
||||
border: 1px solid rgba(200,200,200,0.5);
|
||||
}
|
||||
.close {
|
||||
color: #fff;
|
||||
}
|
||||
.popover.bottom .arrow:after {
|
||||
border-bottom-color: #333;
|
||||
}
|
||||
.popover.top .arrow:after {
|
||||
border-top-color: #333;
|
||||
}
|
||||
.popover.left .arrow:after {
|
||||
border-left-color: #333;
|
||||
}
|
||||
.popover.right .arrow:after {
|
||||
border-right-color: #333;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#website-top-navbar a.btn-link {
|
||||
color: #2c3e50;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#website-top-navbar button.btn-primary {
|
||||
border: 2px solid #e4332e;
|
||||
}
|
||||
#website-top-navbar a.btn-link {
|
||||
color: #fff;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.oe_product section .text-info {
|
||||
color: #2A9CBE;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#website-top-navbar a.btn-link {
|
||||
color: #fff;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
.oe_product section {
|
||||
background: rgba(40, 40, 45, 0.80) !important;
|
||||
}
|
||||
ul.wizard li {
|
||||
background-image: -webkit-linear-gradient(#8a9196,#7a8288 60%,#788084) !important;
|
||||
background-image: linear-gradient(#8a9196,#7a8288 60%,#788084 ) !important;
|
||||
color: #ccc;
|
||||
}
|
||||
ul.wizard .chevron:before {
|
||||
border-left: 10px solid #7C8386 !important;
|
||||
}
|
||||
ul.wizard li.text-primary {
|
||||
color: #fff;
|
||||
}
|
||||
.close {
|
||||
color: #fff;
|
||||
}
|
||||
.popover {
|
||||
-webkit-box-shadow: 0px 0px 20px rgba(0,0,0,0.5);
|
||||
box-shadow: 0px 0px 20px rgba(0,0,0,0.5);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.carousel .carousel-caption.content,
|
||||
.carousel .carousel-caption.content h1,
|
||||
.carousel .carousel-caption.content h2,
|
||||
.carousel .carousel-caption.content h3,
|
||||
.carousel .carousel-caption.content h4,
|
||||
.carousel .carousel-caption.content h5 {
|
||||
color: #ccc;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#website-top-navbar a.btn-link {
|
||||
color: #fff;
|
||||
}
|
||||
.popover .close {
|
||||
color: #fff;
|
||||
}
|
|
@ -328,14 +328,10 @@
|
|||
}
|
||||
.oe_overlay .oe_handle.size .oe_handle_button {
|
||||
z-index: 3;
|
||||
content: "Resize";
|
||||
width: 64px;
|
||||
text-align: center;
|
||||
margin-left: -32px;
|
||||
margin-top: -10px;
|
||||
cursor: row-resize;
|
||||
left: 0px;
|
||||
top: 9px;
|
||||
}
|
||||
.oe_overlay .oe_handle.size .oe_handle_button:hover {
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
|
@ -344,6 +340,18 @@
|
|||
-moz-box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
.oe_overlay .oe_handle.size .size {
|
||||
width: 64px;
|
||||
cursor: row-resize;
|
||||
top: 9px;
|
||||
}
|
||||
.oe_overlay .oe_handle.size .auto_size {
|
||||
width: 20px;
|
||||
padding: 0 5px;
|
||||
top: 1px;
|
||||
margin-left: 36px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.oe_overlay .oe_handle.readonly {
|
||||
cursor: auto !important;
|
||||
}
|
||||
|
|
|
@ -240,18 +240,24 @@
|
|||
bottom: -6px
|
||||
.oe_handle_button
|
||||
z-index: 3
|
||||
content: "Resize"
|
||||
width: 64px
|
||||
text-align: center
|
||||
margin-left: -32px
|
||||
margin-top: -10px
|
||||
cursor: row-resize
|
||||
left: 0px
|
||||
top: 9px
|
||||
&:hover
|
||||
background: rgba(30, 30, 30, .8)
|
||||
color: #fff
|
||||
+box-shadow(0 0 5px 3px rgba(255,255,255,.7))
|
||||
.size
|
||||
width: 64px
|
||||
cursor: row-resize
|
||||
top: 9px
|
||||
.auto_size
|
||||
width: 20px
|
||||
padding: 0 5px
|
||||
top: 1px
|
||||
margin-left: 36px
|
||||
cursor: pointer
|
||||
&.readonly
|
||||
cursor: auto !important
|
||||
&:before, &.size
|
||||
|
|
|
@ -240,7 +240,7 @@ ul.nav-stacked > li > a {
|
|||
display: none;
|
||||
}
|
||||
|
||||
[data-publish='off'] {
|
||||
[data-publish='off'] > *:not(.css_options) {
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
@ -199,7 +199,7 @@ ul.nav-stacked > li > a
|
|||
.btn-success, .css_unpublish
|
||||
display: none
|
||||
|
||||
[data-publish='off']
|
||||
[data-publish='off']>*:not(.css_options)
|
||||
+opacity(0.5)
|
||||
|
||||
/* ---- END of PUBLISH ---- */
|
||||
|
|
|
@ -1789,6 +1789,9 @@
|
|||
if (node.nodeName === 'BR' && node.getAttribute('type') === '_moz') {
|
||||
// <br type="_moz"> appears when focusing RTE in FF, ignore
|
||||
continue;
|
||||
} else if (node.nodeName === 'DIV' && $(node).hasClass('oe_drop_zone')) {
|
||||
// ignore dropzone inserted by snippets
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
}).then(function (val, field, $dialog) {
|
||||
if (val) {
|
||||
var url = '/website/add/' + encodeURI(val);
|
||||
var url = '/website/add' + encodeURI(val);
|
||||
if ($dialog.find('input[type="checkbox"]').is(':checked')) url +="?add_menu=1";
|
||||
document.location = url;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
/* ----------------------------------------------------
|
||||
Widgets
|
||||
---------------------------------------------------- */
|
||||
|
||||
website.prompt = function (options) {
|
||||
/**
|
||||
* A bootstrapped version of prompt() albeit asynchronous
|
||||
|
@ -198,7 +199,7 @@
|
|||
var page = +$a.attr("href").split(",").pop().split('-')[1];
|
||||
data['page'] = page;
|
||||
|
||||
$.post('/website/kanban/', data, function (col) {
|
||||
$.post('/website/kanban', data, function (col) {
|
||||
$col.find("> .thumbnail").remove();
|
||||
$pagination.last().before(col);
|
||||
});
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
},
|
||||
start: function () {
|
||||
if (!window.location.origin) { // fix for ie9
|
||||
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '') + '/';
|
||||
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
|
||||
}
|
||||
document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + "#mobile-preview";
|
||||
document.getElementById("mobile-viewport").src = window.location.origin + (window.location.pathname.length ? '/' : '') + window.location.pathname + "#mobile-preview";
|
||||
this.$el.modal();
|
||||
},
|
||||
destroy: function () {
|
||||
|
|
|
@ -1,6 +1,136 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* Building block / Snippet Editor
|
||||
|
||||
The building blocks appear in the edit bar website. These prebuilt html block
|
||||
allowing the designer to easily generate content on a page (drag and drop).
|
||||
Options allow snippets to add customizations part html code according to their
|
||||
selector (jQuery) and javascript object.
|
||||
|
||||
How to create content?
|
||||
|
||||
Designers can add their own html block in the "snippets" (/website/views/snippets.xml).
|
||||
The block must be added in one of four menus (structure, content, feature or effect).
|
||||
Structure:
|
||||
<div>
|
||||
<div class="oe_snippet_thumbnail">
|
||||
<img class="oe_snippet_thumbnail_img" src="...image src..."/>
|
||||
<span class="oe_snippet_thumbnail_title">...Block Name...</span>
|
||||
</div>
|
||||
<div class="oe_snippet_body">
|
||||
...
|
||||
<!--
|
||||
The block with class 'oe_snippet_body' is inserted in the page.
|
||||
This class is removed when the block is dropped.
|
||||
The block can be made of any html tag and content. -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
How to create options?
|
||||
|
||||
Designers can add their own html block in the "snippet_options" (/website/views/snippets.xml).
|
||||
Structure:
|
||||
|
||||
<div data-snippet-option-id='...' <!-- Required: javascript object id (but javascript
|
||||
for this option object is not required) -->
|
||||
data-selector="..." <!-- Required: jQuery selector.
|
||||
Apply options on all The part of html who
|
||||
match with this jQuery selector.
|
||||
E.g.: If the selector is div, all div will be selected
|
||||
and can be highlighted and assigned an editor. -->
|
||||
data-selector-siblings="..." <!-- Optional: jQuery selector.
|
||||
The html part can be insert or move beside
|
||||
the selected html block -->
|
||||
data-selector-children="..." <!-- Optional: jQuery selector.
|
||||
The html part can be insert or move inside
|
||||
the selected html block -->
|
||||
data-selector-vertical-children='...'> <!-- Optional: jQuery selector.
|
||||
The html part can be insert or move inside
|
||||
the selected html block. The drop zone is
|
||||
displayed vertically -->
|
||||
...
|
||||
<li><a href="#">...</a></li> <!-- Optional: html li list.
|
||||
List of menu items displayed in customize
|
||||
menu. If the li tag have 'data-class', the
|
||||
class is automaticcally added or removed to
|
||||
the html content when the user select this item. -->
|
||||
...
|
||||
<li class="dropdown-submenu" <!-- Optional: html li list exemple. !-->
|
||||
data-required="true"> <!-- Optional: if only one item can be selected
|
||||
and can't be unselect. !-->
|
||||
<a tabindex="-1" href="#">...</a> <!-- bootstrap dropdown button !-->
|
||||
<ul class="dropdown-menu">
|
||||
<li data-value="text_only"><a>...</a></li> <!-- by default data-value is apply
|
||||
like a class to html block !-->
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
How to create a javascript object for an options?
|
||||
|
||||
openerp.website.snippet.options["...option-id..."] = website.snippet.Option.extend({
|
||||
// start is called when the user click into a block or when the user drop a block
|
||||
// into the page (just after the init method).
|
||||
// start is usually used to bind event.
|
||||
//
|
||||
// this.$target: block html inserted inside the page
|
||||
// this.$el: html li list of this options
|
||||
// this.$overlay: html editor overlay who content resize bar, customize menu...
|
||||
start: function () {},
|
||||
|
||||
|
||||
// onFocus is called when the user click inside the block inserted in page
|
||||
// and when the user drop on block into the page
|
||||
onFocus : function () {},
|
||||
|
||||
|
||||
// onBlur is called when the user click outside the block inserted in page, if
|
||||
// the block is focused
|
||||
onBlur : function () {},
|
||||
|
||||
|
||||
// on_clone is called when the snippet is duplicate
|
||||
// @variables: $clone is allready inserted is the page
|
||||
on_clone: function ($clone) {},
|
||||
|
||||
|
||||
// on_remove is called when the snippet is removed (dom is removing after this tigger)
|
||||
on_remove: function () {},
|
||||
|
||||
|
||||
// drop_and_build_snippet is called just after that a thumbnail is drag and dropped
|
||||
// into a drop zone. The content is already inserted in the page.
|
||||
drop_and_build_snippet: function () {},
|
||||
|
||||
// select is called when a user select an item in the li list of options
|
||||
// By default, if the li item have a data-value attribute, the data-vlue it's apply
|
||||
// like a class to the html block (this.$target)
|
||||
// @variables: next_previous = {$next, $prev}
|
||||
// $next = next item selected or false
|
||||
// $prev = previous item selected or false
|
||||
select: function (event, next_previous) {}
|
||||
|
||||
// preview is called when a user is on mouse over or mouse out of an item
|
||||
// variables: next_previous = {$next, $prev}
|
||||
// $next = next item selected or false
|
||||
// $prev = previous item selected or false
|
||||
preview: function (event, next_previous) {}
|
||||
|
||||
// clean_for_save
|
||||
// clean_for_save is called just before to save the vue
|
||||
// Sometime it's important to remove or add some datas (contentEditable, added
|
||||
// classes to a running animation...)
|
||||
clean_for_save: function () {}
|
||||
});
|
||||
|
||||
|
||||
// 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped
|
||||
// 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated
|
||||
|
||||
*/
|
||||
|
||||
|
||||
var website = openerp.website;
|
||||
website.add_template_file('/website/static/src/xml/website.snippets.xml');
|
||||
|
||||
|
@ -19,23 +149,11 @@
|
|||
edit: function () {
|
||||
var self = this;
|
||||
$("[data-oe-model] *, [data-oe-type=html] *").off('click');
|
||||
website.snippet.stop_animation();
|
||||
window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
|
||||
this.snippets.appendTo(this.$el);
|
||||
this.on('rte:ready', this, function () {
|
||||
self.snippets.$button.removeClass("hidden");
|
||||
website.snippet.start_animation();
|
||||
$(website.snippet.readyAnimation).each(function() {
|
||||
var animation = $(this).data("snippet-view");
|
||||
if (animation) {
|
||||
animation.$target.on('focus', '*', function(){
|
||||
animation.stop();
|
||||
});
|
||||
animation.$target.on('blur', '*', function(){
|
||||
animation.start();
|
||||
});
|
||||
}
|
||||
});
|
||||
website.snippet.stop_animation();
|
||||
});
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
|
@ -56,9 +174,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
// 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped
|
||||
// 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated
|
||||
|
||||
if (!website.snippet) website.snippet = {};
|
||||
website.snippet.templateOptions = {};
|
||||
website.snippet.globalSelector = "";
|
||||
|
@ -397,18 +512,17 @@
|
|||
$("#oe_snippets").trigger('snippet-dropped', $target);
|
||||
|
||||
website.snippet.start_animation(true, $target);
|
||||
|
||||
// drop_and_build_snippet
|
||||
self.create_overlay($target);
|
||||
if ($target.data("snippet-editor")) {
|
||||
$target.data("snippet-editor").drop_and_build_snippet($target);
|
||||
$target.data("snippet-editor").drop_and_build_snippet();
|
||||
}
|
||||
for (var k in website.snippet.templateOptions) {
|
||||
$target.find(website.snippet.templateOptions[k].selector).each(function () {
|
||||
var $snippet = $(this);
|
||||
self.create_overlay($snippet);
|
||||
if ($snippet.data("snippet-editor")) {
|
||||
$snippet.data("snippet-editor").drop_and_build_snippet($snippet);
|
||||
$snippet.data("snippet-editor").drop_and_build_snippet();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -665,80 +779,12 @@
|
|||
self.set_active();
|
||||
self.$target.trigger("snippet-style-change", [self, np]);
|
||||
},0);
|
||||
this.select(event, {'$next': $next, '$prev': $prev});
|
||||
this.select({'$next': $next, '$prev': $prev});
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
self.$target.trigger("snippet-style-preview", [self, np]);
|
||||
},0);
|
||||
this.preview(event, np);
|
||||
}
|
||||
},
|
||||
// start is call just after the init
|
||||
start: function () {
|
||||
},
|
||||
/* onFocus
|
||||
* This method is called when the user click inside the snippet in the dom
|
||||
*/
|
||||
onFocus : function () {
|
||||
},
|
||||
|
||||
/* onFocus
|
||||
* This method is called when the user click outside the snippet in the dom, after a focus
|
||||
*/
|
||||
onBlur : function () {
|
||||
},
|
||||
|
||||
/* on_clone
|
||||
* This method is called when the snippet is cloned ($clone is allready inserted)
|
||||
*/
|
||||
on_clone: function ($clone) {
|
||||
},
|
||||
|
||||
/* on_remove
|
||||
* This method is called when the snippet is removed (dom is removing after this call)
|
||||
*/
|
||||
on_remove: function () {
|
||||
},
|
||||
|
||||
/*
|
||||
* drop_and_build_snippet
|
||||
* This method is called just after that a thumbnail is drag and dropped into a drop zone
|
||||
* (after the insertion of this.$body, if this.$body exists)
|
||||
*/
|
||||
drop_and_build_snippet: function ($target) {
|
||||
},
|
||||
/* select
|
||||
* called when a user select an item
|
||||
* li must have data-value attribute
|
||||
* variables: np = {$next, $prev}
|
||||
* $next is false if they are no next item selected
|
||||
* $prev is false if they are no previous item selected
|
||||
*/
|
||||
select: function (event, np) {
|
||||
var self = this;
|
||||
// add or remove html class
|
||||
if (np.$prev) {
|
||||
this.$target.removeClass(np.$prev.data('value' || ""));
|
||||
}
|
||||
if (np.$next) {
|
||||
this.$target.addClass(np.$next.data('value') || "");
|
||||
}
|
||||
},
|
||||
/* preview
|
||||
* called when a user is on mouse over or mouse out of an item
|
||||
* variables: np = {$next, $prev}
|
||||
* $next is false if they are no next item selected
|
||||
* $prev is false if they are no previous item selected
|
||||
*/
|
||||
preview: function (event, np) {
|
||||
var self = this;
|
||||
|
||||
// add or remove html class
|
||||
if (np.$prev) {
|
||||
this.$target.removeClass(np.$prev.data('value') || "");
|
||||
}
|
||||
if (np.$next) {
|
||||
this.$target.addClass(np.$next.data('value') || "");
|
||||
this.preview(np);
|
||||
}
|
||||
},
|
||||
/* set_active
|
||||
|
@ -757,9 +803,48 @@
|
|||
.addClass("active");
|
||||
this.$el.find('li:has(li[data-value].active)').addClass("active");
|
||||
},
|
||||
/* clean_for_save
|
||||
* function called just before save vue
|
||||
*/
|
||||
|
||||
start: function () {
|
||||
},
|
||||
|
||||
onFocus : function () {
|
||||
},
|
||||
|
||||
onBlur : function () {
|
||||
},
|
||||
|
||||
on_clone: function ($clone) {
|
||||
},
|
||||
|
||||
on_remove: function () {
|
||||
},
|
||||
|
||||
drop_and_build_snippet: function () {
|
||||
},
|
||||
|
||||
select: function (np) {
|
||||
var self = this;
|
||||
// add or remove html class
|
||||
if (np.$prev && this.required) {
|
||||
this.$target.removeClass(np.$prev.data('value' || ""));
|
||||
}
|
||||
if (np.$next) {
|
||||
this.$target.addClass(np.$next.data('value') || "");
|
||||
}
|
||||
},
|
||||
|
||||
preview: function (np) {
|
||||
var self = this;
|
||||
|
||||
// add or remove html class
|
||||
if (np.$prev && this.required) {
|
||||
this.$target.removeClass(np.$prev.data('value') || "");
|
||||
}
|
||||
if (np.$next) {
|
||||
this.$target.addClass(np.$next.data('value') || "");
|
||||
}
|
||||
},
|
||||
|
||||
clean_for_save: function () {
|
||||
}
|
||||
});
|
||||
|
@ -776,9 +861,9 @@
|
|||
var src = this._get_bg();
|
||||
this.$el.find("li[data-value].active.oe_custom_bg").data("src", src);
|
||||
},
|
||||
select: function(event, np) {
|
||||
select: function(np) {
|
||||
var self = this;
|
||||
this._super(event, np);
|
||||
this._super(np);
|
||||
if (np.$next) {
|
||||
if (np.$next.hasClass("oe_custom_bg")) {
|
||||
var editor = new website.editor.ImageDialog();
|
||||
|
@ -803,8 +888,8 @@
|
|||
this.$target.removeClass(np.$prev.data("value"));
|
||||
}
|
||||
},
|
||||
preview: function (event, np) {
|
||||
this._super(event, np);
|
||||
preview: function (np) {
|
||||
this._super(np);
|
||||
if (np.$next) {
|
||||
this._set_bg(np.$next.data("src"));
|
||||
}
|
||||
|
@ -872,6 +957,7 @@
|
|||
start : function () {
|
||||
var self = this;
|
||||
this._super();
|
||||
this.$target.carousel({interval: false});
|
||||
this.id = this.$target.attr("id");
|
||||
this.$inner = this.$target.find('.carousel-inner');
|
||||
this.$indicators = this.$target.find('.carousel-indicators');
|
||||
|
@ -1003,7 +1089,7 @@
|
|||
this.$target.find('.carousel-control').off('click').on('click', function () {
|
||||
self.$target.carousel( $(this).data('slide')); });
|
||||
|
||||
this.$target.find('.carousel-inner .content > div').attr('contentEditable', 'true');
|
||||
this.$target.find('.carousel-image, .carousel-inner .content > div').attr('contentEditable', 'true');
|
||||
this.$target.find('.carousel-image').attr('attributeEditable', 'true');
|
||||
this._super();
|
||||
},
|
||||
|
@ -1021,7 +1107,7 @@
|
|||
if (resize_values.w) this.$overlay.find(".oe_handle.w").removeClass("readonly");
|
||||
if (resize_values.size) this.$overlay.find(".oe_handle.size").removeClass("readonly");
|
||||
|
||||
this.$overlay.find(".oe_handle:not(:has(.oe_handle_button)), .oe_handle .oe_handle_button").on('mousedown', function (event){
|
||||
this.$overlay.find(".oe_handle:not(.size), .oe_handle.size .size").on('mousedown', function (event){
|
||||
event.preventDefault();
|
||||
|
||||
var $handle = $(this);
|
||||
|
@ -1123,6 +1209,11 @@
|
|||
$body.mousemove(body_mousemove);
|
||||
$body.mouseup(body_mouseup);
|
||||
});
|
||||
this.$overlay.find(".oe_handle.size .auto_size").on('click', function (event){
|
||||
self.$target.css("height", "");
|
||||
self.BuildingBlock.cover_target(self.$overlay, self.$target);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
getSize: function () {
|
||||
this.grid = {};
|
||||
|
@ -1286,15 +1377,6 @@
|
|||
this.grid.size = 8;
|
||||
return this.grid;
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
this._super();
|
||||
this.$el.find(".js_size_auto").on('click', function (event){
|
||||
self.$target.css("height", "");
|
||||
self.BuildingBlock.cover_target(self.$overlay, self.$target);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
website.snippet.options.parallax = website.snippet.Option.extend({
|
||||
|
@ -1543,9 +1625,9 @@
|
|||
* This method is called just after that a thumbnail is drag and dropped into a drop zone
|
||||
* (after the insertion of this.$body, if this.$body exists)
|
||||
*/
|
||||
drop_and_build_snippet: function ($target) {
|
||||
drop_and_build_snippet: function () {
|
||||
for (var i in this.styles){
|
||||
this.styles[i].drop_and_build_snippet($target);
|
||||
this.styles[i].drop_and_build_snippet();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,11 +1,30 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
// raise an error in test mode if openerp don't exist
|
||||
if (typeof openerp === "undefined") {
|
||||
var error = "openerp is undefined"
|
||||
+ "\nhref: " + window.location.href
|
||||
+ "\nreferrer: " + document.referrer
|
||||
+ "\nlocalStorage: " + JSON.stringify(window.localStorage);
|
||||
if (typeof $ !== "undefined") {
|
||||
error += '\n\n' + $("body").html();
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
var website = window.openerp.website;
|
||||
|
||||
// don't rewrite website.Tour in test mode
|
||||
if (typeof website.Tour !== "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't need template to use bootstrap Tour in automatic mode
|
||||
if (typeof QWeb2 !== "undefined")
|
||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
||||
if (typeof QWeb2 !== "undefined") {
|
||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
||||
|
||||
}
|
||||
|
||||
// don't need to use bootstrap Tour to launch an automatic tour
|
||||
function bootstrap_tour_stub () {
|
||||
|
@ -19,27 +38,28 @@ function bootstrap_tour_stub () {
|
|||
|
||||
|
||||
|
||||
if (website.EditorBar)
|
||||
website.EditorBar.include({
|
||||
tours: [],
|
||||
start: function () {
|
||||
var self = this;
|
||||
var menu = $('#help-menu');
|
||||
_.each(this.tours, function (tour) {
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
tour.reset();
|
||||
tour.run();
|
||||
if (website.EditorBar) {
|
||||
website.EditorBar.include({
|
||||
tours: [],
|
||||
start: function () {
|
||||
var self = this;
|
||||
var menu = $('#help-menu');
|
||||
_.each(this.tours, function (tour) {
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
tour.reset();
|
||||
tour.run();
|
||||
});
|
||||
menu.append($menuItem);
|
||||
});
|
||||
menu.append($menuItem);
|
||||
});
|
||||
return this._super();
|
||||
},
|
||||
registerTour: function (tour) {
|
||||
website.Tour.add(tour);
|
||||
this.tours.push(tour);
|
||||
}
|
||||
});
|
||||
return this._super();
|
||||
},
|
||||
registerTour: function (tour) {
|
||||
website.Tour.add(tour);
|
||||
this.tours.push(tour);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
@ -308,7 +328,8 @@ website.Tour = openerp.Class.extend({
|
|||
self.timer = setTimeout(checkNext, self.defaultDelay);
|
||||
} else {
|
||||
self.reset();
|
||||
throw new Error("Time overlaps to arrive to step " + step.stepId + ": '" + step._title + "'"
|
||||
throw new Error("Can't arrive to step " + step.stepId + ": '" + step._title + "'"
|
||||
+ '\nhref: ' + window.location.href
|
||||
+ '\nelement: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden")))
|
||||
+ '\nwaitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size())
|
||||
+ '\nwaitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size())
|
||||
|
@ -363,12 +384,13 @@ website.Tour = openerp.Class.extend({
|
|||
}
|
||||
},
|
||||
endTour: function () {
|
||||
if (parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1) {
|
||||
var test = parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1;
|
||||
this.reset();
|
||||
if (test) {
|
||||
console.log('ok');
|
||||
} else {
|
||||
console.log('error');
|
||||
}
|
||||
this.reset();
|
||||
},
|
||||
autoNextStep: function () {
|
||||
var self = this;
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
},
|
||||
processTranslatableNodes: function () {
|
||||
var self = this;
|
||||
var source_attr = 'data-oe-source-id';
|
||||
var $editables = $('[data-oe-model="ir.ui.view"]')
|
||||
.not('link, script')
|
||||
.not('.oe_snippets,.oe_snippet, .oe_snippet *')
|
||||
|
@ -78,7 +79,8 @@
|
|||
|
||||
$editables.each(function () {
|
||||
var $node = $(this);
|
||||
var view_id = $node.attr('data-oe-source-id') || $node.attr('data-oe-id');
|
||||
var source_id = $node.parents('[' + source_attr + ']:first').attr(source_attr)|0;
|
||||
var view_id = $node.attr('data-oe-source-id') || source_id || $node.attr('data-oe-id');
|
||||
self.transNode(this, view_id|0);
|
||||
});
|
||||
$('.oe_translatable_text').on('paste', function () {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<button type="button" data-action="save"
|
||||
class="btn btn-primary">Save</button>
|
||||
or
|
||||
<a href="#" data-action="cancel">Discard</a>
|
||||
<a href="#" data-action="cancel" class="btn btn-link">Discard</a>
|
||||
</form>
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
</ul>
|
||||
|
|
|
@ -38,7 +38,10 @@
|
|||
<div class='oe_handle n readonly'><div></div></div>
|
||||
<div class='oe_handle e readonly'><div></div></div>
|
||||
<div class='oe_handle w readonly'><div></div></div>
|
||||
<div class='oe_handle size readonly'><div class="oe_handle_button size">Resize</div></div>
|
||||
<div class='oe_handle size readonly'>
|
||||
<div class="oe_handle_button size">Resize</div>
|
||||
<div class="oe_handle_button auto_size">x</div>
|
||||
</div>
|
||||
<div class='oe_handle s readonly'><div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -923,7 +923,7 @@
|
|||
</div>
|
||||
|
||||
<div data-snippet-option-id='carousel-style'
|
||||
data-selector="div[data-snippet-id='carousel']">
|
||||
data-selector=".carousel:not(.quotecarousel)">
|
||||
<li class="dropdown-submenu" data-required="true">
|
||||
<a tabindex="-1" href="#">Layout</a>
|
||||
<ul class="dropdown-menu">
|
||||
|
@ -952,9 +952,6 @@
|
|||
<div data-snippet-option-id='resize'
|
||||
data-selector="section, .carousel, .parallax"
|
||||
data-selector-children=".oe_structure, [data-oe-type=html]">
|
||||
<li>
|
||||
<a href="#" class="button js_size_auto">Size Automatic</a>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<div data-snippet-option-id='margin-x'
|
||||
|
|
|
@ -206,6 +206,7 @@
|
|||
<template id="website.theme_amelia" name="Amelia" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/amelia.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -218,48 +219,56 @@
|
|||
<template id="website.theme_cosmo" name="Cosmo" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cosmo.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_cyborg" name="Cyborg" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/cyborg.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_flatly" name="Flatly" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/flatly.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_journal" name="Journal" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/journal.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_readable" name="Readable" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/readable.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_simplex" name="Simplex" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/simplex.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_slate" name="Slate" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/slate.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website.theme_spacelab" name="Spacelab" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/spacelab.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -272,6 +281,7 @@
|
|||
<template id="website.theme_yeti" name="Yeti" inherit_option_id="website.theme">
|
||||
<xpath expr="//link[@id='bootstrap_css']" position="replace">
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.min.css' t-ignore="true"/>
|
||||
<link rel='stylesheet' href='/website/static/src/css/bootswatch/yeti.fix.css' t-ignore="true"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -289,8 +289,7 @@
|
|||
|
||||
<template id="debugger" inherit_option_id="website.layout" name="Debugger & Tests">
|
||||
<xpath expr='//t[@name="layout_head"]' position="after">
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.test.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.test.admin.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -760,23 +759,5 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="contact">
|
||||
<address t-ignore="true" class="mb0" itemscope="itemscope" itemtype="http://schema.org/Organization">
|
||||
<div t-att-class="'name' not in fields and 'css_non_editable_mode_hidden'"><span itemprop="name" t-esc="name"/></div>
|
||||
<div itemprop="address" itemscope="itemscope" itemtype="http://schema.org/PostalAddress">
|
||||
<div t-if="address and 'address' in fields" class='css_editable_mode_hidden'>
|
||||
<i t-if="not options.get('no_marker')" class='fa fa-map-marker'/> <span itemprop="streetAddress" t-raw="address.replace('\n', options.get('no_tag_br') and ', ' or ('<br/>%s' % ('' if options.get('no_marker') else '&nbsp; &nbsp; ')))"/>
|
||||
</div>
|
||||
<div t-if="city and 'city' in fields" class='css_editable_mode_hidden'>
|
||||
<i t-if="not options.get('no_marker')" class='fa fa-map-marker'/> <span itemprop="addressLocality" t-raw="city"/>, <span itemprop="addressCountry" t-raw="country_id"/>
|
||||
</div>
|
||||
<div t-if="phone and 'phone' in fields" class='css_editable_mode_hidden'><i t-if="not options.get('no_marker')" class='fa fa-phone'/> <span itemprop="telephone" t-esc="phone"/></div>
|
||||
<div t-if="mobile and 'mobile' in fields" class='css_editable_mode_hidden'><i t-if="not options.get('no_marker')" class='fa fa-mobile-phone'/> <span itemprop="telephone" t-esc="mobile"/></div>
|
||||
<div t-if="fax and 'fax' in fields" class='css_editable_mode_hidden'><i t-if="not options.get('no_marker')" class='fa fa-file-text-o'/> <span itemprop="faxNumber" t-esc="fax"/></div>
|
||||
<div t-if="email and 'email' in fields" class='css_editable_mode_hidden'><i t-if="not options.get('no_marker')" class='fa fa-envelope'/> <span itemprop="email" t-esc="email"/></div>
|
||||
</div>
|
||||
</address>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -42,7 +42,7 @@ class WebsiteBlog(http.Controller):
|
|||
|
||||
@http.route([
|
||||
'/blog',
|
||||
'/blog/page/<int:page>/',
|
||||
'/blog/page/<int:page>',
|
||||
], type='http', auth="public", website=True, multilang=True)
|
||||
def blogs(self, page=1):
|
||||
BYPAGE = 60
|
||||
|
@ -50,7 +50,7 @@ class WebsiteBlog(http.Controller):
|
|||
blog_obj = request.registry['blog.post']
|
||||
total = blog_obj.search(cr, uid, [], count=True, context=context)
|
||||
pager = request.website.pager(
|
||||
url='/blog/',
|
||||
url='/blog',
|
||||
total=total,
|
||||
page=page,
|
||||
step=BYPAGE,
|
||||
|
@ -63,14 +63,14 @@ class WebsiteBlog(http.Controller):
|
|||
})
|
||||
|
||||
@http.route([
|
||||
'/blog/<model("blog.blog"):blog>/',
|
||||
'/blog/<model("blog.blog"):blog>/page/<int:page>/',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>/',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>/page/<int:page>/',
|
||||
'/blog/<model("blog.blog"):blog>/date/<string(length=21):date>/',
|
||||
'/blog/<model("blog.blog"):blog>/date/<string(length=21):date>/page/<int:page>/',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>/date/<string(length=21):date>/',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>/date/<string(length=21):date>/page/<int:page>/',
|
||||
'/blog/<model("blog.blog"):blog>',
|
||||
'/blog/<model("blog.blog"):blog>/page/<int:page>',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>/page/<int:page>',
|
||||
'/blog/<model("blog.blog"):blog>/date/<string(length=21):date>',
|
||||
'/blog/<model("blog.blog"):blog>/date/<string(length=21):date>/page/<int:page>',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>/date/<string(length=21):date>',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<model("blog.tag"):tag>/date/<string(length=21):date>/page/<int:page>',
|
||||
], type='http', auth="public", website=True, multilang=True)
|
||||
def blog(self, blog=None, tag=None, date=None, page=1, **opt):
|
||||
""" Prepare all values to display the blog.
|
||||
|
@ -106,13 +106,13 @@ class WebsiteBlog(http.Controller):
|
|||
domain = []
|
||||
|
||||
if blog:
|
||||
path_filter += "%s/" % blog.id
|
||||
path_filter += "%s" % blog.id
|
||||
domain += [("id", "in", [post.id for post in blog.blog_post_ids])]
|
||||
if tag:
|
||||
path_filter += 'tag/%s/' % tag.id
|
||||
path_filter += 'tag/%s' % tag.id
|
||||
domain += [("id", "in", [post.id for post in tag.blog_post_ids])]
|
||||
if date:
|
||||
path_filter += "date/%s/" % date
|
||||
path_filter += "date/%s" % date
|
||||
domain += [("create_date", ">=", date.split("_")[0]), ("create_date", "<=", date.split("_")[1])]
|
||||
|
||||
blog_post_ids = blog_post_obj.search(cr, uid, domain, context=context)
|
||||
|
@ -147,7 +147,7 @@ class WebsiteBlog(http.Controller):
|
|||
return request.website.render("website_blog.blog_post_short", values)
|
||||
|
||||
@http.route([
|
||||
'/blogpost/<model("blog.post"):blog_post>/',
|
||||
'/blogpost/<model("blog.post"):blog_post>',
|
||||
], type='http', auth="public", website=True, multilang=True)
|
||||
def blog_post(self, blog_post, tag=None, date=None, page=1, enable_editor=None, **post):
|
||||
""" Prepare all values to display the blog.
|
||||
|
@ -245,7 +245,7 @@ class WebsiteBlog(http.Controller):
|
|||
'content': '',
|
||||
'website_published': False,
|
||||
}, context=create_context)
|
||||
return werkzeug.utils.redirect("/blogpost/%s/?enable_editor=1" % new_blog_post_id)
|
||||
return werkzeug.utils.redirect("/blogpost/%s?enable_editor=1" % new_blog_post_id)
|
||||
|
||||
@http.route('/blogpost/duplicate', type='http', auth="public", website=True)
|
||||
def blog_post_copy(self, blog_post_id, **post):
|
||||
|
@ -258,4 +258,4 @@ class WebsiteBlog(http.Controller):
|
|||
cr, uid, context = request.cr, request.uid, request.context
|
||||
create_context = dict(context, mail_create_nosubscribe=True)
|
||||
new_blog_post_id = request.registry['blog.post'].copy(cr, uid, blog_post_id, {}, context=create_context)
|
||||
return werkzeug.utils.redirect("/blogpost/%s/?enable_editor=1" % new_blog_post_id)
|
||||
return werkzeug.utils.redirect("/blogpost/%s?enable_editor=1" % new_blog_post_id)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_span12">
|
||||
<h2 class="oe_slogan">Awesome Open Source Blogging Plateform</h2>
|
||||
<h2 class="oe_slogan">Awesome Open Source Blogging Platform</h2>
|
||||
<h3 class="oe_slogan">Write, Design, Promote, Engage</h3>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
|
@ -15,7 +15,7 @@
|
|||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
Express yourself with the OpenERP enterprise grade blogging
|
||||
plateform. Write beautiful blog posts, engage with visitors,
|
||||
platform. Write beautiful blog posts, engage with visitors,
|
||||
translate content and moderate social streams.
|
||||
</p><p>
|
||||
Get your blog posts efficiently referenced in Google and
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<!-- Layout add nav and footer -->
|
||||
<template id="header_footer_custom" inherit_id="website.layout" name="Footer News Blog Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a t-attf-href="/blog/%(website_blog.blog_blog_1)d/">News</a></li>
|
||||
<li><a t-attf-href="/blog/%(website_blog.blog_blog_1)d">News</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -13,21 +13,21 @@ class WebsiteCrmPartnerAssign(http.Controller):
|
|||
_references_per_page = 20
|
||||
|
||||
@http.route([
|
||||
'/partners/',
|
||||
'/partners/page/<int:page>/',
|
||||
'/partners',
|
||||
'/partners/page/<int:page>',
|
||||
|
||||
'/partners/grade/<int:grade_id>',
|
||||
'/partners/grade/<int:grade_id>/page/<int:page>/',
|
||||
'/partners/grade/<int:grade_id>/page/<int:page>',
|
||||
|
||||
'/partners/country/<int:country_id>',
|
||||
'/partners/country/<country_name>-<int:country_id>',
|
||||
'/partners/country/<int:country_id>/page/<int:page>/',
|
||||
'/partners/country/<country_name>-<int:country_id>/page/<int:page>/',
|
||||
'/partners/country/<int:country_id>/page/<int:page>',
|
||||
'/partners/country/<country_name>-<int:country_id>/page/<int:page>',
|
||||
|
||||
'/partners/grade/<int:grade_id>/country/<int:country_id>/',
|
||||
'/partners/grade/<int:grade_id>/country/<int:country_id>',
|
||||
'/partners/grade/<int:grade_id>/country/<country_name>-<int:country_id>',
|
||||
'/partners/grade/<int:grade_id>/country/<int:country_id>/page/<int:page>/',
|
||||
'/partners/grade/<int:grade_id>/country/<country_name>-<int:country_id>/page/<int:page>/',
|
||||
'/partners/grade/<int:grade_id>/country/<int:country_id>/page/<int:page>',
|
||||
'/partners/grade/<int:grade_id>/country/<country_name>-<int:country_id>/page/<int:page>',
|
||||
|
||||
], type='http', auth="public", website=True, multilang=True)
|
||||
def partners(self, country_id=0, grade_id=0, page=0, **post):
|
||||
|
@ -72,7 +72,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
|
|||
partner_ids = partner_obj.search(
|
||||
request.cr, openerp.SUPERUSER_ID, partner_domain,
|
||||
context=request.context)
|
||||
pager = request.website.pager(url="/partners/", total=len(partner_ids), page=page, step=self._references_per_page, scope=7, url_args=post)
|
||||
pager = request.website.pager(url="/partners", total=len(partner_ids), page=page, step=self._references_per_page, scope=7, url_args=post)
|
||||
|
||||
# search for partners to display
|
||||
partners_data = partner_obj.search_read(request.cr, openerp.SUPERUSER_ID,
|
||||
|
@ -110,7 +110,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
|
|||
}
|
||||
return request.website.render("website_crm_partner_assign.index", values)
|
||||
|
||||
@http.route(['/partners/<int:partner_id>/', '/partners/<partner_name>-<int:partner_id>/'], type='http', auth="public", website=True, multilang=True)
|
||||
@http.route(['/partners/<int:partner_id>', '/partners/<partner_name>-<int:partner_id>'], type='http', auth="public", website=True, multilang=True)
|
||||
def partners_ref(self, partner_id, **post):
|
||||
partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)
|
||||
values = website_partner.get_partner_template_value(partner)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Layout add nav and footer -->
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Footer Partners Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a href="/partners/">Resellers</a></li>
|
||||
<li><a href="/partners">Resellers</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
|||
<t t-foreach="countries" t-as="country_dict">
|
||||
<t t-if="country_dict['country_id']">
|
||||
<li t-att-class="country_dict['country_id'][0] == current_country_id and 'active' or ''">
|
||||
<a t-attf-href="#{ country_dict['country_id'][0] and ('/partners/country/%s' % slug(country_dict['country_id'])) or '/partners/' }#{ search_path }">
|
||||
<a t-attf-href="#{ country_dict['country_id'][0] and ('/partners/country/%s' % slug(country_dict['country_id'])) or '/partners' }#{ search_path }">
|
||||
<span class="badge pull-right" t-esc="country_dict['country_id_count'] or ''"/>
|
||||
<t t-esc="country_dict['country_id'][1]"/>
|
||||
</a>
|
||||
|
@ -93,11 +93,11 @@
|
|||
<t t-set="internal_gid" t-value="partner_data['grade_id'][1]"/>
|
||||
</t>
|
||||
<div class="media">
|
||||
<a class="pull-left" t-attf-href="/partners/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
|
||||
<a class="pull-left" t-attf-href="/partners/#{ slug([partner_data.get('id'), partner_data.get('name')]) }">
|
||||
<img class="media-object" t-attf-src="data:image/png;base64,#{partner_data['image_small']}"/>
|
||||
</a>
|
||||
<div class="media-body" style="min-height: 64px;">
|
||||
<a class="media-heading" t-attf-href="/partners/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/"><t t-if="partner_data['parent_id']"><span t-esc="partner_data['parent_id'][1]"/></t> <span t-esc="partner_data['name']"/></a> - <span t-esc="partner_data['grade_id'][1]"/>
|
||||
<a class="media-heading" t-attf-href="/partners/#{ slug([partner_data.get('id'), partner_data.get('name')]) }"><t t-if="partner_data['parent_id']"><span t-esc="partner_data['parent_id'][1]"/></t> <span t-esc="partner_data['name']"/></a> - <span t-esc="partner_data['grade_id'][1]"/>
|
||||
<div t-esc="partner_data['website_short_description']"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -112,7 +112,7 @@
|
|||
<xpath expr="//ul[@id='reseller_countries']" position="after">
|
||||
<h3>World Map</h3>
|
||||
<ul class="nav">
|
||||
<iframe t-attf-src="/google_map/?width=320&height=240&partner_ids=#{ google_map_partner_ids }&partner_url=/partners/"
|
||||
<iframe t-attf-src="/google_map/?width=320&height=240&partner_ids=#{ google_map_partner_ids }&partner_url=/partners"
|
||||
style="width:320px; height:260px; border:0; padding:0; margin:0;"></iframe>
|
||||
</ul>
|
||||
</xpath>
|
||||
|
|
|
@ -12,12 +12,12 @@ class WebsiteCustomer(http.Controller):
|
|||
_references_per_page = 20
|
||||
|
||||
@http.route([
|
||||
'/customers/',
|
||||
'/customers/page/<int:page>/',
|
||||
'/customers',
|
||||
'/customers/page/<int:page>',
|
||||
'/customers/country/<int:country_id>',
|
||||
'/customers/country/<country_name>-<int:country_id>',
|
||||
'/customers/country/<int:country_id>/page/<int:page>/',
|
||||
'/customers/country/<country_name>-<int:country_id>/page/<int:page>/',
|
||||
'/customers/country/<int:country_id>/page/<int:page>',
|
||||
'/customers/country/<country_name>-<int:country_id>/page/<int:page>',
|
||||
], type='http', auth="public", website=True, multilang=True)
|
||||
def customers(self, country_id=0, page=0, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
|
@ -62,7 +62,7 @@ class WebsiteCustomer(http.Controller):
|
|||
|
||||
# pager
|
||||
pager = request.website.pager(
|
||||
url="/customers/", total=len(partner_ids), page=page, step=self._references_per_page,
|
||||
url="/customers", total=len(partner_ids), page=page, step=self._references_per_page,
|
||||
scope=7, url_args=post
|
||||
)
|
||||
|
||||
|
@ -83,7 +83,7 @@ class WebsiteCustomer(http.Controller):
|
|||
}
|
||||
return request.website.render("website_customer.index", values)
|
||||
|
||||
@http.route(['/customers/<int:partner_id>/', '/customers/<partner_name>-<int:partner_id>/'], type='http', auth="public", website=True, multilang=True)
|
||||
@http.route(['/customers/<int:partner_id>', '/customers/<partner_name>-<int:partner_id>'], type='http', auth="public", website=True, multilang=True)
|
||||
def customer(self, partner_id, **post):
|
||||
partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)
|
||||
values = website_partner.get_partner_template_value(partner)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Layout add nav and footer -->
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Footer Customer References Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a href="/customers/">Our References</a></li>
|
||||
<li><a href="/customers">Our References</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -44,11 +44,11 @@
|
|||
</t>
|
||||
<t t-foreach="partners_data" t-as="partner_data" class="media">
|
||||
<div class="media">
|
||||
<a class="pull-left" t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
|
||||
<a class="pull-left" t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }">
|
||||
<img class="media-object" t-attf-src="data:image/png;base64,#{partner_data.get('image_small')}"/>
|
||||
</a>
|
||||
<div class="media-body" style="min-height: 64px;">
|
||||
<a t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/" t-esc="partner_data.get('name')"/>
|
||||
<a t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }" t-esc="partner_data.get('name')"/>
|
||||
<div t-raw="partner_data.get('website_short_description') or ''"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -68,7 +68,7 @@
|
|||
<template id="opt_country" inherit_option_id="website_customer.index" name="Show Map">
|
||||
<xpath expr="//div[@id='ref_left_column']" position="inside">
|
||||
|
||||
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&partner_url=/customers/&output=embed/"
|
||||
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&partner_url=/customers/&output=embed"
|
||||
style="width:100%; border:0; padding:0; margin:0;"></iframe>
|
||||
</xpath>
|
||||
</template>
|
||||
|
@ -80,7 +80,7 @@
|
|||
<t t-foreach="countries" t-as="country_dict">
|
||||
<t t-if="country_dict['country_id']">
|
||||
<li t-att-class="country_dict['country_id'][0] == current_country_id and 'active' or ''">
|
||||
<a t-attf-href="/customers/#{ country_dict['country_id'][0] and 'country/%s/' % slug(country_dict['country_id']) or '' }#{ search_path }">
|
||||
<a t-attf-href="/customers/#{ country_dict['country_id'][0] and 'country/%s' % slug(country_dict['country_id']) or '' }#{ search_path }">
|
||||
<span class="badge pull-right" t-esc="country_dict['country_id_count'] or '0'"/>
|
||||
<t t-esc="country_dict['country_id'][1]"/>
|
||||
</a>
|
||||
|
@ -143,7 +143,7 @@
|
|||
</div>
|
||||
</address>
|
||||
<div>
|
||||
<a t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/#references/" t-if="implemented_partner_ids">
|
||||
<a t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/#references" t-if="implemented_partner_ids">
|
||||
<t t-esc="len(implemented_partner_ids)"/> references
|
||||
</a>
|
||||
</div>
|
||||
|
@ -158,11 +158,11 @@
|
|||
<t t-if="implemented_partners_data">
|
||||
<h3 id="references">References</h3>
|
||||
<div t-foreach="implemented_partners_data" t-as="partner_data" class="media">
|
||||
<a class="pull-left" t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
|
||||
<a class="pull-left" t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }">
|
||||
<img class="media-object" t-attf-src="data:image/png;base64,#{partner_data.get('image_small')}"/>
|
||||
</a>
|
||||
<div class="media-body" style="min-height: 64px;">
|
||||
<a class="media-heading" t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
|
||||
<a class="media-heading" t-attf-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }">
|
||||
<t t-if="partner_data.get('parent_id')"><span t-esc="partner_data.get('parent_id')[1]"/></t> <span t-esc="partner_data.get('name')"/>
|
||||
</a>
|
||||
<div t-if="partner_data.get('website_short_description')" t-raw="partner_data.get('website_short_description')"/>
|
||||
|
|
|
@ -33,7 +33,7 @@ from openerp import tools
|
|||
import werkzeug.urls
|
||||
|
||||
class website_event(http.Controller):
|
||||
@http.route(['/event/', '/event/page/<int:page>'], type='http', auth="public", website=True, multilang=True)
|
||||
@http.route(['/event', '/event/page/<int:page>'], type='http', auth="public", website=True, multilang=True)
|
||||
def events(self, page=1, **searches):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
event_obj = request.registry['event.event']
|
||||
|
@ -134,7 +134,7 @@ class website_event(http.Controller):
|
|||
event_count = event_obj.search(
|
||||
request.cr, request.uid, dom_without("none"), count=True,
|
||||
context=request.context)
|
||||
pager = request.website.pager(url="/event/", total=event_count, page=page, step=step, scope=5)
|
||||
pager = request.website.pager(url="/event", total=event_count, page=page, step=step, scope=5)
|
||||
|
||||
order = 'website_published desc, date_begin'
|
||||
if searches.get('date','all') == 'old':
|
||||
|
@ -202,4 +202,4 @@ class website_event(http.Controller):
|
|||
'date_end': (date_begin + timedelta(days=(1))).strftime('%Y-%m-%d'),
|
||||
}
|
||||
event_id = Event.create(request.cr, request.uid, vals, context=context)
|
||||
return request.redirect("/event/%s/?enable_editor=1" % event_id)
|
||||
return request.redirect("/event/%s?enable_editor=1" % event_id)
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<li t-foreach="event_ids" t-as="event" class="media">
|
||||
<div itemscope="itemscope" itemtype="http://schema.org/Event" class="media-body">
|
||||
<h4 class="media-heading">
|
||||
<a itemprop="url" t-att-class="event.state == 'done' and 'text-success'" t-attf-href="/event/#{ slug(event) }/#{(not event.menu_id) and 'register/' or ''}"><span itemprop="name" t-field="event.name"> </span></a>
|
||||
<a itemprop="url" t-att-class="event.state == 'done' and 'text-success'" t-attf-href="/event/#{ slug(event) }/#{(not event.menu_id) and 'register' or ''}"><span itemprop="name" t-field="event.name"> </span></a>
|
||||
<small t-if="not event.website_published" class="label label-danger">not published</small>
|
||||
</h4>
|
||||
<div>
|
||||
|
@ -248,7 +248,7 @@
|
|||
<div class="container">
|
||||
<h1 class="mt32">Event not found!</h1>
|
||||
<p>Sorry, the requested event is not available anymore.</p>
|
||||
<p><a t-attf-href="/event/">Return to the event list.</a></p>
|
||||
<p><a t-attf-href="/event">Return to the event list.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -78,7 +78,7 @@ class website_event(website_event):
|
|||
order_obj.write(request.cr, SUPERUSER_ID, [order.id], {'order_line': [(4, order_line_id)]}, context=request.context)
|
||||
|
||||
if not _values:
|
||||
return request.redirect("/event/%s/" % event_id)
|
||||
return request.redirect("/event/%s" % event_id)
|
||||
return request.redirect("/shop/checkout")
|
||||
|
||||
def _add_event(self, event_name="New Event", context={}, **kwargs):
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
id: 'event_buy_tickets',
|
||||
name: "Try to buy tickets for event",
|
||||
path: '/event',
|
||||
testPath: '/(event|shop)',
|
||||
init: function () {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
|
@ -44,7 +43,7 @@
|
|||
{
|
||||
title: "Complete checkout",
|
||||
waitFor: '#top_menu .my_cart_quantity:contains(5)',
|
||||
element: 'form[action="/shop/confirm_order/"] .btn:contains("Confirm")',
|
||||
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
|
||||
onload: function (tour) {
|
||||
if ($("input[name='name']").val() === "")
|
||||
$("input[name='name']").val("website_sale-test-shoptest");
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue