[MERGE] Sync with trunk
bzr revid: tde@openerp.com-20140212103823-aifj3y0dwir0cbyv
|
@ -1601,7 +1601,6 @@
|
|||
<label for="value_amount" string="Amount To Pay" attrs="{'invisible':[('value','=','balance')]}"/>
|
||||
<div attrs="{'invisible':[('value','=','balance')]}">
|
||||
<field name="value_amount" class="oe_inline"/>
|
||||
<label string="%%" class="oe_inline" attrs="{'invisible':['!',('value','=','procent')]}" />
|
||||
</div>
|
||||
</group>
|
||||
<group string="Due Date Computation">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010-2012 OpenERP SA (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2014 OpenERP SA (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -30,7 +30,9 @@ Allow users to sign up through OAuth2 Provider.
|
|||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['auth_oauth', 'auth_signup'],
|
||||
'data': [],
|
||||
'data': [
|
||||
'views/auth_oauth_signup.xml',
|
||||
],
|
||||
'js': [],
|
||||
'css': [],
|
||||
'qweb': [],
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- vim:et:si:ts=2:sts=2:sw=2 -->
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="auth_oauth_signup.signup" inherit_id="auth_signup.signup" name="OAuth Signup buttons">
|
||||
<xpath expr="//button[@type='submit']" position="before">
|
||||
<div class="pull-right">
|
||||
<div t-foreach="providers or []" t-as="p">
|
||||
<a t-att-href="p['auth_link']" class="btn btn-link">
|
||||
<i t-att-class="p['css_class']"/>
|
||||
<t t-esc="p['body']"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
|
@ -30,13 +30,16 @@ from openerp.tools import exception_to_unicode
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Home(openerp.addons.web.controllers.main.Home):
|
||||
class AuthSignup(openerp.addons.web.controllers.main.Home):
|
||||
|
||||
@http.route()
|
||||
def web_login(self, *args, **kw):
|
||||
mode = request.params.get('mode')
|
||||
qcontext = request.params.copy()
|
||||
super_response = super(AuthSignup, self).web_login(*args, **kw)
|
||||
response = webmain.render_bootstrap_template(request.session.db, 'auth_signup.signup', qcontext, lazy=True)
|
||||
if isinstance(super_response, LazyResponse):
|
||||
response.params['values'].update(super_response.params['values'])
|
||||
token = qcontext.get('token', None)
|
||||
token_infos = None
|
||||
if token:
|
||||
|
@ -60,10 +63,9 @@ class Home(openerp.addons.web.controllers.main.Home):
|
|||
qcontext.update(config)
|
||||
|
||||
if 'error' in qcontext or mode not in ('reset', 'signup') or (not token and not config[mode]):
|
||||
response = super(Home, self).web_login(*args, **kw)
|
||||
if isinstance(response, LazyResponse):
|
||||
response.params['values'].update(config)
|
||||
return response
|
||||
if isinstance(super_response, LazyResponse):
|
||||
super_response.params['values'].update(config)
|
||||
return super_response
|
||||
|
||||
if request.httprequest.method == 'GET':
|
||||
if token_infos:
|
||||
|
@ -86,7 +88,7 @@ class Home(openerp.addons.web.controllers.main.Home):
|
|||
request.cr.commit()
|
||||
except SignupError, e:
|
||||
qcontext['error'] = exception_to_unicode(e)
|
||||
return super(Home, self).web_login(*args, **kw)
|
||||
return super(AuthSignup, self).web_login(*args, **kw)
|
||||
|
||||
return response
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<data>
|
||||
<template id="auth_signup.login" inherit_id="web.login" name="Sign up - Reset Password">
|
||||
<xpath expr="//button[@type='submit']" position="before">
|
||||
<a t-if="signup" t-attf-href="?mode=signup{{ '&debug' if debug else '' }}" class="btn btn-link pull-right">Sign up</a>
|
||||
<a t-if="reset" t-attf-href="?mode=reset{{ '&debug' if debug else '' }}" class="btn btn-link pull-right">Reset Password</a>
|
||||
<a t-if="signup" t-attf-href="?{{ keep_query('*', mode='signup') }}" class="btn btn-link pull-right">Sign up</a>
|
||||
<a t-if="reset" t-attf-href="?{{ keep_query('*', mode='reset') }}" class="btn btn-link pull-right">Reset Password</a>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
|
||||
<t t-set="reset_without_token" t-value="mode == 'reset' and not token"/>
|
||||
|
||||
<form class="oe_signup_form" role="form" t-attf-action="/web/login{{ '?debug' if debug else '' }}" method="post">
|
||||
<form class="oe_signup_form" role="form" method="post">
|
||||
<t t-call="web.database_select"/>
|
||||
|
||||
<div class="form-group field-name" t-if="not reset_without_token">
|
||||
|
@ -68,7 +68,7 @@
|
|||
<input type="hidden" name="mode" t-att-value="mode"/>
|
||||
<input type="hidden" name="token" t-att-value="token"/>
|
||||
<div class="clearfix oe_login_buttons">
|
||||
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
|
||||
<a t-attf-href="?{{ keep_query('*', mode='login') }}" class="btn btn-link pull-right">Back to Login</a>
|
||||
<button type="submit" class="btn btn-primary pull-left">
|
||||
<t t-if="mode == 'signup'">Sign up</t>
|
||||
<t t-if="mode == 'reset'">Reset password</t>
|
||||
|
|
|
@ -123,8 +123,9 @@ class crm_case_section(osv.osv):
|
|||
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
|
||||
} for i in range(self._period_number - 1, -1, -1)]
|
||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
for group in group_obj:
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern)
|
||||
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
|
||||
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
|
||||
return section_result
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
<div>Use template
|
||||
<!--FIX: To avoid css issue of many2one field in footer temporary used oe_form (BUG:1152464)-->
|
||||
<field name="template_id" nolabel="1" class='oe_inline'
|
||||
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)" domain="[('model_id.model','=',model)]"/>
|
||||
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)" domain="[('model_id.model','=',model)]"
|
||||
context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
|
||||
</div>
|
||||
<button icon="/email_template/static/src/img/email_template_save.png"
|
||||
type="object" name="save_as_template" string="Save as new template" class="oe_link"
|
||||
|
|
|
@ -387,6 +387,7 @@ class gamification_challenge(osv.Model):
|
|||
end_date = challenge.end_date
|
||||
|
||||
for line in challenge.line_ids:
|
||||
# FIXME: allow to restrict to a subset of users
|
||||
for user in challenge.user_ids:
|
||||
|
||||
goal_obj = self.pool.get('gamification.goal')
|
||||
|
@ -400,8 +401,9 @@ class gamification_challenge(osv.Model):
|
|||
# resume canceled goals
|
||||
domain.append(('state', '=', 'canceled'))
|
||||
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
|
||||
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
|
||||
goal_obj.update(cr, uid, canceled_goal_ids, context=context)
|
||||
if canceled_goal_ids:
|
||||
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
|
||||
goal_obj.update(cr, uid, canceled_goal_ids, context=context)
|
||||
|
||||
# skip to next user
|
||||
continue
|
||||
|
@ -516,13 +518,6 @@ class gamification_challenge(osv.Model):
|
|||
domain.append(('user_id', '=', user_id))
|
||||
sorting = goal_obj._order
|
||||
limit = 1
|
||||
# initialise in case search returns no results
|
||||
line_data.update({
|
||||
'id': 0,
|
||||
'current': 0,
|
||||
'completeness': 0,
|
||||
'state': 'draft',
|
||||
})
|
||||
else:
|
||||
line_data.update({
|
||||
'own_goal_id': False,
|
||||
|
@ -559,7 +554,8 @@ class gamification_challenge(osv.Model):
|
|||
'completeness': goal.completeness,
|
||||
'state': goal.state,
|
||||
})
|
||||
res_lines.append(line_data)
|
||||
if goal_ids:
|
||||
res_lines.append(line_data)
|
||||
return res_lines
|
||||
|
||||
##### Reporting #####
|
||||
|
|
|
@ -163,7 +163,8 @@ class gamification_goal(osv.Model):
|
|||
string="Challenge",
|
||||
type='many2one',
|
||||
relation='gamification.challenge',
|
||||
store=True),
|
||||
store=True, readonly=True,
|
||||
help="Challenge that generated the goal, assign challenge to users to generate goals with a value in this field."),
|
||||
'start_date': fields.date('Start Date'),
|
||||
'end_date': fields.date('End Date'), # no start and end = always active
|
||||
'target_goal': fields.float('To Reach',
|
||||
|
|
|
@ -42,6 +42,7 @@ class res_users_gamification_group(osv.Model):
|
|||
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
||||
if challenge_ids:
|
||||
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
|
||||
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
|
||||
return write_res
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
|
@ -56,6 +57,7 @@ class res_users_gamification_group(osv.Model):
|
|||
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
||||
if challenge_ids:
|
||||
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
|
||||
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
|
||||
return write_res
|
||||
|
||||
# def get_goals_todo_info(self, cr, uid, context=None):
|
||||
|
@ -83,13 +85,15 @@ class res_users_gamification_group(osv.Model):
|
|||
challenge_ids = challenge_obj.search(cr, uid, [('user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
|
||||
for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
|
||||
# serialize goals info to be able to use it in javascript
|
||||
all_goals_info.append({
|
||||
'id': challenge.id,
|
||||
'name': challenge.name,
|
||||
'visibility_mode': challenge.visibility_mode,
|
||||
'currency': user.company_id.currency_id.id,
|
||||
'lines': challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context),
|
||||
})
|
||||
lines = challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context)
|
||||
if lines:
|
||||
all_goals_info.append({
|
||||
'id': challenge.id,
|
||||
'name': challenge.name,
|
||||
'visibility_mode': challenge.visibility_mode,
|
||||
'currency': user.company_id.currency_id.id,
|
||||
'lines': lines,
|
||||
})
|
||||
|
||||
return all_goals_info
|
||||
|
||||
|
@ -129,4 +133,5 @@ class res_groups_gamification_group(osv.Model):
|
|||
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', ids)], context=context)
|
||||
if challenge_ids:
|
||||
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
|
||||
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
|
||||
return write_res
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<group string="Reference">
|
||||
<field name="definition_id" on_change="on_change_definition_id(definition_id)" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="challenge_id" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="challenge_id" />
|
||||
</group>
|
||||
<group string="Schedule">
|
||||
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
|
@ -280,8 +280,8 @@
|
|||
|
||||
<!-- menus in settings - technical feature required -->
|
||||
<menuitem id="gamification_menu" name="Gamification Tools" parent="base.menu_administration" groups="base.group_no_one" />
|
||||
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="0"/>
|
||||
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="10"/>
|
||||
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="0"/>
|
||||
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="10"/>
|
||||
<menuitem id="gamification_definition_menu" parent="gamification_menu" action="goal_definition_list_action" sequence="20"/>
|
||||
<menuitem id="gamification_badge_menu" parent="gamification_menu" action="badge_list_action" sequence="30"/>
|
||||
|
||||
|
|
|
@ -8,14 +8,14 @@ msgstr ""
|
|||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2013-06-27 16:03+0000\n"
|
||||
"PO-Revision-Date: 2012-12-16 11:28+0000\n"
|
||||
"PO-Revision-Date: 2014-02-08 18:21+0000\n"
|
||||
"Last-Translator: Grzegorz Grzelak (OpenGLOBE.pl) <grzegorz@openglobe.pl>\n"
|
||||
"Language-Team: Polish <pl@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-01-28 06:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16914)\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-09 06:19+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: google_drive
|
||||
#: model:ir.ui.menu,name:google_drive.menu_google_drive_config
|
||||
|
@ -149,7 +149,7 @@ msgstr ""
|
|||
#. module: google_drive
|
||||
#: field:google.drive.config,filter_id:0
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
msgstr "Filtr"
|
||||
|
||||
#. module: google_drive
|
||||
#: field:google.drive.config,name_template:0
|
||||
|
|
|
@ -21,16 +21,15 @@
|
|||
|
||||
import logging
|
||||
|
||||
from openerp import tools
|
||||
from openerp.modules.module import get_module_resource
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class hr_employee_category(osv.osv):
|
||||
class hr_employee_category(osv.Model):
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
if not ids:
|
||||
|
@ -73,9 +72,9 @@ class hr_employee_category(osv.osv):
|
|||
]
|
||||
|
||||
|
||||
class hr_job(osv.osv):
|
||||
class hr_job(osv.Model):
|
||||
|
||||
def _no_of_employee(self, cr, uid, ids, name, args, context=None):
|
||||
def _get_nbr_employees(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
for job in self.browse(cr, uid, ids, context=context):
|
||||
nb_employees = len(job.employee_ids or [])
|
||||
|
@ -93,59 +92,81 @@ class hr_job(osv.osv):
|
|||
return res
|
||||
|
||||
_name = "hr.job"
|
||||
_description = "Job Description"
|
||||
_inherit = ['mail.thread']
|
||||
_description = "Job Position"
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_columns = {
|
||||
'name': fields.char('Job Name', size=128, required=True, select=True),
|
||||
'expected_employees': fields.function(_no_of_employee, string='Total Forecasted Employees',
|
||||
'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees',
|
||||
help='Expected number of employees for this job position after new recruitment.',
|
||||
store = {
|
||||
'hr.job': (lambda self,cr,uid,ids,c=None: ids, ['no_of_recruitment'], 10),
|
||||
'hr.employee': (_get_job_position, ['job_id'], 10),
|
||||
}, type='integer',
|
||||
multi='no_of_employee'),
|
||||
'no_of_employee': fields.function(_no_of_employee, string="Current Number of Employees",
|
||||
multi='_get_nbr_employees'),
|
||||
'no_of_employee': fields.function(_get_nbr_employees, string="Current Number of Employees",
|
||||
help='Number of employees currently occupying this job position.',
|
||||
store = {
|
||||
'hr.employee': (_get_job_position, ['job_id'], 10),
|
||||
}, type='integer',
|
||||
multi='no_of_employee'),
|
||||
'no_of_recruitment': fields.integer('Expected in Recruitment', help='Number of new employees you expect to recruit.'),
|
||||
multi='_get_nbr_employees'),
|
||||
'no_of_recruitment': fields.integer('Expected New Employees', help='Number of new employees you expect to recruit.'),
|
||||
'no_of_hired_employee': fields.integer('Hired Employees', help='Number of hired employees for this job position during recruitment phase.'),
|
||||
'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees', groups='base.group_user'),
|
||||
'description': fields.text('Job Description'),
|
||||
'requirements': fields.text('Requirements'),
|
||||
'department_id': fields.many2one('hr.department', 'Department'),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
'state': fields.selection([('open', 'No Recruitment'), ('recruit', 'Recruitement in Progress')], 'Status', readonly=True, required=True,
|
||||
help="By default 'In position', set it to 'In Recruitment' if recruitment process is going on for this job position."),
|
||||
'state': fields.selection([('open', 'Recruitment Closed'), ('recruit', 'Recruitment in Progress')],
|
||||
string='Status', readonly=True, required=True,
|
||||
track_visibility='always',
|
||||
help="By default 'Closed', set it to 'In Recruitment' if recruitment process is going on for this job position."),
|
||||
'write_date': fields.datetime('Update Date', readonly=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=c),
|
||||
'no_of_recruitment': 0,
|
||||
'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=ctx),
|
||||
'state': 'open',
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
|
||||
('hired_employee_check', "CHECK ( no_of_hired_employee <= no_of_recruitment )", "Number of hired employee must be less than expected number of employee in recruitment."),
|
||||
]
|
||||
|
||||
|
||||
def on_change_expected_employee(self, cr, uid, ids, no_of_recruitment, no_of_employee, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
return {'value': {'expected_employees': no_of_recruitment + no_of_employee}}
|
||||
|
||||
def job_recruitement(self, cr, uid, ids, *args):
|
||||
for job in self.browse(cr, uid, ids):
|
||||
def set_recruit(self, cr, uid, ids, context=None):
|
||||
for job in self.browse(cr, uid, ids, context=context):
|
||||
no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment
|
||||
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
|
||||
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment}, context=context)
|
||||
return True
|
||||
|
||||
def job_open(self, cr, uid, ids, *args):
|
||||
self.write(cr, uid, ids, {'state': 'open', 'no_of_recruitment': 0})
|
||||
def set_open(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {
|
||||
'state': 'open',
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0
|
||||
}, context=context)
|
||||
return True
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default.update({
|
||||
'employee_ids': [],
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0,
|
||||
})
|
||||
if 'name' in default:
|
||||
job = self.browse(cr, uid, id, context=context)
|
||||
default['name'] = _("%s (copy)") % (job.name)
|
||||
return super(hr_job, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
# ----------------------------------------
|
||||
# Compatibility methods
|
||||
# ----------------------------------------
|
||||
_no_of_employee = _get_nbr_employees # v7 compatibility
|
||||
job_open = set_open # v7 compatibility
|
||||
job_recruitment = set_recruit # v7 compatibility
|
||||
|
||||
|
||||
class hr_employee(osv.osv):
|
||||
_name = "hr.employee"
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</group>
|
||||
<group string="Position">
|
||||
<field name="department_id" on_change="onchange_department_id(department_id)"/>
|
||||
<field name="job_id" options='{"no_open": True}' domain="[('state','!=','old')]" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
|
||||
<field name="job_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id"/>
|
||||
</group>
|
||||
|
@ -333,8 +333,8 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Job" version="7.0">
|
||||
<header>
|
||||
<button name="job_recruitement" string="Launch Recruitement" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="job_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_recruit" string="Launch Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
|
||||
</header>
|
||||
<sheet>
|
||||
|
@ -342,20 +342,20 @@
|
|||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" class="oe_inline"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group name="job_data">
|
||||
<field name="no_of_employee" groups="base.group_user"/>
|
||||
<field name="no_of_recruitment" on_change="on_change_expected_employee(no_of_recruitment,no_of_employee)"/>
|
||||
<field name="expected_employees" groups="base.group_user"/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
</group>
|
||||
<div class="oe_right" name="buttons"/>
|
||||
<group name="employee_data">
|
||||
<field name="department_id" class="oe_inline"/>
|
||||
<label for="no_of_employee"/>no_of_recruitment
|
||||
<div>
|
||||
<field name="no_of_employee" class="oe_inline"/>
|
||||
<p><field name="no_of_recruitment" groups="base.group_user" colspan="0" class="oe_inline" style="padding-top: 1px"/> new employee(s) expected</p>
|
||||
</div>
|
||||
</group>
|
||||
<div>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</div>
|
||||
<div>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="requirements"/>
|
||||
<field name="requirements"/>
|
||||
</div>
|
||||
|
@ -378,6 +378,7 @@
|
|||
<field name="no_of_employee"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="expected_employees"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
@ -389,34 +390,18 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Jobs">
|
||||
<field name="name" string="Job"/>
|
||||
<filter icon="terp-camera_test" domain="[('state','=','open')]" string="In Position" help="In Position"/>
|
||||
<filter icon="terp-personal+" domain="[('state','=','recruit')]" string="In Recruitment" help="In Recruitment"/>
|
||||
<filter domain="[('state','=','open')]" string="In Position"/>
|
||||
<filter domain="[('state','=','recruit')]" string="In Recruitment" name="in_recruitment"/>
|
||||
<field name="department_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
<filter string="Department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_employee_form" model="ir.ui.view">
|
||||
<field name="name">hr.job.employee.form</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Job" version="7.0">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
</group>
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_hr_job">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
|
@ -441,7 +426,6 @@
|
|||
</record>
|
||||
|
||||
<menuitem name="Recruitment" id="base.menu_crm_case_job_req_main" parent="menu_hr_root" groups="base.group_hr_user"/>
|
||||
<menuitem parent="hr.menu_hr_configuration" id="menu_hr_job" action="action_hr_job" sequence="6"/>
|
||||
|
||||
<!-- hr.department -->
|
||||
<record id="view_department_form" model="ir.ui.view">
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<group name="recruitment_grp">
|
||||
<label for="id" string="Talent Management"/>
|
||||
<div name="recruitment">
|
||||
<div>
|
||||
<div name="hr_recruitment">
|
||||
<field name="module_hr_recruitment" class="oe_inline"/>
|
||||
<label for="module_hr_recruitment"/>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
name: HR Officer
|
||||
login: hro
|
||||
password: hro
|
||||
email: hro@example.com
|
||||
-
|
||||
I added groups for HR Officer.
|
||||
-
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
- state == 'open'
|
||||
- no_of_recruitment == 0
|
||||
-
|
||||
Now, Recruitement is started so I start recruitement of Job Postion of "Developer" Profile.
|
||||
Now, Recruitment is started so I start recruitment of Job Postion of "Developer" Profile.
|
||||
-
|
||||
!python {model: hr.job}: |
|
||||
self.job_recruitement(cr, uid, [ref('job_developer')])
|
||||
self.job_recruitment(cr, uid, [ref('job_developer')])
|
||||
-
|
||||
I check 'state' and number of 'Expected in Recruitment' after initiating the recruitment
|
||||
-
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="employee_id" on_change="onchange_employee_id(employee_id)"/>
|
||||
<field name="job_id" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
|
||||
<field name="job_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type_id"/>
|
||||
|
|
|
@ -37,4 +37,5 @@ Badge received are displayed on the user profile.
|
|||
'views/gamification.xml',
|
||||
],
|
||||
'js': ['static/src/js/gamification.js'],
|
||||
'auto_install': True,
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<field name="view_type">form</field>
|
||||
<field name="name">Goals History</field>
|
||||
<field name="view_mode">tree,kanban</field>
|
||||
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_type': True}</field>
|
||||
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_definition': True}</field>
|
||||
<field name="domain">[('challenge_id.category', '=', 'hr')]</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
|
|
|
@ -8,14 +8,14 @@ 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: 2012-12-16 12:00+0000\n"
|
||||
"PO-Revision-Date: 2014-02-08 18:26+0000\n"
|
||||
"Last-Translator: Grzegorz Grzelak (OpenGLOBE.pl) <grzegorz@openglobe.pl>\n"
|
||||
"Language-Team: Polish <pl@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-01-28 06:26+0000\n"
|
||||
"X-Generator: Launchpad (build 16914)\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-09 06:19+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: hr_payroll
|
||||
#: field:hr.payslip.line,condition_select:0
|
||||
|
@ -87,7 +87,7 @@ msgstr "Reguła wynagrodzenia nadrzędnego"
|
|||
#: field:hr.payslip.run,slip_ids:0
|
||||
#: model:ir.actions.act_window,name:hr_payroll.act_hr_employee_payslip_list
|
||||
msgid "Payslips"
|
||||
msgstr "Pasek wypłaty"
|
||||
msgstr "Paski wypłaty"
|
||||
|
||||
#. module: hr_payroll
|
||||
#: field:hr.payroll.structure,parent_id:0
|
||||
|
@ -132,7 +132,7 @@ msgstr "do"
|
|||
#: view:hr.payslip.run:0
|
||||
#: model:ir.model,name:hr_payroll.model_hr_payslip_run
|
||||
msgid "Payslip Batches"
|
||||
msgstr "Grupy pasków"
|
||||
msgstr "Listy płac"
|
||||
|
||||
#. module: hr_payroll
|
||||
#: view:hr.payslip.employees:0
|
||||
|
@ -183,7 +183,7 @@ msgstr "Suma"
|
|||
#. module: hr_payroll
|
||||
#: model:ir.actions.act_window,name:hr_payroll.act_children_salary_rules
|
||||
msgid "All Children Rules"
|
||||
msgstr ""
|
||||
msgstr "Wszystkie reguły podrzędne"
|
||||
|
||||
#. module: hr_payroll
|
||||
#: view:hr.payslip:0
|
||||
|
@ -194,7 +194,7 @@ msgstr "Dane wejściowe"
|
|||
#. module: hr_payroll
|
||||
#: constraint:hr.payslip:0
|
||||
msgid "Payslip 'Date From' must be before 'Date To'."
|
||||
msgstr ""
|
||||
msgstr "'Data od' musi być wcześniejsza niż 'Data do'."
|
||||
|
||||
#. module: hr_payroll
|
||||
#: view:hr.salary.rule.category:0
|
||||
|
|
|
@ -37,13 +37,14 @@ You can define the different phases of interviews and easily rate the applicant
|
|||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg'],
|
||||
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg','static/src/img/down1.png'],
|
||||
'depends': [
|
||||
'decimal_precision',
|
||||
'hr',
|
||||
'survey',
|
||||
'calendar',
|
||||
'fetchmail',
|
||||
'web_kanban_gauge',
|
||||
],
|
||||
'data': [
|
||||
'wizard/hr_recruitment_create_partner_job_view.xml',
|
||||
|
@ -58,7 +59,11 @@ You can define the different phases of interviews and easily rate the applicant
|
|||
'hr_recruitment_data.xml',
|
||||
],
|
||||
'demo': ['hr_recruitment_demo.xml'],
|
||||
'js': [
|
||||
'static/src/js/job_position.js',
|
||||
],
|
||||
'test': ['test/recruitment_process.yml'],
|
||||
'css':['static/src/css/job_position.css'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
|
|
|
@ -19,12 +19,10 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import tools
|
||||
|
||||
from datetime import datetime
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html2plaintext
|
||||
|
||||
|
||||
AVAILABLE_PRIORITIES = [
|
||||
('', ''),
|
||||
|
@ -246,7 +244,8 @@ class hr_applicant(osv.Model):
|
|||
if job_id:
|
||||
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
|
||||
department_id = job_record and job_record.department_id and job_record.department_id.id or False
|
||||
return {'value': {'department_id': department_id}}
|
||||
user_id = job_record and job_record.user_id and job_record.user_id.id or False
|
||||
return {'value': {'department_id': department_id, 'user_id': user_id}}
|
||||
|
||||
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
|
||||
if not stage_id:
|
||||
|
@ -331,19 +330,12 @@ class hr_applicant(osv.Model):
|
|||
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
|
||||
return value
|
||||
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context):
|
||||
domain = ['&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)]
|
||||
return {
|
||||
'name': _('Attachments'),
|
||||
'domain': domain,
|
||||
'res_model': 'ir.attachment',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_id': False,
|
||||
'view_mode': 'tree,form',
|
||||
'view_type': 'form',
|
||||
'limit': 80,
|
||||
'context': "{'default_res_model': '%s'}" % (self._name)
|
||||
}
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
|
||||
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
|
||||
action = self.pool.get(model).read(cr, uid, action_id, context=context)
|
||||
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
|
||||
action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)])
|
||||
return action
|
||||
|
||||
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
|
||||
recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
|
||||
|
@ -364,7 +356,7 @@ class hr_applicant(osv.Model):
|
|||
val = msg.get('from').split('<')[0]
|
||||
defaults = {
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'partner_name':val,
|
||||
'partner_name': val,
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
|
@ -378,13 +370,20 @@ class hr_applicant(osv.Model):
|
|||
def create(self, cr, uid, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
context['mail_create_nolog'] = True
|
||||
if vals.get('department_id') and not context.get('default_department_id'):
|
||||
context['default_department_id'] = vals.get('department_id')
|
||||
|
||||
if vals.get('job_id') or context.get('default_job_id'):
|
||||
job_id = vals.get('job_id') or context.get('default_job_id')
|
||||
vals.update(self.onchange_job(cr, uid, [], job_id, context=context)['value'])
|
||||
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
|
||||
applicant = self.browse(cr, uid, obj_id, context=context)
|
||||
if applicant.job_id:
|
||||
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('Applicant <b>created</b>'), subtype="hr_recruitment.mt_job_new_applicant", context=context)
|
||||
name = applicant.partner_name if applicant.partner_name else applicant.name
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [applicant.job_id.id],
|
||||
body=_('New application from %s') % name,
|
||||
subtype="hr_recruitment.mt_job_applicant_new", context=context)
|
||||
return obj_id
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
|
@ -404,6 +403,15 @@ class hr_applicant(osv.Model):
|
|||
else:
|
||||
res = super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
# post processing: if job changed, post a message on the job
|
||||
if vals.get('job_id'):
|
||||
for applicant in self.browse(cr, uid, ids, context=None):
|
||||
name = applicant.partner_name if applicant.partner_name else applicant.name
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [vals['job_id']],
|
||||
body=_('New application from %s') % name,
|
||||
subtype="hr_recruitment.mt_job_applicant_new", context=context)
|
||||
|
||||
# post processing: if stage changed, post a message in the chatter
|
||||
if vals.get('stage_id'):
|
||||
stage = self.pool['hr.recruitment.stage'].browse(cr, uid, vals['stage_id'], context=context)
|
||||
|
@ -444,7 +452,7 @@ class hr_applicant(osv.Model):
|
|||
address_id = self.pool.get('res.partner').address_get(cr, uid, [applicant.partner_id.id], ['contact'])['contact']
|
||||
contact_name = self.pool.get('res.partner').name_get(cr, uid, [applicant.partner_id.id])[0][1]
|
||||
if applicant.job_id and (applicant.partner_name or contact_name):
|
||||
applicant.job_id.write({'no_of_recruitment': applicant.job_id.no_of_recruitment - 1})
|
||||
applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1}, context=context)
|
||||
emp_id = hr_employee.create(cr, uid, {'name': applicant.partner_name or contact_name,
|
||||
'job_id': applicant.job_id.id,
|
||||
'address_home_id': address_id,
|
||||
|
@ -454,6 +462,10 @@ class hr_applicant(osv.Model):
|
|||
'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False,
|
||||
})
|
||||
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [applicant.job_id.id],
|
||||
body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
|
||||
subtype="hr_recruitment.mt_job_applicant_hired", context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Warning!'), _('You must define an Applied Job and a Contact Name for this applicant.'))
|
||||
|
||||
|
@ -490,16 +502,37 @@ class hr_job(osv.osv):
|
|||
_inherit = "hr.job"
|
||||
_name = "hr.job"
|
||||
_inherits = {'mail.alias': 'alias_id'}
|
||||
|
||||
def _get_attached_docs(self, cr, uid, ids, field_name, arg, context=None):
|
||||
res = {}
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
for job_id in ids:
|
||||
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', '=', job_id)], context=context)
|
||||
res[job_id] = attachment_obj.search(
|
||||
cr, uid, [
|
||||
'|',
|
||||
'&', ('res_model', '=', 'hr.job'), ('res_id', '=', job_id),
|
||||
'&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)
|
||||
], context=context)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"),
|
||||
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True,
|
||||
help="Email alias for this job position. New emails will automatically "
|
||||
"create new applicants for this job position."),
|
||||
'address_id': fields.many2one('res.partner', 'Job Location', help="Address where employees are working"),
|
||||
'application_ids': fields.one2many('hr.applicant', 'job_id', 'Applications'),
|
||||
'manager_id': fields.related('department_id', 'manager_id', type='many2one', string='Department Manager', relation='hr.employee', readonly=True, store=True),
|
||||
'document_ids': fields.function(_get_attached_docs, type='one2many', relation='ir.attachment', string='Applications'),
|
||||
'user_id': fields.many2one('res.users', 'Recruitment Responsible', track_visibility='onchange'),
|
||||
'color': fields.integer('Color Index'),
|
||||
}
|
||||
|
||||
def _address_get(self, cr, uid, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return user.company_id.partner_id.id
|
||||
|
||||
_defaults = {
|
||||
'address_id': _address_get
|
||||
}
|
||||
|
@ -541,6 +574,18 @@ class hr_job(osv.osv):
|
|||
'nodestroy': True,
|
||||
}
|
||||
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
|
||||
#open attachments of job and related applicantions.
|
||||
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
|
||||
action = self.pool.get(model).read(cr, uid, action_id, context=context)
|
||||
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', 'in', ids)], context=context)
|
||||
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
|
||||
action['domain'] = str(['|', '&', ('res_model', '=', 'hr.job'), ('res_id', 'in', ids), '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)])
|
||||
return action
|
||||
|
||||
def action_set_no_of_recruitment(self, cr, uid, id, value, context=None):
|
||||
return self.write(cr, uid, [id], {'no_of_recruitment': value}, context=context)
|
||||
|
||||
|
||||
class applicant_category(osv.osv):
|
||||
""" Category of applicant """
|
||||
|
|
|
@ -495,13 +495,9 @@
|
|||
<field name="alias_name">jobs</field>
|
||||
<field name="alias_model_id" ref="model_hr_applicant"/>
|
||||
<field name="alias_user_id" ref="base.user_root"/>
|
||||
<field name="alias_parent_model_id" ref="model_hr_job"/>
|
||||
</record>
|
||||
|
||||
<!-- Job-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_job_new_applicant" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
</record>
|
||||
<!-- Applicant-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
|
@ -515,12 +511,35 @@
|
|||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<record id="mt_applicant_employee" model="mail.message.subtype">
|
||||
<record id="mt_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Applicant hired</field>
|
||||
</record>
|
||||
<!-- Job-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_job_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">Applicant Created</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_new')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_stage_changed" model="mail.message.subtype">
|
||||
<field name="name">Applicant Stage Changed</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_stage_changed')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_hired')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
|
||||
<!-- Applicant Categories(Tag) -->
|
||||
<record id="tag_applicant_reserve" model="hr.applicant_category">
|
||||
<field name="name">Reserve</field>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
######################## JOB OPPORTUNITIES (menu) ###########################
|
||||
<record model="ir.actions.act_window" id="crm_case_categ0_act_job">
|
||||
<field name="name">Applications</field>
|
||||
|
@ -9,16 +8,15 @@
|
|||
<field name="view_mode">kanban,tree,form,graph,calendar</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="view_crm_case_jobs_filter"/>
|
||||
<field name="context">{'empty_list_help_model': 'hr.job'}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new job applicant.
|
||||
<p>
|
||||
OpenERP helps you track applicants in the recruitment
|
||||
process and follow up all operations: meetings, interviews, etc.
|
||||
</p><p>
|
||||
OpenERP helps you track applicants in the recruitment process
|
||||
and follow up all operations: meetings, interviews, etc.
|
||||
Candidates and their cv's are automatically created when they
|
||||
apply for a job. If you install the document management modules,
|
||||
all resumes are indexed automatically, so that you can easily
|
||||
search through their content in the recruitment menu.
|
||||
Applicants and their attached CV are created automatically when an email is sent.
|
||||
If you install the document management modules, all resumes are indexed automatically,
|
||||
so that you can easily search through their content.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -58,15 +56,10 @@
|
|||
sequence="1"/>
|
||||
|
||||
<!-- ALL JOBS REQUESTS -->
|
||||
|
||||
<menuitem parent="base.menu_crm_case_job_req_main" id="hr.menu_hr_job_position" action="action_hr_job" sequence="1"/>
|
||||
<menuitem
|
||||
name="Applications"
|
||||
parent="base.menu_crm_case_job_req_main"
|
||||
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="1"/>
|
||||
|
||||
|
||||
<menuitem parent="hr.menu_hr_configuration" id="hr.menu_hr_job" action="hr.action_hr_job" sequence="2"/>
|
||||
|
||||
|
||||
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="2"/>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</record>
|
||||
|
||||
|
||||
<!-- Jobs -->
|
||||
<!-- Applicants -->
|
||||
<record model="ir.ui.view" id="crm_case_tree_view_job">
|
||||
<field name="name">Applicants</field>
|
||||
<field name="model">hr.applicant</field>
|
||||
|
@ -91,10 +91,10 @@
|
|||
<label for="partner_name" class="oe_edit_only"/>
|
||||
<h2 style="display: inline-block;">
|
||||
<field name="partner_name" class="oe_inline"/>
|
||||
<button string="Create Employee" name="create_employee_from_applicant" type="object"
|
||||
class="oe_link oe_inline" style="margin-left: 8px;"
|
||||
attrs="{'invisible': [('emp_id', '!=', False)]}"/>
|
||||
</h2>
|
||||
<button string="Create Employee" name="create_employee_from_applicant" type="object"
|
||||
class="oe_link oe_inline" style="margin-left: 8px;"
|
||||
attrs="{'invisible': [('emp_id', '!=', False)]}"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
|
@ -307,46 +307,190 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- HR Job -->
|
||||
<record model="ir.actions.act_window" id="action_hr_job_applications">
|
||||
<field name="name">Applications</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="view_mode">kanban,tree,form,graph,calendar</field>
|
||||
<field name="context">{'search_default_job_id': [active_id], 'default_job_id': active_id, 'empty_list_help_model': 'hr.job'}</field>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
OpenERP helps you track applicants in the recruitment
|
||||
process and follow up all operations: meetings, interviews, etc.
|
||||
</p><p>
|
||||
Applicants and their attached CV are created automatically when an email is sent.
|
||||
If you install the document management modules, all resumes are indexed automatically,
|
||||
so that you can easily search through their content.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Jobs -->
|
||||
<record id="view_job_filter_recruitment" model="ir.ui.view">
|
||||
<field name="name">Job</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_job_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="department_id" positon="after">
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_job_survey" model="ir.ui.view">
|
||||
<field name="name">hr.job.form1</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="job_data" position="inside">
|
||||
<group name="employee_data" position="inside">
|
||||
<label for="survey_id" groups="base.group_user"/>
|
||||
<div groups="base.group_user">
|
||||
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
|
||||
<button string="Print Interview" name="action_print_survey" type="object" attrs="{'invisible':[('survey_id','=',False)]}" class="oe_inline oe_link"/>
|
||||
</div>
|
||||
<label for="address_id"/>
|
||||
<div>
|
||||
<field name="address_id" context="{'show_address': 1}"/>
|
||||
<span class="oe_grey">(empty = remote work)</span>
|
||||
</div>
|
||||
</group>
|
||||
<field name="expected_employees" position="after">
|
||||
<label for="survey_id" groups="base.group_user"/>
|
||||
<div groups="base.group_user">
|
||||
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
|
||||
<button class="oe_inline"
|
||||
string="Interview"
|
||||
name="action_print_survey" type="object"
|
||||
attrs="{'invisible':[('survey_id','=',False)]}"/>
|
||||
</div>
|
||||
</field>
|
||||
<xpath expr="//group[@name='job_data']" position="after">
|
||||
<group name="group_alias"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_name" string="Email Alias"/>
|
||||
<div name="alias_def">
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<label for="alias_name" string="Specific Email Address" attrs="{'invisible': [('alias_domain', '=', False)]}" help ="Define a specific contact address for this job position. If you keep it empty, the default email address will be used which is in human resources settings"/>
|
||||
<div name="alias_def" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
string="Email Alias" required="0"/>
|
||||
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="alias_contact" class="oe_inline" string="Accept Emails From"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<field name="user_id" class="oe_inline"/>
|
||||
</xpath>
|
||||
<div name="buttons" position="inside">
|
||||
<button string="Applications" name="%(action_hr_job_applications)d" context="{'default_user_id': user_id}" type="action"/>
|
||||
<button string="Documents" name="action_get_attachment_tree_view" type="object"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_kanban" model="ir.ui.view">
|
||||
<field name="name">hr.job.kanban</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban version="7.0" class="oe_background_grey">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="color"/>
|
||||
<field name="application_ids"/>
|
||||
<field name="document_ids"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="survey_id"/>
|
||||
<field name="state"/>
|
||||
<field name="user_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_job oe_kanban_card oe_kanban_global_click">
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban oe_custom">
|
||||
<span class="oe_e">í</span>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<t t-if="widget.view.is_action_enabled('edit')">
|
||||
<li><a type="edit">Edit...</a></li>
|
||||
</t>
|
||||
<t t-if="widget.view.is_action_enabled('delete')">
|
||||
<li><a type="delete">Delete</a></li>
|
||||
</t>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class = "oe_kanban_content">
|
||||
<t t-if="record.user_id.raw_value">
|
||||
<img t-att-src="kanban_image('res.users', 'image_medium', record.user_id.raw_value[0])" t-att-title="record.user_id.value" class="oe_kanban_avatar oe_job_avatar"/>
|
||||
</t>
|
||||
<t t-if="record.user_id.raw_value === false">
|
||||
<img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_avatar oe_job_avatar"/>
|
||||
</t>
|
||||
<div class="oe_job_detail">
|
||||
<div class="oe_job oe_name oe_kanban_ellipsis">
|
||||
<field name="name"/>
|
||||
</div>
|
||||
<div class="oe_job oe_department oe_kanban_ellipsis">
|
||||
<field name="department_id"/>
|
||||
<span t-if="record.manager_id.value" class="oe_manager_name">
|
||||
(<t t-esc="record.manager_id.value"/>)
|
||||
</span>
|
||||
</div>
|
||||
<div class="oe_job_alias oe_kanban_ellipsis" t-if=" record.alias_id.value and record.state.raw_value == 'recruit'">
|
||||
<span class="oe_e">%%</span><small><field name="alias_id"/></small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<t t-if="record.state.raw_value == 'recruit'">
|
||||
<div class="oe_applications">
|
||||
<a name="%(action_hr_job_applications)d" type="action">
|
||||
<span t-if="record.application_ids.raw_value.length gt 1"><t t-esc="record.application_ids.raw_value.length"/> Applications</span>
|
||||
<span t-if="record.application_ids.raw_value.length lt 2"><t t-esc="record.application_ids.raw_value.length"/> Application</span>
|
||||
</a>
|
||||
<br/>
|
||||
<a t-if="record.document_ids.raw_value.length gt 0" name="action_get_attachment_tree_view" type="object">
|
||||
<span t-if="record.document_ids.raw_value.length gt 1"><t t-esc="record.document_ids.raw_value.length"/> Documents</span>
|
||||
<span t-if="record.document_ids.raw_value.length lt 2"><t t-esc="record.document_ids.raw_value.length"/> Document</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oe_job_justgage">
|
||||
<field state="recruit" name="no_of_hired_employee" widget="gauge"
|
||||
style="width:160px; height: 120px;"
|
||||
options="{
|
||||
'max_field': 'no_of_recruitment',
|
||||
'label': 'Hired Employees',
|
||||
'on_change': 'action_set_no_of_recruitment',
|
||||
'on_click_label': 'employee(s) to recruit',
|
||||
'force_set': False,
|
||||
'gauge_value_field': 'no_of_recruitment',
|
||||
}">
|
||||
Hired Employees
|
||||
</field>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="record.state.raw_value == 'open'">
|
||||
<div class="oe_start_recruitment">
|
||||
<p><b>click here</b>, To start the recruitment</p>
|
||||
<img src="/hr_recruitment/static/src/img/down1.png"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="oe_launch_recruitment">
|
||||
<a t-if="record.state.raw_value == 'open'" data-name="job_recruitment" data-type="object" class="oe_kanban_action">Launch Recruitment</a>
|
||||
<a t-if="record.state.raw_value == 'recruit'" data-name="job_open" data-type="object" class="oe_kanban_action">Recruitment Done</a>
|
||||
<a t-if="record.survey_id.raw_value"> | </a>
|
||||
<a t-if="record.survey_id.raw_value" data-name="action_print_survey" data-type="object" class="oe_kanban_action">Print Interview</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- hr related job position menu action -->
|
||||
<record model="ir.actions.act_window" id="action_hr_job">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{'search_default_in_recruitment': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click here to create a new job or remove the filter on "In Recruitment" to recruit for an on hold job.
|
||||
</p>
|
||||
<p>
|
||||
Define job position profile and manage recruitment in a context of a particular job: print interview survey, define number of expected new employees, and manage its recruitment pipe
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Stage Tree View -->
|
||||
<record model="ir.ui.view" id="hr_recruitment_stage_tree">
|
||||
<field name="name">hr.recruitment.stage.tree</field>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
||||
# Copyright (C) 2004-Today OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,9 +19,11 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
class hr_applicant_settings(osv.osv_memory):
|
||||
|
||||
class hr_applicant_settings(osv.TransientModel):
|
||||
_name = 'hr.config.settings'
|
||||
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
|
||||
|
||||
|
@ -32,6 +34,44 @@ class hr_applicant_settings(osv.osv_memory):
|
|||
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
|
||||
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
|
||||
help='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
|
||||
'and create automatically application documents in the system.'),
|
||||
'and create automatically application documents in the system.',
|
||||
deprecated='Will be removed with OpenERP v8, not applicable anymore. Use aliases instead.'),
|
||||
'alias_prefix': fields.char('Default Alias Name for Jobs'),
|
||||
'alias_domain': fields.char('Alias Domain'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'alias_domain': lambda self, cr, uid, context: self.pool['mail.alias']._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
|
||||
}
|
||||
|
||||
def _find_default_job_alias_id(self, cr, uid, context=None):
|
||||
alias_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'hr_recruitment.mail_alias_jobs')
|
||||
if not alias_id:
|
||||
alias_ids = self.pool['mail.alias'].search(
|
||||
cr, uid, [
|
||||
('alias_model_id.model', '=', 'hr.applicant'),
|
||||
('alias_force_thread_id', '=', 0),
|
||||
('alias_parent_model_id.model', '=', 'hr.job'),
|
||||
('alias_parent_thread_id', '=', 0),
|
||||
('alias_defaults', '=', '{}')
|
||||
], context=context)
|
||||
alias_id = alias_ids and alias_ids[0] or False
|
||||
return alias_id
|
||||
|
||||
def get_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
alias_name = False
|
||||
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
|
||||
if alias_id:
|
||||
alias_name = self.pool['mail.alias'].browse(cr, uid, alias_id, context=context).alias_name
|
||||
return {'alias_prefix': alias_name}
|
||||
|
||||
def set_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
|
||||
if not alias_id:
|
||||
create_ctx = dict(context, alias_model_name='hr.applicant', alias_parent_model_name='hr.job')
|
||||
alias_id = self.pool['mail.alias'].create(cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx)
|
||||
else:
|
||||
mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context)
|
||||
return True
|
||||
|
|
|
@ -19,6 +19,14 @@
|
|||
<label for="module_document"/>
|
||||
</div>
|
||||
</div>
|
||||
<xpath expr="//div[@name='hr_recruitment']" position="after">
|
||||
<div attrs="{'invisible': ['|',('module_hr_recruitment','=',False),('alias_domain', '=', False)]}">
|
||||
<label string="Default job email address"/>
|
||||
<field name="alias_prefix" class="oe_inline" attrs="{'required': [('alias_domain', '!=', False)]}"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
job_position.css: job_position.sass
|
||||
sass --trace -t expanded job_position.sass job_position.css
|
|
@ -0,0 +1,86 @@
|
|||
.openerp .oe_kanban_job{
|
||||
width: 355px;
|
||||
min-height: 165px !important;
|
||||
}
|
||||
.openerp .oe_job_alias{
|
||||
margin: 3px;
|
||||
}
|
||||
.openerp .oe_job_detail{
|
||||
height: 70px;
|
||||
width: 308px
|
||||
}
|
||||
.openerp .oe_job_alias .oe_e {
|
||||
font-size: 30px;
|
||||
line-height: 6px;
|
||||
vertical-align: top;
|
||||
margin-right: 3px;
|
||||
color: white;
|
||||
text-shadow: 0px 0px 2px black;
|
||||
float: left;
|
||||
}
|
||||
.openerp .oe_job {
|
||||
font-size: 112%;
|
||||
position: inline;
|
||||
margin: 3px 3px;
|
||||
color: #4c4c4c;
|
||||
height: 16px;
|
||||
}
|
||||
.openerp img.oe_job_avatar {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 295px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.openerp .oe_launch_recruitment{
|
||||
float: left;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: 10px;
|
||||
}
|
||||
.openerp div.oe_applications {
|
||||
position: absolute;;
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.openerp .oe_applications > a > span:hover{
|
||||
margin: 4px 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.openerp .oe_name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.openerp .oe_manager_name {
|
||||
width: 135px;
|
||||
font-size: 11px;
|
||||
color: gray;
|
||||
}
|
||||
.openerp .oe_job_justgage {
|
||||
float: right;
|
||||
margin-top: -40px;
|
||||
margin-right: -58px;
|
||||
width:200px;
|
||||
height:130px;
|
||||
}
|
||||
.openerp .oe_department {
|
||||
width: 350px;
|
||||
}
|
||||
.openerp .oe_start_recruitment {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.openerp .oe_start_recruitment p {
|
||||
font-size: 14px;
|
||||
color: gray;
|
||||
padding-left: 50px;
|
||||
}
|
||||
.openerp .oe_start_recruitment img {
|
||||
margin-top: -22px;
|
||||
width: 32px;
|
||||
height: 34px;
|
||||
float: left;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.openerp .oe_job_messages{
|
||||
margin-top: 40px !important;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
.openerp
|
||||
.oe_kanban_job
|
||||
width: 355px
|
||||
min-height: 165px !important
|
||||
.oe_job_alias
|
||||
margin: 3px
|
||||
.oe_job_detail
|
||||
height: 70px
|
||||
width: 308px
|
||||
.oe_job_alias .oe_e
|
||||
font-size: 30px
|
||||
line-height: 6px
|
||||
vertical-align: top
|
||||
margin-right: 3px
|
||||
color: white
|
||||
text-shadow: 0px 0px 2px black
|
||||
float: left
|
||||
.oe_job
|
||||
font-size: 112%
|
||||
position: inline
|
||||
margin: 3px 3px
|
||||
color: #4c4c4c
|
||||
height: 16px
|
||||
img.oe_job_avatar
|
||||
position: absolute
|
||||
width: 24px
|
||||
height: 24px
|
||||
margin-left: 295px
|
||||
margin-top: -5px
|
||||
.oe_launch_recruitment
|
||||
float: left
|
||||
position: absolute
|
||||
bottom: 3px
|
||||
left: 10px
|
||||
div.oe_applications
|
||||
position: absolute
|
||||
margin-top: 16px
|
||||
font-size: 14px
|
||||
.oe_applications > a > span:hover
|
||||
margin: 4px 0
|
||||
text-decoration: underline
|
||||
.oe_name
|
||||
font-size: 14px
|
||||
font-weight: bold
|
||||
.oe_manager_name
|
||||
width: 135px
|
||||
font-size: 11px
|
||||
color: gray
|
||||
.oe_job_justgage
|
||||
float: right
|
||||
margin-top: -40px
|
||||
margin-right: -58px
|
||||
width: 200px
|
||||
height: 130px
|
||||
.oe_department
|
||||
width: 350px
|
||||
.oe_start_recruitment
|
||||
padding-top: 10px
|
||||
p
|
||||
font-size: 14px
|
||||
color: gray
|
||||
padding-left: 50px
|
||||
img
|
||||
margin-top: -22px
|
||||
width: 32px
|
||||
height: 34px
|
||||
float: left
|
||||
padding-left: 12px
|
||||
.oe_job_messages
|
||||
margin-top: 40px !important
|
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1,13 @@
|
|||
openerp.hr_recruitment = function (openerp) {
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'hr.job') {
|
||||
this.$('.oe_applications a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import commands
|
||||
import logging
|
||||
import simplejson
|
||||
import os
|
||||
|
@ -11,7 +12,7 @@ import math
|
|||
import md5
|
||||
import openerp.addons.hw_proxy.controllers.main as hw_proxy
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock
|
||||
from Queue import Queue, Empty
|
||||
|
||||
try:
|
||||
|
@ -39,6 +40,7 @@ class EscposDriver(Thread):
|
|||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.queue = Queue()
|
||||
self.lock = Lock()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
|
||||
def connected_usb_devices(self):
|
||||
|
@ -47,6 +49,13 @@ class EscposDriver(Thread):
|
|||
if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
|
||||
connected.append(device)
|
||||
return connected
|
||||
|
||||
def lockedstart(self):
|
||||
self.lock.acquire()
|
||||
if not self.isAlive():
|
||||
self.daemon = True
|
||||
self.start()
|
||||
self.lock.release()
|
||||
|
||||
def get_escpos_printer(self):
|
||||
try:
|
||||
|
@ -86,7 +95,6 @@ class EscposDriver(Thread):
|
|||
_logger.warning('ESC/POS Device Disconnected: '+message)
|
||||
|
||||
def run(self):
|
||||
self.queue = Queue()
|
||||
while True:
|
||||
try:
|
||||
timestamp, task, data = self.queue.get(True)
|
||||
|
@ -105,6 +113,8 @@ class EscposDriver(Thread):
|
|||
elif task == 'cashbox':
|
||||
if timestamp >= time.time() - 12:
|
||||
self.open_cashbox(printer)
|
||||
elif task == 'printstatus':
|
||||
self.print_status(printer)
|
||||
elif task == 'status':
|
||||
pass
|
||||
|
||||
|
@ -113,10 +123,31 @@ class EscposDriver(Thread):
|
|||
_logger.error(e);
|
||||
|
||||
def push_task(self,task, data = None):
|
||||
if not self.isAlive():
|
||||
self.start()
|
||||
self.lockedstart()
|
||||
self.queue.put((time.time(),task,data))
|
||||
|
||||
def print_status(self,eprint):
|
||||
localips = ['0.0.0.0','127.0.0.1','127.0.1.1']
|
||||
ips = [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
|
||||
ips = [ ip for ip in ips if ip not in localips ]
|
||||
eprint.text('\n\n')
|
||||
eprint.set(align='center',type='b',height=2,width=2)
|
||||
eprint.text('PosBox Status\n')
|
||||
eprint.text('\n')
|
||||
eprint.set(align='center')
|
||||
|
||||
if len(ips) == 0:
|
||||
eprint.text('ERROR: Could not connect to LAN\n\nPlease check that the PosBox is correc-\ntly connected with a network cable,\n that the LAN is setup with DHCP, and\nthat network addresses are available')
|
||||
elif len(ips) == 1:
|
||||
eprint.text('IP Address\n'+ips[0]+'\n')
|
||||
else:
|
||||
eprint.text('IP Addresses\n')
|
||||
for ip in ips:
|
||||
eprint.text(ip+'\n')
|
||||
|
||||
eprint.text('\n\n')
|
||||
eprint.cut()
|
||||
|
||||
def print_receipt_body(self,eprint,receipt):
|
||||
|
||||
def check(string):
|
||||
|
@ -134,7 +165,6 @@ class EscposDriver(Thread):
|
|||
else:
|
||||
return str(amount)
|
||||
|
||||
|
||||
def printline(left, right='', width=40, ratio=0.5, indent=0):
|
||||
lwidth = int(width * ratio)
|
||||
rwidth = width - lwidth
|
||||
|
@ -157,6 +187,7 @@ class EscposDriver(Thread):
|
|||
|
||||
# Receipt Header
|
||||
if receipt['company']['logo']:
|
||||
eprint.set(align='center')
|
||||
eprint.print_base64_image(receipt['company']['logo'])
|
||||
eprint.text('\n')
|
||||
else:
|
||||
|
@ -245,6 +276,8 @@ class EscposDriver(Thread):
|
|||
driver = EscposDriver()
|
||||
|
||||
hw_proxy.drivers['escpos'] = driver
|
||||
|
||||
driver.push_task('printstatus')
|
||||
|
||||
class EscposProxy(hw_proxy.Proxy):
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import os
|
|||
import time
|
||||
from os import listdir
|
||||
from os.path import join
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock
|
||||
from select import select
|
||||
from Queue import Queue, Empty
|
||||
|
||||
|
@ -26,6 +26,7 @@ except ImportError:
|
|||
class Scanner(Thread):
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.lock = Lock()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
self.input_dir = '/dev/input/by-id/'
|
||||
self.barcodes = Queue()
|
||||
|
@ -86,6 +87,12 @@ class Scanner(Thread):
|
|||
57:(" "," "),
|
||||
}
|
||||
|
||||
def lockedstart(self):
|
||||
self.lock.acquire()
|
||||
if not self.isAlive():
|
||||
self.start()
|
||||
self.lock.release()
|
||||
|
||||
def set_status(self, status, message = None):
|
||||
if status == self.status['status']:
|
||||
if message != None and message != self.status['messages'][-1]:
|
||||
|
@ -102,8 +109,6 @@ class Scanner(Thread):
|
|||
elif status == 'disconnected' and message:
|
||||
_logger.warning('Disconnected Barcode Scanner: '+message)
|
||||
|
||||
|
||||
|
||||
def get_device(self):
|
||||
try:
|
||||
if not evdev:
|
||||
|
@ -135,6 +140,8 @@ class Scanner(Thread):
|
|||
busy reading another barcode
|
||||
"""
|
||||
|
||||
self.lockedstart()
|
||||
|
||||
while True:
|
||||
try:
|
||||
timestamp, barcode = self.barcodes.get(True, 5)
|
||||
|
@ -144,8 +151,7 @@ class Scanner(Thread):
|
|||
return ''
|
||||
|
||||
def get_status(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
self.lockedstart()
|
||||
return self.status
|
||||
|
||||
def run(self):
|
||||
|
@ -209,7 +215,6 @@ hw_proxy.drivers['scanner'] = s
|
|||
class ScannerDriver(hw_proxy.Proxy):
|
||||
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
|
||||
def scanner(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
return s.get_barcode()
|
||||
|
||||
|
||||
|
|
|
@ -154,6 +154,12 @@ class mail_alias(osv.Model):
|
|||
sequence = (sequence + 1) if sequence else 2
|
||||
return new_name
|
||||
|
||||
def _clean_and_make_unique(self, cr, uid, name, context=None):
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
name = remove_accents(name).lower().split('@')[0]
|
||||
name = re.sub(r'[^\w+.]+', '-', name)
|
||||
return self._find_unique(cr, uid, name, context=context)
|
||||
|
||||
def migrate_to_alias(self, cr, child_model_name, child_table_name, child_model_auto_init_fct,
|
||||
alias_model_name, alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={},
|
||||
alias_generate_name=False, context=None):
|
||||
|
@ -199,7 +205,7 @@ class mail_alias(osv.Model):
|
|||
alias_vals['alias_parent_thread_id'] = obj_data['id']
|
||||
alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
|
||||
alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
|
||||
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id})
|
||||
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id}, context={'mail_notrack': True})
|
||||
_logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])
|
||||
|
||||
# Finally attempt to reinstate the missing constraint
|
||||
|
@ -227,11 +233,7 @@ class mail_alias(osv.Model):
|
|||
model_name = context.get('alias_model_name')
|
||||
parent_model_name = context.get('alias_parent_model_name')
|
||||
if vals.get('alias_name'):
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
|
||||
alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
|
||||
alias_name = self._find_unique(cr, uid, alias_name, context=context)
|
||||
vals['alias_name'] = alias_name
|
||||
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
|
||||
if model_name:
|
||||
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0]
|
||||
vals['alias_model_id'] = model_id
|
||||
|
@ -240,6 +242,12 @@ class mail_alias(osv.Model):
|
|||
vals['alias_parent_model_id'] = model_id
|
||||
return super(mail_alias, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
""""give uniqe alias name if given alias name is allready assigned"""
|
||||
if vals.get('alias_name'):
|
||||
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
|
||||
return super(mail_alias, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def open_document(self, cr, uid, ids, context=None):
|
||||
alias = self.browse(cr, uid, ids, context=context)[0]
|
||||
if not alias.alias_model_id or not alias.alias_force_thread_id:
|
||||
|
|
|
@ -113,9 +113,9 @@ class mail_thread(osv.AbstractModel):
|
|||
object_id.alias_id.alias_model_id.model == self._name and \
|
||||
object_id.alias_id.alias_force_thread_id == 0:
|
||||
alias = object_id.alias_id
|
||||
elif catchall_domain and model: # no specific res_id given -> generic help message, take an example alias (i.e. alias of some section_id)
|
||||
if not alias and catchall_domain and model: # no res_id or res_id not linked to an alias -> generic help message, take a generic alias of the model
|
||||
alias_obj = self.pool.get('mail.alias')
|
||||
alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False)], context=context, order='id ASC')
|
||||
alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False), ('alias_parent_thread_id', '=', False)], context=context, order='id ASC')
|
||||
if alias_ids and len(alias_ids) == 1:
|
||||
alias = alias_obj.browse(cr, uid, alias_ids[0], context=context)
|
||||
|
||||
|
@ -384,7 +384,10 @@ class mail_thread(osv.AbstractModel):
|
|||
track_ctx = dict(context)
|
||||
if 'lang' not in track_ctx:
|
||||
track_ctx['lang'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).lang
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
|
||||
if not context.get('mail_notrack'):
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
|
||||
else:
|
||||
tracked_fields = []
|
||||
if tracked_fields:
|
||||
records = self.browse(cr, uid, ids, context=track_ctx)
|
||||
initial_values = dict((this.id, dict((key, getattr(this, key)) for key in tracked_fields.keys())) for this in records)
|
||||
|
|
|
@ -7,14 +7,14 @@ msgstr ""
|
|||
"Project-Id-Version: OpenERP Server 6.0dev\n"
|
||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
|
||||
"PO-Revision-Date: 2011-12-06 10:25+0000\n"
|
||||
"Last-Translator: qdp (OpenERP) <qdp-launchpad@tinyerp.com>\n"
|
||||
"PO-Revision-Date: 2014-02-12 04:36+0000\n"
|
||||
"Last-Translator: Bluce <igamall@yahoo.com.tw>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-01-28 05:39+0000\n"
|
||||
"X-Generator: Launchpad (build 16914)\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-12 05:29+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: mrp
|
||||
#: help:mrp.config.settings,module_mrp_repair:0
|
||||
|
@ -47,7 +47,7 @@ msgstr "工作中心使用率"
|
|||
#. module: mrp
|
||||
#: view:mrp.routing.workcenter:0
|
||||
msgid "Routing Work Centers"
|
||||
msgstr "途程工作中心"
|
||||
msgstr "製程工作中心"
|
||||
|
||||
#. module: mrp
|
||||
#: field:mrp.production.workcenter.line,cycle:0
|
||||
|
@ -83,12 +83,12 @@ msgstr "MRP 工作中心"
|
|||
#: model:ir.actions.act_window,name:mrp.mrp_routing_action
|
||||
#: model:ir.ui.menu,name:mrp.menu_mrp_routing_action
|
||||
msgid "Routings"
|
||||
msgstr "途程"
|
||||
msgstr "製程"
|
||||
|
||||
#. module: mrp
|
||||
#: view:mrp.bom:0
|
||||
msgid "Search Bill Of Material"
|
||||
msgstr "搜尋材料清單"
|
||||
msgstr "搜尋物料清單"
|
||||
|
||||
#. module: mrp
|
||||
#: model:process.node,note:mrp.process_node_stockproduct1
|
||||
|
@ -244,6 +244,13 @@ msgid ""
|
|||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
"點選建立一個製程(Routing)。\n"
|
||||
"</p><p>\n"
|
||||
"製程(Routing)允許您建立並管理在工作中心內應被遵循的製造程序(即工作順序),用以生產產品。\n"
|
||||
"它們被附加於用以定義所需原物料的物料清單(BOM)。\n"
|
||||
"</p>\n"
|
||||
" "
|
||||
|
||||
#. module: mrp
|
||||
#: view:mrp.production:0
|
||||
|
@ -299,7 +306,7 @@ msgstr "預定之貨品"
|
|||
#. module: mrp
|
||||
#: selection:mrp.bom,type:0
|
||||
msgid "Sets / Phantom"
|
||||
msgstr "套件 / 虛項"
|
||||
msgstr "組 / 虛擬物料"
|
||||
|
||||
#. module: mrp
|
||||
#: view:mrp.production:0
|
||||
|
@ -315,7 +322,7 @@ msgstr "於外部計劃中之位置的引用。"
|
|||
#. module: mrp
|
||||
#: model:res.groups,name:mrp.group_mrp_routings
|
||||
msgid "Manage Routings"
|
||||
msgstr "管理途程"
|
||||
msgstr "管理製程"
|
||||
|
||||
#. module: mrp
|
||||
#: model:ir.model,name:mrp.model_mrp_product_produce
|
||||
|
@ -393,7 +400,7 @@ msgstr "確認生產"
|
|||
msgid ""
|
||||
"The system creates an order (production or purchased) depending on the sold "
|
||||
"quantity and the products parameters."
|
||||
msgstr ""
|
||||
msgstr "系統依據已售數量與產品參數建立一個供給訂單(製造單或採購單)。"
|
||||
|
||||
#. module: mrp
|
||||
#: model:process.transition,note:mrp.process_transition_servicemts0
|
||||
|
@ -461,6 +468,15 @@ msgid ""
|
|||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" 點選建立一個新的屬性。\n"
|
||||
" </p><p>\n"
|
||||
" OpenERP的屬性(properties)是使用於選擇正確的物料表(BoM)於製造產品,\n"
|
||||
" 當您有不同的方法來建立同一產品。您能指定數個屬性給每一個物料表。\n"
|
||||
" 當一位業務人員建立一張銷售訂單時,他們可以將銷售訂單關聯至數個屬性,\n"
|
||||
" 並且OpenERP將根據需求自動選擇物料表(BoM)。\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
|
||||
#. module: mrp
|
||||
#: view:mrp.production:0
|
||||
|
@ -539,7 +555,7 @@ msgstr ""
|
|||
msgid ""
|
||||
"The Bill of Material is linked to a routing, i.e. the succession of work "
|
||||
"centers."
|
||||
msgstr ""
|
||||
msgstr "物料清單(BOM)已連結至一個製程(工作順序),如各工作中心的接續"
|
||||
|
||||
#. module: mrp
|
||||
#: view:mrp.production:0
|
||||
|
@ -566,7 +582,7 @@ msgstr "強制預留"
|
|||
#. module: mrp
|
||||
#: field:report.mrp.inout,value:0
|
||||
msgid "Stock value"
|
||||
msgstr ""
|
||||
msgstr "庫存評價"
|
||||
|
||||
#. module: mrp
|
||||
#: model:ir.actions.act_window,name:mrp.action_product_bom_structure
|
||||
|
@ -773,7 +789,7 @@ msgstr "每月"
|
|||
msgid ""
|
||||
"Unit of Measure (Unit of Measure) is the unit of measurement for the "
|
||||
"inventory control"
|
||||
msgstr ""
|
||||
msgstr "度量單位(UoM)是庫存管理的計算單位。"
|
||||
|
||||
#. module: mrp
|
||||
#: report:bom.structure:0
|
||||
|
@ -824,7 +840,7 @@ msgstr "標示為開始"
|
|||
#. module: mrp
|
||||
#: view:mrp.production:0
|
||||
msgid "Partial"
|
||||
msgstr "部份"
|
||||
msgstr "分批"
|
||||
|
||||
#. module: mrp
|
||||
#: report:mrp.production.order:0
|
||||
|
|
|
@ -71,8 +71,6 @@ class mrp_workcenter(osv.osv):
|
|||
value = {'costs_hour': cost.standard_price}
|
||||
return {'value': value}
|
||||
|
||||
|
||||
|
||||
class mrp_routing(osv.osv):
|
||||
"""
|
||||
For specifying the routings of Work Centers.
|
||||
|
@ -261,16 +259,20 @@ class mrp_bom(osv.osv):
|
|||
(_check_product, 'BoM line product should not be same as BoM product.', ['product_id']),
|
||||
]
|
||||
|
||||
def onchange_product_id(self, cr, uid, ids, product_id, name, context=None):
|
||||
def onchange_product_id(self, cr, uid, ids, product_id, name, product_qty=0, context=None):
|
||||
""" Changes UoM and name if product_id changes.
|
||||
@param name: Name of the field
|
||||
@param product_id: Changed product_id
|
||||
@return: Dictionary of changed values
|
||||
"""
|
||||
res = {}
|
||||
if product_id:
|
||||
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
|
||||
return {'value': {'name': prod.name, 'product_uom': prod.uom_id.id}}
|
||||
return {}
|
||||
res['value'] = {'name': prod.name, 'product_uom': prod.uom_id.id, 'product_uos_qty': 0, 'product_uos': False}
|
||||
if prod.uos_id.id:
|
||||
res['value']['product_uos_qty'] = product_qty * prod.uos_coeff
|
||||
res['value']['product_uos'] = prod.uos_id.id
|
||||
return res
|
||||
|
||||
def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
|
||||
res = {'value':{}}
|
||||
|
@ -553,16 +555,19 @@ class mrp_production(osv.osv):
|
|||
return {'value': {'location_dest_id': src}}
|
||||
return {}
|
||||
|
||||
def product_id_change(self, cr, uid, ids, product_id, context=None):
|
||||
def product_id_change(self, cr, uid, ids, product_id, product_qty=0, context=None):
|
||||
""" Finds UoM of changed product.
|
||||
@param product_id: Id of changed product.
|
||||
@return: Dictionary of values.
|
||||
"""
|
||||
result = {}
|
||||
if not product_id:
|
||||
return {'value': {
|
||||
'product_uom': False,
|
||||
'bom_id': False,
|
||||
'routing_id': False
|
||||
'routing_id': False,
|
||||
'product_uos_qty': 0,
|
||||
'product_uos': False
|
||||
}}
|
||||
bom_obj = self.pool.get('mrp.bom')
|
||||
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
|
||||
|
@ -571,14 +576,13 @@ class mrp_production(osv.osv):
|
|||
if bom_id:
|
||||
bom_point = bom_obj.browse(cr, uid, bom_id, context=context)
|
||||
routing_id = bom_point.routing_id.id or False
|
||||
|
||||
product_uom_id = product.uom_id and product.uom_id.id or False
|
||||
result = {
|
||||
'product_uom': product_uom_id,
|
||||
'bom_id': bom_id,
|
||||
'routing_id': routing_id,
|
||||
}
|
||||
return {'value': result}
|
||||
product_uos_id = product.uos_id and product.uos_id.id or False
|
||||
result['value'] = {'product_uos_qty': 0, 'product_uos': False, 'product_uom': product_uom_id, 'bom_id': bom_id, 'routing_id': routing_id}
|
||||
if product.uos_id.id:
|
||||
result['value']['product_uos_qty'] = product_qty * product.uos_coeff
|
||||
result['value']['product_uos'] = product.uos_id.id
|
||||
return result
|
||||
|
||||
def bom_id_change(self, cr, uid, ids, bom_id, context=None):
|
||||
""" Finds routing for changed BoM.
|
||||
|
|
|
@ -347,10 +347,10 @@
|
|||
<form string="Bill of Material" version="7.0">
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id, name, context)" context="{'default_supply_method':'produce'}" class="oe_inline"/>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id, name, product_qty, context)" context="{'default_supply_method':'produce'}" class="oe_inline"/>
|
||||
<label for="product_qty" string="Quantity"/>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline"/>
|
||||
<field name="product_qty" class="oe_inline" on_change="onchange_product_id(product_id, name, product_qty, context)"/>
|
||||
<field name="product_uom" class="oe_inline" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
|
||||
</div>
|
||||
<label for="product_uos_qty" groups="product.group_uos"/>
|
||||
|
@ -366,7 +366,7 @@
|
|||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<field name="name" groups="base.group_no_one"/>
|
||||
<field name="name" groups="product.group_mrp_properties"/>
|
||||
<field name="code" string="Reference"/>
|
||||
<field name="type"/>
|
||||
<p colspan="2" class="oe_grey" attrs="{'invisible': [('type','=','normal')]}">
|
||||
|
@ -646,15 +646,14 @@
|
|||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id" on_change="product_id_change(product_id)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False)]" class="oe_inline" context='{"default_supply_method":"produce", "default_type": "product"}'/>
|
||||
<field name="product_id" on_change="product_id_change(product_id, product_qty)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False)]" class="oe_inline" context='{"default_supply_method":"produce", "default_type": "product"}'/>
|
||||
<label for="product_qty"/>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline"/>
|
||||
<field name="product_qty" class="oe_inline" on_change="product_id_change(product_id, product_qty)"/>
|
||||
<field name="product_uom" groups="product.group_uom" class="oe_inline"/>
|
||||
<button type="action"
|
||||
icon="terp-accessories-archiver+"
|
||||
name="%(mrp.action_change_production_qty)d"
|
||||
string="(Update)" states="confirmed" class="oe_edit_only oe_link"/>
|
||||
string="Update" states="confirmed" class="oe_edit_only oe_link"/>
|
||||
</div>
|
||||
<label for="product_uos_qty" groups="product.group_uos"/>
|
||||
<div groups="product.group_uos">
|
||||
|
|
|
@ -162,25 +162,37 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
},
|
||||
|
||||
// find a proxy and connects to it. for options see find_proxy
|
||||
// - force_ip : only try to connect to the specified ip.
|
||||
// - port: what port to listen to (default 8069)
|
||||
// - progress(fac) : callback for search progress ( fac in [0,1] )
|
||||
autoconnect: function(options){
|
||||
var self = this;
|
||||
this.set_connection_status('connecting',{});
|
||||
var found_url = new $.Deferred();
|
||||
var success = new $.Deferred();
|
||||
this.find_proxy(options)
|
||||
.then(function(proxies){
|
||||
if(proxies.length > 0){
|
||||
self.connect(proxies[0])
|
||||
.then(function(){
|
||||
success.resolve();
|
||||
},function(){
|
||||
self.set_connection_status('disconnected');
|
||||
success.reject();
|
||||
});
|
||||
}else{
|
||||
self.set_connection_status('disconnected');
|
||||
success.reject();
|
||||
}
|
||||
|
||||
if ( options.force_ip ){
|
||||
// if the ip is forced by server config, bailout on fail
|
||||
found_url = this.try_hard_to_connect(options.force_ip, options)
|
||||
}else if( localStorage['hw_proxy_url'] ){
|
||||
// try harder when we remember a good proxy url
|
||||
found_url = this.try_hard_to_connect(localStorage['hw_proxy_url'], options)
|
||||
.then(null,function(){
|
||||
return self.find_proxy(options);
|
||||
});
|
||||
}else{
|
||||
// just find something quick
|
||||
found_url = this.find_proxy(options);
|
||||
}
|
||||
|
||||
success = found_url.then(function(url){
|
||||
return self.connect(url);
|
||||
});
|
||||
|
||||
success.fail(function(){
|
||||
self.set_connection_status('disconnected');
|
||||
});
|
||||
|
||||
return success;
|
||||
},
|
||||
|
||||
|
@ -217,10 +229,50 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
}
|
||||
},
|
||||
|
||||
// returns as a deferred a list of valid hosts urls that can be used as proxy.
|
||||
// try several time to connect to a known proxy url
|
||||
try_hard_to_connect: function(url,options){
|
||||
options = options || {};
|
||||
var port = ':' + (options.port || '8069');
|
||||
|
||||
this.set_connection_status('connecting');
|
||||
|
||||
if(url.indexOf('//') < 0){
|
||||
url = 'http://'+url;
|
||||
}
|
||||
|
||||
if(url.indexOf(':',5) < 0){
|
||||
url = url+port;
|
||||
}
|
||||
|
||||
// try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
|
||||
function try_real_hard_to_connect(url, retries, done){
|
||||
|
||||
done = done || new $.Deferred();
|
||||
|
||||
var c = $.ajax({
|
||||
url: url + '/hw_proxy/hello',
|
||||
method: 'GET',
|
||||
timeout: 1000,
|
||||
})
|
||||
.done(function(){
|
||||
done.resolve(url);
|
||||
})
|
||||
.fail(function(){
|
||||
if(retries > 0){
|
||||
try_real_hard_to_connect(url,retries-1,done);
|
||||
}else{
|
||||
done.reject();
|
||||
}
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
return try_real_hard_to_connect(url,3);
|
||||
},
|
||||
|
||||
// returns as a deferred a valid host url that can be used as proxy.
|
||||
// options:
|
||||
// - port: what port to listen to (default 8069)
|
||||
// - force_ip : limit the search to the specified ip
|
||||
// - progress(fac) : callback for search progress ( fac in [0,1] )
|
||||
find_proxy: function(options){
|
||||
options = options || {};
|
||||
|
@ -228,36 +280,17 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
var port = ':' + (options.port || '8069');
|
||||
var urls = [];
|
||||
var found = false;
|
||||
var proxies = [];
|
||||
var done = new $.Deferred();
|
||||
var parallel = 8;
|
||||
var done = new $.Deferred(); // will be resolved with the proxies valid urls
|
||||
var threads = [];
|
||||
var progress = 0;
|
||||
|
||||
this.set_connection_status('connecting');
|
||||
|
||||
if(options.force_ip){
|
||||
var url = options.force_ip;
|
||||
if(url.indexOf('//') < 0){
|
||||
url = 'http://'+url;
|
||||
}
|
||||
if(url.indexOf(':',5) < 0){
|
||||
url = url+port;
|
||||
}
|
||||
urls.push(url);
|
||||
}else{
|
||||
if(localStorage['hw_proxy_url']){
|
||||
urls.push(localStorage['hw_proxy_url']);
|
||||
}
|
||||
|
||||
urls.push('http://localhost'+port);
|
||||
|
||||
for(var i = 0; i < 256; i++){
|
||||
urls.push('http://192.168.0.'+i+port);
|
||||
urls.push('http://192.168.1.'+i+port);
|
||||
urls.push('http://192.168.2.'+i+port);
|
||||
urls.push('http://10.0.0.'+i+port);
|
||||
}
|
||||
urls.push('http://localhost'+port);
|
||||
for(var i = 0; i < 256; i++){
|
||||
urls.push('http://192.168.0.'+i+port);
|
||||
urls.push('http://192.168.1.'+i+port);
|
||||
urls.push('http://10.0.0.'+i+port);
|
||||
}
|
||||
|
||||
var prog_inc = 1/urls.length;
|
||||
|
@ -269,40 +302,39 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
}
|
||||
}
|
||||
|
||||
function thread(url,done){
|
||||
if(!url){
|
||||
function thread(done){
|
||||
var url = urls.shift();
|
||||
|
||||
done = done || new $.Deferred();
|
||||
|
||||
if( !url || found || !self.searching_for_proxy ){
|
||||
done.resolve();
|
||||
return done;
|
||||
}
|
||||
|
||||
var c = $.ajax({
|
||||
url: url + '/hw_proxy/hello',
|
||||
method: 'GET',
|
||||
timeout: 300,
|
||||
timeout: 400,
|
||||
}).done(function(){
|
||||
found = true;
|
||||
update_progress();
|
||||
proxies.push(url);
|
||||
done.resolve(url);
|
||||
})
|
||||
.fail(function(){
|
||||
update_progress();
|
||||
var next_url = urls.shift();
|
||||
if(found ||! self.searching_for_proxy || !next_url){
|
||||
done.resolve();
|
||||
}else{
|
||||
thread(next_url,done);
|
||||
}
|
||||
thread(done);
|
||||
});
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
this.searching_for_proxy = true;
|
||||
|
||||
for(var i = 0; i < Math.min(parallel,urls.length); i++){
|
||||
threads.push(thread(urls.shift(),new $.Deferred()));
|
||||
for(var i = 0, len = Math.min(parallel,urls.length); i < len; i++){
|
||||
threads.push(thread());
|
||||
}
|
||||
|
||||
var done = new $.Deferred();
|
||||
|
||||
$.when.apply($,threads).then(function(){
|
||||
var urls = [];
|
||||
for(var i = 0; i < arguments.length; i++){
|
||||
|
@ -310,7 +342,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
urls.push(arguments[i]);
|
||||
}
|
||||
}
|
||||
done.resolve(urls);
|
||||
done.resolve(urls[0]);
|
||||
});
|
||||
|
||||
return done;
|
||||
|
|
|
@ -544,11 +544,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
this.order.removeOrderline(this);
|
||||
return;
|
||||
}else{
|
||||
var quant = Math.max(parseFloat(quantity) || 0, 0);
|
||||
var quant = parseFloat(quantity) || 0;
|
||||
var unit = this.get_unit();
|
||||
if(unit){
|
||||
this.quantity = Math.max(unit.rounding, round_pr(quant, unit.rounding));
|
||||
this.quantityStr = this.quantity.toFixed(Math.max(0,Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))));
|
||||
this.quantity = round_pr(quant, unit.rounding);
|
||||
this.quantityStr = this.quantity.toFixed(Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10)));
|
||||
}else{
|
||||
this.quantity = quant;
|
||||
this.quantityStr = '' + this.quantity;
|
||||
|
@ -1104,10 +1104,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}
|
||||
},
|
||||
switchSign: function() {
|
||||
console.log('switchsing');
|
||||
var oldBuffer;
|
||||
oldBuffer = this.get('buffer');
|
||||
this.set({
|
||||
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
|
||||
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
|
||||
});
|
||||
this.trigger('set_value',this.get('buffer'));
|
||||
},
|
||||
|
|
|
@ -1125,8 +1125,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
is_paid: function(){
|
||||
var currentOrder = this.pos.get('selectedOrder');
|
||||
return (currentOrder.getTotalTaxIncluded() >= 0.000001
|
||||
&& currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
|
||||
return (currentOrder.getTotalTaxIncluded() < 0.000001
|
||||
|| currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
|
||||
|
||||
},
|
||||
validate_order: function(options) {
|
||||
|
|
|
@ -258,8 +258,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
var total = order ? order.getTotalTaxIncluded() : 0;
|
||||
var taxes = order ? total - order.getTotalTaxExcluded() : 0;
|
||||
|
||||
this.el.querySelector('.summary .total > .value').innerText = this.format_currency(total);
|
||||
this.el.querySelector('.summary .total .subentry .value').innerText = this.format_currency(taxes);
|
||||
this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
|
||||
this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -635,9 +635,9 @@
|
|||
Shop: <t t-esc="widget.pos.shop.name"/><br />
|
||||
<br />
|
||||
<t t-if="widget.pos.config.receipt_header">
|
||||
<pre>
|
||||
<div style='text-align:center'>
|
||||
<t t-esc="widget.pos.config.receipt_header" />
|
||||
</pre>
|
||||
</div>
|
||||
<br />
|
||||
</t>
|
||||
<table>
|
||||
|
@ -711,9 +711,9 @@
|
|||
</table>
|
||||
<t t-if="widget.pos.config.receipt_footer">
|
||||
<br />
|
||||
<pre>
|
||||
<div style='text-align:center'>
|
||||
<t t-esc="widget.pos.config.receipt_footer" />
|
||||
</pre>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
|
|
@ -96,6 +96,55 @@
|
|||
<field name="factor">1.0</field>
|
||||
</record>
|
||||
|
||||
<!--Americanization of units of measure-->
|
||||
<record id="product_uom_lb" model="product.uom">
|
||||
<field name="name">lb(s)</field>
|
||||
<field name="category_id" ref="product_uom_categ_kgm"/>
|
||||
<field name="factor">2.20462</field>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_oz" model="product.uom">
|
||||
<field name="name">oz(s)</field>
|
||||
<field name="category_id" ref="product_uom_categ_kgm"/>
|
||||
<field name="factor">35.274</field>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_inch" model="product.uom">
|
||||
<field name="name">inch(es)</field>
|
||||
<field name="category_id" ref="uom_categ_length"/>
|
||||
<field name="factor">39.3701</field>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_foot" model="product.uom">
|
||||
<field name="name">foot(ft)</field>
|
||||
<field name="category_id" ref="uom_categ_length"/>
|
||||
<field name="factor">3.28084</field>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_mile" model="product.uom">
|
||||
<field name="name">mile(s)</field>
|
||||
<field name="category_id" ref="uom_categ_length"/>
|
||||
<field name="factor_inv" eval="1609.34"/>
|
||||
<field name="uom_type">bigger</field>
|
||||
</record>
|
||||
<record id="product_uom_floz" model="product.uom">
|
||||
<field name="name">fl oz</field>
|
||||
<field name="category_id" ref="product_uom_categ_vol"/>
|
||||
<field name="factor">33.814</field>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_qt" model="product.uom">
|
||||
<field name="name">qt</field>
|
||||
<field name="category_id" ref="product_uom_categ_vol"/>
|
||||
<field name="factor">1.05669</field>
|
||||
<field name="uom_type">smaller</field>
|
||||
</record>
|
||||
<record id="product_uom_gal" model="product.uom">
|
||||
<field name="name">gal(s)</field>
|
||||
<field name="category_id" ref="product_uom_categ_vol"/>
|
||||
<field name="factor_inv" eval="3.78541"/>
|
||||
<field name="uom_type">bigger</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
... to here, it should be in product_demo but we cant just move it
|
||||
|
|
|
@ -12,4 +12,15 @@
|
|||
qty = self._compute_qty(cr, uid, from_uom_id, qty, to_uom_id)
|
||||
assert qty == 1.02, "Qty is not correspond."
|
||||
assert price == 2000000.0, "Price is not correspond."
|
||||
|
||||
-
|
||||
I convert Liters into Gallons with price.
|
||||
-
|
||||
!python {model: product.uom}: |
|
||||
from_uom_id = ref("product_uom_litre")
|
||||
to_uom_id = ref("product_uom_gal")
|
||||
price = 2
|
||||
qty = 30.28
|
||||
price = self._compute_price(cr, uid, from_uom_id, price, to_uom_id)
|
||||
qty = self._compute_qty(cr, uid, from_uom_id, qty, to_uom_id)
|
||||
assert qty == 8, "Qty does not correspond."
|
||||
assert round(price,2) == 7.57, "Price does not correspond."
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
# Mongolian 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-02-08 12:41+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Mongolian <mn@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-02-09 06:19+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\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 "Таамагласан бохир ашиг * 100 / Таамагласан борлуулалт"
|
||||
|
||||
#. 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 "Нийт бохир ашиг * 100 / Эргэц"
|
||||
|
||||
#. 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 "Барааны Бохир Ашиг"
|
|
@ -301,7 +301,7 @@
|
|||
<field name="monthly_invoiced" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
|
||||
options="{'max_field': 'invoiced_target'}">Invoiced</field>
|
||||
<field name="invoiced_forecast" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
|
||||
options="{'max_field': 'invoiced_target', 'action_change': 'action_forecast'}">Forecast</field>
|
||||
options="{'max_field': 'invoiced_target', 'on_change': 'action_forecast'}">Forecast</field>
|
||||
</div>
|
||||
<div class="oe_center oe_salesteams_help" style="color:#bbbbbb;" t-if="!record.invoiced_target.raw_value">
|
||||
<br/>Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
|
||||
|
|
|
@ -179,6 +179,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
result.append({
|
||||
'name': v.inherit_option_id.name,
|
||||
'id': v.id,
|
||||
'xml_id': v.xml_id,
|
||||
'inherit_id': v.inherit_id.id,
|
||||
'header': True,
|
||||
'active': False
|
||||
|
@ -187,6 +188,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
result.append({
|
||||
'name': v.name,
|
||||
'id': v.id,
|
||||
'xml_id': v.xml_id,
|
||||
'inherit_id': v.inherit_id.id,
|
||||
'header': False,
|
||||
'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
|
||||
|
@ -239,13 +241,20 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
|
||||
@http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True)
|
||||
def attach(self, func, upload):
|
||||
req = request.httprequest
|
||||
|
||||
url = message = None
|
||||
try:
|
||||
image_data = upload.read()
|
||||
image = Image.open(cStringIO.StringIO(image_data))
|
||||
w, h = image.size
|
||||
if w*h > 42e6: # Nokia Lumia 1020 photo resolution
|
||||
raise ValueError(
|
||||
u"Image size excessive, uploaded images must be smaller "
|
||||
u"than 42 million pixel")
|
||||
|
||||
attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
|
||||
'name': upload.filename,
|
||||
'datas': upload.read().encode('base64'),
|
||||
'datas': image_data.encode('base64'),
|
||||
'datas_fname': upload.filename,
|
||||
'res_model': 'ir.ui.view',
|
||||
}, request.context)
|
||||
|
@ -259,7 +268,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
})
|
||||
except Exception, e:
|
||||
logger.exception("Failed to upload image to attachment")
|
||||
message = str(e)
|
||||
message = unicode(e)
|
||||
|
||||
return """<script type='text/javascript'>
|
||||
window.parent['%s'](%s, %s);
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
</record>
|
||||
<record id="action_website_tutorial" model="ir.actions.act_url">
|
||||
<field name="name">Website With Tutorial</field>
|
||||
<field name="url">/?tutorial.banner=true</field>
|
||||
<field name="url">/#tutorial.banner=true</field>
|
||||
<field name="target">self</field>
|
||||
</record>
|
||||
<record id="action_website_homepage" model="ir.actions.act_url">
|
||||
|
|
|
@ -167,5 +167,5 @@ class PageConverter(werkzeug.routing.PathConverter):
|
|||
|
||||
for view in views:
|
||||
xid = xids[view['id']]
|
||||
if xid and (not query or query in xid):
|
||||
if xid and (not query or query.lower() in xid.lower()):
|
||||
yield xid
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import copy
|
||||
import simplejson
|
||||
import werkzeug
|
||||
|
||||
from lxml import etree, html
|
||||
|
||||
from openerp.addons.website.models import website
|
||||
from openerp.http import request
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
class view(osv.osv):
|
||||
|
@ -117,6 +121,43 @@ class view(osv.osv):
|
|||
|
||||
return arch
|
||||
|
||||
def render(self, cr, uid, id_or_xml_id, values=None, engine='ir.qweb', context=None):
|
||||
if getattr(request, 'website_enabled', False):
|
||||
engine='website.qweb'
|
||||
|
||||
if isinstance(id_or_xml_id, list):
|
||||
id_or_xml_id = id_or_xml_id[0]
|
||||
if isinstance(id_or_xml_id, (int, long)):
|
||||
id_or_xml_id = self.get_view_xmlid(cr, uid, id_or_xml_id)
|
||||
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
qcontext = dict(editable=False)
|
||||
qcontext.update(
|
||||
context.copy(),
|
||||
website=request.website,
|
||||
url_for=website.url_for,
|
||||
slug=website.slug,
|
||||
res_company=request.website.company_id,
|
||||
user_id=self.pool.get("res.users").browse(cr, uid, uid),
|
||||
)
|
||||
|
||||
# add some values
|
||||
if values:
|
||||
qcontext.update(values)
|
||||
|
||||
# in edit mode ir.ui.view will tag nodes
|
||||
context['inherit_branding'] = qcontext['editable']
|
||||
|
||||
view_obj = request.website.get_template(id_or_xml_id)
|
||||
if 'main_object' not in qcontext:
|
||||
qcontext['main_object'] = view_obj
|
||||
|
||||
values = qcontext
|
||||
|
||||
return super(view, self).render(cr, uid, id_or_xml_id, values=values, engine=engine, context=context)
|
||||
|
||||
def save(self, cr, uid, res_id, value, xpath=None, context=None):
|
||||
""" Update a view section. The view section may embed fields to write
|
||||
|
||||
|
|
|
@ -29,6 +29,14 @@ class website_config_settings(osv.osv_memory):
|
|||
values[fname] = v[0] if v and self._columns[fname]._type == 'many2one' else v
|
||||
return {'value' : values}
|
||||
|
||||
# FIXME in trunk for god sake. Change the fields above to fields.char instead of fields.related,
|
||||
# and create the function set_website who will set the value on the website_id
|
||||
# create does not forward the values to the related many2one. Write does.
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
config_id = super(website_config_settings, self).create(cr, uid, vals, context=context)
|
||||
self.write(cr, uid, config_id, vals, context=context)
|
||||
return config_id
|
||||
|
||||
_defaults = {
|
||||
'website_id': lambda self,cr,uid,c: self.pool.get('website').search(cr, uid, [], context=c)[0],
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import fnmatch
|
||||
import inspect
|
||||
import itertools
|
||||
import logging
|
||||
|
@ -7,7 +6,6 @@ import math
|
|||
import re
|
||||
import urlparse
|
||||
|
||||
import simplejson
|
||||
import werkzeug
|
||||
import werkzeug.exceptions
|
||||
import werkzeug.wrappers
|
||||
|
@ -24,24 +22,19 @@ from openerp.addons.web.http import request, LazyResponse
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def keep_query(*args, **kw):
|
||||
if not args and not kw:
|
||||
args = ('*',)
|
||||
params = kw.copy()
|
||||
query_params = frozenset(werkzeug.url_decode(request.httprequest.query_string).keys())
|
||||
for keep_param in args:
|
||||
for param in fnmatch.filter(query_params, keep_param):
|
||||
if param not in params and param in request.params:
|
||||
params[param] = request.params[param]
|
||||
return werkzeug.urls.url_encode(params)
|
||||
|
||||
def url_for(path_or_uri, lang=None):
|
||||
if isinstance(path_or_uri, unicode):
|
||||
path_or_uri = path_or_uri.encode('utf-8')
|
||||
current_path = request.httprequest.path
|
||||
if isinstance(current_path, unicode):
|
||||
current_path = current_path.encode('utf-8')
|
||||
location = path_or_uri.strip()
|
||||
force_lang = lang is not None
|
||||
url = urlparse.urlparse(location)
|
||||
|
||||
if request and not url.netloc and not url.scheme and (url.path or force_lang):
|
||||
location = urlparse.urljoin(request.httprequest.path, location)
|
||||
location = urlparse.urljoin(current_path, location)
|
||||
|
||||
lang = lang or request.context.get('lang')
|
||||
langs = [lg[0] for lg in request.website.get_languages()]
|
||||
|
||||
|
@ -59,7 +52,7 @@ def url_for(path_or_uri, lang=None):
|
|||
ps.insert(1, lang)
|
||||
location = '/'.join(ps)
|
||||
|
||||
return location
|
||||
return location.decode('utf-8')
|
||||
|
||||
def is_multilang_url(path, langs=None):
|
||||
if not langs:
|
||||
|
@ -78,7 +71,11 @@ def is_multilang_url(path, langs=None):
|
|||
|
||||
def slugify(s, max_length=None):
|
||||
if slugify_lib:
|
||||
return slugify_lib.slugify(s, max_length)
|
||||
# There are 2 different libraries only python-slugify is supported
|
||||
try:
|
||||
return slugify_lib.slugify(s, max_length=max_length)
|
||||
except TypeError:
|
||||
pass
|
||||
spaceless = re.sub(r'\s+', '-', s)
|
||||
specialless = re.sub(r'[^-_A-Za-z0-9]', '', spaceless)
|
||||
return specialless[:max_length]
|
||||
|
@ -215,7 +212,6 @@ class website(osv.osv):
|
|||
|
||||
request.redirect = lambda url: werkzeug.utils.redirect(url_for(url))
|
||||
request.context.update(
|
||||
is_master_lang=is_master_lang,
|
||||
editable=is_website_publisher,
|
||||
translatable=not is_master_lang,
|
||||
)
|
||||
|
@ -228,37 +224,8 @@ class website(osv.osv):
|
|||
return self.pool["ir.ui.view"].browse(cr, uid, view_id, context=context)
|
||||
|
||||
def _render(self, cr, uid, ids, template, values=None, context=None):
|
||||
user = self.pool.get("res.users")
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
# Take a context
|
||||
qweb_values = context.copy()
|
||||
# add some values
|
||||
if values:
|
||||
qweb_values.update(values)
|
||||
# fill some defaults
|
||||
qweb_values.update(
|
||||
request=request,
|
||||
json=simplejson,
|
||||
website=request.website,
|
||||
url_for=url_for,
|
||||
keep_query=keep_query,
|
||||
slug=slug,
|
||||
res_company=request.website.company_id,
|
||||
user_id=user.browse(cr, uid, uid),
|
||||
quote_plus=werkzeug.url_quote_plus,
|
||||
)
|
||||
qweb_values.setdefault('editable', False)
|
||||
|
||||
# in edit mode ir.ui.view will tag nodes
|
||||
context['inherit_branding'] = qweb_values['editable']
|
||||
|
||||
view = self.get_template(cr, uid, ids, template)
|
||||
|
||||
if 'main_object' not in qweb_values:
|
||||
qweb_values['main_object'] = view
|
||||
return view.render(qweb_values, engine='website.qweb', context=context)
|
||||
# TODO: remove this. (just kept for backward api compatibility for saas-3)
|
||||
return self.pool['ir.ui.view'].render(cr, uid, template, values=values, context=context)
|
||||
|
||||
def render(self, cr, uid, ids, template, values=None, status_code=None, context=None):
|
||||
def callback(template, values, context):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@import "compass/css3"
|
||||
@import "compass/css3/user-interface"
|
||||
@import "compass/css3/transition"
|
||||
@import "compass/support"
|
||||
|
||||
//smartphones, iPhone, portrait 480x320 phones
|
||||
$smart_phone: 320px
|
||||
|
@ -23,3 +24,46 @@ $desktop: 1025px
|
|||
@content
|
||||
::selection
|
||||
@content
|
||||
|
||||
// Lifted from compass-animation
|
||||
// LICENSED UNDER THE MIT LICENSE (MIT)
|
||||
//
|
||||
// Copyright (c) 2012 Eric Meyer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@mixin set-experimental-support($moz: false, $webkit: false, $ms: false, $o: false, $khtml: false)
|
||||
$experimental-support-for-mozilla: $moz
|
||||
$experimental-support-for-webkit: $webkit
|
||||
$experimental-support-for-microsoft: $ms
|
||||
$experimental-support-for-opera: $o
|
||||
$experimental-support-for-khtml: $khtml
|
||||
|
||||
@mixin with-only-support-for($moz: false, $webkit: false, $ms: false, $o: false, $khtml: false)
|
||||
// Capture the current state
|
||||
$original-moz: $experimental-support-for-mozilla
|
||||
$original-webkit: $experimental-support-for-webkit
|
||||
$original-o: $experimental-support-for-opera
|
||||
$original-ms: $experimental-support-for-microsoft
|
||||
$original-khtml: $experimental-support-for-khtml
|
||||
|
||||
@include set-experimental-support($moz, $webkit, $ms, $o, $khtml)
|
||||
@content
|
||||
@include set-experimental-support($original-moz, $original-webkit, $original-ms, $original-o, $original-khtml)
|
||||
|
||||
@mixin keyframes($name)
|
||||
@-moz-keyframes #{$name}
|
||||
+with-only-support-for($moz: true)
|
||||
@content
|
||||
@-webkit-keyframes #{$name}
|
||||
+with-only-support-for($webkit: true)
|
||||
@content
|
||||
@-o-keyframes #{$name}
|
||||
+with-only-support-for($o: true)
|
||||
@content
|
||||
@keyframes #{$name}
|
||||
+with-only-support-for
|
||||
@content
|
||||
|
|
|
@ -195,13 +195,19 @@ ul.oe_menu_editor .disclose {
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
.modal.nosave .modal-footer button.save {
|
||||
display: none;
|
||||
}
|
||||
.modal.nosave .modal-footer button.wait {
|
||||
.modal.nosave .wait {
|
||||
display: inline-block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
.modal.nosave .modal-body .filepicker, .modal.nosave .modal-body .image-preview {
|
||||
display: none;
|
||||
}
|
||||
.modal.nosave .modal-body .wait {
|
||||
width: 100%;
|
||||
}
|
||||
.modal.nosave .modal-footer .save {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal .font-icons-icons {
|
||||
font-size: 2em;
|
||||
|
|
|
@ -162,12 +162,17 @@ ul.oe_menu_editor
|
|||
.modal-footer
|
||||
text-align: left
|
||||
|
||||
.modal.nosave .modal-footer
|
||||
button.save
|
||||
display: none
|
||||
button.wait
|
||||
.modal.nosave
|
||||
.wait
|
||||
display: inline-block !important
|
||||
visibility: visible !important
|
||||
.modal-body
|
||||
.filepicker, .image-preview
|
||||
display: none
|
||||
.wait
|
||||
width: 100%
|
||||
.modal-footer .save
|
||||
display: none
|
||||
|
||||
// fontawesome modal
|
||||
.modal
|
||||
|
|
|
@ -223,7 +223,7 @@
|
|||
-webkit-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
|
||||
-moz-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
|
||||
box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
|
||||
border-color: rgba(0, 0, 0, 0.5);
|
||||
border-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.oe_overlay .oe_handle {
|
||||
display: block !important;
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
border-style: dashed
|
||||
border-width: 1px
|
||||
+box-shadow(0px 0px 0px 1px rgba(255,255,255,0.3), 0px 0px 0px 1px rgba(255,255,255,0.3) inset)
|
||||
border-color: rgba(0, 0, 0, 0.5)
|
||||
border-color: rgba(0, 0, 0, 0.3)
|
||||
.oe_handle
|
||||
display: block !important
|
||||
pointer-events: auto
|
||||
|
|
|
@ -276,12 +276,16 @@ ul.nav-stacked > li > a {
|
|||
}
|
||||
|
||||
/* ---- SNIPPETS --- */
|
||||
.oe_img_bg {
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
[data-snippet-id], .colmd, .hr, .blockquote {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
[data-snippet-id] {
|
||||
[data-snippet-id]:not([data-snippet-id="carousel"]) {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
@ -290,7 +294,7 @@ ul.nav-stacked > li > a {
|
|||
}
|
||||
.carousel-inner .item {
|
||||
height: 100%;
|
||||
background-size: 100%;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.carousel .carousel-caption {
|
||||
|
@ -360,7 +364,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
|||
|
||||
.parallax {
|
||||
position: relative;
|
||||
background-size: 100%;
|
||||
background-size: cover;
|
||||
display: table;
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
|
@ -395,7 +399,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
|||
}
|
||||
|
||||
.oe_green {
|
||||
background-color: #51d466;
|
||||
background-color: #169c78;
|
||||
color: white;
|
||||
}
|
||||
.oe_green .text-muted {
|
||||
|
@ -403,7 +407,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
|||
}
|
||||
|
||||
.oe_blue_light {
|
||||
background-color: #4791d2;
|
||||
background-color: #41b6ab;
|
||||
color: white;
|
||||
}
|
||||
.oe_blue_light .text-muted {
|
||||
|
@ -416,7 +420,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
|||
}
|
||||
|
||||
.oe_orange {
|
||||
background-color: #e67e22;
|
||||
background-color: #f05442;
|
||||
color: white;
|
||||
}
|
||||
.oe_orange .text-muted {
|
||||
|
@ -432,7 +436,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
|||
}
|
||||
|
||||
.oe_red {
|
||||
background-color: #f75353;
|
||||
background-color: #9c1b31;
|
||||
color: white;
|
||||
}
|
||||
.oe_red .text-muted {
|
||||
|
@ -440,10 +444,6 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
|
|||
}
|
||||
|
||||
/* Misc */
|
||||
.oe_img_bg {
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.texttop {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
|
|
@ -224,19 +224,24 @@ ul.nav-stacked > li > a
|
|||
a
|
||||
color: white
|
||||
|
||||
|
||||
/* ---- SNIPPETS --- */
|
||||
|
||||
.oe_img_bg
|
||||
background-size: 100%
|
||||
|
||||
[data-snippet-id],.colmd,.hr,.blockquote
|
||||
overflow: hidden
|
||||
|
||||
@media (max-width: 400px)
|
||||
[data-snippet-id]
|
||||
[data-snippet-id]:not([data-snippet-id="carousel"])
|
||||
height: auto !important
|
||||
|
||||
.carousel-inner
|
||||
height: 100%
|
||||
.item
|
||||
height: 100%
|
||||
background-size: 100%
|
||||
background-size: cover
|
||||
.carousel
|
||||
.carousel-caption
|
||||
left: auto
|
||||
|
@ -296,7 +301,7 @@ div.carousel[data-snippet-id="slider"]
|
|||
|
||||
.parallax
|
||||
position: relative
|
||||
background-size: 100%
|
||||
background-size: cover
|
||||
display: table
|
||||
width: 100%
|
||||
min-height: 100px
|
||||
|
@ -323,13 +328,13 @@ div.carousel[data-snippet-id="slider"]
|
|||
color: white
|
||||
|
||||
.oe_green
|
||||
background-color: #51d466
|
||||
background-color: #169C78
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
|
||||
.oe_blue_light
|
||||
background-color: #4791d2
|
||||
background-color: #41b6ab
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
|
@ -339,7 +344,7 @@ div.carousel[data-snippet-id="slider"]
|
|||
color: white
|
||||
|
||||
.oe_orange
|
||||
background-color: #e67e22
|
||||
background-color: #f05442
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
|
@ -351,16 +356,13 @@ div.carousel[data-snippet-id="slider"]
|
|||
color: #ddd
|
||||
|
||||
.oe_red
|
||||
background-color: #f75353
|
||||
background-color: #9C1b31
|
||||
color: white
|
||||
.text-muted
|
||||
color: #ddd
|
||||
|
||||
/* Misc */
|
||||
|
||||
.oe_img_bg
|
||||
background-size: 100%
|
||||
|
||||
.texttop
|
||||
vertical-align: top
|
||||
|
||||
|
|
Before Width: | Height: | Size: 370 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 392 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 825 KiB After Width: | Height: | Size: 199 KiB |
Before Width: | Height: | Size: 584 KiB After Width: | Height: | Size: 223 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 348 KiB |
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 732 KiB After Width: | Height: | Size: 166 KiB |
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 30 KiB |
|
@ -10,6 +10,9 @@
|
|||
|
||||
if (!is_smartphone) {
|
||||
website.ready().then(website.init_editor);
|
||||
} else {
|
||||
// remove padding of fake editor bar
|
||||
document.body.style.padding = 0;
|
||||
}
|
||||
|
||||
$(document).on('click', 'a.js_link2post', function (ev) {
|
||||
|
@ -411,6 +414,7 @@
|
|||
openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': view_name }).then(
|
||||
function(result) {
|
||||
_.each(result, function (item) {
|
||||
if (item.xml_id === "website.debugger" && !window.location.search.match(/[&?]debug(&|$)/)) return;
|
||||
if (item.header) {
|
||||
menu.append('<li class="dropdown-header">' + item.name + '</li>');
|
||||
} else {
|
||||
|
@ -437,6 +441,12 @@
|
|||
});
|
||||
},
|
||||
start: function() {
|
||||
// remove placeholder editor bar
|
||||
var fakebar = document.getElementById('website-top-navbar-placeholder');
|
||||
if (fakebar) {
|
||||
fakebar.parentNode.removeChild(fakebar);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.saving_mutex = new openerp.Mutex();
|
||||
|
||||
|
@ -628,7 +638,7 @@
|
|||
image_dialog(editor, new CKEDITOR.dom.element(previous));
|
||||
$image_button.hide();
|
||||
previous = null;
|
||||
});
|
||||
}, 'btn-sm');
|
||||
|
||||
// previous is the state of the button-trigger: it's the
|
||||
// currently-ish hovered element which can trigger a button showing.
|
||||
|
@ -1073,7 +1083,7 @@
|
|||
} else {
|
||||
// Create the page, get the URL back
|
||||
done = $.get(_.str.sprintf(
|
||||
'/pagenew/%s?noredirect=1', encodeURI(data.id)))
|
||||
'/website/add/%s?noredirect=1', encodeURI(data.id)))
|
||||
.then(function (response) {
|
||||
self.make_link(response, false, data.id);
|
||||
});
|
||||
|
@ -1265,7 +1275,7 @@
|
|||
}),
|
||||
|
||||
start: function () {
|
||||
this.$('.modal-footer [disabled]').text("Uploading…");
|
||||
this.$('button.wait').text("Uploading…");
|
||||
var $options = this.$('.image-style').children();
|
||||
this.image_styles = $options.map(function () { return this.value; }).get();
|
||||
|
||||
|
@ -1298,13 +1308,16 @@
|
|||
* Sets the provided image url as the dialog's value-to-save and
|
||||
* refreshes the preview element to use it.
|
||||
*/
|
||||
set_image: function (url) {
|
||||
set_image: function (url, error) {
|
||||
this.$('input.url').val(
|
||||
error ? '' : url);
|
||||
this.$('input.url').val(url);
|
||||
this.preview_image();
|
||||
},
|
||||
|
||||
file_selection: function () {
|
||||
this.$el.addClass('nosave');
|
||||
this.$('form').removeClass('has-error').find('.help-block').empty();
|
||||
this.$('button.filepicker').removeClass('btn-danger btn-success');
|
||||
|
||||
var self = this;
|
||||
|
@ -1319,25 +1332,38 @@
|
|||
},
|
||||
file_selected: function(url, error) {
|
||||
var $button = this.$('button.filepicker');
|
||||
if (error) {
|
||||
if (!error) {
|
||||
$button.addClass('btn-success');
|
||||
} else {
|
||||
url = null;
|
||||
this.$('form').addClass('has-error')
|
||||
.find('.help-block').text(error);
|
||||
$button.addClass('btn-danger');
|
||||
return;
|
||||
}
|
||||
$button.addClass('btn-success');
|
||||
this.set_image(url);
|
||||
this.set_image(url, error);
|
||||
},
|
||||
preview_image: function () {
|
||||
this.$el.removeClass('nosave');
|
||||
var loaded = function () {
|
||||
this.$el.removeClass('nosave');
|
||||
}.bind(this);
|
||||
var image = this.$('input.url').val();
|
||||
if (!image) { return; }
|
||||
if (!image) { loaded(); return; }
|
||||
|
||||
this.$('img.image-preview')
|
||||
var $img = this.$('img.image-preview')
|
||||
.attr('src', image)
|
||||
.removeClass(this.image_styles.join(' '))
|
||||
.addClass(this.$('select.image-style').val());
|
||||
|
||||
if ($img.prop('complete')) {
|
||||
loaded();
|
||||
} else {
|
||||
$img.load(loaded)
|
||||
}
|
||||
},
|
||||
browse_existing: function (e) {
|
||||
e.preventDefault();
|
||||
this.$('form').removeClass('has-error').find('.help-block').empty();
|
||||
this.$('button.filepicker').removeClass('btn-danger btn-success');
|
||||
new website.editor.ExistingImageDialog(this).appendTo(document.body);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"use strict";
|
||||
|
||||
var website = openerp.website;
|
||||
var _t = openerp._t;
|
||||
website.is_editable = true;
|
||||
website.is_editable_button = true;
|
||||
|
||||
|
@ -16,7 +17,8 @@
|
|||
'click a[data-action=new_page]': function (ev) {
|
||||
ev.preventDefault();
|
||||
website.prompt({
|
||||
window_title: "New Page",
|
||||
id: "editor_new_page",
|
||||
window_title: _t("New Page"),
|
||||
input: "Page Title",
|
||||
}).then(function (val) {
|
||||
if (val) {
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
var offset = 0;
|
||||
var padding = parseInt($(document.body).css("padding-top"));
|
||||
if (speed < 1) {
|
||||
var inner_offset = self.$target.outerHeight() - this.height / this.width * document.body.clientWidth;
|
||||
var inner_offset = - self.$target.outerHeight() + this.height / this.width * document.body.clientWidth;
|
||||
var outer_offset = self.$target.offset().top - (document.body.clientHeight - self.$target.outerHeight()) - padding;
|
||||
offset = - outer_offset * speed + inner_offset;
|
||||
} else {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
var self = this;
|
||||
$("[data-oe-model]").on('click', function (event) {
|
||||
var $this = $(event.srcElement);
|
||||
var tag = $this[0].tagName.toLowerCase();
|
||||
var tag = $this[0] && $this[0].tagName.toLowerCase();
|
||||
if (!(tag === 'a' || tag === "button") && !$this.parents("a, button").length) {
|
||||
self.$('[data-action="edit"]').parent().effect('bounce', {distance: 18, times: 5}, 250);
|
||||
}
|
||||
|
@ -416,6 +416,25 @@
|
|||
}
|
||||
setTimeout(function () {
|
||||
$("#oe_snippets").trigger('snippet-dropped', $target);
|
||||
|
||||
// reset snippet for rte
|
||||
$target.removeData("snippet-editor");
|
||||
if ($target.data("overlay")) {
|
||||
$target.data("overlay").remove();
|
||||
$target.removeData("overlay");
|
||||
}
|
||||
self.create_overlay($target);
|
||||
$target.find("[data-snippet-id]").each(function () {
|
||||
var $snippet = $(this);
|
||||
$snippet.removeData("snippet-editor");
|
||||
if ($snippet.data("overlay")) {
|
||||
$snippet.data("overlay").remove();
|
||||
$snippet.removeData("overlay");
|
||||
}
|
||||
self.create_overlay($snippet);
|
||||
});
|
||||
// end
|
||||
|
||||
self.make_active($target);
|
||||
},0);
|
||||
} else {
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
var tests = {};
|
||||
var droptest = function () {
|
||||
var errors = [];
|
||||
for (var snippet_id in tests) {
|
||||
if (!tests[snippet_id]['activated'] || !tests[snippet_id]['dropped']){
|
||||
console.log("Can't dropped or activated snippet: " + snippet_id);
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
console.log(tests);
|
||||
throw new Error("Can't dropped or activated at least one snippet");
|
||||
}
|
||||
$("#oe_snippets").off('snippet-activated snippet-dropped');
|
||||
};
|
||||
var droptesttime = setTimeout(droptest,0);
|
||||
|
||||
$("#oe_snippets").off('snippet-activated snippet-dropped')
|
||||
.on('snippet-activated', function (event, dom) {
|
||||
tests[$(dom).data('src-snippet-id')]['activated'] = true;
|
||||
clearTimeout(droptesttime);
|
||||
droptesttime = setTimeout(droptest,0);
|
||||
})
|
||||
.on('snippet-dropped', function (event, dom, src_snipped_id) {
|
||||
tests[$(dom).data('src-snippet-id')]['dropped'] = true;
|
||||
clearTimeout(droptesttime);
|
||||
droptesttime = setTimeout(droptest,0);
|
||||
});
|
||||
var $thumbnails = $('#oe_snippets div.oe_snippet[data-snippet-id] .oe_snippet_thumbnail');
|
||||
$thumbnails.each(function () {
|
||||
var $thumbnail = $(this);
|
||||
tests[$thumbnail.parent().data('snippet-id')] = {};
|
||||
var position = $thumbnail.position();
|
||||
$thumbnail.trigger( $.Event( "mousedown", { which: 1, pageX: position.left, pageY: position.top } ) );
|
||||
$thumbnail.trigger( $.Event( "mousemove", { which: 1, pageX: position.left+100, pageY: position.top+100 } ) );
|
||||
$first_drop = $(".oe_drop_zone").first();
|
||||
position = $first_drop.position();
|
||||
$first_drop.trigger( $.Event( "mouseup", { which: 1, pageX: position.left+20, pageY: position.top+20 } ) );
|
||||
clearTimeout(droptesttime);
|
||||
droptesttime = setTimeout(droptest,0);
|
||||
});
|
|
@ -2,113 +2,114 @@
|
|||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
var _t = openerp._t;
|
||||
|
||||
website.EditorBar.include({
|
||||
start: function () {
|
||||
this.registerTour(new website.BannerTour(this));
|
||||
this.registerTour(new website.Tour.Banner(this));
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
|
||||
website.BannerTour = website.Tour.extend({
|
||||
website.Tour.Banner = website.Tour.extend({
|
||||
id: 'banner',
|
||||
name: "Insert a banner",
|
||||
name: "Build a page",
|
||||
path: '/page/website.homepage',
|
||||
init: function (editor) {
|
||||
init: function () {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
title: "Welcome to your website!",
|
||||
content: "This tutorial will guide you to build your home page. We will start by adding a banner.",
|
||||
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
|
||||
backdrop: true,
|
||||
title: _t("Welcome to your website!"),
|
||||
content: _t("This tutorial will guide you to build your home page. We will start by adding a banner."),
|
||||
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
|
||||
},
|
||||
{
|
||||
waitNot: '.popover.tour',
|
||||
element: 'button[data-action=edit]',
|
||||
placement: 'bottom',
|
||||
title: "Edit this page",
|
||||
content: "Every page of your website can be modified through the <i>Edit</i> button.",
|
||||
template: self.popover({ fixed: true }),
|
||||
title: _t("Edit this page"),
|
||||
content: _t("Every page of your website can be modified through the <i>Edit</i> button."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Insert building blocks",
|
||||
content: "To add content in a page, you can insert building blocks.",
|
||||
template: self.popover({ fixed: true }),
|
||||
title: _t("Insert building blocks"),
|
||||
content: _t("To add content in a page, you can insert building blocks."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
snippet: 'carousel',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Banner",
|
||||
content: "Drag the Banner block and drop it in your page.",
|
||||
template: self.popover({ fixed: true }),
|
||||
title: _t("Drag & Drop a Banner"),
|
||||
content: _t("Drag the Banner block and drop it in your page."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
waitFor: '.oe_overlay_options .oe_options:visible',
|
||||
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption',
|
||||
sampleText: 'My Title',
|
||||
placement: 'top',
|
||||
title: "Customize banner's text",
|
||||
content: "Click in the text and start editing it. Click continue once it's done.",
|
||||
title: _("Customize banner's text"),
|
||||
content: _("Click in the text and start editing it."),
|
||||
popover: { next: _t("Continue") },
|
||||
},
|
||||
{
|
||||
waitNot: '#wrap [data-snippet-id=carousel]:first .carousel-caption:contains("Your Banner Title")',
|
||||
element: '.oe_overlay_options .oe_options',
|
||||
placement: 'left',
|
||||
title: "Customize the banner",
|
||||
content: "Customize any block through this menu. Try to change the background of the banner.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
title: _t("Customize the banner"),
|
||||
content: _t("Customize any block through this menu. Try to change the background of the banner."),
|
||||
popover: { next: _t("Continue") },
|
||||
},
|
||||
{
|
||||
waitNot: '.popover.tour',
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Add Another Block",
|
||||
content: "Let's add another building block to your page.",
|
||||
template: self.popover({ fixed: true }),
|
||||
title: _t("Add Another Block"),
|
||||
content: _t("Let's add another building block to your page."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
snippet: 'three-columns',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Block",
|
||||
content: "Drag the <em>'3 Columns'</em> block and drop it below the banner.",
|
||||
template: self.popover({ fixed: true }),
|
||||
title: _t("Drag & Drop This Block"),
|
||||
content: _t("Drag the <em>'Three Columns'</em> block and drop it below the banner."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
waitFor: '.oe_overlay_options .oe_options:visible',
|
||||
element: 'button[data-action=save]',
|
||||
placement: 'right',
|
||||
title: "Save your modifications",
|
||||
content: "Publish your page by clicking on the <em>'Save'</em> button.",
|
||||
template: self.popover({ fixed: true }),
|
||||
title: _t("Save your modifications"),
|
||||
content: _t("Publish your page by clicking on the <em>'Save'</em> button."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
waitFor: 'button[data-action=edit]:visible',
|
||||
title: "Congratulation!",
|
||||
content: "Your homepage has been updated.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
title: _("Good Job!"),
|
||||
content: _("Well done, you created your homepage."),
|
||||
popover: { next: _t("Continue") },
|
||||
},
|
||||
{
|
||||
waitNot: '.popover.tour',
|
||||
element: 'a[data-action=show-mobile-preview]',
|
||||
placement: 'bottom',
|
||||
title: "Test Your Mobile Version",
|
||||
content: "Let's check how your homepage looks like on mobile devices.",
|
||||
template: self.popover({ fixed: true }),
|
||||
title: _t("Test Your Mobile Version"),
|
||||
content: _t("Let's check how your homepage looks like on mobile devices."),
|
||||
popover: { fixed: true },
|
||||
},
|
||||
{
|
||||
element: 'button[data-dismiss=modal]',
|
||||
element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]',
|
||||
placement: 'right',
|
||||
title: "Close Mobile Preview",
|
||||
content: "Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog.",
|
||||
title: _t("Close Mobile Preview"),
|
||||
content: _t("Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog."),
|
||||
},
|
||||
{
|
||||
waitNot: '.modal',
|
||||
title: "Congrats",
|
||||
content: "Congratulation. This tour is finished.",
|
||||
template: self.popover({ fixed: true, next: "Close Tutorial" }),
|
||||
element: '#content-menu-button',
|
||||
placement: 'bottom',
|
||||
title: _t("Add new pages and menus"),
|
||||
content: _t("The 'Content' menu allows you to add pages or add the top menu."),
|
||||
popover: { next: _t("Close Tutorial") },
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
|
||||
if (typeof QWeb2 !== "undefined")
|
||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
||||
|
||||
if (website.EditorBar)
|
||||
website.EditorBar.include({
|
||||
tours: [],
|
||||
start: function () {
|
||||
// $('.tour-backdrop').click(function (e) {
|
||||
// e.stopImmediatePropagation();
|
||||
// e.preventDefault();
|
||||
// });
|
||||
var self = this;
|
||||
var menu = $('#help-menu');
|
||||
_.each(this.tours, function (tour) {
|
||||
|
@ -21,31 +20,11 @@ website.EditorBar.include({
|
|||
});
|
||||
menu.append($menuItem);
|
||||
});
|
||||
|
||||
this.waitRTEReady = false;
|
||||
this.on('rte:called', this, function () {self.waitRTEReady = true; });
|
||||
this.on('rte:ready', this, function () {self.waitRTEReady = false;});
|
||||
|
||||
var res = this._super();
|
||||
website.Tour.waitReady.call(this, this.testRunning);
|
||||
return res;
|
||||
return this._super();
|
||||
},
|
||||
registerTour: function (tour) {
|
||||
website.Tour.add(tour);
|
||||
this.tours.push(tour);
|
||||
},
|
||||
testRunning: function () {
|
||||
if (this.waitRTEReady) {
|
||||
this.on('rte:ready', this, function () {
|
||||
website.Tour.each(function () {
|
||||
this.running();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
website.Tour.each(function () {
|
||||
this.running();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -82,23 +61,12 @@ $.ajaxSetup({
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
website.Tour = openerp.Class.extend({
|
||||
steps: [],
|
||||
defaultDelay: 50, //ms
|
||||
defaultOverLaps: 5000, //ms
|
||||
localStorage: window.localStorage,
|
||||
init: function (url) {
|
||||
this.tour = new Tour({
|
||||
name: this.id,
|
||||
storage: this.tourStorage,
|
||||
keyboard: false,
|
||||
template: this.popover(),
|
||||
onHide: function () {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
this.registerSteps();
|
||||
},
|
||||
init: function () {},
|
||||
|
||||
run: function (automatic) {
|
||||
this.reset();
|
||||
|
@ -111,13 +79,18 @@ website.Tour = openerp.Class.extend({
|
|||
|
||||
if (automatic) {
|
||||
this.localStorage.setItem("tour-"+this.id+"-test-automatic", true);
|
||||
} else {
|
||||
this.localStorage.removeItem("tour-"+this.id+"-test-automatic");
|
||||
}
|
||||
this.automatic = automatic;
|
||||
|
||||
// redirect to begin of the tour
|
||||
if (this.path) {
|
||||
var path = this.path.split('?');
|
||||
window.location.href = path[0] + "?tutorial."+this.id+"=true" + path.slice(1, path.length).join("?");
|
||||
return;
|
||||
// redirect to begin of the tour in function of the language
|
||||
if (!this.testUrl(this.path+"(#.*)?$")) {
|
||||
var path = this.path.split('#');
|
||||
window.location.href = "/"+this.getLang()+path[0] + "#tutorial."+this.id+"=true&" + path.slice(1, path.length).join("#");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
@ -125,12 +98,13 @@ website.Tour = openerp.Class.extend({
|
|||
website.Tour.waitReady.call(this, function () {self._running();});
|
||||
},
|
||||
running: function () {
|
||||
if (+this.localStorage.getItem("tour-"+this.id+"-test") >= this.steps.length) {
|
||||
var self = this;
|
||||
if (+this.localStorage.getItem("tour-"+this.id+"-test") >= this.steps.length-1) {
|
||||
this.endTour();
|
||||
return;
|
||||
}
|
||||
|
||||
if (website.Tour.is_busy() || !this.testUrl()) return;
|
||||
if (website.Tour.is_busy()) return;
|
||||
|
||||
// launch tour with url
|
||||
this.checkRunningUrl();
|
||||
|
@ -138,22 +112,32 @@ website.Tour = openerp.Class.extend({
|
|||
// mark tour as busy (only one test running)
|
||||
if (this.localStorage.getItem("tour-"+this.id+"-test") != null) {
|
||||
website.Tour.busy = true;
|
||||
this.automatic = !!this.localStorage.getItem("tour-"+this.id+"-test-automatic");
|
||||
}
|
||||
|
||||
if (!this.testPathUrl()) {
|
||||
if (this.automatic) {
|
||||
this.timer = setTimeout(function () {
|
||||
self.reset();
|
||||
throw new Error("Wrong url for running " + self.id
|
||||
+ '\ntestPath: ' + self.testPath
|
||||
+ '\nhref: ' + window.location.href
|
||||
+ "\nreferrer: " + document.referrer
|
||||
);
|
||||
},this.defaultOverLaps);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
website.Tour.waitReady.call(this, function () {self._running();});
|
||||
},
|
||||
|
||||
_running: function () {
|
||||
var stepId = this.localStorage.getItem("tour-"+this.id+"-test");
|
||||
var automatic = !!this.localStorage.getItem("tour-"+this.id+"-test-automatic");
|
||||
|
||||
|
||||
if (stepId != null) {
|
||||
if (!this.check(this.step(stepId))) {
|
||||
var step = this.next(stepId);
|
||||
stepId = step ? step.stepId : stepId;
|
||||
}
|
||||
this.nextStep(stepId, automatic ? this.autoNextStep : null, automatic ? 5000 : null);
|
||||
this.registerTour();
|
||||
this.nextStep(stepId, this.automatic ? this.autoNextStep : null, this.automatic ? this.defaultOverLaps : null);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -174,30 +158,51 @@ website.Tour = openerp.Class.extend({
|
|||
$('.popover.tour').remove();
|
||||
},
|
||||
|
||||
testUrl: function () {
|
||||
return !this.testPath || this.testPath.test(window.location.href);
|
||||
getLang: function () {
|
||||
return $("html").attr("lang").replace(/-/, '_');
|
||||
},
|
||||
testUrl: function (url) {
|
||||
return new RegExp("(/"+this.getLang()+")?"+url, "i").test(window.location.href);
|
||||
},
|
||||
testPathUrl: function () {
|
||||
if (!this.testPath || this.testUrl(this.testPath)) return true;
|
||||
},
|
||||
checkRunningUrl: function () {
|
||||
if (window.location.search.indexOf("tutorial."+this.id+"=true") > -1) {
|
||||
if (window.location.hash.indexOf("tutorial."+this.id+"=true") > -1) {
|
||||
this.localStorage.setItem("tour-"+this.id+"-test", 0);
|
||||
window.location.href = window.location.href.replace(/tutorial.+=true&?/, '');
|
||||
window.location.hash = window.location.hash.replace(/tutorial.+=true&?/, '');
|
||||
}
|
||||
},
|
||||
|
||||
registerTour: function () {
|
||||
this.tour = new Tour({
|
||||
name: this.id,
|
||||
storage: this.tourStorage,
|
||||
keyboard: false,
|
||||
template: this.popover(),
|
||||
onHide: function () {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
this.registerSteps();
|
||||
},
|
||||
registerSteps: function () {
|
||||
for (var index=0, len=this.steps.length; index<len; index++) {
|
||||
var step = this.steps[index];
|
||||
step.stepId = step.stepId || ""+index;
|
||||
|
||||
if (!step.waitNot && index > 0 && $(this.steps[index-1].template).has("button[data-role='next']").size()) {
|
||||
step.waitNot = '.popover.tour';
|
||||
if (!step.waitNot && index > 0 && this.steps[index-1] &&
|
||||
this.steps[index-1].popover && this.steps[index-1].popover.next) {
|
||||
step.waitNot = '.popover.tour:visible';
|
||||
}
|
||||
if (!step.waitFor && index > 0 && this.steps[index-1].snippet) {
|
||||
step.waitFor = '.oe_overlay_options .oe_options:visible';
|
||||
}
|
||||
|
||||
step._title = step.title;
|
||||
step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
|
||||
step._title = step._title || step.title;
|
||||
step.title = this.popoverTitle({ title: step._title });
|
||||
step.template = step.template || this.popover( step.popover );
|
||||
|
||||
if (!step.element) step.orphan = true;
|
||||
if (step.snippet) {
|
||||
step.element = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail';
|
||||
|
@ -205,9 +210,10 @@ website.Tour = openerp.Class.extend({
|
|||
|
||||
}
|
||||
|
||||
if ($(this.steps[index-1].template).has("button[data-role='next']").size()) {
|
||||
if (this.steps[index-1] &&
|
||||
this.steps[index-1].popover && this.steps[index-1].popover.next) {
|
||||
var step = {
|
||||
stepId: index,
|
||||
stepId: ""+index,
|
||||
waitNot: '.popover.tour:visible'
|
||||
};
|
||||
this.steps.push(step);
|
||||
|
@ -216,8 +222,21 @@ website.Tour = openerp.Class.extend({
|
|||
this.tour.addSteps(this.steps);
|
||||
},
|
||||
|
||||
popoverTitle: function (options) {
|
||||
try {
|
||||
return openerp.qweb.render('website.tour_popover_title', options);
|
||||
} catch (e) {
|
||||
if (!this.automatic) throw e;
|
||||
return options.title;
|
||||
}
|
||||
},
|
||||
popover: function (options) {
|
||||
return openerp.qweb.render('website.tour_popover', options);
|
||||
try {
|
||||
return openerp.qweb.render('website.tour_popover', options);
|
||||
} catch (e) {
|
||||
if (!this.automatic) throw e;
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
timer: null,
|
||||
|
@ -265,7 +284,12 @@ 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("Time overlaps to arrive to step " + step.stepId + ": '" + step._title + "'"
|
||||
+ '\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())
|
||||
+ '\n\n' + $("body").html()
|
||||
);
|
||||
}
|
||||
}
|
||||
checkNext();
|
||||
|
@ -288,7 +312,7 @@ website.Tour = openerp.Class.extend({
|
|||
$(".popover.tour").remove();
|
||||
// go to step in bootstrap tour
|
||||
this.tour.goto(index);
|
||||
if (step.callback) step.callback();
|
||||
if (step.onload) step.onload();
|
||||
next = steps.shift();
|
||||
break;
|
||||
}
|
||||
|
@ -315,7 +339,11 @@ website.Tour = openerp.Class.extend({
|
|||
}
|
||||
},
|
||||
endTour: function () {
|
||||
console.log('{ "event": "success" }');
|
||||
if (parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1) {
|
||||
console.log('{ "event": "success" }');
|
||||
} else {
|
||||
console.log('{ "event": "canceled" }');
|
||||
}
|
||||
this.reset();
|
||||
},
|
||||
autoNextStep: function () {
|
||||
|
@ -351,7 +379,10 @@ website.Tour = openerp.Class.extend({
|
|||
} else if (step.sampleText) {
|
||||
|
||||
$element.trigger($.Event("keydown", { srcElement: $element }));
|
||||
if ($element.is("select") || $element.is("input") ) {
|
||||
if ($element.is("input") ) {
|
||||
$element.val(step.sampleText);
|
||||
} if ($element.is("select")) {
|
||||
$element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true);
|
||||
$element.val(step.sampleText);
|
||||
} else {
|
||||
$element.html(step.sampleText);
|
||||
|
@ -397,7 +428,10 @@ website.Tour.busy = false;
|
|||
website.Tour.add = function (tour) {
|
||||
website.Tour.waitReady(function () {
|
||||
tour = tour.id ? tour : new tour();
|
||||
website.Tour.tours[tour.id] = tour;
|
||||
if (!website.Tour.tours[tour.id]) {
|
||||
website.Tour.tours[tour.id] = tour;
|
||||
tour.running();
|
||||
}
|
||||
});
|
||||
};
|
||||
website.Tour.get = function (id) {
|
||||
|
@ -413,7 +447,7 @@ website.Tour.each = function (callback) {
|
|||
website.Tour.waitReady = function (callback) {
|
||||
var self = this;
|
||||
$(document).ready(function () {
|
||||
if ($.ajaxBusy == null || $.ajaxBusy) {
|
||||
if ($.ajaxBusy) {
|
||||
$(document).ajaxStop(function() {
|
||||
setTimeout(function () {
|
||||
callback.call(self);
|
||||
|
@ -428,18 +462,20 @@ website.Tour.waitReady = function (callback) {
|
|||
});
|
||||
};
|
||||
website.Tour.run_test = function (id) {
|
||||
website.Tour.get(id).run(true);
|
||||
website.Tour.waitReady(function () {
|
||||
if (!website.Tour.is_busy()) {
|
||||
website.Tour.tours[id].run(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
website.Tour.is_busy = function () {
|
||||
for (var k in this.localStorage) {
|
||||
if (!k.indexOf("tour-")) {
|
||||
return true;
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return website.Tour.busy;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}());
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
|
||||
website.Tour.LoginEdit = website.Tour.extend({
|
||||
id: 'login_edit',
|
||||
name: "Try to log as admin and check editor",
|
||||
path: '/',
|
||||
init: function () {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
title: "click login",
|
||||
element: '#top_menu a[href*="/web/login"]',
|
||||
},
|
||||
{
|
||||
title: "insert login",
|
||||
element: '.oe_login_form input[name="login"]',
|
||||
sampleText: "admin",
|
||||
},
|
||||
{
|
||||
title: "insert password",
|
||||
waitFor: '.oe_login_form input[name="login"][value!=""]',
|
||||
element: '.oe_login_form input[name="password"]',
|
||||
sampleText: "admin",
|
||||
},
|
||||
{
|
||||
title: "select 2 Standard tickets",
|
||||
waitFor: '.oe_login_form input[name="password"][value!=""]',
|
||||
element: '.oe_login_form button',
|
||||
},
|
||||
{
|
||||
title: "go back to website from backend",
|
||||
element: 'a[data-action-model="ir.actions.act_url"]:contains("Website")',
|
||||
},
|
||||
{
|
||||
title: 'try to edit',
|
||||
waitNot: '#wrap .carousel',
|
||||
element: 'button[data-action=edit]:visible',
|
||||
},
|
||||
{
|
||||
title: 'check edit mode',
|
||||
waitFor: 'button[data-action=save]:visible',
|
||||
},
|
||||
{
|
||||
title: 'check branding',
|
||||
waitFor: '#wrap[data-oe-model="ir.ui.view"]',
|
||||
},
|
||||
{
|
||||
title: 'check rte',
|
||||
waitFor: '#oe_rte_toolbar',
|
||||
},
|
||||
{
|
||||
title: 'check insert block button',
|
||||
element: '[data-action="snippet"]:visible',
|
||||
},
|
||||
{
|
||||
title: 'add snippets',
|
||||
snippet: 'carousel',
|
||||
},
|
||||
{
|
||||
title: 'try to save',
|
||||
waitFor: '.oe_overlay_options .oe_options:visible',
|
||||
element: 'button[data-action=save]:visible',
|
||||
},
|
||||
{
|
||||
title: 'check saved',
|
||||
waitFor: '#wrap div.carousel',
|
||||
element: 'button[data-action=edit]:visible',
|
||||
},
|
||||
{
|
||||
title: 'try to re-edit',
|
||||
waitFor: 'button[data-action=save]:visible',
|
||||
element: '#wrap .carousel',
|
||||
},
|
||||
{
|
||||
title: 'remove snippet',
|
||||
element: '.oe_snippet_remove',
|
||||
},
|
||||
{
|
||||
title: 'try to re-save',
|
||||
waitNot: '#wrap .carousel',
|
||||
element: 'button[data-action=save]:visible',
|
||||
},
|
||||
{
|
||||
title: "click admin",
|
||||
waitFor: 'button[data-action=edit]:visible',
|
||||
element: 'a:contains("Administrator")',
|
||||
},
|
||||
{
|
||||
title: "click logout",
|
||||
element: '#top_menu a[href*="/logout"]',
|
||||
},
|
||||
{
|
||||
title: "check logout",
|
||||
waitFor: '#top_menu a[href*="/web/login"]',
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
// for test without editor bar
|
||||
website.Tour.add(website.Tour.LoginEdit);
|
||||
|
||||
}());
|