[MRG]Merge with lp:openobject-addons
bzr revid: cod@tinyerp.com-20140319104323-036jlo6ywrn0dm5x
This commit is contained in:
commit
d93cdf4c10
|
@ -30,7 +30,7 @@ from openerp import SUPERUSER_ID
|
|||
from openerp import tools
|
||||
from openerp.osv import fields, osv, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools.float_utils import float_round
|
||||
from openerp.tools.float_utils import float_round as round
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
|
@ -1937,15 +1937,15 @@ class account_tax(osv.osv):
|
|||
#
|
||||
'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the tax declaration."),
|
||||
'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the tax declaration."),
|
||||
'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
|
||||
'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
|
||||
'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
|
||||
# Same fields for refund invoices
|
||||
|
||||
'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the tax declaration."),
|
||||
'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the tax declaration."),
|
||||
'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1."),
|
||||
'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1."),
|
||||
'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
|
||||
'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
'description': fields.char('Tax Code'),
|
||||
|
@ -2143,7 +2143,7 @@ class account_tax(osv.osv):
|
|||
tax_compute_precision = precision
|
||||
if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
|
||||
tax_compute_precision += 5
|
||||
totalin = totalex = float_round(price_unit * quantity, precision)
|
||||
totalin = totalex = round(price_unit * quantity, precision)
|
||||
tin = []
|
||||
tex = []
|
||||
for tax in taxes:
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
on_change="onchange_partner_id(type,partner_id,date_invoice,payment_term, partner_bank_id,company_id)"
|
||||
context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}"
|
||||
domain="[('supplier', '=', True)]"/>
|
||||
<field name="fiscal_position" widget="selection"/>
|
||||
<field name="fiscal_position" options="{'no_create': True}"/>
|
||||
<field name="origin"/>
|
||||
<field name="supplier_invoice_number"/>
|
||||
<label for="reference_type"/>
|
||||
|
@ -183,7 +183,7 @@
|
|||
<field domain="[('company_id', '=', company_id), ('type', '=', 'payable')]"
|
||||
name="account_id" groups="account.group_account_user"/>
|
||||
<field name="journal_id" groups="account.group_account_user"
|
||||
on_change="onchange_journal_id(journal_id, context)" widget="selection"/>
|
||||
on_change="onchange_journal_id(journal_id, context)" options="{'no_create': True}"/>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
<field name="check_total" groups="account.group_supplier_inv_check_total"/>
|
||||
</group>
|
||||
|
@ -253,7 +253,7 @@
|
|||
<field domain="[('partner_id', '=', partner_id)]" name="partner_bank_id" on_change="onchange_partner_bank(partner_bank_id)"/>
|
||||
<field name="user_id" string="Responsible" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="payment_term" widget="selection"/>
|
||||
<field name="payment_term" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="move_id" groups="account.group_account_user"/>
|
||||
|
@ -324,12 +324,12 @@
|
|||
context="{'search_default_customer':1, 'show_address': 1}"
|
||||
options='{"always_reload": True}'
|
||||
domain="[('customer', '=', True)]"/>
|
||||
<field name="fiscal_position" widget="selection" />
|
||||
<field name="fiscal_position" options="{'no_create': True}" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_invoice"/>
|
||||
<field name="journal_id" groups="account.group_account_user"
|
||||
on_change="onchange_journal_id(journal_id, context)" widget="selection"/>
|
||||
on_change="onchange_journal_id(journal_id, context)" options="{'no_create': True}"/>
|
||||
<field domain="[('company_id', '=', company_id),('type','=', 'receivable')]"
|
||||
name="account_id" groups="account.group_account_user"/>
|
||||
|
||||
|
|
|
@ -41,14 +41,7 @@ class account_voucher(osv.osv):
|
|||
'number': fields.char('Number', size=32),
|
||||
}
|
||||
|
||||
def onchange_amount(self, cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=None):
|
||||
""" Inherited - add amount_in_word and allow_check_writting in returned value dictionary """
|
||||
if not context:
|
||||
context = {}
|
||||
default = super(account_voucher, self).onchange_amount(cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=context)
|
||||
if 'value' in default:
|
||||
amount = 'amount' in default['value'] and default['value']['amount'] or amount
|
||||
|
||||
def _amount_to_text(self, cr, uid, amount, currency_id, context=None):
|
||||
# Currency complete name is not available in res.currency model
|
||||
# Exceptions done here (EUR, USD, BRL) cover 75% of cases
|
||||
# For other currencies, display the currency code
|
||||
|
@ -63,7 +56,16 @@ class account_voucher(osv.osv):
|
|||
currency_name = currency.name
|
||||
#TODO : generic amount_to_text is not ready yet, otherwise language (and country) and currency can be passed
|
||||
#amount_in_word = amount_to_text(amount, context=context)
|
||||
amount_in_word = amount_to_text(amount, currency=currency_name)
|
||||
return amount_to_text(amount, currency=currency_name)
|
||||
|
||||
def onchange_amount(self, cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=None):
|
||||
""" Inherited - add amount_in_word and allow_check_writting in returned value dictionary """
|
||||
if not context:
|
||||
context = {}
|
||||
default = super(account_voucher, self).onchange_amount(cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=context)
|
||||
if 'value' in default:
|
||||
amount = 'amount' in default['value'] and default['value']['amount'] or amount
|
||||
amount_in_word = self._amount_to_text(cr, uid, amount, currency_id, context=context)
|
||||
default['value'].update({'amount_in_word':amount_in_word})
|
||||
if journal_id:
|
||||
allow_check_writing = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context).allow_check_writing
|
||||
|
@ -92,6 +94,19 @@ class account_voucher(osv.osv):
|
|||
},
|
||||
'nodestroy': True
|
||||
}
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if vals.get('amount') and vals.get('journal_id') and 'amount_in_word' not in vals:
|
||||
vals['amount_in_word'] = self._amount_to_text(cr, uid, vals['amount'], vals.get('currency_id') or \
|
||||
self.pool['account.journal'].browse(cr, uid, vals['journal_id'], context=context).currency.id or \
|
||||
self.pool['res.company'].browse(cr, uid, vals['company_id']).currency_id.id, context=context)
|
||||
return super(account_voucher, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if vals.get('amount') and vals.get('journal_id') and 'amount_in_word' not in vals:
|
||||
vals['amount_in_word'] = self._amount_to_text(cr, uid, vals['amount'], vals.get('currency_id') or \
|
||||
self.pool['account.journal'].browse(cr, uid, vals['journal_id'], context=context).currency.id or \
|
||||
self.pool['res.company'].browse(cr, uid, vals['company_id']).currency_id.id, context=context)
|
||||
return super(account_voucher, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
|
||||
"""
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
<para style="terp_default_8">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
<pre style="terp_default_9_followup_id">[[ format(get_text(o,data['form']['followup_id'])) ]]</pre>
|
||||
<para style="terp_default_9"><pre style="terp_default_9_followup_id">[[ format(get_text(o,data['form']['followup_id'])) ]]</pre></para>
|
||||
<para style="terp_default_9">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
|
|
|
@ -463,14 +463,12 @@ def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_val
|
|||
# if at least one modification has been found
|
||||
for model_id, resource_id in lines:
|
||||
line_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, model_id).model
|
||||
name = pool.get(line_model).name_get(cr, uid, [resource_id])[0][1]
|
||||
|
||||
vals = {
|
||||
'method': method,
|
||||
'object_id': model_id,
|
||||
'user_id': uid,
|
||||
'res_id': resource_id,
|
||||
'name': name,
|
||||
}
|
||||
if (model_id, resource_id) not in old_values and method not in ('copy', 'read'):
|
||||
# the resource was not existing so we are forcing the method to 'create'
|
||||
|
@ -481,7 +479,11 @@ def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_val
|
|||
# the resource is not existing anymore so we are forcing the method to 'unlink'
|
||||
# (because it could also come with the value 'write' if we are deleting the
|
||||
# record through a one2many field)
|
||||
name = old_values[(model_id, resource_id)]['value'].get('name',False)
|
||||
vals.update({'method': 'unlink'})
|
||||
else :
|
||||
name = pool[line_model].name_get(cr, uid, [resource_id])[0][1]
|
||||
vals.update({'name': name})
|
||||
# create the audittrail log in super admin mode, only if a change has been detected
|
||||
if lines[(model_id, resource_id)]:
|
||||
log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals)
|
||||
|
|
|
@ -2,3 +2,4 @@ import controllers
|
|||
import auth_oauth
|
||||
import res_users
|
||||
import res_config
|
||||
import ir_configparameter
|
||||
|
|
|
@ -15,6 +15,7 @@ from openerp.tools.translate import _
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
#----------------------------------------------------------
|
||||
# helpers
|
||||
#----------------------------------------------------------
|
||||
|
@ -30,11 +31,15 @@ def fragment_to_query_string(func):
|
|||
var s = l.search ? (l.search === '?' ? '' : '&') : '?';
|
||||
r = l.pathname + l.search + s + q;
|
||||
}
|
||||
if (r == l.pathname) {
|
||||
r = '/';
|
||||
}
|
||||
window.location = r;
|
||||
</script></head><body></body></html>"""
|
||||
return func(self, *a, **kw)
|
||||
return wrapper
|
||||
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Controller
|
||||
#----------------------------------------------------------
|
||||
|
@ -111,6 +116,7 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
|||
response.qcontext.update(providers=providers)
|
||||
return response
|
||||
|
||||
|
||||
class OAuthController(http.Controller):
|
||||
|
||||
@http.route('/auth_oauth/signin', type='http', auth='none')
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv
|
||||
|
||||
class ir_configparameter(osv.Model):
|
||||
_inherit = 'ir.config_parameter'
|
||||
|
||||
def init(self, cr, force=False):
|
||||
super(ir_configparameter, self).init(cr, force=force)
|
||||
if force:
|
||||
IMD = self.pool['ir.model.data']
|
||||
oauth_oe = IMD.xmlid_to_object(cr, SUPERUSER_ID, 'auth_oauth.provider_openerp')
|
||||
dbuuid = self.get_param(cr, SUPERUSER_ID, 'database.uuid')
|
||||
oauth_oe.write({'client_id': dbuuid})
|
|
@ -13,15 +13,15 @@
|
|||
<template id="auth_signup.fields" name="Auth Signup/ResetPassword form fields">
|
||||
<t t-call="web.database_select"/>
|
||||
|
||||
<div class="form-group field-login">
|
||||
<label for="login" class="control-label">Your Email</label>
|
||||
<input type="text" name="login" t-att-value="login" id="login" class="form-control" autofocus="autofocus"
|
||||
required="required" t-att-readonly="'readonly' if only_passwords else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group field-name">
|
||||
<label for="name" class="control-label">Your Name</label>
|
||||
<input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="e.g. John Doe"
|
||||
required="required" t-att-autofocus="'autofocus' if not only_passwords else None" t-att-readonly="'readonly' if only_passwords else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group field-login">
|
||||
<label for="login" class="control-label">Your Email</label>
|
||||
<input type="text" name="login" t-att-value="login" id="login" class="form-control"
|
||||
required="required" t-att-readonly="'readonly' if only_passwords else None"/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -114,6 +114,8 @@ class ir_import(orm.TransientModel):
|
|||
elif field['type'] == 'one2many' and depth:
|
||||
f['fields'] = self.get_fields(
|
||||
cr, uid, field['relation'], context=context, depth=depth-1)
|
||||
if self.pool['res.users'].has_group(cr, uid, 'base.group_no_one'):
|
||||
f['fields'].append({'id' : '.id', 'name': '.id', 'string': _("Database ID"), 'required': False, 'fields': []})
|
||||
|
||||
fields.append(f)
|
||||
|
||||
|
|
|
@ -37,8 +37,6 @@ from openerp.tools.translate import _
|
|||
from openerp.http import request
|
||||
from operator import itemgetter
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -63,6 +61,7 @@ def calendar_id2real_id(calendar_id=None, with_date=False):
|
|||
return int(real_id)
|
||||
return calendar_id and int(calendar_id) or calendar_id
|
||||
|
||||
|
||||
def get_real_ids(ids):
|
||||
if isinstance(ids, (str, int, long)):
|
||||
return calendar_id2real_id(ids)
|
||||
|
@ -76,6 +75,7 @@ class calendar_attendee(osv.Model):
|
|||
Calendar Attendee Information
|
||||
"""
|
||||
_name = 'calendar.attendee'
|
||||
_rec_name = 'cn'
|
||||
_description = 'Attendee information'
|
||||
|
||||
def _compute_data(self, cr, uid, ids, name, arg, context=None):
|
||||
|
@ -223,7 +223,7 @@ class calendar_attendee(osv.Model):
|
|||
})
|
||||
|
||||
for attendee in self.browse(cr, uid, ids, context=context):
|
||||
if attendee.email and email_from:
|
||||
if attendee.email and email_from and attendee.email != email_from:
|
||||
ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context)
|
||||
mail_id = template_pool.send_mail(cr, uid, template_id, attendee.id, context=local_context)
|
||||
|
||||
|
@ -334,22 +334,22 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
res = {}
|
||||
base_request = """
|
||||
SELECT
|
||||
crm.id,
|
||||
crm.date - interval '1' minute * calcul_delta.max_delta AS first_alarm,
|
||||
cal.id,
|
||||
cal.date - interval '1' minute * calcul_delta.max_delta AS first_alarm,
|
||||
CASE
|
||||
WHEN crm.recurrency THEN crm.end_date - interval '1' minute * calcul_delta.min_delta
|
||||
ELSE crm.date_deadline - interval '1' minute * calcul_delta.min_delta
|
||||
WHEN cal.recurrency THEN cal.end_date - interval '1' minute * calcul_delta.min_delta
|
||||
ELSE cal.date_deadline - interval '1' minute * calcul_delta.min_delta
|
||||
END as last_alarm,
|
||||
crm.date as first_event_date,
|
||||
cal.date as first_event_date,
|
||||
CASE
|
||||
WHEN crm.recurrency THEN crm.end_date
|
||||
ELSE crm.date_deadline
|
||||
WHEN cal.recurrency THEN cal.end_date
|
||||
ELSE cal.date_deadline
|
||||
END as last_event_date,
|
||||
calcul_delta.min_delta,
|
||||
calcul_delta.max_delta,
|
||||
crm.rrule AS rule
|
||||
cal.rrule AS rule
|
||||
FROM
|
||||
calendar_event AS crm
|
||||
calendar_event AS cal
|
||||
RIGHT JOIN
|
||||
(
|
||||
SELECT
|
||||
|
@ -359,11 +359,11 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
LEFT JOIN calendar_alarm AS alarm ON alarm.id = rel.calendar_alarm_id
|
||||
WHERE alarm.type in %s
|
||||
GROUP BY rel.calendar_event_id
|
||||
) AS calcul_delta ON calcul_delta.calendar_event_id = crm.id
|
||||
) AS calcul_delta ON calcul_delta.calendar_event_id = cal.id
|
||||
"""
|
||||
|
||||
filter_user = """
|
||||
LEFT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = crm.id
|
||||
RIGHT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = cal.id
|
||||
AND part_rel.res_partner_id = %s
|
||||
"""
|
||||
|
||||
|
@ -384,21 +384,14 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
#Add filter on hours
|
||||
tuple_params += (seconds, seconds,)
|
||||
|
||||
cr.execute("""
|
||||
SELECT
|
||||
*
|
||||
FROM (
|
||||
"""
|
||||
+ base_request
|
||||
+ """
|
||||
) AS ALL_EVENTS
|
||||
WHERE
|
||||
ALL_EVENTS.first_alarm < (now() at time zone 'utc' + interval '%s' second )
|
||||
AND ALL_EVENTS.last_alarm > (now() at time zone 'utc' - interval '%s' second )
|
||||
""", tuple_params)
|
||||
cr.execute("""SELECT *
|
||||
FROM ( %s ) AS ALL_EVENTS
|
||||
WHERE ALL_EVENTS.first_alarm < (now() at time zone 'utc' + interval '%%s' second )
|
||||
AND ALL_EVENTS.last_alarm > (now() at time zone 'utc' - interval '%%s' second )
|
||||
""" % base_request, tuple_params)
|
||||
|
||||
for event_id, first_alarm, last_alarm, first_meeting, last_meeting, min_duration, max_duration, rule in cr.fetchall():
|
||||
res[event_id].update({
|
||||
res[event_id] = {
|
||||
'event_id': event_id,
|
||||
'first_alarm': first_alarm,
|
||||
'last_alarm': last_alarm,
|
||||
|
@ -407,7 +400,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
'min_duration': min_duration,
|
||||
'max_duration': max_duration,
|
||||
'rrule': rule
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
|
@ -433,7 +426,6 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
res.append(alert)
|
||||
return res
|
||||
|
||||
|
||||
def get_next_mail(self, cr, uid, context=None):
|
||||
cron = self.pool.get('ir.cron').search(cr, uid, [('model', 'ilike', self._name)], context=context)
|
||||
if cron and len(cron) == 1:
|
||||
|
@ -458,13 +450,13 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
all_events = self.get_next_potential_limit_alarm(cr, uid, cron_interval, notif=False, context=context)
|
||||
|
||||
for event in all_events: # .values()
|
||||
max_delta = all_events[event]['max_duration'];
|
||||
max_delta = all_events[event]['max_duration']
|
||||
curEvent = self.pool.get('calendar.event').browse(cr, uid, event, context=context)
|
||||
if curEvent.recurrency:
|
||||
bFound = False
|
||||
LastFound = False
|
||||
for one_date in self.pool.get('calendar.event').get_recurrent_date_by_event(cr, uid, curEvent, context=context):
|
||||
in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S');
|
||||
in_date_format = one_date.replace(tzinfo=None)
|
||||
LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, cron_interval, notif=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
|
@ -475,7 +467,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
if bFound and not LastFound: # if the precedent event had an alarm but not this one, we can stop the search for this event
|
||||
break
|
||||
else:
|
||||
in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S');
|
||||
in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S')
|
||||
LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, cron_interval, notif=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
|
@ -483,19 +475,23 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
|
||||
def get_next_notif(self, cr, uid, context=None):
|
||||
ajax_check_every_seconds = 300
|
||||
partner = self.pool.get('res.users').browse(cr,uid,uid,context=context).partner_id;
|
||||
partner = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id
|
||||
all_notif = []
|
||||
|
||||
if not partner:
|
||||
return []
|
||||
|
||||
all_events = self.get_next_potential_limit_alarm(cr, uid, ajax_check_every_seconds, partner_id=partner.id, mail=False, context=context)
|
||||
|
||||
for event in all_events: # .values()
|
||||
max_delta = all_events[event]['max_duration'];
|
||||
max_delta = all_events[event]['max_duration']
|
||||
curEvent = self.pool.get('calendar.event').browse(cr, uid, event, context=context)
|
||||
if curEvent.recurrency:
|
||||
bFound = False
|
||||
LastFound = False
|
||||
for one_date in self.pool.get("calendar.event").get_recurrent_date_by_event(cr, uid, curEvent, context=context):
|
||||
in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S');
|
||||
LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,ajax_check_every_seconds,after=partner.cal_last_notif,mail=False,context=context)
|
||||
in_date_format = one_date.replace(tzinfo=None)
|
||||
LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner.calendar_last_notif_ack, mail=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
|
||||
|
@ -504,8 +500,8 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
if bFound and not LastFound: # if the precedent event had alarm but not this one, we can stop the search fot this event
|
||||
break
|
||||
else:
|
||||
in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S');
|
||||
LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,ajax_check_every_seconds,partner.cal_last_notif,mail=False,context=context)
|
||||
in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S')
|
||||
LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, partner.calendar_last_notif_ack, mail=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
|
||||
|
@ -520,7 +516,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
alarm = self.pool['calendar.alarm'].browse(cr, uid, alert['alarm_id'], context=context)
|
||||
|
||||
if alarm.type == 'email':
|
||||
res = self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, event.attendee_ids, template_xmlid='calendar_template_meeting_reminder', context=context)
|
||||
res = self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, [att.id for att in event.attendee_ids], template_xmlid='calendar_template_meeting_reminder', context=context)
|
||||
|
||||
return res
|
||||
|
||||
|
@ -684,19 +680,23 @@ class calendar_event(osv.Model):
|
|||
return [d.astimezone(pytz.UTC) for d in rset1]
|
||||
|
||||
def _get_recurrency_end_date(self, data, context=None):
|
||||
if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')):
|
||||
data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S')
|
||||
if data.get('rrule_type') in ('daily', unicode('count')):
|
||||
rel_date = relativedelta(days=data.get('count') + 1)
|
||||
elif data.get('rrule_type') in ('weekly', unicode('weekly')):
|
||||
rel_date = relativedelta(days=(data.get('count') + 1) * 7)
|
||||
elif data.get('rrule_type') in ('monthly', unicode('monthly')):
|
||||
rel_date = relativedelta(months=data.get('count') + 1)
|
||||
elif data.get('rrule_type') in ('yearly', unicode('yearly')):
|
||||
rel_date = relativedelta(years=data.get('count') + 1)
|
||||
end_date = data_date_deadline + rel_date
|
||||
else:
|
||||
if not data.get('recurrency'):
|
||||
return False
|
||||
|
||||
end_type = data.get('end_type')
|
||||
end_date = data.get('end_date')
|
||||
|
||||
if end_type == 'count' and all(data.get(key) for key in ['count', 'rrule_type', 'date_deadline']):
|
||||
count = data['count'] + 1
|
||||
delay, mult = {
|
||||
'daily': ('days', 1),
|
||||
'weekly': ('days', 7),
|
||||
'monthly': ('months', 1),
|
||||
'yearly': ('years', 1),
|
||||
}[data['rrule_type']]
|
||||
|
||||
deadline = datetime.strptime(data['date_deadline'], tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
return deadline + relativedelta(**{delay: count * mult})
|
||||
return end_date
|
||||
|
||||
def _find_my_attendee(self, cr, uid, meeting_ids, context=None):
|
||||
|
@ -778,7 +778,11 @@ class calendar_event(osv.Model):
|
|||
result[event] = ""
|
||||
return result
|
||||
|
||||
# retro compatibility function
|
||||
def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None):
|
||||
return self._set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=context)
|
||||
|
||||
def _set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=None):
|
||||
if not isinstance(ids, list):
|
||||
ids = [ids]
|
||||
data = self._get_empty_rrule_data()
|
||||
|
@ -816,7 +820,7 @@ class calendar_event(osv.Model):
|
|||
'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}),
|
||||
'location': fields.char('Location', help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}),
|
||||
'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}),
|
||||
'rrule': fields.function(_get_rulestring, type='char', fnct_inv=_rrule_write, store=True, string='Recurrent Rule'),
|
||||
'rrule': fields.function(_get_rulestring, type='char', fnct_inv=_set_rulestring, store=True, string='Recurrent Rule'),
|
||||
'rrule_type': fields.selection([('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'), ('yearly', 'Year(s)')], 'Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval"),
|
||||
'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"),
|
||||
'recurrent_id': fields.integer('Recurrent ID'),
|
||||
|
@ -1089,7 +1093,6 @@ class calendar_event(osv.Model):
|
|||
else:
|
||||
return ids
|
||||
|
||||
|
||||
def compute_rule_string(self, data):
|
||||
"""
|
||||
Compute rule string according to value type RECUR of iCalendar from the values given.
|
||||
|
@ -1174,7 +1177,7 @@ class calendar_event(osv.Model):
|
|||
#repeat monthly by nweekday ((weekday, weeknumber), )
|
||||
if r._bynweekday:
|
||||
data['week_list'] = day_list[r._bynweekday[0][0]].upper()
|
||||
data['byday'] = r._bynweekday[0][1]
|
||||
data['byday'] = str(r._bynweekday[0][1])
|
||||
data['month_by'] = 'day'
|
||||
data['rrule_type'] = 'monthly'
|
||||
|
||||
|
@ -1238,7 +1241,6 @@ class calendar_event(osv.Model):
|
|||
first_occurence = list(rrule.rrulestr(str_rrule + ";COUNT=1", dtstart=datetime.strptime(date, "%Y-%m-%d %H:%M:%S"), forceset=True))[0]
|
||||
return {'value': {'date': first_occurence.strftime("%Y-%m-%d") + ' 00:00:00'}}
|
||||
|
||||
|
||||
def check_partners_email(self, cr, uid, partner_ids, context=None):
|
||||
""" Verify that selected partner_ids have an email_address defined.
|
||||
Otherwise throw a warning. """
|
||||
|
@ -1251,12 +1253,10 @@ class calendar_event(osv.Model):
|
|||
warning_msg = _('The following contacts have no email address :')
|
||||
for partner in partner_wo_email_lst:
|
||||
warning_msg += '\n- %s' % (partner.name)
|
||||
return {'warning':
|
||||
{
|
||||
return {'warning': {
|
||||
'title': _('Email addresses not found'),
|
||||
'message': warning_msg,
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
# ----------------------------------------
|
||||
# OpenChatter
|
||||
|
@ -1365,8 +1365,7 @@ class calendar_event(osv.Model):
|
|||
rrule_type=False,
|
||||
rrule='',
|
||||
recurrency=False,
|
||||
end_date = datetime.strptime(values.get('date', False) or data.get('date'),"%Y-%m-%d %H:%M:%S")
|
||||
+ timedelta(hours=values.get('duration', False) or data.get('duration'))
|
||||
end_date=datetime.strptime(values.get('date', False) or data.get('date'), "%Y-%m-%d %H:%M:%S") + timedelta(hours=values.get('duration', False) or data.get('duration'))
|
||||
)
|
||||
|
||||
#do not copy the id
|
||||
|
@ -1389,9 +1388,6 @@ class calendar_event(osv.Model):
|
|||
'flags': {'form': {'action_buttons': True, 'options': {'mode': 'edit'}}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
def _only_changes_to_apply_on_real_ids(field_names):
|
||||
''' return True if changes are only to be made on the real ids'''
|
||||
|
@ -1402,7 +1398,6 @@ class calendar_event(osv.Model):
|
|||
|
||||
context = context or {}
|
||||
|
||||
|
||||
if isinstance(ids, (str, int, long)):
|
||||
if len(str(ids).split('-')) == 1:
|
||||
ids = [int(ids)]
|
||||
|
@ -1436,7 +1431,7 @@ class calendar_event(osv.Model):
|
|||
# set end_date for calendar searching
|
||||
if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \
|
||||
(values.get('rrule_type') or values.get('count') or values.get('date') or values.get('date_deadline')):
|
||||
for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context):
|
||||
for data in self.read(cr, uid, ids, ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context):
|
||||
end_date = self._get_recurrency_end_date(data, context=context)
|
||||
super(calendar_event, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context)
|
||||
|
||||
|
@ -1467,11 +1462,12 @@ class calendar_event(osv.Model):
|
|||
if not 'user_id' in vals: # Else bug with quick_create when we are filter on an other user
|
||||
vals['user_id'] = uid
|
||||
|
||||
if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \
|
||||
(vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')):
|
||||
vals['end_date'] = self._get_recurrency_end_date(vals, context=context)
|
||||
|
||||
res = super(calendar_event, self).create(cr, uid, vals, context=context)
|
||||
|
||||
data = self.read(cr, uid, [res], ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context)[0]
|
||||
end_date = self._get_recurrency_end_date(data, context=context)
|
||||
self.write(cr, uid, [res], {'end_date': end_date}, context=context)
|
||||
|
||||
self.create_attendees(cr, uid, [res], context=context)
|
||||
return res
|
||||
|
||||
|
@ -1528,7 +1524,7 @@ class calendar_event(osv.Model):
|
|||
continue
|
||||
if r['class'] == 'private':
|
||||
for f in r.keys():
|
||||
if f not in ('id', 'date', 'date_deadline', 'duration', 'user_id', 'state', 'interval', 'count'):
|
||||
if f not in ('id', 'date', 'date_deadline', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date'):
|
||||
if isinstance(r[f], list):
|
||||
r[f] = []
|
||||
else:
|
||||
|
@ -1639,6 +1635,7 @@ class ir_http(osv.AbstractModel):
|
|||
raise BadRequest(error_message)
|
||||
return True
|
||||
|
||||
|
||||
class invite_wizard(osv.osv_memory):
|
||||
_inherit = 'mail.wizard.invite'
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
<strong>${object.event_id.name}</strong>
|
||||
</div>
|
||||
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
|
||||
<strong style="margin-left:12px">Hello ${object.cn}</strong> ,<br/><p style="margin-left:12px">${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.</p>
|
||||
<strong style="margin-left:12px">Dear ${object.cn}</strong> ,<br/><p style="margin-left:12px">${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.</p>
|
||||
</div>
|
||||
<div style="height: auto;margin-left:12px;margin-top:30px;">
|
||||
<table>
|
||||
|
@ -151,50 +151,58 @@
|
|||
</td>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
|
||||
% if object.event_id.location:
|
||||
<tr style=" height: 30px;">
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
% if object.event_id.location:
|
||||
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Where
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="1" style="vertical-align:top;">
|
||||
<td style="vertical-align:top;">
|
||||
% if object.event_id.location:
|
||||
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
|
||||
: ${object.event_id.location}
|
||||
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
|
||||
</span>
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if object.event_id.description :
|
||||
<tr style=" height:auto;">
|
||||
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
% if object.event_id.description :
|
||||
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
What
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
<td style="vertical-align:text-top;">
|
||||
% if object.event_id.description :
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${object.event_id.description}
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<tr style=" height:auto;">
|
||||
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Duration
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr style=" height: 30px;">
|
||||
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
<div>
|
||||
|
@ -219,9 +227,9 @@
|
|||
</table>
|
||||
</div>
|
||||
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;">
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -259,9 +267,9 @@
|
|||
<strong>${object.event_id.name}</strong>
|
||||
</div>
|
||||
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
|
||||
<strong style="margin-left:12px">Hello ${object.cn}</strong> ,<br/>
|
||||
<strong style="margin-left:12px">Dear ${object.cn}</strong> ,<br/>
|
||||
<p style="margin-left:12px">The date of the meeting has been changed...<br/>
|
||||
The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.date}.</p>
|
||||
The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.display_time}.</p>
|
||||
</div>
|
||||
<div style="height: auto;margin-left:12px;margin-top:30px;">
|
||||
<table>
|
||||
|
@ -276,50 +284,58 @@
|
|||
</td>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
|
||||
% if object.event_id.location:
|
||||
<tr style=" height: 30px;">
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
% if object.event_id.location:
|
||||
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Where
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="1" style="vertical-align:top;">
|
||||
<td style="vertical-align:top;">
|
||||
% if object.event_id.location:
|
||||
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
|
||||
: ${object.event_id.location}
|
||||
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
|
||||
</span>
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if object.event_id.description :
|
||||
<tr style=" height:auto;">
|
||||
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
% if object.event_id.description :
|
||||
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
What
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
<td style="vertical-align:text-top;">
|
||||
% if object.event_id.description :
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${object.event_id.description}
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<tr style=" height:auto;">
|
||||
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Duration
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr style=" height: 30px;">
|
||||
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
<div>
|
||||
|
@ -344,9 +360,9 @@
|
|||
</table>
|
||||
</div>
|
||||
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;">
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -384,8 +400,8 @@
|
|||
<strong>${object.event_id.name}</strong>
|
||||
</div>
|
||||
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
|
||||
<strong style="margin-left:12px">Hello ${object.cn}</strong> ,<br/>
|
||||
<p style="margin-left:12px">this it a rmeinder for the event below : </p>
|
||||
<strong style="margin-left:12px">Dear ${object.cn}</strong> ,<br/>
|
||||
<p style="margin-left:12px">That is a reminder for the event below : </p>
|
||||
</div>
|
||||
<div style="height: auto;margin-left:12px;margin-top:30px;">
|
||||
<table>
|
||||
|
@ -400,50 +416,58 @@
|
|||
</td>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
|
||||
% if object.event_id.location:
|
||||
<tr style=" height: 30px;">
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
% if object.event_id.location:
|
||||
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Where
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="1" style="vertical-align:top;">
|
||||
<td style="vertical-align:top;">
|
||||
% if object.event_id.location:
|
||||
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
|
||||
: ${object.event_id.location}
|
||||
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
|
||||
</span>
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if object.event_id.description :
|
||||
<tr style=" height:auto;">
|
||||
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
% if object.event_id.description :
|
||||
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
What
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
<td style="vertical-align:text-top;">
|
||||
% if object.event_id.description :
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${object.event_id.description}
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<tr style=" height:auto;">
|
||||
|
||||
<tr>
|
||||
<td style="vertical-align:top;">
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Duration
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
% if not object.event_id.allday and object.event_id.duration:
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
|
||||
</div>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr style=" height: 30px;">
|
||||
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
<div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<record id="calendar_event_1" model="calendar.event">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.partner_root'),ref('base.res_partner_1'),ref('base.res_partner_6')])]"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.res_partner_6')])]"/>
|
||||
<field name="name">Follow-up for Project proposal</field>
|
||||
<field name="description">Meeting to discuss project plan and hash out the details of implementation.</field>
|
||||
<field eval="time.strftime('%Y-%m-03 10:20:00')" name="date"/>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import simplejson
|
||||
import urllib
|
||||
import openerp
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
import openerp.addons.web.controllers.main as webmain
|
||||
import json
|
||||
from openerp.addons.web.http import SessionExpiredException
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
|
||||
class meeting_invitation(http.Controller):
|
||||
|
||||
|
@ -36,7 +34,7 @@ class meeting_invitation(http.Controller):
|
|||
meeting_pool = registry.get('calendar.event')
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
with registry.cursor() as cr:
|
||||
attendee_data = meeting_pool.get_attendee(cr, openerp.SUPERUSER_ID, id);
|
||||
attendee_data = meeting_pool.get_attendee(cr, openerp.SUPERUSER_ID, id)
|
||||
attendee = attendee_pool.search_read(cr, openerp.SUPERUSER_ID, [('access_token', '=', token)], [])
|
||||
|
||||
if attendee:
|
||||
|
@ -67,6 +65,5 @@ class meeting_invitation(http.Controller):
|
|||
uid = request.session.uid
|
||||
context = request.session.context
|
||||
with registry.cursor() as cr:
|
||||
res = registry.get("res.partner").calendar_last_notif(cr,uid,context=context)
|
||||
res = registry.get("res.partner").calendar_last_notif_ack(cr, uid, context=context)
|
||||
return res
|
||||
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
openerp.calendar = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
var QWeb = instance.web.qweb;
|
||||
|
||||
instance.calendar = {};
|
||||
|
||||
|
||||
instance.web.WebClient = instance.web.WebClient.extend({
|
||||
|
||||
|
||||
get_notif_box: function(me) {
|
||||
return $(me).closest(".ui-notify-message-style");
|
||||
},
|
||||
get_next_notif: function() {
|
||||
var self= this;
|
||||
this.rpc("/calendar/notify")
|
||||
.then(
|
||||
.done(
|
||||
function(result) {
|
||||
_.each(result, function(res) {
|
||||
setTimeout(function() {
|
||||
|
@ -44,12 +47,19 @@ openerp.calendar = function(instance) {
|
|||
},res.timer * 1000);
|
||||
});
|
||||
}
|
||||
);
|
||||
)
|
||||
.fail(function (err, ev) {
|
||||
if (err.code === -32098) {
|
||||
// Prevent the CrashManager to display an error
|
||||
// in case of an xhr error not due to a server error
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
check_notifications: function() {
|
||||
var self= this;
|
||||
self.get_next_notif();
|
||||
setInterval(function(){
|
||||
self.intervalNotif = setInterval(function(){
|
||||
self.get_next_notif();
|
||||
}, 5 * 60 * 1000 );
|
||||
},
|
||||
|
@ -59,6 +69,11 @@ openerp.calendar = function(instance) {
|
|||
this._super();
|
||||
this.check_notifications();
|
||||
},
|
||||
//Override addons/web/static/src/js/chrome.js
|
||||
on_logout: function() {
|
||||
this._super();
|
||||
clearInterval(self.intervalNotif);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -65,3 +65,20 @@
|
|||
-
|
||||
!python {model: calendar.event}: |
|
||||
self.write(cr, uid, [ref("calendar_event_alldaytestevent0")], {'alarm_ids': [(6,0,[ref("res_alarm_daybeforeeventstarts0")])]})
|
||||
-
|
||||
I create a recuring rule for my event
|
||||
-
|
||||
!record {model: calendar.event, id: calendar.event_sprintreview1}:
|
||||
name: Begin of month meeting
|
||||
date: !eval time.strftime('%Y-%m-%d 12:00:00')
|
||||
recurrency: true
|
||||
rrule: FREQ=MONTHLY;INTERVAL=1;COUNT=12;BYDAY=1MO
|
||||
-
|
||||
I check that the attributes are set correctly
|
||||
-
|
||||
!assert {model: calendar.event, id: calendar.event_sprintreview1}:
|
||||
- rrule_type == 'monthly'
|
||||
- count == 12
|
||||
- month_by == 'day'
|
||||
- byday == '1'
|
||||
- week_list == 'MO'
|
||||
|
|
|
@ -23,9 +23,7 @@ from openerp.osv import fields, osv
|
|||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
#
|
||||
# calendar.event is defined in module calendar
|
||||
#
|
||||
|
||||
class calendar_event(osv.Model):
|
||||
""" Model for Calendar Event """
|
||||
_inherit = 'calendar.event'
|
||||
|
@ -48,31 +46,11 @@ class calendar_attendee(osv.osv):
|
|||
_inherit = 'calendar.attendee'
|
||||
_description = 'Calendar Attendee'
|
||||
|
||||
def _compute_data(self, cr, uid, ids, name, arg, context=None):
|
||||
"""
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of compute data’s IDs
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
name = name[0]
|
||||
result = super(calendar_attendee, self)._compute_data(cr, uid, ids, name, arg, context=context)
|
||||
|
||||
for attdata in self.browse(cr, uid, ids, context=context):
|
||||
id = attdata.id
|
||||
result[id] = {}
|
||||
if name == 'categ_id':
|
||||
if attdata.ref and 'categ_id' in attdata.ref._columns:
|
||||
result[id][name] = (attdata.ref.categ_id.id, attdata.ref.categ_id.name,)
|
||||
else:
|
||||
result[id][name] = False
|
||||
return result
|
||||
def _noop(self, cr, uid, ids, name, arg, context=None):
|
||||
return dict.fromkeys(ids, False)
|
||||
|
||||
_columns = {
|
||||
'categ_id': fields.function(_compute_data, \
|
||||
string='Event Type', type="many2one", \
|
||||
relation="crm.case.categ", multi='categ_id'),
|
||||
'categ_id': fields.function(_noop, string='Event Type', deprecated="Unused Field - TODO : Remove it in trunk", type="many2one", relation="crm.case.categ"),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -313,7 +313,10 @@ class crm_lead(format_address, osv.osv):
|
|||
stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context)
|
||||
if not stage.on_change:
|
||||
return {'value': {}}
|
||||
return {'value': {'probability': stage.probability}}
|
||||
vals = {'probability': stage.probability}
|
||||
if stage.probability >= 100 or (stage.probability == 0 and stage.sequence > 1):
|
||||
vals['date_closed'] = fields.datetime.now()
|
||||
return {'value': vals}
|
||||
|
||||
def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
|
||||
values = {}
|
||||
|
|
|
@ -95,7 +95,6 @@
|
|||
<form string="Leads Form" version="7.0">
|
||||
<header>
|
||||
<button name="%(crm.action_crm_lead2opportunity_partner)d" string="Convert to Opportunity" type="action"
|
||||
attrs="{'invisible': [('probability', '=', 100)]}"
|
||||
help="Convert to Opportunity" class="oe_highlight"/>
|
||||
<field name="stage_id" widget="statusbar" clickable="True"
|
||||
domain="['&', '|', ('case_default', '=', True), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
<field name="view_mode">tree,calendar</field>
|
||||
<field name="view_id" ref="crm_case_inbound_phone_tree_view"/>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="context">{'search_default_state': 'done', 'default_state': 'done'}</field>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_phonecalls_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
<search string="Search Phonecalls">
|
||||
<field name="name" string="Phonecalls"/>
|
||||
<field name="date"/>
|
||||
<field name="state"/>
|
||||
<separator/>
|
||||
<filter icon="terp-gtk-go-back-rtl" string="To Do" name="current" domain="[('state','=','open')]"/>
|
||||
<separator/>
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
Salesman create a mass convert wizard and convert all the leads.
|
||||
-
|
||||
!python {model: crm.lead2opportunity.partner.mass}: |
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01")})
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01"), 'no_force_assignation': False})
|
||||
id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department'), 'deduplicate': False}, context=context)
|
||||
self.mass_convert(cr, uid, [id], context=context)
|
||||
-
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import email_split
|
||||
import re
|
||||
|
||||
class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
|
@ -42,20 +43,21 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}}
|
||||
|
||||
def _get_duplicated_leads(self, cr, uid, partner_id, email, context=None):
|
||||
"""
|
||||
Search for opportunities that have the same partner and that arent done or cancelled
|
||||
"""
|
||||
lead_obj = self.pool.get('crm.lead')
|
||||
results = []
|
||||
emails = set(email_split(email) + [email])
|
||||
final_stage_domain = [('stage_id.probability', '<', 100), '|', ('stage_id.probability', '>', 0), ('stage_id.sequence', '<=', 1)]
|
||||
partner_match_domain = []
|
||||
for email in emails:
|
||||
partner_match_domain.append(('email_from', '=ilike', email))
|
||||
if partner_id:
|
||||
# Search for opportunities that have the same partner and that arent done or cancelled
|
||||
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
|
||||
for id in ids:
|
||||
results.append(id)
|
||||
email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
|
||||
if email:
|
||||
ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
|
||||
for id in ids:
|
||||
results.append(id)
|
||||
return list(set(results))
|
||||
|
||||
partner_match_domain.append(('partner_id', '=', partner_id))
|
||||
partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain
|
||||
if not partner_match_domain:
|
||||
return []
|
||||
return lead_obj.search(cr, uid, partner_match_domain + final_stage_domain)
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
"""
|
||||
|
@ -69,11 +71,10 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
if context.get('active_id'):
|
||||
tomerge = [int(context['active_id'])]
|
||||
|
||||
email = False
|
||||
partner_id = res.get('partner_id')
|
||||
lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)
|
||||
email = lead.partner_id and lead.partner_id.email or lead.email_from
|
||||
|
||||
#TOFIX: use mail.mail_message.to_mail
|
||||
tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email))
|
||||
tomerge = list(set(tomerge))
|
||||
|
||||
|
@ -100,10 +101,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
else:
|
||||
user_in_section = False
|
||||
if not user_in_section:
|
||||
section_id = False
|
||||
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
|
||||
if section_ids:
|
||||
section_id = section_ids[0]
|
||||
result = self.pool['crm.lead'].on_change_user(cr, uid, ids, user_id, context=context)
|
||||
section_id = result.get('value') and result['value'].get('section_id') and result['value']['section_id'] or False
|
||||
return {'value': {'section_id': section_id}}
|
||||
|
||||
def view_init(self, cr, uid, fields, context=None):
|
||||
|
@ -126,14 +125,17 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
lead_ids = vals.get('lead_ids', [])
|
||||
team_id = vals.get('section_id', False)
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
for lead_id in lead_ids:
|
||||
partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id, context=context)
|
||||
# FIXME: cannot pass user_ids as the salesman allocation only works in batch
|
||||
res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
|
||||
# FIXME: must perform salesman allocation in batch separately here
|
||||
leads = lead.browse(cr, uid, lead_ids, context=context)
|
||||
for lead_id in leads:
|
||||
partner_id = self._create_partner(cr, uid, lead_id.id, data.action, lead_id.partner_id.id, context=context)
|
||||
res = lead.convert_opportunity(cr, uid, [lead_id.id], partner_id, [], False, context=context)
|
||||
user_ids = vals.get('user_ids', False)
|
||||
if context.get('no_force_assignation'):
|
||||
leads_to_allocate = [lead_id.id for lead_id in leads if not lead_id.user_id]
|
||||
else:
|
||||
leads_to_allocate = lead_ids
|
||||
if user_ids:
|
||||
lead.allocate_salesman(cr, uid, lead_ids, user_ids, team_id=team_id, context=context)
|
||||
lead.allocate_salesman(cr, uid, leads_to_allocate, user_ids, team_id=team_id, context=context)
|
||||
return res
|
||||
|
||||
def action_apply(self, cr, uid, ids, context=None):
|
||||
|
@ -144,15 +146,19 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
if context is None:
|
||||
context = {}
|
||||
|
||||
lead_obj = self.pool['crm.lead']
|
||||
|
||||
w = self.browse(cr, uid, ids, context=context)[0]
|
||||
opp_ids = [o.id for o in w.opportunity_ids]
|
||||
if w.name == 'merge':
|
||||
lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, w.user_id.id, w.section_id.id, context=context)
|
||||
lead_id = lead_obj.merge_opportunity(cr, uid, opp_ids, context=context)
|
||||
lead_ids = [lead_id]
|
||||
lead = self.pool.get('crm.lead').read(cr, uid, lead_id, ['type'], context=context)
|
||||
lead = lead_obj.read(cr, uid, lead_id, ['type', 'user_id'], context=context)
|
||||
if lead['type'] == "lead":
|
||||
context.update({'active_ids': lead_ids})
|
||||
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
|
||||
elif not context.get('no_force_assignation') or not lead['user_id']:
|
||||
lead_obj.write(cr, uid, lead_id, {'user_id': w.user_id.id, 'section_id': w.section_id.id}, context=context)
|
||||
else:
|
||||
lead_ids = context.get('active_ids', [])
|
||||
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
|
||||
|
@ -191,6 +197,8 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
|||
('each_exist_or_create', 'Use existing partner or create'),
|
||||
('nothing', 'Do not link to a customer')
|
||||
], 'Related Customer', required=True),
|
||||
# Uncomment me in trunk
|
||||
# 'force_assignation': fields.boolean('Force assignation', help='If unchecked, this will leave the salesman of duplicated opportunities'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
|
@ -266,6 +274,10 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
|||
active_ids = active_ids.difference(merged_lead_ids)
|
||||
active_ids = active_ids.union(remaining_lead_ids)
|
||||
ctx['active_ids'] = list(active_ids)
|
||||
# Remove me in trunk
|
||||
ctx['no_force_assignation'] = ctx.get('no_force_assignation', True)
|
||||
# Uncomment me in trunk
|
||||
# ctx['no_force_assignation'] = not data.force_assignation
|
||||
return self.action_apply(cr, uid, ids, context=ctx)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -58,6 +58,8 @@
|
|||
<group string="Assign opportunities to">
|
||||
<field name="section_id" groups="base.group_multi_salesteams"/>
|
||||
<field name="user_ids" widget="many2many_tags"/>
|
||||
<!-- Uncomment me in trunk -->
|
||||
<!-- <field name="force_assignation" /> -->
|
||||
</group>
|
||||
<label for="opportunity_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found" attrs="{'invisible': [('deduplicate', '=', False)]}"/>
|
||||
<group attrs="{'invisible': [('deduplicate', '=', False)]}">
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Merge Leads/Opportunities" version="7.0">
|
||||
<group string="Assign opportunities to">
|
||||
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, context)"/>
|
||||
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, section_id, context)"/>
|
||||
<field name="section_id" class="oe_inline"/>
|
||||
</group>
|
||||
<group string="Select Leads/Opportunities">
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<div class="oe_title">
|
||||
<h3>
|
||||
<span class="oe_grey">( </span>
|
||||
<field name="partner_latitude" nolabel="1" readonly="1" class="oe_inline"/>
|
||||
<field name="partner_latitude" nolabel="1" class="oe_inline"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','<=',0)]}">N </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','>=',0)]}">S </span>
|
||||
<field name="partner_longitude" class="oe_inline" readonly="1" nolabel="1"/>
|
||||
<field name="partner_longitude" class="oe_inline" nolabel="1"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','<=',0)]}">E </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','>=',0)]}">W </span>
|
||||
<span class="oe_grey">) </span>
|
||||
|
@ -88,10 +88,10 @@
|
|||
<div>
|
||||
<h3>
|
||||
<span class="oe_grey">( </span>
|
||||
<field name="partner_latitude" nolabel="1" readonly="1" class="oe_inline"/>
|
||||
<field name="partner_latitude" nolabel="1" class="oe_inline"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','<=',0)]}">N </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','>=',0)]}">S </span>
|
||||
<field name="partner_longitude" class="oe_inline" readonly="1" nolabel="1"/>
|
||||
<field name="partner_longitude" class="oe_inline" nolabel="1"/>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','<=',0)]}">E </span>
|
||||
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','>=',0)]}">W </span>
|
||||
<span class="oe_grey">) </span>
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
openerp.crm_partner_assign = function (instance) {
|
||||
instance.crm_partner_assign = instance.crm_partner_assign || {};
|
||||
instance.crm_partner_assign.next_or_list = function(parent) {
|
||||
if (parent.inner_widget.active_view === "form"){
|
||||
var form = parent.inner_widget.views.form.controller;
|
||||
form.dataset.remove_ids([form.dataset.ids[form.dataset.index]]);
|
||||
form.reload();
|
||||
if (!form.dataset.ids.length){
|
||||
parent.inner_widget.switch_mode('list');
|
||||
var view = parent.inner_widget.active_view;
|
||||
var controller = parent.inner_widget.views[view].controller;
|
||||
if (view === "form"){
|
||||
if (controller.dataset.size()) {
|
||||
controller.execute_pager_action('next');
|
||||
} else {
|
||||
controller.do_action('history_back');
|
||||
}
|
||||
}
|
||||
else{
|
||||
parent.inner_widget.views[parent.inner_widget.active_view].controller.reload();
|
||||
controller.do_action({ type: 'ir.actions.act_window_close' });
|
||||
if (view === "list"){
|
||||
controller.records.remove(controller.records.get(parent.dialog_widget.action.context.active_id));
|
||||
}
|
||||
parent.do_action({ type: 'ir.actions.act_window_close' });
|
||||
};
|
||||
instance.web.client_actions.add("next_or_list", "instance.crm_partner_assign.next_or_list");
|
||||
}
|
|
@ -24,6 +24,8 @@ import base64
|
|||
import datetime
|
||||
import dateutil.relativedelta as relativedelta
|
||||
import logging
|
||||
import lxml
|
||||
import urlparse
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
|
@ -61,6 +63,15 @@ try:
|
|||
'quote': quote,
|
||||
'urlencode': urlencode,
|
||||
'datetime': datetime,
|
||||
'len': len,
|
||||
'abs': abs,
|
||||
'min': min,
|
||||
'max': max,
|
||||
'sum': sum,
|
||||
'filter': filter,
|
||||
'reduce': reduce,
|
||||
'map': map,
|
||||
'round': round,
|
||||
|
||||
# dateutil.relativedelta is an old-style class and cannot be directly
|
||||
# instanciated wihtin a jinja2 expression, so a lambda "proxy" is
|
||||
|
@ -70,6 +81,7 @@ try:
|
|||
except ImportError:
|
||||
_logger.warning("jinja2 not available, templating features will not work!")
|
||||
|
||||
|
||||
class email_template(osv.osv):
|
||||
"Templates for sending email"
|
||||
_name = "email.template"
|
||||
|
@ -82,7 +94,48 @@ class email_template(osv.osv):
|
|||
res['model_id'] = self.pool['ir.model'].search(cr, uid, [('model', '=', res.pop('model'))], context=context)[0]
|
||||
return res
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
def _replace_local_links(self, cr, uid, html, context=None):
|
||||
""" Post-processing of html content to replace local links to absolute
|
||||
links, using web.base.url as base url. """
|
||||
if not html:
|
||||
return html
|
||||
|
||||
# form a tree
|
||||
root = lxml.html.fromstring(html)
|
||||
if not len(root) and root.text is None and root.tail is None:
|
||||
html = '<div>%s</div>' % html
|
||||
root = lxml.html.fromstring(html)
|
||||
|
||||
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
|
||||
(base_scheme, base_netloc, bpath, bparams, bquery, bfragment) = urlparse.urlparse(base_url)
|
||||
|
||||
def _process_link(url):
|
||||
new_url = url
|
||||
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
|
||||
if not scheme and not netloc:
|
||||
new_url = urlparse.urlunparse((base_scheme, base_netloc, path, params, query, fragment))
|
||||
return new_url
|
||||
|
||||
# check all nodes, replace :
|
||||
# - img src -> check URL
|
||||
# - a href -> check URL
|
||||
for node in root.iter():
|
||||
if node.tag == 'a':
|
||||
node.set('href', _process_link(node.get('href')))
|
||||
elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
|
||||
node.set('src', _process_link(node.get('src')))
|
||||
|
||||
html = lxml.html.tostring(root, pretty_print=False, method='html')
|
||||
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
|
||||
if html.startswith('<div>') and html.endswith('</div>'):
|
||||
html = html[5:-6]
|
||||
return html
|
||||
|
||||
def render_post_process(self, cr, uid, html, context=None):
|
||||
html = self._replace_local_links(cr, uid, html, context=context)
|
||||
return html
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
|
||||
"""Render the given template text, replace mako expressions ``${expr}``
|
||||
with the result of evaluating these expressions with
|
||||
an evaluation context containing:
|
||||
|
@ -125,6 +178,10 @@ class email_template(osv.osv):
|
|||
if render_result == u"False":
|
||||
render_result = u""
|
||||
results[res_id] = render_result
|
||||
|
||||
if post_process:
|
||||
for res_id, result in results.iteritems():
|
||||
results[res_id] = self.render_post_process(cr, uid, result, context=context)
|
||||
return results
|
||||
|
||||
def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None):
|
||||
|
@ -183,7 +240,7 @@ class email_template(osv.osv):
|
|||
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
|
||||
help="Optional preferred server for outgoing mails. If not set, the highest "
|
||||
"priority one will be used."),
|
||||
'body_html': fields.html('Body', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
|
||||
'body_html': fields.html('Body', translate=True, sanitize=False, help="Rich-text/HTML version of the message (placeholders may be used here)"),
|
||||
'report_name': fields.char('Report Filename', translate=True,
|
||||
help="Name to use for the generated report file (may contain placeholders)\n"
|
||||
"The extension can be omitted and will then come from the report type."),
|
||||
|
@ -356,17 +413,20 @@ class email_template(osv.osv):
|
|||
results = dict()
|
||||
for template, template_res_ids in templates_to_res_ids.iteritems():
|
||||
# generate fields value for all res_ids linked to the current template
|
||||
for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']:
|
||||
generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context)
|
||||
for field in fields:
|
||||
generated_field_values = self.render_template_batch(
|
||||
cr, uid, getattr(template, field), template.model, template_res_ids,
|
||||
post_process=(field == 'body_html'),
|
||||
context=context)
|
||||
for res_id, field_value in generated_field_values.iteritems():
|
||||
results.setdefault(res_id, dict())[field] = field_value
|
||||
# update values for all res_ids
|
||||
for res_id in template_res_ids:
|
||||
values = results[res_id]
|
||||
if template.user_signature:
|
||||
if 'body_html' in fields and template.user_signature:
|
||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
|
||||
if values['body_html']:
|
||||
if values.get('body_html'):
|
||||
values['body'] = tools.html_sanitize(values['body_html'])
|
||||
values.update(
|
||||
mail_server_id=template.mail_server_id.id or False,
|
||||
|
|
|
@ -162,16 +162,18 @@ class mail_compose_message(osv.TransientModel):
|
|||
partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
|
||||
return partner_ids
|
||||
|
||||
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None):
|
||||
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
|
||||
""" Call email_template.generate_email(), get fields relevant for
|
||||
mail.compose.message, transform email_cc and email_to into partner_ids """
|
||||
# filter template values
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
|
||||
if fields is None:
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id']
|
||||
returned_fields = fields + ['attachments']
|
||||
values = dict.fromkeys(res_ids, False)
|
||||
|
||||
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
|
||||
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=context)
|
||||
for res_id in res_ids:
|
||||
res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))
|
||||
res_id_values = dict((field, template_values[res_id][field]) for field in returned_fields if template_values[res_id].get(field))
|
||||
res_id_values['body'] = res_id_values.pop('body_html', '')
|
||||
|
||||
# transform email_to, email_cc into partner_ids
|
||||
|
@ -189,7 +191,10 @@ class mail_compose_message(osv.TransientModel):
|
|||
""" Override to handle templates. """
|
||||
# generate template-based values
|
||||
if wizard.template_id:
|
||||
template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context)
|
||||
template_values = self.generate_email_for_composer_batch(
|
||||
cr, uid, wizard.template_id.id, res_ids,
|
||||
fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id'],
|
||||
context=context)
|
||||
else:
|
||||
template_values = dict.fromkeys(res_ids, dict())
|
||||
# generate composer values
|
||||
|
@ -206,8 +211,8 @@ class mail_compose_message(osv.TransientModel):
|
|||
template_values[res_id].update(composer_values[res_id])
|
||||
return template_values
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context)
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
|
||||
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context, post_process=post_process)
|
||||
|
||||
# Compatibility methods
|
||||
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
|
||||
|
|
|
@ -52,7 +52,7 @@ class report_event_registration(osv.osv):
|
|||
# TOFIX this request won't select events that have no registration
|
||||
cr.execute(""" CREATE VIEW report_event_registration AS (
|
||||
SELECT
|
||||
e.id::char || '/' || coalesce(r.id::char,'') AS id,
|
||||
e.id::varchar || '/' || coalesce(r.id::varchar,'') AS id,
|
||||
e.id AS event_id,
|
||||
e.user_id AS user_id,
|
||||
r.user_id AS user_id_registration,
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
<group expand="1" string="Group By...">
|
||||
<filter string="Participant / Contact" icon="terp-personal" context="{'group_by':'name_registration'}" help="Registration contact"/>
|
||||
<filter string="Register" icon="terp-personal" context="{'group_by':'user_id_registration'}" help="Registration contact" groups="base.group_no_one"/>
|
||||
<filter string="Speaker" name="speaker" icon="terp-personal+" context="{'group_by': 'speaker_id'}" groups="base.group_no_one"/>
|
||||
<filter string="Event Responsible" name="user_id" icon="terp-personal" context="{'group_by': 'user_id'}"/>
|
||||
<filter string="Event" name="event" icon="terp-crm" context="{'group_by':'event_id', 'max_reg_event_visible':0}"/>
|
||||
<filter string="Event Type" icon="terp-crm" context="{'group_by':'event_type'}"/>
|
||||
|
|
|
@ -21,23 +21,15 @@
|
|||
|
||||
import operator
|
||||
import simplejson
|
||||
import re
|
||||
import urllib
|
||||
import warnings
|
||||
|
||||
from openerp import tools
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
|
||||
from openerp.addons.web.http import request
|
||||
import werkzeug.utils
|
||||
|
||||
from datetime import datetime, timedelta, date
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil import parser
|
||||
import pytz
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import osv
|
||||
from collections import namedtuple
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
@ -63,9 +55,11 @@ class Meta(type):
|
|||
methods['__getitem__'] = getattr
|
||||
return type.__new__(typ, name, parents, methods)
|
||||
|
||||
|
||||
class Struct(object):
|
||||
__metaclass__ = Meta
|
||||
|
||||
|
||||
class OpenerpEvent(Struct):
|
||||
event = False
|
||||
found = False
|
||||
|
@ -77,6 +71,7 @@ class OpenerpEvent(Struct):
|
|||
attendee_id = False
|
||||
synchro = False
|
||||
|
||||
|
||||
class GmailEvent(Struct):
|
||||
event = False
|
||||
found = False
|
||||
|
@ -85,6 +80,7 @@ class GmailEvent(Struct):
|
|||
update = False
|
||||
status = False
|
||||
|
||||
|
||||
class SyncEvent(object):
|
||||
def __init__(self):
|
||||
self.OE = OpenerpEvent()
|
||||
|
@ -110,7 +106,6 @@ class SyncEvent(object):
|
|||
tmpSrc = 'OE'
|
||||
assert tmpSrc in ['GG', 'OE']
|
||||
|
||||
|
||||
#if self.OP.action == None:
|
||||
if self[tmpSrc].isRecurrence:
|
||||
if self[tmpSrc].status:
|
||||
|
@ -119,11 +114,9 @@ class SyncEvent(object):
|
|||
self.OP = Exclude(tmpSrc, 'Need to Exclude (Me = First event from recurrence) from recurrence')
|
||||
|
||||
elif self[tmpSrc].isInstance:
|
||||
self.OP= Update(tmpSrc, 'Only need to update, because already an exclu');
|
||||
self.OP = Update(tmpSrc, 'Only need to update, because already an exclu')
|
||||
else:
|
||||
self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event');
|
||||
#end-if self.OP.action == None:
|
||||
|
||||
self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event')
|
||||
else:
|
||||
if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
|
||||
self.OP = Update('OE', 'Event already updated by another user, but not synchro with my google calendar')
|
||||
|
@ -131,7 +124,7 @@ class SyncEvent(object):
|
|||
else:
|
||||
self.OP = NothingToDo("", 'Not update needed')
|
||||
else:
|
||||
self.OP = NothingToDo("", "Both are already deleted");
|
||||
self.OP = NothingToDo("", "Both are already deleted")
|
||||
|
||||
# New in openERP... Create on create_events of synchronize function
|
||||
elif self.OE.found and not self.GG.found:
|
||||
|
@ -189,14 +182,23 @@ class SyncOperation(object):
|
|||
def __str__(self):
|
||||
return 'in__STR__'
|
||||
|
||||
|
||||
class Create(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class Update(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class Delete(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class NothingToDo(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class Exclude(SyncOperation):
|
||||
pass
|
||||
|
||||
|
@ -285,7 +287,6 @@ class google_calendar(osv.AbstractModel):
|
|||
'access_token': token,
|
||||
'maxResults': 1000,
|
||||
'timeMin': self.get_start_time_to_synchro(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
|
||||
|
||||
}
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
||||
|
@ -439,14 +440,9 @@ class google_calendar(osv.AbstractModel):
|
|||
return res
|
||||
|
||||
def synchronize_events(self, cr, uid, ids, context=None):
|
||||
gc_obj = self.pool['google.calendar']
|
||||
|
||||
# Create all new events from OpenERP into Gmail, if that is not recurrent event
|
||||
self.create_new_events(cr, uid, context=context)
|
||||
|
||||
self.bind_recurring_events_to_google(cr, uid, context)
|
||||
|
||||
|
||||
res = self.update_events(cr, uid, context)
|
||||
|
||||
return {
|
||||
|
@ -455,7 +451,6 @@ class google_calendar(osv.AbstractModel):
|
|||
}
|
||||
|
||||
def create_new_events(self, cr, uid, context=None):
|
||||
gc_pool = self.pool['google.calendar']
|
||||
ev_obj = self.pool['calendar.event']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
|
@ -505,7 +500,7 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
if new_google_internal_event_id:
|
||||
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
|
||||
res = self.update_recurrent_event_exclu(cr, uid,new_google_internal_event_id, source_attendee_record.google_internal_event_id,att.event_id, context=context)
|
||||
self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context)
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
|
||||
cr.commit()
|
||||
|
||||
|
@ -583,7 +578,6 @@ class google_calendar(osv.AbstractModel):
|
|||
for current_event in event_to_synchronize[base_event]:
|
||||
event_to_synchronize[base_event][current_event].compute_OP()
|
||||
#print event_to_synchronize[base_event]
|
||||
#print "========================================================"
|
||||
|
||||
######################
|
||||
# DO ACTION #
|
||||
|
@ -593,13 +587,9 @@ class google_calendar(osv.AbstractModel):
|
|||
for current_event in event_to_synchronize[base_event]:
|
||||
cr.commit()
|
||||
event = current_event[1] # event is an Sync Event !
|
||||
|
||||
actToDo = event.OP
|
||||
actSrc = event.OP.src
|
||||
|
||||
# if not isinstance(actToDo, NothingToDo):
|
||||
# print event
|
||||
|
||||
context['curr_attendee'] = event.OE.attendee_id
|
||||
|
||||
if isinstance(actToDo, NothingToDo):
|
||||
|
@ -647,7 +637,6 @@ class google_calendar(osv.AbstractModel):
|
|||
calendar_event.unlink(cr, uid, event.OE.event_id, unlink_level=0, context=context)
|
||||
return True
|
||||
|
||||
|
||||
def check_and_sync(self, cr, uid, oe_event, google_event, context):
|
||||
if datetime.strptime(oe_event.oe_update_date, "%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
self.update_to_google(cr, uid, oe_event, google_event, context)
|
||||
|
@ -685,7 +674,6 @@ class google_calendar(osv.AbstractModel):
|
|||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
refresh = current_user.google_calendar_rtoken
|
||||
all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken, self.STR_SERVICE, context=context)
|
||||
|
||||
vals = {}
|
||||
|
@ -696,7 +684,7 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
def need_authorize(self, cr, uid, context=None):
|
||||
current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
return current_user.google_calendar_rtoken == False
|
||||
return current_user.google_calendar_rtoken is False
|
||||
|
||||
def get_calendar_scope(self, RO=False):
|
||||
readonly = RO and '.readonly' or ''
|
||||
|
@ -728,6 +716,7 @@ class google_calendar(osv.AbstractModel):
|
|||
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
|
||||
return True
|
||||
|
||||
|
||||
class res_users(osv.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
|
@ -773,7 +762,6 @@ class calendar_attendee(osv.Model):
|
|||
'google_internal_event_id': fields.char('Google Calendar Event Id', size=256),
|
||||
'oe_synchro_date': fields.datetime('OpenERP Synchro Date'),
|
||||
}
|
||||
|
||||
_sql_constraints = [('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')]
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
|
@ -787,5 +775,4 @@ class calendar_attendee(osv.Model):
|
|||
# Except if it come from an update_from_google
|
||||
if not context.get('curr_attendee', False) and not context.get('NewMeeting', False):
|
||||
self.pool['calendar.event'].write(cr, uid, ref, {'oe_update_date': datetime.now()}, context)
|
||||
|
||||
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)
|
|
@ -0,0 +1,963 @@
|
|||
# Amharic translation for openobject-addons
|
||||
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
|
||||
"PO-Revision-Date: 2014-03-18 13:39+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Amharic <am@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-19 05:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,name:hr.process_node_openerpuser0
|
||||
msgid "Openerp user"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_timesheet_sheet:0
|
||||
msgid "Allow timesheets validation by managers"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,requirements:0
|
||||
msgid "Requirements"
|
||||
msgstr "አስፈላጊ"
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,name:hr.process_transition_contactofemployee0
|
||||
msgid "Link the employee to information"
|
||||
msgstr "የሰራተኞች መረጃ ማገናኘት"
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,sinid:0
|
||||
msgid "SIN No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_board_hr
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_dashboard
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_main
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_reporting
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_root
|
||||
#: model:ir.ui.menu,name:hr.menu_human_resources_configuration
|
||||
msgid "Human Resources"
|
||||
msgstr "የሰው ሀይል አስተዳደር"
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,image_medium:0
|
||||
msgid ""
|
||||
"Medium-sized photo of the employee. It is automatically resized as a "
|
||||
"128x128px image, with aspect ratio preserved. Use this field in form views "
|
||||
"or some kanban views."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Time Tracking"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: view:hr.job:0
|
||||
msgid "Group By..."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.view_department_form_installer
|
||||
msgid "Create Your Departments"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,no_of_employee:0
|
||||
msgid "Number of employees currently occupying this job position."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_evaluation:0
|
||||
msgid "Organize employees periodic evaluation"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,department_id:0
|
||||
#: view:hr.job:0
|
||||
#: field:hr.job,department_id:0
|
||||
#: model:ir.model,name:hr.model_hr_department
|
||||
msgid "Department"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,work_email:0
|
||||
msgid "Work Email"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,image:0
|
||||
msgid ""
|
||||
"This field holds the image used as photo for the employee, limited to "
|
||||
"1024x1024px."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_holidays:0
|
||||
msgid "This installs the module hr_holidays."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "Jobs"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "In Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_unread:0
|
||||
msgid "Unread Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,company_id:0
|
||||
#: view:hr.employee:0
|
||||
#: view:hr.job:0
|
||||
#: field:hr.job,company_id:0
|
||||
msgid "Company"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,no_of_recruitment:0
|
||||
msgid "Expected in Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:res.users,employee_ids:0
|
||||
msgid "Related employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: constraint:hr.employee.category:0
|
||||
msgid "Error! You cannot create recursive Categories."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_recruitment:0
|
||||
msgid "This installs the module hr_recruitment."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Birth"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_view_categ_form
|
||||
#: model:ir.ui.menu,name:hr.menu_view_employee_category_form
|
||||
msgid "Employee Tags"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "Launch Recruitement"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,name:hr.process_transition_employeeuser0
|
||||
msgid "Link a user to an employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,parent_id:0
|
||||
msgid "Parent Department"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_config
|
||||
msgid "Leaves"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Married"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_ids:0
|
||||
msgid "Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Talent Management"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_timesheet_sheet:0
|
||||
msgid "This installs the module hr_timesheet_sheet."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Mobile:"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Position"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,message_unread:0
|
||||
msgid "If checked new messages require your attention."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,color:0
|
||||
msgid "Color Index"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,note:hr.process_transition_employeeuser0
|
||||
msgid ""
|
||||
"The Related user field on the Employee form allows to link the OpenERP user "
|
||||
"(and her rights) to the employee."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,image_medium:0
|
||||
msgid "Medium-sized photo"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,identification_id:0
|
||||
msgid "Identification No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,gender:0
|
||||
msgid "Female"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_new_config
|
||||
msgid "Attendance"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,work_phone:0
|
||||
msgid "Work Phone"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee.category,child_ids:0
|
||||
msgid "Child Categories"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,description:0
|
||||
#: model:ir.model,name:hr.model_hr_job
|
||||
msgid "Job Description"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,work_location:0
|
||||
msgid "Office Location"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_follower_ids:0
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: model:ir.model,name:hr.model_hr_employee
|
||||
#: model:process.node,name:hr.process_node_employee0
|
||||
msgid "Employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,note:hr.process_node_employeecontact0
|
||||
msgid "Other information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,image_small:0
|
||||
msgid ""
|
||||
"Small-sized photo of the employee. It is automatically resized as a 64x64px "
|
||||
"image, with aspect ratio preserved. Use this field anywhere a small image is "
|
||||
"required."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,birthday:0
|
||||
msgid "Date of Birth"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,no_of_recruitment:0
|
||||
msgid "Number of new employees you expect to recruit."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.client,name:hr.action_client_hr_menu
|
||||
msgid "Open HR Menu"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,message_summary:0
|
||||
msgid ""
|
||||
"Holds the Chatter summary (number of messages, ...). This summary is "
|
||||
"directly in html format in order to be inserted in kanban views."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_account_analytic_analysis:0
|
||||
msgid ""
|
||||
"This installs the module account_analytic_analysis, which will install sales "
|
||||
"management too."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:board.board:0
|
||||
msgid "Human Resources Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,job_id:0
|
||||
#: view:hr.job:0
|
||||
msgid "Job"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,no_of_employee:0
|
||||
msgid "Current Number of Employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,member_ids:0
|
||||
msgid "Members"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_configuration
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,note:hr.process_node_employee0
|
||||
msgid "Employee form and structure"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_expense:0
|
||||
msgid "Manage employees expenses"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Tel:"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Divorced"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee.category,parent_id:0
|
||||
msgid "Parent Category"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
#: model:ir.actions.act_window,name:hr.open_module_tree_department
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_department_tree
|
||||
msgid "Departments"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,name:hr.process_node_employeecontact0
|
||||
msgid "Employee Contact"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.action_hr_job
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to define a new job position.\n"
|
||||
" </p><p>\n"
|
||||
" Job Positions are used to define jobs and their "
|
||||
"requirements.\n"
|
||||
" You can keep track of the number of employees you have per "
|
||||
"job\n"
|
||||
" position and follow the evolution according to what you "
|
||||
"planned\n"
|
||||
" for the future.\n"
|
||||
" </p><p>\n"
|
||||
" You can attach a survey to a job position. It will be used "
|
||||
"in\n"
|
||||
" the recruitment process to evaluate the applicants for this "
|
||||
"job\n"
|
||||
" position.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,gender:0
|
||||
msgid "Male"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid ""
|
||||
"$('.oe_employee_picture').load(function() { if($(this).width() > "
|
||||
"$(this).height()) { $(this).addClass('oe_employee_picture_wide') } });"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_evaluation:0
|
||||
msgid "This installs the module hr_evaluation."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: constraint:hr.employee:0
|
||||
msgid "Error! You cannot create recursive hierarchy of Employee(s)."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_attendance:0
|
||||
msgid "This installs the module hr_attendance."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,image_small:0
|
||||
msgid "Smal-sized photo"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee.category:0
|
||||
#: model:ir.model,name:hr.model_hr_employee_category
|
||||
msgid "Employee Category"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,category_ids:0
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_contract:0
|
||||
msgid "This installs the module hr_contract."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Related User"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee.category,name:0
|
||||
msgid "Category"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "Stop Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_attendance:0
|
||||
msgid "Install attendances feature"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,bank_account_id:0
|
||||
msgid "Employee bank salary account"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,note:0
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_view_employee_tree
|
||||
msgid "Employees Structure"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Contact Information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_holidays:0
|
||||
msgid "Manage holidays, leaves and allocation requests"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,child_ids:0
|
||||
msgid "Child Departments"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: view:hr.job:0
|
||||
#: field:hr.job,state:0
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,otherid:0
|
||||
msgid "Other Id"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.process,name:hr.process_process_employeecontractprocess0
|
||||
msgid "Employee Contract"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Contracts"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,message_ids:0
|
||||
msgid "Messages and communication history"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,ssnid:0
|
||||
msgid "SSN No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_is_follower:0
|
||||
msgid "Is a Follower"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_recruitment:0
|
||||
msgid "Manage the recruitment process"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Human Resources Management"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Install your country's payroll"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,bank_account_id:0
|
||||
msgid "Bank Account Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
msgid "Companies"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,message_summary:0
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.transition,note:hr.process_transition_contactofemployee0
|
||||
msgid ""
|
||||
"In the Employee form, there are different kind of information like Contact "
|
||||
"information."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.open_view_employee_list_my
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to add a new employee.\n"
|
||||
" </p><p>\n"
|
||||
" With just a quick glance on the OpenERP employee screen, "
|
||||
"you\n"
|
||||
" can easily find all the information you need for each "
|
||||
"person;\n"
|
||||
" contact data, job position, availability, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "HR Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Citizenship & Other Info"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: constraint:hr.department:0
|
||||
msgid "Error! You cannot create recursive departments."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,address_id:0
|
||||
msgid "Working Address"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Public Information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,marital:0
|
||||
msgid "Marital Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.model,name:hr.model_ir_actions_act_window
|
||||
msgid "ir.actions.act_window"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,last_login:0
|
||||
msgid "Latest Connection"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,image:0
|
||||
msgid "Photo"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.open_module_tree_department
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to create a department.\n"
|
||||
" </p><p>\n"
|
||||
" OpenERP's department structure is used to manage all "
|
||||
"documents\n"
|
||||
" related to employees by departments: expenses, timesheets,\n"
|
||||
" leaves and holidays, recruitments, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_timesheet:0
|
||||
msgid "This installs the module hr_timesheet."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,expected_employees:0
|
||||
msgid ""
|
||||
"Expected number of employees for this job position after new recruitment."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.view_department_form_installer
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to define a new department.\n"
|
||||
" </p><p>\n"
|
||||
" Your departments structure is used to manage all documents\n"
|
||||
" related to employees by departments: expenses and "
|
||||
"timesheets,\n"
|
||||
" leaves and holidays, recruitments, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
msgid "Personal Information"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,city:0
|
||||
msgid "City"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,passport_id:0
|
||||
msgid "Passport No"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,mobile_phone:0
|
||||
msgid "Work Mobile"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.job,state:0
|
||||
msgid "Recruitement in Progress"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_account_analytic_analysis:0
|
||||
msgid ""
|
||||
"Allow invoicing based on timesheets (the sale application will be installed)"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee.category:0
|
||||
msgid "Employees Categories"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,address_home_id:0
|
||||
msgid "Home Address"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_timesheet:0
|
||||
msgid "Manage timesheets"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.open_payroll_modules
|
||||
msgid "Payroll"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Single"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,name:0
|
||||
msgid "Job Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.job:0
|
||||
msgid "In Position"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_payroll:0
|
||||
msgid "This installs the module hr_payroll."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_contract:0
|
||||
msgid "Record contracts per employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.department:0
|
||||
msgid "department"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,country_id:0
|
||||
msgid "Nationality"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Additional Features"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,notes:0
|
||||
msgid "Notes"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.action2
|
||||
msgid "Subordinate Hierarchy"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,resource_id:0
|
||||
msgid "Resource"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,complete_name:0
|
||||
#: field:hr.employee,name_related:0
|
||||
#: field:hr.employee.category,complete_name:0
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,gender:0
|
||||
msgid "Gender"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee.category,employee_ids:0
|
||||
#: field:hr.job,employee_ids:0
|
||||
#: model:ir.actions.act_window,name:hr.hr_employee_normal_action_tree
|
||||
#: model:ir.actions.act_window,name:hr.open_view_employee_list
|
||||
#: model:ir.actions.act_window,name:hr.open_view_employee_list_my
|
||||
#: model:ir.ui.menu,name:hr.menu_open_view_employee_list_my
|
||||
msgid "Employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,sinid:0
|
||||
msgid "Social Insurance Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,name:0
|
||||
msgid "Department Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_reporting_timesheet
|
||||
msgid "Reports"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.config.settings,module_hr_payroll:0
|
||||
msgid "Manage payroll"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
#: model:ir.actions.act_window,name:hr.action_human_resources_configuration
|
||||
msgid "Configure Human Resources"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.job,state:0
|
||||
msgid "No Recruitment"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.employee,ssnid:0
|
||||
msgid "Social Security Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:process.node,note:hr.process_node_openerpuser0
|
||||
msgid "Creation of a OpenERP user"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,login:0
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.job,expected_employees:0
|
||||
msgid "Total Forecasted Employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.job,state:0
|
||||
msgid ""
|
||||
"By default 'In position', set it to 'In Recruitment' if recruitment process "
|
||||
"is going on for this job position."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.model,name:hr.model_res_users
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,name:hr.action_hr_job
|
||||
#: model:ir.ui.menu,name:hr.menu_hr_job
|
||||
msgid "Job Positions"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.actions.act_window,help:hr.open_board_hr
|
||||
msgid ""
|
||||
"<div class=\"oe_empty_custom_dashboard\">\n"
|
||||
" <p>\n"
|
||||
" <b>Human Resources dashboard is empty.</b>\n"
|
||||
" </p><p>\n"
|
||||
" To add your first report into this dashboard, go to any\n"
|
||||
" menu, switch to list or graph view, and click <i>'Add "
|
||||
"to\n"
|
||||
" Dashboard'</i> in the extended search options.\n"
|
||||
" </p><p>\n"
|
||||
" You can filter and group data before inserting into the\n"
|
||||
" dashboard using the search options.\n"
|
||||
" </p>\n"
|
||||
" </div>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,coach_id:0
|
||||
msgid "Coach"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: sql_constraint:hr.job:0
|
||||
msgid "The name of the job position must be unique per company!"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: help:hr.config.settings,module_hr_expense:0
|
||||
msgid "This installs the module hr_expense."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: model:ir.model,name:hr.model_hr_config_settings
|
||||
msgid "hr.config.settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.department,manager_id:0
|
||||
#: view:hr.employee:0
|
||||
#: field:hr.employee,parent_id:0
|
||||
msgid "Manager"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: selection:hr.employee,marital:0
|
||||
msgid "Widower"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: field:hr.employee,child_ids:0
|
||||
msgid "Subordinates"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr
|
||||
#: view:hr.config.settings:0
|
||||
msgid "Apply"
|
||||
msgstr ""
|
|
@ -263,6 +263,10 @@ class hr_expense_expense(osv.osv):
|
|||
|
||||
#convert eml into an osv-valid format
|
||||
lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.employee_id.address_home_id, exp.date_confirm, context=context)), eml)
|
||||
journal_id = move_obj.browse(cr, uid, move_id, context).journal_id
|
||||
# post the journal entry if 'Skip 'Draft' State for Manual Entries' is checked
|
||||
if journal_id.entry_posted:
|
||||
move_obj.button_validate(cr, uid, [move_id], context)
|
||||
move_obj.write(cr, uid, [move_id], {'line_id': lines}, context=context)
|
||||
self.write(cr, uid, ids, {'account_move_id': move_id, 'state': 'done'}, context=context)
|
||||
return True
|
||||
|
|
|
@ -371,6 +371,7 @@ class hr_holidays(osv.osv):
|
|||
'end_date': record.date_to,
|
||||
'date_deadline': record.date_to,
|
||||
'state': 'open', # to block that meeting date in the calendar
|
||||
'class': 'confidential'
|
||||
}
|
||||
#Add the partner_id (if exist) as an attendee
|
||||
if record.user_id and record.user_id.partner_id:
|
||||
|
|
|
@ -23,7 +23,7 @@ import time
|
|||
from datetime import date, datetime, timedelta
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import config
|
||||
from openerp.tools import config, float_compare
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class hr_payslip(osv.osv):
|
||||
|
@ -86,6 +86,7 @@ class hr_payslip(osv.osv):
|
|||
def process_sheet(self, cr, uid, ids, context=None):
|
||||
move_pool = self.pool.get('account.move')
|
||||
period_pool = self.pool.get('account.period')
|
||||
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Payroll')
|
||||
timenow = time.strftime('%Y-%m-%d')
|
||||
|
||||
for slip in self.browse(cr, uid, ids, context=context):
|
||||
|
@ -149,7 +150,7 @@ class hr_payslip(osv.osv):
|
|||
line_ids.append(credit_line)
|
||||
credit_sum += credit_line[2]['credit'] - credit_line[2]['debit']
|
||||
|
||||
if debit_sum > credit_sum:
|
||||
if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1:
|
||||
acc_id = slip.journal_id.default_credit_account_id.id
|
||||
if not acc_id:
|
||||
raise osv.except_osv(_('Configuration Error!'),_('The Expense Journal "%s" has not properly configured the Credit Account!')%(slip.journal_id.name))
|
||||
|
@ -165,7 +166,7 @@ class hr_payslip(osv.osv):
|
|||
})
|
||||
line_ids.append(adjust_credit)
|
||||
|
||||
elif debit_sum < credit_sum:
|
||||
elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1:
|
||||
acc_id = slip.journal_id.default_debit_account_id.id
|
||||
if not acc_id:
|
||||
raise osv.except_osv(_('Configuration Error!'),_('The Expense Journal "%s" has not properly configured the Debit Account!')%(slip.journal_id.name))
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,146 @@
|
|||
# Chinese (Simplified) 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-11-24 02:53+0000\n"
|
||||
"PO-Revision-Date: 2014-03-13 05:55+0000\n"
|
||||
"Last-Translator: LaoBiao.JX <betty2349@gmail.com>\n"
|
||||
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-14 06:36+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: sql_constraint:account.invoice:0
|
||||
msgid "Invoice Number must be unique per Company!"
|
||||
msgstr "发票号必须在公司范围内唯一"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: model:ir.model,name:l10n_be_invoice_bba.model_account_invoice
|
||||
msgid "Invoice"
|
||||
msgstr "发票"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: constraint:res.partner:0
|
||||
msgid "Error ! You cannot create recursive associated members."
|
||||
msgstr "错误,您不能创建循环引用的会员用户"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: constraint:account.invoice:0
|
||||
msgid "Invalid BBA Structured Communication !"
|
||||
msgstr "BBA结构化传输有误!"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: selection:res.partner,out_inv_comm_algorithm:0
|
||||
msgid "Random"
|
||||
msgstr "随机"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: help:res.partner,out_inv_comm_type:0
|
||||
msgid "Select Default Communication Type for Outgoing Invoices."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: help:res.partner,out_inv_comm_algorithm:0
|
||||
msgid ""
|
||||
"Select Algorithm to generate the Structured Communication on Outgoing "
|
||||
"Invoices."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:109
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:135
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The daily maximum of outgoing invoices with an automatically generated BBA "
|
||||
"Structured Communications has been exceeded!\n"
|
||||
"Please create manually a unique BBA Structured Communication."
|
||||
msgstr "自动生成结构化BBA传输已超出每日销售发票的最大值,请手动建立BBA结构化传输"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:150
|
||||
#, python-format
|
||||
msgid "Error!"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:121
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The Partner should have a 3-7 digit Reference Number for the generation of "
|
||||
"BBA Structured Communications!\n"
|
||||
"Please correct the Partner record."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: constraint:res.partner:0
|
||||
msgid "Error: Invalid ean code"
|
||||
msgstr "错误:无效的EAN编码"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:108
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:120
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:134
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:162
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:172
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:197
|
||||
#, python-format
|
||||
msgid "Warning!"
|
||||
msgstr "警告!"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: selection:res.partner,out_inv_comm_algorithm:0
|
||||
msgid "Customer Reference"
|
||||
msgstr "客户参考号"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: field:res.partner,out_inv_comm_type:0
|
||||
msgid "Communication Type"
|
||||
msgstr "讯息类型"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:173
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:198
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The BBA Structured Communication has already been used!\n"
|
||||
"Please create manually a unique BBA Structured Communication."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: selection:res.partner,out_inv_comm_algorithm:0
|
||||
msgid "Date"
|
||||
msgstr "事务处理日期"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: model:ir.model,name:l10n_be_invoice_bba.model_res_partner
|
||||
msgid "Partner"
|
||||
msgstr "合作伙伴"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:151
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Unsupported Structured Communication Type Algorithm '%s' !\n"
|
||||
"Please contact your OpenERP support channel."
|
||||
msgstr "不支持的结构化传输算法类型\"%s\"!请联系你的OpenERP 维护人员"
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: field:res.partner,out_inv_comm_algorithm:0
|
||||
msgid "Communication Algorithm"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_be_invoice_bba
|
||||
#: code:addons/l10n_be_invoice_bba/invoice.py:163
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Empty BBA Structured Communication!\n"
|
||||
"Please fill in a unique BBA Structured Communication."
|
||||
msgstr ""
|
|
@ -138,7 +138,7 @@ class wizard_multi_charts_accounts(osv.osv_memory):
|
|||
def _process_taxes_translations(self, cr, uid, obj_multi, company_id, langs, field, context=None):
|
||||
obj_tax_template = self.pool.get('account.tax.template')
|
||||
obj_tax = self.pool.get('account.tax')
|
||||
in_ids = sorted([x.id for x in obj_multi.chart_template_id.tax_template_ids])
|
||||
in_ids = [x.id for x in obj_multi.chart_template_id.tax_template_ids]
|
||||
out_ids = obj_tax.search(cr, uid, [('company_id', '=', company_id)], order='id')
|
||||
return self.process_translations(cr, uid, langs, obj_tax_template, field, in_ids, obj_tax, out_ids, context=context)
|
||||
|
||||
|
|
|
@ -186,12 +186,11 @@ class IrAttachment(osv.Model):
|
|||
def get_attachment_type(self, cr, uid, ids, name, args, context=None):
|
||||
result = {}
|
||||
for attachment in self.browse(cr, uid, ids, context=context):
|
||||
fileext = os.path.splitext(attachment.datas_fname)[1].lower()
|
||||
if not fileext or not fileext[1:] in self._fileext_to_type:
|
||||
return 'unknown'
|
||||
result[attachment.id] = self._fileext_to_type[fileext[1:]]
|
||||
fileext = os.path.splitext(attachment.datas_fname or '')[1].lower()[1:]
|
||||
result[attachment.id] = self._fileext_to_type.get(fileext, 'unknown')
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'file_type': fields.function(get_attachment_type, type='char', string='File Type'),
|
||||
'file_type_icon': fields.function(get_attachment_type, type='char', string='File Type Icon'),
|
||||
'file_type': fields.related('file_type_icon', type='char'), # FIXME remove in trunk
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ class mail_mail(osv.Model):
|
|||
elif mail.model and mail.res_id:
|
||||
fragment.update(model=mail.model, res_id=mail.res_id)
|
||||
|
||||
url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment)))
|
||||
url = urljoin(base_url, "/web?%s#%s" % (urlencode(query), urlencode(fragment)))
|
||||
return _("""<span class='oe_mail_footer_access'><small>Access your messages and documents <a style='color:inherit' href="%s">in OpenERP</a></small></span>""") % url
|
||||
else:
|
||||
return None
|
||||
|
|
|
@ -351,12 +351,12 @@ class mail_message(osv.Model):
|
|||
partner_tree = dict((partner[0], partner) for partner in partners)
|
||||
|
||||
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
|
||||
attachments = ir_attachment_obj.read(cr, SUPERUSER_ID, list(attachment_ids), ['id', 'datas_fname', 'name', 'file_type'], context=context)
|
||||
attachments = ir_attachment_obj.read(cr, SUPERUSER_ID, list(attachment_ids), ['id', 'datas_fname', 'name', 'file_type_icon'], context=context)
|
||||
attachments_tree = dict((attachment['id'], {
|
||||
'id': attachment['id'],
|
||||
'filename': attachment['datas_fname'],
|
||||
'name': attachment['name'],
|
||||
'file_type': attachment['file_type'],
|
||||
'file_type_icon': attachment['file_type_icon'],
|
||||
}) for attachment in attachments)
|
||||
|
||||
# 3. Update message dictionaries
|
||||
|
|
|
@ -161,6 +161,7 @@ class mail_thread(osv.AbstractModel):
|
|||
if res[id]['message_unread_count']:
|
||||
title = res[id]['message_unread_count'] > 1 and _("You have %d unread messages") % res[id]['message_unread_count'] or _("You have one unread message")
|
||||
res[id]['message_summary'] = "<span class='oe_kanban_mail_new' title='%s'><span class='oe_e'>9</span> %d %s</span>" % (title, res[id].pop('message_unread_count'), _("New"))
|
||||
res[id].pop('message_unread_count', None)
|
||||
return res
|
||||
|
||||
def read_followers_data(self, cr, uid, follower_ids, context=None):
|
||||
|
@ -209,7 +210,7 @@ class mail_thread(osv.AbstractModel):
|
|||
], context=context)
|
||||
for fol in fol_obj.browse(cr, uid, fol_ids, context=context):
|
||||
thread_subtype_dict = res[fol.res_id]['message_subtype_data']
|
||||
for subtype in fol.subtype_ids:
|
||||
for subtype in [st for st in fol.subtype_ids if st.name in thread_subtype_dict]:
|
||||
thread_subtype_dict[subtype.name]['followed'] = True
|
||||
res[fol.res_id]['message_subtype_data'] = thread_subtype_dict
|
||||
|
||||
|
|
|
@ -88,10 +88,10 @@
|
|||
-->
|
||||
<t t-name="mail.thread.message.attachments">
|
||||
<t t-foreach='widget.attachment_ids' t-as='attachment'>
|
||||
<t t-if="attachment.file_type !== 'webimage'">
|
||||
<t t-if="attachment.file_type_icon !== 'webimage'">
|
||||
<div t-attf-class="oe_attachment #{attachment.upload ? 'oe_uploading' : ''}">
|
||||
<a t-att-href='attachment.url' target="_blank">
|
||||
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type + '.png'"></img>
|
||||
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type_icon + '.png'"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name' /></div>
|
||||
</a>
|
||||
<div class='oe_delete oe_e' title="Delete this attachment" t-att-data-id="attachment.id">[</div>
|
||||
|
@ -100,7 +100,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachment.file_type === 'webimage'">
|
||||
<t t-if="attachment.file_type_icon === 'webimage'">
|
||||
<div t-attf-class="oe_attachment oe_preview #{attachment.upload ? 'oe_uploading' : ''}">
|
||||
<a t-att-href='attachment.url' target="_blank">
|
||||
<img t-att-src="widget.attachments_resize_image(attachment.id, [100,80])"></img>
|
||||
|
|
|
@ -352,10 +352,10 @@ class mail_compose_message(osv.TransientModel):
|
|||
:return dict results: for each res_id, the generated template values for
|
||||
subject, body, email_from and reply_to
|
||||
"""
|
||||
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context)
|
||||
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context)
|
||||
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context)
|
||||
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context)
|
||||
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context=context)
|
||||
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context=context, post_process=True)
|
||||
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context=context)
|
||||
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context=context)
|
||||
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
for res_id in res_ids:
|
||||
|
@ -367,7 +367,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
}
|
||||
return results
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
|
||||
""" Render the given template text, replace mako-like expressions ``${expr}``
|
||||
with the result of evaluating these expressions with an evaluation context
|
||||
containing:
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<record id="membership_0" model="product.product">
|
||||
<field name="membership">True</field>
|
||||
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/>
|
||||
<field eval="time.strftime('%Y-12-01')" name="membership_date_to"/>
|
||||
<field eval="time.strftime('%Y-12-31')" name="membership_date_to"/>
|
||||
<field name="name">Gold Membership</field>
|
||||
<field name="list_price">180</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
|
@ -16,7 +16,7 @@
|
|||
<record id="membership_1" model="product.product">
|
||||
<field name="membership">True</field>
|
||||
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/>
|
||||
<field eval="time.strftime('%Y-12-01')" name="membership_date_to"/>
|
||||
<field eval="time.strftime('%Y-12-31')" name="membership_date_to"/>
|
||||
<field name="name">Silver Membership</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="list_price">80</field>
|
||||
|
@ -26,7 +26,7 @@
|
|||
<record id="membership_2" model="product.product">
|
||||
<field name="membership">True</field>
|
||||
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/>
|
||||
<field eval="time.strftime('%Y-12-01')" name="membership_date_to"/>
|
||||
<field eval="time.strftime('%Y-12-31')" name="membership_date_to"/>
|
||||
<field name="name">Basic Membership</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="list_price">40</field>
|
||||
|
|
|
@ -29,6 +29,7 @@ from openerp.tools import float_compare
|
|||
from openerp.tools.translate import _
|
||||
from openerp import tools, SUPERUSER_ID
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.addons.product import _common
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Work Centers
|
||||
|
@ -322,7 +323,7 @@ class mrp_bom(osv.osv):
|
|||
"""
|
||||
routing_obj = self.pool.get('mrp.routing')
|
||||
factor = factor / (bom.product_efficiency or 1.0)
|
||||
factor = rounding(factor, bom.product_rounding)
|
||||
factor = _common.ceiling(factor, bom.product_rounding)
|
||||
if factor < bom.product_rounding:
|
||||
factor = bom.product_rounding
|
||||
result = []
|
||||
|
@ -378,6 +379,8 @@ class mrp_bom(osv.osv):
|
|||
|
||||
|
||||
def rounding(f, r):
|
||||
# TODO for trunk: log deprecation warning
|
||||
# _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.")
|
||||
import math
|
||||
if not r:
|
||||
return f
|
||||
|
|
|
@ -59,6 +59,7 @@ The following topics should be covered by this module:
|
|||
'test/test_mrp_repair_b4inv.yml',
|
||||
'test/test_mrp_repair_afterinv.yml',
|
||||
'test/test_mrp_repair_cancel.yml',
|
||||
'test/test_mrp_repair_fee.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
|
|
|
@ -108,10 +108,12 @@ class mrp_repair(osv.osv):
|
|||
return res
|
||||
|
||||
def _get_lines(self, cr, uid, ids, context=None):
|
||||
result = {}
|
||||
for line in self.pool.get('mrp.repair.line').browse(cr, uid, ids, context=context):
|
||||
result[line.repair_id.id] = True
|
||||
return result.keys()
|
||||
return self.pool['mrp.repair'].search(
|
||||
cr, uid, [('operations', 'in', ids)], context=context)
|
||||
|
||||
def _get_fee_lines(self, cr, uid, ids, context=None):
|
||||
return self.pool['mrp.repair'].search(
|
||||
cr, uid, [('fees_lines', 'in', ids)], context=context)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Repair Reference',size=24, required=True, states={'confirmed':[('readonly',True)]}),
|
||||
|
@ -160,18 +162,21 @@ class mrp_repair(osv.osv):
|
|||
'repaired': fields.boolean('Repaired', readonly=True),
|
||||
'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount',
|
||||
store={
|
||||
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
|
||||
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
|
||||
'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
|
||||
'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
|
||||
}),
|
||||
'amount_tax': fields.function(_amount_tax, string='Taxes',
|
||||
store={
|
||||
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
|
||||
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
|
||||
'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
|
||||
'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
|
||||
}),
|
||||
'amount_total': fields.function(_amount_total, string='Total',
|
||||
store={
|
||||
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
|
||||
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
|
||||
'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
|
||||
'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
|
||||
}),
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
-
|
||||
Testing total amount update function
|
||||
-
|
||||
I check the total amount of mrp_repair_rmrp1 is 100
|
||||
-
|
||||
!assert {model: mrp.repair, id: mrp_repair_rmrp1, string=amount_total should be 100}:
|
||||
- amount_total == 100
|
||||
-
|
||||
I add a new fee line
|
||||
-
|
||||
!record {model: mrp.repair, id: mrp_repair_rmrp1}:
|
||||
fees_lines:
|
||||
- name: 'Assembly Service Cost'
|
||||
product_id: product.product_assembly
|
||||
product_uom_qty: 1.0
|
||||
product_uom: product.product_uom_hour
|
||||
price_unit: 12.0
|
||||
to_invoice: True
|
||||
-
|
||||
I check the total amount of mrp_repair_rmrp1 is now 112
|
||||
-
|
||||
!assert {model: mrp.repair, id: mrp_repair_rmrp1, string=amount_total should be 112}:
|
||||
- amount_total == 112
|
|
@ -14,6 +14,10 @@ _logger = logging.getLogger(__name__)
|
|||
class pad_common(osv.osv_memory):
|
||||
_name = 'pad.common'
|
||||
|
||||
def pad_is_configured(self, cr, uid, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return bool(user.company_id.pad_server)
|
||||
|
||||
def pad_generate_url(self, cr, uid, context=None):
|
||||
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id;
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
.oe_pad_loading{
|
||||
text-align: center;
|
||||
opacity: 0.75;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.etherpad_readonly ul, .etherpad_readonly ol {
|
||||
|
|
|
@ -1,31 +1,56 @@
|
|||
openerp.pad = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
|
||||
instance.web.form.FieldPad = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeWidgetMixin, {
|
||||
template: 'FieldPad',
|
||||
content: "",
|
||||
init: function() {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this.set("configured", true);
|
||||
this.on("change:configured", this, this.switch_configured);
|
||||
this._configured_deferred = this.view.dataset.call('pad_is_configured').done(function(data) {
|
||||
self.set("configured", !!data);
|
||||
}).fail(function(data, event) {
|
||||
event.preventDefault();
|
||||
self.set("configured", true);
|
||||
});
|
||||
},
|
||||
initialize_content: function() {
|
||||
var self = this;
|
||||
this.switch_configured();
|
||||
this.$('.oe_pad_switch').click(function() {
|
||||
self.$el.toggleClass('oe_pad_fullscreen');
|
||||
self.$el.find('.oe_pad_switch').toggleClass('fa-expand fa-compress');
|
||||
self.view.$el.find('.oe_chatter').toggle();
|
||||
});
|
||||
this._configured_deferred.always(function() {
|
||||
var configured = self.get('configured');
|
||||
self.$(".oe_unconfigured").toggle(!configured);
|
||||
self.$(".oe_configured").toggle(configured);
|
||||
});
|
||||
this.render_value();
|
||||
},
|
||||
switch_configured: function() {
|
||||
this.$(".oe_unconfigured").toggle(! this.get("configured"));
|
||||
this.$(".oe_configured").toggle(this.get("configured"));
|
||||
},
|
||||
render_value: function() {
|
||||
var self = this;
|
||||
if (this.get("configured") && ! this.get("value")) {
|
||||
self.view.dataset.call('pad_generate_url', {
|
||||
this._configured_deferred.always(function() {
|
||||
if (! self.get('configured')) {
|
||||
return;
|
||||
};
|
||||
var value = self.get('value');
|
||||
if (self.get('effective_readonly')) {
|
||||
if (_.str.startsWith(value, 'http')) {
|
||||
this.pad_loading_request = self.view.dataset.call('pad_get_content', {url: value}).done(function(data) {
|
||||
self.$('.oe_pad_content').removeClass('oe_pad_loading').html('<div class="oe_pad_readonly"><div>');
|
||||
self.$('.oe_pad_readonly').html(data);
|
||||
}).fail(function() {
|
||||
self.$('.oe_pad_content').text(_t('Unable to load pad'));
|
||||
});
|
||||
} else {
|
||||
self.$('.oe_pad_content').addClass('oe_pad_loading').show().text(_t("This pad will be initialized on first edit"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
var def = $.when();
|
||||
if (! value || !_.str.startsWith(value, 'http')) {
|
||||
def = self.view.dataset.call('pad_generate_url', {
|
||||
context: {
|
||||
model: self.view.model,
|
||||
field_name: self.name,
|
||||
|
@ -39,28 +64,19 @@ openerp.pad = function(instance) {
|
|||
}
|
||||
});
|
||||
}
|
||||
this.$('.oe_pad_content').html("");
|
||||
var value = this.get('value');
|
||||
if (this.pad_loading_request) {
|
||||
this.pad_loading_request.abort();
|
||||
}
|
||||
def.then(function() {
|
||||
value = self.get('value');
|
||||
if (_.str.startsWith(value, 'http')) {
|
||||
if (! this.get('effective_readonly')) {
|
||||
var content = '<iframe width="100%" height="100%" frameborder="0" src="' + value + '?showChat=false&userName=' + this.session.username + '"></iframe>';
|
||||
this.$('.oe_pad_content').html(content);
|
||||
this._dirty_flag = true;
|
||||
} else {
|
||||
this.content = '<div class="oe_pad_loading">... Loading pad ...</div>';
|
||||
this.pad_loading_request = $.get(value + '/export/html').done(function(data) {
|
||||
groups = /\<\s*body\s*\>(.*?)\<\s*\/body\s*\>/.exec(data);
|
||||
data = (groups || []).length >= 2 ? groups[1] : '';
|
||||
self.$('.oe_pad_content').html('<div class="oe_pad_readonly"><div>');
|
||||
self.$('.oe_pad_readonly').html(data);
|
||||
}).fail(function() {
|
||||
self.$('.oe_pad_content').text('Unable to load pad');
|
||||
var content = '<iframe width="100%" height="100%" frameborder="0" src="' + value + '?showChat=false&userName=' + self.session.username + '"></iframe>';
|
||||
self.$('.oe_pad_content').html(content);
|
||||
self._dirty_flag = true;
|
||||
}
|
||||
else {
|
||||
self.$('.oe_pad_content').text(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -275,9 +275,12 @@ class PaymentAcquirer(osv.Model):
|
|||
</div>""" % (amount, payment_header)
|
||||
return result % html_block.decode("utf-8")
|
||||
|
||||
def render_payment_block(self, cr, uid, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, context=None):
|
||||
def render_payment_block(self, cr, uid, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, company_id=None, context=None):
|
||||
html_forms = []
|
||||
acquirer_ids = self.search(cr, uid, [('website_published', '=', True), ('validation', '=', 'automatic')], context=context)
|
||||
domain = [('website_published', '=', True), ('validation', '=', 'automatic')]
|
||||
if company_id:
|
||||
domain.append(('company_id', '=', company_id))
|
||||
acquirer_ids = self.search(cr, uid, domain, context=context)
|
||||
for acquirer_id in acquirer_ids:
|
||||
button = self.render(
|
||||
cr, uid, acquirer_id,
|
||||
|
|
|
@ -9,14 +9,11 @@ class AccountPaymentConfig(osv.TransientModel):
|
|||
_columns = {
|
||||
'module_payment_paypal': fields.boolean(
|
||||
'Manage Payments Using Paypal',
|
||||
help='Blahblahblah\n'
|
||||
'-It installs the module payment_paypal.'),
|
||||
help='-It installs the module payment_paypal.'),
|
||||
'module_payment_ogone': fields.boolean(
|
||||
'Manage Payments Using Ogone',
|
||||
help='Blahblahblah\n'
|
||||
'-It installs the module payment_ogone.'),
|
||||
help='-It installs the module payment_ogone.'),
|
||||
'module_payment_adyen': fields.boolean(
|
||||
'Manage Payments Using Adyen',
|
||||
help='Blahblahblah\n'
|
||||
'-It installs the module payment_adyen.'),
|
||||
help='-It installs the module payment_adyen.'),
|
||||
}
|
||||
|
|
|
@ -30,10 +30,10 @@ class PaymentAcquirerOgone(osv.Model):
|
|||
@TDETODO: complete me
|
||||
"""
|
||||
return {
|
||||
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.asp' % env,
|
||||
'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % env,
|
||||
'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect.asp' % env,
|
||||
'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % env,
|
||||
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard_utf8.asp' % (env,),
|
||||
'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect_utf8.asp' % (env,),
|
||||
'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect_utf8.asp' % (env,),
|
||||
'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % (env,),
|
||||
}
|
||||
|
||||
_columns = {
|
||||
|
|
|
@ -70,6 +70,7 @@ class AcquirerPaypal(osv.Model):
|
|||
else:
|
||||
paypal_view = self.pool['ir.model.data'].get_object(cr, uid, 'payment_paypal', 'paypal_acquirer_button')
|
||||
self.create(cr, uid, {
|
||||
'name': 'paypal',
|
||||
'paypal_email_account': company_paypal_account,
|
||||
'view_template_id': paypal_view.id,
|
||||
}, context=context)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<field name="inherit_id" ref="account.view_account_config_settings"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='payment_acquirer']" version="7.0" position="inside">
|
||||
<button name='%(payment.acquirer_list)d' type="action"
|
||||
<button name='%(payment.action_payment_acquirer)d' type="action"
|
||||
string="Configure payment acquiring methods" class="oe_link"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
@ -8,7 +8,7 @@ import random
|
|||
|
||||
from openerp import http
|
||||
from openerp.http import request
|
||||
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
|
||||
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template, login_redirect
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -59,7 +59,7 @@ class PosController(http.Controller):
|
|||
def a(self, debug=False, **k):
|
||||
|
||||
if not request.session.uid:
|
||||
return http.local_redirect('/web/login?redirect=/pos/web')
|
||||
return login_redirect()
|
||||
|
||||
js_list = manifest_list('js',db=request.db, debug=debug)
|
||||
css_list = manifest_list('css',db=request.db, debug=debug)
|
||||
|
|
|
@ -514,13 +514,18 @@ class pos_order(osv.osv):
|
|||
_order = "id desc"
|
||||
|
||||
def create_from_ui(self, cr, uid, orders, context=None):
|
||||
#_logger.info("orders: %r", orders)
|
||||
|
||||
# Keep only new orders
|
||||
submitted_references = [o['data']['name'] for o in orders]
|
||||
existing_orders = self.search_read(cr, uid, domain=[('pos_reference', 'in', submitted_references)], fields=['pos_reference'], context=context)
|
||||
existing_references = set([o['pos_reference'] for o in existing_orders])
|
||||
orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
|
||||
|
||||
order_ids = []
|
||||
for tmp_order in orders:
|
||||
for tmp_order in orders_to_save:
|
||||
to_invoice = tmp_order['to_invoice']
|
||||
order = tmp_order['data']
|
||||
|
||||
|
||||
order_id = self.create(cr, uid, {
|
||||
'name': order['name'],
|
||||
'user_id': order['user_id'] or False,
|
||||
|
@ -542,7 +547,6 @@ class pos_order(osv.osv):
|
|||
if order['amount_return']:
|
||||
session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context)
|
||||
cash_journal = session.cash_journal_id
|
||||
cash_statement = False
|
||||
if not cash_journal:
|
||||
cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids)
|
||||
if not len(cash_journal_ids):
|
||||
|
@ -556,7 +560,11 @@ class pos_order(osv.osv):
|
|||
'journal': cash_journal.id,
|
||||
}, context=context)
|
||||
order_ids.append(order_id)
|
||||
|
||||
try:
|
||||
self.signal_paid(cr, uid, [order_id])
|
||||
except Exception as e:
|
||||
_logger.error('Could not mark POS Order as Paid: %s', tools.ustr(e))
|
||||
|
||||
if to_invoice:
|
||||
self.action_invoice(cr, uid, [order_id], context)
|
||||
|
|
|
@ -672,7 +672,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
// returns true if the ean is a valid EAN codebar number by checking the control digit.
|
||||
// ean must be a string
|
||||
check_ean: function(ean){
|
||||
return this.ean_checksum(ean) === Number(ean[ean.length-1]);
|
||||
return /^\d+$/.test(ean) && this.ean_checksum(ean) === Number(ean[ean.length-1]);
|
||||
},
|
||||
// returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
|
||||
sanitize_ean:function(ean){
|
||||
|
@ -757,8 +757,13 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
scan: function(code){
|
||||
if(code.length < 3){
|
||||
return;
|
||||
}else if(code.length === 13 && /^\d+$/.test(code)){
|
||||
}else if(code.length === 13 && this.check_ean(code)){
|
||||
var parse_result = this.parse_ean(code);
|
||||
}else if(code.length === 12 && this.check_ean('0'+code)){
|
||||
// many barcode scanners strip the leading zero of ean13 barcodes.
|
||||
// This is because ean-13 are UCP-A with an additional zero at the beginning,
|
||||
// so by stripping zeros you get retrocompatibility with UCP-A systems.
|
||||
var parse_result = this.parse_ean('0'+code);
|
||||
}else if(this.pos.db.get_product_by_reference(code)){
|
||||
var parse_result = {
|
||||
encoding: 'reference',
|
||||
|
@ -767,12 +772,16 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
prefix: '',
|
||||
};
|
||||
}else{
|
||||
var parse_result = {
|
||||
encoding: 'error',
|
||||
type: 'error',
|
||||
code: code,
|
||||
prefix: '',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (parse_result.type === 'error') { //most likely a checksum error, raise warning
|
||||
console.warn('WARNING: barcode checksum error:',parse_result);
|
||||
}else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
|
||||
if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
|
||||
if(this.action_callback['product']){
|
||||
this.action_callback['product'](parse_result);
|
||||
}
|
||||
|
|
|
@ -422,38 +422,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
// this method returns a deferred indicating wether the sending was successful or not
|
||||
// there is a timeout parameter which is set to 2 seconds by default.
|
||||
_flush_order: function( order_id, options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
timeout = typeof options.timeout === 'number' ? options.timeout : 7500;
|
||||
|
||||
this.set('synch',{state:'connecting', pending: this.get('synch').pending});
|
||||
|
||||
var order = this.db.get_order(order_id);
|
||||
order.to_invoice = options.to_invoice || false;
|
||||
|
||||
if(!order){
|
||||
// flushing a non existing order always fails
|
||||
return (new $.Deferred()).reject();
|
||||
}
|
||||
|
||||
// we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice,
|
||||
// then we want to notify the user that we are waiting on something )
|
||||
var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: !options.to_invoice, timeout:timeout});
|
||||
|
||||
rpc.fail(function(unused,event){
|
||||
// prevent an error popup creation by the rpc failure
|
||||
// we want the failure to be silent as we send the orders in the background
|
||||
event.preventDefault();
|
||||
console.error('Failed to send order:',order);
|
||||
});
|
||||
|
||||
rpc.done(function(){
|
||||
self.db.remove_order(order_id);
|
||||
var pending = self.db.get_orders().length;
|
||||
self.set('synch',{state: pending ? 'connecting' : 'connected', pending:pending});
|
||||
});
|
||||
|
||||
return rpc;
|
||||
return this._flush_all_orders([this.db.get_order(order_id)], options);
|
||||
},
|
||||
|
||||
// attempts to send all the locally stored orders. As with _flush_order, it should only be
|
||||
|
@ -462,21 +431,57 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
// even if none of them could actually be sent.
|
||||
_flush_all_orders: function () {
|
||||
var self = this;
|
||||
var orders = this.db.get_orders();
|
||||
var tried_all = new $.Deferred();
|
||||
self.set('synch', {
|
||||
state: 'connecting',
|
||||
pending: self.get('synch').pending
|
||||
});
|
||||
return self._save_to_server(self.db.get_orders()).done(function () {
|
||||
var pending = self.db.get_orders().length;
|
||||
self.set('synch', {
|
||||
state: pending ? 'connecting' : 'connected',
|
||||
pending: pending
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
function rec_flush(index){
|
||||
if(index < orders.length){
|
||||
self._flush_order(orders[index].id).always(function(){
|
||||
rec_flush(index+1);
|
||||
})
|
||||
}else{
|
||||
tried_all.resolve();
|
||||
// send an array of orders to the server
|
||||
// available options:
|
||||
// - timeout: timeout for the rpc call in ms
|
||||
_save_to_server: function (orders, options) {
|
||||
if (!orders || !orders.length) {
|
||||
var result = $.Deferred();
|
||||
result.resolve();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
rec_flush(0);
|
||||
|
||||
return tried_all;
|
||||
options = options || {};
|
||||
|
||||
var self = this;
|
||||
var timeout = typeof options.timeout === 'number' ? options.timeout : 7500 * orders.length;
|
||||
|
||||
// we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice,
|
||||
// then we want to notify the user that we are waiting on something )
|
||||
var posOrderModel = new instance.web.Model('pos.order');
|
||||
return posOrderModel.call('create_from_ui',
|
||||
[_.map(orders, function (order) {
|
||||
order.to_invoice = options.to_invoice || false;
|
||||
return order;
|
||||
})],
|
||||
undefined,
|
||||
{
|
||||
shadow: !options.to_invoice,
|
||||
timeout: timeout
|
||||
}
|
||||
).then(function () {
|
||||
_.each(orders, function (order) {
|
||||
self.db.remove_order(order.id);
|
||||
});
|
||||
}).fail(function (unused, event){
|
||||
// prevent an error popup creation by the rpc failure
|
||||
// we want the failure to be silent as we send the orders in the background
|
||||
event.preventDefault();
|
||||
console.error('Failed to send orders:', orders);
|
||||
});
|
||||
},
|
||||
|
||||
scan_product: function(parsed_code){
|
||||
|
|
|
@ -35,14 +35,14 @@ class mail_message(osv.Model):
|
|||
"""
|
||||
if uid == SUPERUSER_ID:
|
||||
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
||||
context=context, count=False, access_rights_uid=access_rights_uid)
|
||||
context=context, count=count, access_rights_uid=access_rights_uid)
|
||||
group_ids = self.pool.get('res.users').browse(cr, uid, uid, context=context).groups_id
|
||||
group_user_id = self.pool.get("ir.model.data").get_object_reference(cr, uid, 'base', 'group_user')[1]
|
||||
if group_user_id not in [group.id for group in group_ids]:
|
||||
args = [('subtype_id', '!=', False)] + list(args)
|
||||
|
||||
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
||||
context=context, count=False, access_rights_uid=access_rights_uid)
|
||||
context=context, count=count, access_rights_uid=access_rights_uid)
|
||||
|
||||
def check_access_rule(self, cr, uid, ids, operation, context=None):
|
||||
""" Add Access rules of mail.message for non-employee user:
|
||||
|
|
|
@ -6,11 +6,17 @@
|
|||
z-index: 0;
|
||||
}
|
||||
|
||||
|
||||
.openerp .oe_form .oe_form_embedded_html.view_portal_payment_options {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.openerp .payment_acquirers {
|
||||
margin: -40px 0 -32px -24px;
|
||||
position: relative;
|
||||
padding: 10px 15px;
|
||||
right: -125px; /* improved margin according bootstrap3 */
|
||||
width: 650px;
|
||||
margin-left: 80px;
|
||||
|
||||
background: #729FCF;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#729FCF), to(#3465A4));
|
||||
|
|
|
@ -39,7 +39,7 @@ class sale_order(osv.Model):
|
|||
if this.state not in ('draft', 'cancel') and not this.invoiced:
|
||||
result[this.id] = payment_acquirer.render_payment_block(
|
||||
cr, uid, this.name, this.amount_total, this.pricelist_id.currency_id.id,
|
||||
partner_id=this.partner_id.id, context=context)
|
||||
partner_id=this.partner_id.id, company_id=this.company_id.id, context=context)
|
||||
return result
|
||||
|
||||
def action_quotation_send(self, cr, uid, ids, context=None):
|
||||
|
@ -90,7 +90,7 @@ class account_invoice(osv.Model):
|
|||
if this.type == 'out_invoice' and this.state not in ('draft', 'done') and not this.reconciled:
|
||||
result[this.id] = payment_acquirer.render_payment_block(
|
||||
cr, uid, this.number, this.residual, this.currency_id.id,
|
||||
partner_id=this.partner_id.id, context=context)
|
||||
partner_id=this.partner_id.id, company_id=this.company_id.id, context=context)
|
||||
return result
|
||||
|
||||
def action_invoice_sent(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<notebook version="7.0" position="before">
|
||||
<field name="portal_payment_options" groups="portal_sale.group_payment_options"/>
|
||||
<field name="portal_payment_options" groups="portal_sale.group_payment_options" class="view_portal_payment_options"/>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -19,7 +19,7 @@
|
|||
<field name="inherit_id" ref="account.invoice_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<notebook version="7.0" position="before">
|
||||
<field name="portal_payment_options" groups="portal_sale.group_payment_options"/>
|
||||
<field name="portal_payment_options" groups="portal_sale.group_payment_options" class="view_portal_payment_options"/>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div>
|
||||
<field name="group_payment_options" class="oe_inline"/>
|
||||
<label for="group_payment_options"/>
|
||||
<button name='%(payment.acquirer_list)d' type="action"
|
||||
<button name='%(payment.action_payment_acquirer)d' type="action"
|
||||
string="Configure payment acquiring methods" class="oe_link"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
|
|
@ -222,7 +222,7 @@
|
|||
<field name="product_id" on_change="onchange_product_id(product_id)"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
|
||||
<field name="product_uom" groups="product.group_uom"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
|
|
|
@ -63,7 +63,6 @@ Print product labels with barcode.
|
|||
],
|
||||
'test': [
|
||||
'product_pricelist_demo.yml',
|
||||
'test/product_uom.yml',
|
||||
'test/product_pricelist.yml',
|
||||
],
|
||||
'installable': True,
|
||||
|
|
|
@ -18,12 +18,18 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp import tools
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def rounding(f, r):
|
||||
# TODO for trunk: log deprecation warning
|
||||
# _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.")
|
||||
return tools.float_round(f, precision_rounding=r)
|
||||
|
||||
# TODO for trunk: add rounding method parameter to tools.float_round and use this method as hook
|
||||
def ceiling(f, r):
|
||||
if not r:
|
||||
return f
|
||||
return round(f / r) * r
|
||||
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
return math.ceil(f / r) * r
|
||||
|
|
|
@ -21,8 +21,7 @@
|
|||
|
||||
import time
|
||||
|
||||
from _common import rounding
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
@ -116,11 +115,29 @@ class product_pricelist(osv.osv):
|
|||
if name and operator == '=' and not args:
|
||||
# search on the name of the pricelist and its currency, opposite of name_get(),
|
||||
# Used by the magic context filter in the product search view.
|
||||
query_args = {'name': name, 'limit': limit}
|
||||
query_args = {'name': name, 'limit': limit, 'lang': (context or {}).get('lang') or 'en_US'}
|
||||
query = """SELECT p.id
|
||||
FROM product_pricelist p JOIN
|
||||
res_currency c ON (p.currency_id = c.id)
|
||||
WHERE p.name || ' (' || c.name || ')' = %(name)s
|
||||
FROM ((
|
||||
SELECT pr.id, pr.name
|
||||
FROM product_pricelist pr JOIN
|
||||
res_currency cur ON
|
||||
(pr.currency_id = cur.id)
|
||||
WHERE pr.name || ' (' || cur.name || ')' = %(name)s
|
||||
)
|
||||
UNION (
|
||||
SELECT tr.res_id as id, tr.value as name
|
||||
FROM ir_translation tr JOIN
|
||||
product_pricelist pr ON (
|
||||
pr.id = tr.res_id AND
|
||||
tr.type = 'model' AND
|
||||
tr.name = 'product.pricelist,name' AND
|
||||
tr.lang = %(lang)s
|
||||
) JOIN
|
||||
res_currency cur ON
|
||||
(pr.currency_id = cur.id)
|
||||
WHERE tr.value || ' (' || cur.name || ')' = %(name)s
|
||||
)
|
||||
) p
|
||||
ORDER BY p.name"""
|
||||
if limit:
|
||||
query += " LIMIT %(limit)s"
|
||||
|
@ -180,7 +197,8 @@ class product_pricelist(osv.osv):
|
|||
if ((v.date_start is False) or (v.date_start <= date)) and ((v.date_end is False) or (v.date_end >= date)):
|
||||
version = v
|
||||
break
|
||||
|
||||
if not version:
|
||||
raise osv.except_osv(_('Warning!'), _("At least one pricelist has no active version !\nPlease create or activate one."))
|
||||
categ_ids = {}
|
||||
for p in products:
|
||||
categ = p.categ_id
|
||||
|
@ -269,7 +287,8 @@ class product_pricelist(osv.osv):
|
|||
if price is not False:
|
||||
price_limit = price
|
||||
price = price * (1.0+(rule.price_discount or 0.0))
|
||||
price = rounding(price, rule.price_round) #TOFIX: rounding with tools.float_rouding
|
||||
if rule.price_round:
|
||||
price = tools.float_round(price, precision_rounding=rule.price_round)
|
||||
price += (rule.price_surcharge or 0.0)
|
||||
if rule.price_min_margin:
|
||||
price = max(price, price_limit+rule.price_min_margin)
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
import math
|
||||
import re
|
||||
|
||||
from _common import rounding
|
||||
from _common import ceiling
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
|
@ -178,7 +178,7 @@ class product_uom(osv.osv):
|
|||
return qty
|
||||
amount = qty / from_unit.factor
|
||||
if to_unit:
|
||||
amount = rounding(amount * to_unit.factor, to_unit.rounding)
|
||||
amount = ceiling(amount * to_unit.factor, to_unit.rounding)
|
||||
return amount
|
||||
|
||||
def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
|
||||
|
@ -527,6 +527,7 @@ class product_product(osv.osv):
|
|||
cr, uid, pricelist, operator='=', context=context, limit=1)
|
||||
pricelist = pricelist_ids[0][0] if pricelist_ids else pricelist
|
||||
|
||||
if isinstance(pricelist, (int, long)):
|
||||
products = self.browse(cr, uid, ids, context=context)
|
||||
qtys = map(lambda x: (x, quantity, partner), products)
|
||||
pl = plobj.browse(cr, uid, pricelist, context=context)
|
||||
|
@ -548,15 +549,10 @@ class product_product(osv.osv):
|
|||
_product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
|
||||
|
||||
def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
|
||||
res = dict.fromkeys(ids, 0.0)
|
||||
res = {}
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
|
||||
# retrieve pricelist
|
||||
pricelist = None
|
||||
if context.get('pricelist'):
|
||||
pricelist = self.pool['product.pricelist'].browse(cr, uid, context.get('pricelist'), context=context)
|
||||
base_currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id
|
||||
|
||||
for id in ids:
|
||||
res.setdefault(id, 0.0)
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
if 'uom' in context:
|
||||
uom = product.uos_id or product.uom_id
|
||||
|
@ -565,10 +561,6 @@ class product_product(osv.osv):
|
|||
else:
|
||||
res[product.id] = product.list_price
|
||||
res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
|
||||
# update the result, according to the eventual pricelist currency
|
||||
if pricelist and pricelist.currency_id:
|
||||
res[product.id] = self.pool['res.currency'].compute(
|
||||
cr, uid, base_currency.id, pricelist.currency_id.id, res[product.id], round=False, context=context)
|
||||
return res
|
||||
|
||||
def _save_product_lst_price(self, cr, uid, product_id, field_name, field_value, arg, context=None):
|
||||
|
|
|
@ -221,7 +221,6 @@
|
|||
<kanban>
|
||||
<field name="color"/>
|
||||
<field name="type"/>
|
||||
<field name="image_small"/>
|
||||
<field name="list_price"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
-
|
||||
In order to test conversation of UOM,
|
||||
-
|
||||
I convert Grams into TON with price.
|
||||
-
|
||||
!python {model: product.uom}: |
|
||||
from_uom_id = ref("product_uom_gram")
|
||||
to_uom_id = ref("product_uom_ton")
|
||||
price = 2
|
||||
qty = 1020000
|
||||
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 == 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,5 @@
|
|||
from . import test_uom
|
||||
|
||||
fast_suite = [
|
||||
test_uom,
|
||||
]
|
|
@ -0,0 +1,37 @@
|
|||
from openerp.tests.common import TransactionCase
|
||||
|
||||
class TestUom(TransactionCase):
|
||||
"""Tests for unit of measure conversion"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestUom, self).setUp()
|
||||
self.product = self.registry('product.product')
|
||||
self.uom = self.registry('product.uom')
|
||||
self.imd = self.registry('ir.model.data')
|
||||
|
||||
def test_10_conversion(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
gram_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_gram')[1]
|
||||
tonne_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_ton')[1]
|
||||
|
||||
qty = self.uom._compute_qty(cr, uid, gram_id, 1020000, tonne_id)
|
||||
self.assertEquals(qty, 1.02, "Converted quantity does not correspond.")
|
||||
|
||||
price = self.uom._compute_price(cr, uid, gram_id, 2, tonne_id)
|
||||
self.assertEquals(price, 2000000.0, "Converted price does not correspond.")
|
||||
|
||||
def test_20_rounding(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
unit_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_unit')[1]
|
||||
categ_unit_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_categ_unit')[1]
|
||||
|
||||
score_id = self.uom.create(cr, uid, {
|
||||
'name': 'Score',
|
||||
'factor_inv': 20,
|
||||
'uom_type': 'bigger',
|
||||
'rounding': 1.0,
|
||||
'category_id': categ_unit_id
|
||||
})
|
||||
|
||||
qty = self.uom._compute_qty(cr, uid, unit_id, 2, score_id)
|
||||
self.assertEquals(qty, 1, "Converted quantity should be rounded up.")
|
|
@ -0,0 +1,283 @@
|
|||
# Amharic translation for openobject-addons
|
||||
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:06+0000\n"
|
||||
"PO-Revision-Date: 2014-03-18 08:01+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Amharic <am@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-19 05:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
#: field:product.product,turnover:0
|
||||
msgid "Turnover"
|
||||
msgstr "ተመላሽ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,expected_margin_rate:0
|
||||
msgid "Expected Margin (%)"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.margin,from_date:0
|
||||
msgid "From"
|
||||
msgstr "ከ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,total_cost:0
|
||||
msgid ""
|
||||
"Sum of Multiplication of Invoice price and quantity of Supplier Invoices "
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.margin,to_date:0
|
||||
msgid "To"
|
||||
msgstr "ለ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,total_margin:0
|
||||
msgid "Turnover - Standard price"
|
||||
msgstr "የተመላሽ መደበኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,total_margin_rate:0
|
||||
msgid "Total Margin Rate(%)"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: selection:product.margin,invoice_state:0
|
||||
#: selection:product.product,invoice_state:0
|
||||
msgid "Draft, Open and Paid"
|
||||
msgstr "ያልተከፈለና የተከፈለ ደረሰኞች"
|
||||
|
||||
#. module: product_margin
|
||||
#: code:addons/product_margin/wizard/product_margin.py:73
|
||||
#: model:ir.actions.act_window,name:product_margin.product_margin_act_window
|
||||
#: model:ir.ui.menu,name:product_margin.menu_action_product_margin
|
||||
#: view:product.product:0
|
||||
#, python-format
|
||||
msgid "Product Margins"
|
||||
msgstr "የእቃው አይነት በአንድ መጠን ሲጨምር"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,purchase_avg_price:0
|
||||
#: field:product.product,sale_avg_price:0
|
||||
msgid "Avg. Unit Price"
|
||||
msgstr "የእቃዎች መካከለኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,sale_num_invoiced:0
|
||||
msgid "# Invoiced in Sale"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Catalog Price"
|
||||
msgstr "ቅናሽ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: selection:product.margin,invoice_state:0
|
||||
#: selection:product.product,invoice_state:0
|
||||
msgid "Paid"
|
||||
msgstr "ተከፈል"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
#: field:product.product,sales_gap:0
|
||||
msgid "Sales Gap"
|
||||
msgstr "የሽያጭ ክፍተት"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sales_gap:0
|
||||
msgid "Expected Sale - Turn Over"
|
||||
msgstr "ሊሸጥ የሚችል እቃ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,sale_expected:0
|
||||
msgid "Expected Sale"
|
||||
msgstr "ሊሸጥ የሚችል እቃ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Standard Price"
|
||||
msgstr "የእቃው መደበኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,purchase_num_invoiced:0
|
||||
msgid "Sum of Quantity in Supplier Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,date_to:0
|
||||
msgid "Margin Date To"
|
||||
msgstr "የእቃው መጠን የጨመረበት ቀን"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Analysis Criteria"
|
||||
msgstr "የመመዘኛ መስፈርት"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
#: field:product.product,total_cost:0
|
||||
msgid "Total Cost"
|
||||
msgstr "አጠቃላይ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,normal_cost:0
|
||||
msgid "Sum of Multiplication of Cost price and quantity of Supplier Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,expected_margin:0
|
||||
msgid "Expected Margin"
|
||||
msgstr "የሚጠበቅ ጭማሪ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "#Purchased"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,expected_margin_rate:0
|
||||
msgid "Expected margin * 100 / Expected Sale"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sale_avg_price:0
|
||||
msgid "Avg. Price in Customer Invoices."
|
||||
msgstr "የመካከለኛ ዋጋ ለገዢዎች"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,purchase_avg_price:0
|
||||
msgid "Avg. Price in Supplier Invoices "
|
||||
msgstr "የመካከለኛ ዋጋ አቅራቢዎች "
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.margin,invoice_state:0
|
||||
#: field:product.product,invoice_state:0
|
||||
msgid "Invoice State"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,purchase_gap:0
|
||||
msgid "Normal Cost - Total Cost"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sale_expected:0
|
||||
msgid ""
|
||||
"Sum of Multiplication of Sale Catalog price and quantity of Customer Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,total_margin:0
|
||||
msgid "Total Margin"
|
||||
msgstr "የሁሉም ዋጋ በአንድ መጠን ሲጨምር"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,date_from:0
|
||||
msgid "Margin Date From"
|
||||
msgstr "እቃው ከጨመረበት ቀን ጀምሮ"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,turnover:0
|
||||
msgid ""
|
||||
"Sum of Multiplication of Invoice price and quantity of Customer Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,normal_cost:0
|
||||
msgid "Normal Cost"
|
||||
msgstr "መደበኛ ዋጋ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Purchases"
|
||||
msgstr "ግዢዎች"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,purchase_num_invoiced:0
|
||||
msgid "# Invoiced in Purchase"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,expected_margin:0
|
||||
msgid "Expected Sale - Normal Cost"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "Properties categories"
|
||||
msgstr "በአይነታቸው መከፍፈል"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,total_margin_rate:0
|
||||
msgid "Total margin * 100 / Turnover"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "Open Margins"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: selection:product.margin,invoice_state:0
|
||||
#: selection:product.product,invoice_state:0
|
||||
msgid "Open and Paid"
|
||||
msgstr "የተከፈተና የትከፈል"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Sales"
|
||||
msgstr "ሽያጭ"
|
||||
|
||||
#. module: product_margin
|
||||
#: model:ir.model,name:product_margin.model_product_product
|
||||
msgid "Product"
|
||||
msgstr "ውጤት"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "General Information"
|
||||
msgstr "አጠቃላይ መርጃ"
|
||||
|
||||
#. module: product_margin
|
||||
#: field:product.product,purchase_gap:0
|
||||
msgid "Purchase Gap"
|
||||
msgstr "የግዢ ክፍተት"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "Cancel"
|
||||
msgstr "መሰረዝ"
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.product:0
|
||||
msgid "Margins"
|
||||
msgstr "በአንድ መጠን ሲጨምር"
|
||||
|
||||
#. module: product_margin
|
||||
#: help:product.product,sale_num_invoiced:0
|
||||
msgid "Sum of Quantity in Customer Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: product_margin
|
||||
#: view:product.margin:0
|
||||
msgid "or"
|
||||
msgstr "ወይም"
|
||||
|
||||
#. module: product_margin
|
||||
#: model:ir.model,name:product_margin.model_product_margin
|
||||
msgid "Product Margin"
|
||||
msgstr "የእቃው መጨመር"
|
|
@ -53,9 +53,9 @@ class product_product(osv.osv):
|
|||
states = ('draft', 'open', 'paid')
|
||||
|
||||
sqlstr="""select
|
||||
sum(l.price_unit * l.quantity)/sum(l.quantity) as avg_unit_price,
|
||||
sum(l.price_unit * l.quantity)/sum(nullif(l.quantity,0)) as avg_unit_price,
|
||||
sum(l.quantity) as num_qty,
|
||||
sum(l.quantity * (l.price_subtotal/l.quantity)) as total,
|
||||
sum(l.quantity * (l.price_subtotal/(nullif(l.quantity,0)))) as total,
|
||||
sum(l.quantity * pt.list_price) as sale_expected,
|
||||
sum(l.quantity * pt.standard_price) as normal_cost
|
||||
from account_invoice_line l
|
||||
|
|
|
@ -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: 2013-08-13 04:04+0000\n"
|
||||
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
|
||||
"PO-Revision-Date: 2014-03-15 06:42+0000\n"
|
||||
"Last-Translator: Victor Yu <vivianyw@163.com>\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-03-04 06:19+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-16 05:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: project
|
||||
#: view:project.project:0
|
||||
|
@ -671,7 +671,7 @@ msgstr "新任务"
|
|||
#. module: project
|
||||
#: field:project.config.settings,module_project_issue_sheet:0
|
||||
msgid "Invoice working time on issues"
|
||||
msgstr ""
|
||||
msgstr "依据问题工作时间开票"
|
||||
|
||||
#. module: project
|
||||
#: view:project.project:0
|
||||
|
@ -828,7 +828,7 @@ msgstr "花费的时间"
|
|||
#: view:project.project:0
|
||||
#: view:project.task:0
|
||||
msgid "í"
|
||||
msgstr ""
|
||||
msgstr "í"
|
||||
|
||||
#. module: project
|
||||
#: field:account.analytic.account,company_uom_id:0
|
||||
|
@ -958,7 +958,7 @@ msgstr "本项目和相关项目的任务实际花费的小时数合计"
|
|||
msgid ""
|
||||
"Follow this project to automatically track the events associated to tasks "
|
||||
"and issues of this project."
|
||||
msgstr ""
|
||||
msgstr "关注此项目的同时,追踪此项目的任务和问题关联的事件。"
|
||||
|
||||
#. module: project
|
||||
#: view:project.task:0
|
||||
|
@ -1018,7 +1018,7 @@ msgstr "信息和通信历史记录"
|
|||
msgid ""
|
||||
"To invoice or setup invoicing and renewal options, go to the related "
|
||||
"contract:"
|
||||
msgstr ""
|
||||
msgstr "开票或设定发票更新选项,到相关的合同:"
|
||||
|
||||
#. module: project
|
||||
#: field:project.task.delegate,state:0
|
||||
|
|
|
@ -48,8 +48,9 @@ class project_issue(osv.Model):
|
|||
_mail_post_access = 'read'
|
||||
_track = {
|
||||
'stage_id': {
|
||||
'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
|
||||
'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence != 1,
|
||||
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
|
||||
'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1,
|
||||
'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1,
|
||||
},
|
||||
'user_id': {
|
||||
'project_issue.mt_issue_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
|
||||
|
@ -74,7 +75,7 @@ class project_issue(osv.Model):
|
|||
def _get_default_stage_id(self, cr, uid, context=None):
|
||||
""" Gives default stage_id """
|
||||
project_id = self._get_default_project_id(cr, uid, context=context)
|
||||
return self.stage_find(cr, uid, [], project_id, [('sequence', '=', 1)], context=context)
|
||||
return self.stage_find(cr, uid, [], project_id, [('fold', '=', False)], context=context)
|
||||
|
||||
def _resolve_project_id_from_context(self, cr, uid, context=None):
|
||||
""" Returns ID of project based on the value of 'default_project_id'
|
||||
|
|
|
@ -200,7 +200,7 @@
|
|||
<group>
|
||||
<field name="date_order"/>
|
||||
<field name="origin" attrs="{'invisible': [('origin','=',False)]}"/>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
</group>
|
||||
</group>
|
||||
|
|
|
@ -488,7 +488,7 @@ class Report(http.Controller):
|
|||
|
||||
return request.make_response(barcode, headers=[('Content-Type', 'image/png')])
|
||||
|
||||
@http.route('/report/download/', type='http', auth="user")
|
||||
@http.route('/report/download', type='http', auth="user")
|
||||
def report_attachment(self, data, token):
|
||||
"""This function is used by 'qwebactionmanager.js' in order to trigger the download of
|
||||
a report of any type.
|
||||
|
@ -515,7 +515,7 @@ class Report(http.Controller):
|
|||
response.set_cookie('fileToken', token)
|
||||
return response
|
||||
|
||||
@http.route('/report/check_wkhtmltopdf/', type='json', auth="user")
|
||||
@http.route('/report/check_wkhtmltopdf', type='json', auth="user")
|
||||
def check_wkhtmltopdf(self):
|
||||
"""Check the presence of wkhtmltopdf and return its version. If wkhtmltopdf
|
||||
cannot be found, return False.
|
||||
|
|
|
@ -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: 2013-11-19 10:53+0000\n"
|
||||
"Last-Translator: Guipo Hao <hrlpool@sohu.com>\n"
|
||||
"PO-Revision-Date: 2014-03-15 07:15+0000\n"
|
||||
"Last-Translator: Victor Yu <vivianyw@163.com>\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-03-04 07:04+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-16 05:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: sale
|
||||
#: help:sale.order,message_summary:0
|
||||
|
@ -247,7 +247,7 @@ msgstr "计量单位 "
|
|||
#: code:addons/sale/wizard/sale_make_invoice_advance.py:101
|
||||
#, python-format
|
||||
msgid "Incorrect Data"
|
||||
msgstr ""
|
||||
msgstr "错误的数据"
|
||||
|
||||
#. module: sale
|
||||
#: code:addons/sale/wizard/sale_make_invoice_advance.py:102
|
||||
|
@ -1570,7 +1570,7 @@ msgstr "查找未开票明细"
|
|||
#. module: sale
|
||||
#: model:ir.model,name:sale.model_account_config_settings
|
||||
msgid "account.config.settings"
|
||||
msgstr ""
|
||||
msgstr "会计.配置.设定"
|
||||
|
||||
#. module: sale
|
||||
#: sql_constraint:sale.order:0
|
||||
|
@ -2080,7 +2080,7 @@ msgstr "产品销售单位"
|
|||
#. module: sale
|
||||
#: model:process.node,note:sale.process_node_quotation0
|
||||
msgid "Draft state of sales order"
|
||||
msgstr "销售订单草稿状态"
|
||||
msgstr "草稿状态的销售订单"
|
||||
|
||||
#. module: sale
|
||||
#: field:sale.order,origin:0
|
||||
|
@ -2286,7 +2286,9 @@ msgstr ""
|
|||
msgid ""
|
||||
"Before choosing a product,\n"
|
||||
" select a customer in the sales form."
|
||||
msgstr "在挑选产品前,在 销售表单选择一个客户"
|
||||
msgstr ""
|
||||
"在挑选产品前,\n"
|
||||
" 在销售表单中选择一个客户。"
|
||||
|
||||
#. module: sale
|
||||
#: view:sale.order:0
|
||||
|
|
|
@ -205,8 +205,8 @@
|
|||
<field groups="base.group_no_one" name="origin"/>
|
||||
</group>
|
||||
<group name="sale_pay">
|
||||
<field name="payment_term" widget="selection"/>
|
||||
<field name="fiscal_position" widget="selection"/>
|
||||
<field name="payment_term" options="{'no_create': True}"/>
|
||||
<field name="fiscal_position" options="{'no_create': True}"/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="section_id" widget="selection" groups="base.group_multi_salesteams"/>
|
||||
<field name="section_id" options="{'no_create': True}" groups="base.group_multi_salesteams"/>
|
||||
<field name="categ_ids" widget="many2many_tags"/>
|
||||
</field>
|
||||
</field>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<field name="company_id" readonly="True"/>
|
||||
</field>
|
||||
<field name="fiscal_position" position="after">
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
|
||||
</field>
|
||||
<field name="product_id" position="replace">
|
||||
<field name="product_id"
|
||||
|
|
|
@ -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:06+0000\n"
|
||||
"PO-Revision-Date: 2012-12-09 05:00+0000\n"
|
||||
"Last-Translator: sum1201 <Unknown>\n"
|
||||
"PO-Revision-Date: 2014-03-16 16:41+0000\n"
|
||||
"Last-Translator: Victor Yu <vivianyw@163.com>\n"
|
||||
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 07:34+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-17 04:48+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: share
|
||||
#: code:addons/share/wizard/share_wizard.py:842
|
||||
|
@ -76,6 +76,8 @@ msgid ""
|
|||
" %s\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"附件没弄好,你可以在OpenERP服务器上查看: %s\n"
|
||||
"\n"
|
||||
|
||||
#. module: share
|
||||
#: model:ir.module.category,name:share.module_category_share
|
||||
|
@ -147,7 +149,7 @@ msgstr "共享URL"
|
|||
#: code:addons/share/wizard/share_wizard.py:881
|
||||
#, python-format
|
||||
msgid "These are your credentials to access this protected area:\n"
|
||||
msgstr ""
|
||||
msgstr "这是你访问这个保护区域的凭证:\n"
|
||||
|
||||
#. module: share
|
||||
#: view:share.wizard:0
|
||||
|
@ -173,7 +175,7 @@ msgstr ""
|
|||
#, python-format
|
||||
msgid ""
|
||||
"Please indicate the emails of the persons to share with, one per line."
|
||||
msgstr ""
|
||||
msgstr "请标明分享的用户电子邮箱,每行一个。"
|
||||
|
||||
#. module: share
|
||||
#: help:share.wizard,domain:0
|
||||
|
@ -189,7 +191,7 @@ msgstr "下一个"
|
|||
#: code:addons/share/wizard/share_wizard.py:662
|
||||
#, python-format
|
||||
msgid "Action and Access Mode are required to create a shared access."
|
||||
msgstr ""
|
||||
msgstr "操作和访问模式都需要要创建一个共享入口。"
|
||||
|
||||
#. module: share
|
||||
#: code:addons/share/wizard/share_wizard.py:850
|
||||
|
@ -256,7 +258,7 @@ msgstr "非共享组"
|
|||
msgid ""
|
||||
"An email notification with instructions has been sent to the following "
|
||||
"people:"
|
||||
msgstr ""
|
||||
msgstr "带有操作说明的邮件通知已经发送到以下人了:"
|
||||
|
||||
#. module: share
|
||||
#: code:addons/share/wizard/share_wizard.py:77
|
||||
|
@ -273,6 +275,8 @@ msgid ""
|
|||
"Sales, HR, etc.)\n"
|
||||
"It is open source and can be found on http://www.openerp.com."
|
||||
msgstr ""
|
||||
"OpenERP是强大的、用户友好的商业应用套装(CRM,销售、人事、等)\n"
|
||||
"而且是开源的,可以在 http://www.openerp.com 找到并下载。"
|
||||
|
||||
#. module: share
|
||||
#: field:share.wizard,action_id:0
|
||||
|
@ -282,7 +286,7 @@ msgstr "共享操作"
|
|||
#. module: share
|
||||
#: help:share.wizard,record_name:0
|
||||
msgid "Name of the shared record, if sharing a precise record"
|
||||
msgstr ""
|
||||
msgstr "如果共享一个精确记录,记录下分享名"
|
||||
|
||||
#. module: share
|
||||
#: field:res.users,share:0
|
||||
|
@ -337,7 +341,7 @@ msgstr "创建新的"
|
|||
#. module: share
|
||||
#: help:share.wizard,name:0
|
||||
msgid "Title for the share (displayed to users as menu and shortcut name)"
|
||||
msgstr ""
|
||||
msgstr "共享标题(显示给用户的菜单和快捷方式的名称)"
|
||||
|
||||
#. module: share
|
||||
#: code:addons/share/wizard/share_wizard.py:636
|
||||
|
@ -348,7 +352,7 @@ msgstr "创建用户 %s (%s )间接共享过滤器,在用户组 %s"
|
|||
#. module: share
|
||||
#: help:share.wizard,share_root_url:0
|
||||
msgid "Main access page for users that are granted shared access"
|
||||
msgstr ""
|
||||
msgstr "用户主要访问的页面被授权共享访问"
|
||||
|
||||
#. module: share
|
||||
#: code:addons/share/wizard/share_wizard.py:206
|
||||
|
@ -356,7 +360,7 @@ msgstr ""
|
|||
msgid ""
|
||||
"You must configure your email address in the user preferences before using "
|
||||
"the Share button."
|
||||
msgstr ""
|
||||
msgstr "在使用分享按钮前,你必须在用户首选项处配置你的电子邮件。"
|
||||
|
||||
#. module: share
|
||||
#: model:res.groups,name:share.group_share_user
|
||||
|
@ -387,7 +391,7 @@ msgstr "数据库"
|
|||
#. module: share
|
||||
#: view:share.wizard:0
|
||||
msgid "Share with these People (one email per line)"
|
||||
msgstr ""
|
||||
msgstr "与这些人分享(每行一个电子邮件)"
|
||||
|
||||
#. module: share
|
||||
#: field:share.wizard,domain:0
|
||||
|
@ -409,7 +413,7 @@ msgstr "简述"
|
|||
#: help:share.wizard,embed_code:0
|
||||
msgid ""
|
||||
"Embed this code in your documents to provide a link to the shared document."
|
||||
msgstr ""
|
||||
msgstr "在你的文档中嵌入下列代码以提供到分享文档的连接。"
|
||||
|
||||
#. module: share
|
||||
#: code:addons/share/wizard/share_wizard.py:513
|
||||
|
@ -431,7 +435,7 @@ msgstr "共享你的单据"
|
|||
#. module: share
|
||||
#: view:share.wizard:0
|
||||
msgid "Or insert the following code where you want to embed your documents"
|
||||
msgstr ""
|
||||
msgstr "或者在你想要嵌入你的文档时,插入下列代码"
|
||||
|
||||
#. module: share
|
||||
#: code:addons/share/wizard/share_wizard.py:886
|
||||
|
@ -439,7 +443,7 @@ msgstr ""
|
|||
msgid ""
|
||||
"The documents have been automatically added to your current OpenERP "
|
||||
"documents.\n"
|
||||
msgstr ""
|
||||
msgstr "一份文档已经被自动增加到你现在的OpenERP文档中了。\n"
|
||||
|
||||
#. module: share
|
||||
#: model:ir.model,name:share.model_share_wizard_result_line
|
||||
|
|
|
@ -906,7 +906,7 @@ class share_result_line(osv.osv_memory):
|
|||
'login': fields.related('user_id', 'login', string='Login', type='char', size=64, required=True, readonly=True),
|
||||
'password': fields.char('Password', size=64, readonly=True),
|
||||
'share_url': fields.function(_share_url, string='Share URL', type='char', size=512),
|
||||
'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True),
|
||||
'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True, ondelete='cascade'),
|
||||
'newly_created': fields.boolean('Newly created', readonly=True),
|
||||
}
|
||||
_defaults = {
|
||||
|
|
|
@ -2232,14 +2232,14 @@ class stock_move(osv.osv):
|
|||
if move.picking_id:
|
||||
pickings.add(move.picking_id.id)
|
||||
if move.move_dest_id and move.move_dest_id.state == 'waiting':
|
||||
self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'})
|
||||
self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context)
|
||||
if context.get('call_unlink',False) and move.move_dest_id.picking_id:
|
||||
workflow.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
|
||||
self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False})
|
||||
self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context)
|
||||
if not context.get('call_unlink',False):
|
||||
for pick in self.pool.get('stock.picking').browse(cr, uid, list(pickings), context=context):
|
||||
if all(move.state == 'cancel' for move in pick.move_lines):
|
||||
self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'})
|
||||
self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'}, context=context)
|
||||
|
||||
for id in ids:
|
||||
workflow.trg_trigger(uid, 'stock.move', id, cr)
|
||||
|
|
|
@ -792,7 +792,7 @@
|
|||
</group>
|
||||
<notebook>
|
||||
<page string="Products">
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'picking_type': 'internal'}" options='{"reload_on_button": true}'/>
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree', 'picking_type': 'internal'}" options='{"reload_on_button": true}'/>
|
||||
<field name="note" placeholder="Add an internal note..." class="oe_inline"/>
|
||||
</page>
|
||||
<page string="Additional Info">
|
||||
|
@ -926,7 +926,7 @@
|
|||
<field name="partner_id" on_change="onchange_partner_in(partner_id)" string="Customer" domain="[('customer','=',True)]" />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='move_lines']" position="replace">
|
||||
<field name="move_lines" context="{'address_out_id': partner_id, 'picking_type': 'out', 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
<field name="move_lines" context="{'address_out_id': partner_id, 'picking_type': 'out', 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet" position="after">
|
||||
<div class="oe_chatter">
|
||||
|
@ -1053,7 +1053,7 @@
|
|||
<field name="partner_id" on_change="onchange_partner_in(partner_id)" string="Supplier" domain="[('supplier','=',True)]" context="{'default_supplier':1,'default_customer':0}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='move_lines']" position="replace">
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'picking_type': 'in', 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
<field name="move_lines" context="{'address_in_id': partner_id, 'picking_type': 'in', 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'stock.view_move_picking_tree'}" options='{"reload_on_button": true}'/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet" position="after">
|
||||
<div class="oe_chatter">
|
||||
|
|
|
@ -106,15 +106,16 @@ class stock_fill_inventory(osv.osv_memory):
|
|||
datas = {}
|
||||
res[location] = {}
|
||||
move_ids = move_obj.search(cr, uid, ['|',('location_dest_id','=',location),('location_id','=',location),('state','=','done')], context=context)
|
||||
|
||||
local_context = dict(context)
|
||||
local_context['raise-exception'] = False
|
||||
for move in move_obj.browse(cr, uid, move_ids, context=context):
|
||||
lot_id = move.prodlot_id.id
|
||||
prod_id = move.product_id.id
|
||||
if move.location_dest_id.id != move.location_id.id:
|
||||
if move.location_dest_id.id == location:
|
||||
qty = uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id)
|
||||
qty = uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
|
||||
else:
|
||||
qty = -uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id)
|
||||
qty = -uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
|
||||
|
||||
|
||||
if datas.get((prod_id, lot_id)):
|
||||
|
|
|
@ -256,20 +256,18 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
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, {
|
||||
Attachments = request.registry['ir.attachment']
|
||||
attachment_id = Attachments.create(request.cr, request.uid, {
|
||||
'name': upload.filename,
|
||||
'datas': image_data.encode('base64'),
|
||||
'datas_fname': upload.filename,
|
||||
'res_model': 'ir.ui.view',
|
||||
}, request.context)
|
||||
|
||||
url = website.urlplus('/website/image', {
|
||||
'model': 'ir.attachment',
|
||||
'id': attachment_id,
|
||||
'field': 'datas',
|
||||
'max_height': MAX_IMAGE_HEIGHT,
|
||||
'max_width': MAX_IMAGE_WIDTH,
|
||||
})
|
||||
[attachment] = Attachments.read(
|
||||
request.cr, request.uid, [attachment_id], ['website_url'],
|
||||
context=request.context)
|
||||
url = attachment['website_url']
|
||||
except Exception, e:
|
||||
logger.exception("Failed to upload image to attachment")
|
||||
message = unicode(e)
|
||||
|
@ -316,6 +314,23 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
'/website/image/<model>/<id>/<field>'
|
||||
], auth="public", website=True)
|
||||
def website_image(self, model, id, field, max_width=maxint, max_height=maxint):
|
||||
""" Fetches the requested field and ensures it does not go above
|
||||
(max_width, max_height), resizing it if necessary.
|
||||
|
||||
Resizing is bypassed if the object provides a $field_big, which will
|
||||
be interpreted as a pre-resized version of the base field.
|
||||
|
||||
If the record is not found or does not have the requested field,
|
||||
returns a placeholder image via :meth:`~.placeholder`.
|
||||
|
||||
Sets and checks conditional response parameters:
|
||||
* :mailheader:`ETag` is always set (and checked)
|
||||
* :mailheader:`Last-Modified is set iif the record has a concurrency
|
||||
field (``__last_update``)
|
||||
|
||||
The requested field is assumed to be base64-encoded image data in
|
||||
all cases.
|
||||
"""
|
||||
Model = request.registry[model]
|
||||
|
||||
response = werkzeug.wrappers.Response()
|
||||
|
@ -330,9 +345,11 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
if not ids:
|
||||
return self.placeholder(response)
|
||||
|
||||
presized = '%s_big' % field
|
||||
concurrency = '__last_update'
|
||||
[record] = Model.read(request.cr, openerp.SUPERUSER_ID, [id],
|
||||
[concurrency, field], context=request.context)
|
||||
[concurrency, field, presized],
|
||||
context=request.context)
|
||||
|
||||
if concurrency in record:
|
||||
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
@ -356,25 +373,28 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
if response.status_code == 304:
|
||||
return response
|
||||
|
||||
data = record[field].decode('base64')
|
||||
fit = int(max_width), int(max_height)
|
||||
data = (record.get(presized) or record[field]).decode('base64')
|
||||
|
||||
buf = cStringIO.StringIO(data)
|
||||
|
||||
image = Image.open(buf)
|
||||
image.load()
|
||||
image = Image.open(cStringIO.StringIO(data))
|
||||
response.mimetype = Image.MIME[image.format]
|
||||
|
||||
# record provides a pre-resized version of the base field, use that
|
||||
# directly
|
||||
if record.get(presized):
|
||||
response.set_data(data)
|
||||
return response
|
||||
|
||||
fit = int(max_width), int(max_height)
|
||||
w, h = image.size
|
||||
max_w, max_h = fit
|
||||
|
||||
if w < max_w and h < max_h:
|
||||
response.data = data
|
||||
response.set_data(data)
|
||||
else:
|
||||
image.thumbnail(fit, Image.ANTIALIAS)
|
||||
image.save(response.stream, image.format)
|
||||
# invalidate content-length computed by make_conditional as writing
|
||||
# to response.stream does not do it (as of werkzeug 0.9.3)
|
||||
# invalidate content-length computed by make_conditional as
|
||||
# writing to response.stream does not do it (as of werkzeug 0.9.3)
|
||||
del response.headers['Content-Length']
|
||||
|
||||
return response
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<t t-set="additional_title">Partner Detail</t>
|
||||
<div id="wrap">
|
||||
<div class="oe_structure">
|
||||
<section data-snippet-id="title">
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
@ -73,7 +73,7 @@ response = request.website.render("website.template_partner_post", values)
|
|||
<t t-set="additional_title">Partners</t>
|
||||
<div id="wrap">
|
||||
<div class="oe_structure">
|
||||
<section data-snippet-id="title">
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
|
|
@ -78,21 +78,21 @@ class ir_http(orm.AbstractModel):
|
|||
|
||||
return self._dispatch()
|
||||
|
||||
def _postprocess_args(self, arguments):
|
||||
url = request.httprequest.url
|
||||
for arg in arguments.itervalues():
|
||||
if isinstance(arg, orm.browse_record) and isinstance(arg._uid, RequestUID):
|
||||
placeholder = arg._uid
|
||||
arg._uid = request.uid
|
||||
def _postprocess_args(self, arguments, rule):
|
||||
if not getattr(request, 'website_enabled', False):
|
||||
return super(ir_http, self)._postprocess_args(arguments, rule)
|
||||
|
||||
for arg, val in arguments.items():
|
||||
# Replace uid placeholder by the current request.uid
|
||||
if isinstance(val, orm.browse_record) and isinstance(val._uid, RequestUID):
|
||||
val._uid = request.uid
|
||||
try:
|
||||
good_slug = slug(arg)
|
||||
if str(arg.id) != placeholder.value and placeholder.value != good_slug:
|
||||
# TODO: properly recompose the url instead of using replace()
|
||||
url = url.replace(placeholder.value, good_slug)
|
||||
except KeyError:
|
||||
_, path = rule.build(arguments)
|
||||
assert path is not None
|
||||
except Exception:
|
||||
return self._handle_exception(werkzeug.exceptions.NotFound())
|
||||
if url != request.httprequest.url:
|
||||
werkzeug.exceptions.abort(werkzeug.utils.redirect(url))
|
||||
if path != request.httprequest.path:
|
||||
return werkzeug.utils.redirect(path)
|
||||
|
||||
def _handle_exception(self, exception=None, code=500):
|
||||
if isinstance(exception, werkzeug.exceptions.HTTPException) and hasattr(exception, 'response') and exception.response:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import copy
|
||||
import re
|
||||
import simplejson
|
||||
import werkzeug
|
||||
|
||||
|
@ -158,6 +159,29 @@ class view(osv.osv):
|
|||
|
||||
return super(view, self).render(cr, uid, id_or_xml_id, values=values, engine=engine, context=context)
|
||||
|
||||
def _pretty_arch(self, arch):
|
||||
# remove_blank_string does not seem to work on HTMLParser, and
|
||||
# pretty-printing with lxml more or less requires stripping
|
||||
# whitespace: http://lxml.de/FAQ.html#why-doesn-t-the-pretty-print-option-reformat-my-xml-output
|
||||
# so serialize to XML, parse as XML (remove whitespace) then serialize
|
||||
# as XML (pretty print)
|
||||
arch_no_whitespace = etree.fromstring(
|
||||
etree.tostring(arch, encoding='utf-8'),
|
||||
parser=etree.XMLParser(encoding='utf-8', remove_blank_text=True))
|
||||
arch_pretty_indent_2 = etree.tostring(
|
||||
arch_no_whitespace, encoding='unicode', pretty_print=True)
|
||||
|
||||
# pretty_print uses a fixed indent level of 2, we want an indent of 4,
|
||||
# double up leading spaces.
|
||||
def repl(m):
|
||||
indent = len(m.group(0)) / 2
|
||||
return u' ' * 4 * indent
|
||||
# FIXME: If py2.7 only, can use re.M in sub and don't have to do replacement line by line
|
||||
return u'\n'.join(
|
||||
re.sub(ur'^((?: )+)', repl, line)
|
||||
for line in arch_pretty_indent_2.split(u'\n')
|
||||
)
|
||||
|
||||
def save(self, cr, uid, res_id, value, xpath=None, context=None):
|
||||
""" Update a view section. The view section may embed fields to write
|
||||
|
||||
|
@ -183,5 +207,5 @@ class view(osv.osv):
|
|||
|
||||
arch = self.replace_arch_section(cr, uid, res_id, xpath, arch_section, context=context)
|
||||
self.write(cr, uid, res_id, {
|
||||
'arch': etree.tostring(arch, encoding='utf-8').decode('utf-8')
|
||||
'arch': self._pretty_arch(arch)
|
||||
}, context=context)
|
||||
|
|
|
@ -3,7 +3,7 @@ from openerp.osv import fields, osv
|
|||
|
||||
class website_config_settings(osv.osv_memory):
|
||||
_name = 'website.config.settings'
|
||||
_inherit = 'base.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
_columns = {
|
||||
'website_id': fields.many2one('website', string="website", required=True),
|
||||
|
|
|
@ -9,6 +9,7 @@ import urlparse
|
|||
|
||||
import werkzeug
|
||||
import werkzeug.exceptions
|
||||
import werkzeug.utils
|
||||
import werkzeug.wrappers
|
||||
# optional python-slugify import (https://github.com/un33k/python-slugify)
|
||||
try:
|
||||
|
@ -88,7 +89,7 @@ def slug(value):
|
|||
else:
|
||||
# assume name_search result tuple
|
||||
id, name = value
|
||||
slugname = slugify(name)
|
||||
slugname = slugify(name or '')
|
||||
if not slugname:
|
||||
return str(id)
|
||||
return "%s-%d" % (slugname, id)
|
||||
|
@ -579,10 +580,26 @@ class ir_attachment(osv.osv):
|
|||
return hashlib.new('sha1', attachment_dict['datas']).hexdigest()
|
||||
return None
|
||||
|
||||
def _datas_big(self, cr, uid, ids, name, arg, context=None):
|
||||
result = dict.fromkeys(ids, False)
|
||||
if context and context.get('bin_size'):
|
||||
return result
|
||||
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
if not record.datas: continue
|
||||
try:
|
||||
result[record.id] = openerp.tools.image_resize_image_big(record.datas)
|
||||
except IOError: # apparently the error PIL.Image.open raises
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'datas_checksum': fields.function(_datas_checksum, size=40,
|
||||
string="Datas checksum", type='char', store=True, select=True),
|
||||
'website_url': fields.function(_website_url_get, string="Attachment URL", type='char')
|
||||
'website_url': fields.function(_website_url_get, string="Attachment URL", type='char'),
|
||||
'datas_big': fields.function (_datas_big, type='binary', store=True,
|
||||
string="Resized file content"),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
|
@ -594,13 +611,40 @@ class ir_attachment(osv.osv):
|
|||
return super(ir_attachment, self).create(
|
||||
cr, uid, values, context=context)
|
||||
|
||||
def try_remove(self, cr, uid, ids, context=None):
|
||||
""" Removes a web-based image attachment if it is used by no view
|
||||
(template)
|
||||
|
||||
Returns a dict mapping attachments which would not be removed (if any)
|
||||
mapped to the views preventing their removal
|
||||
"""
|
||||
Views = self.pool['ir.ui.view']
|
||||
attachments_to_remove = []
|
||||
# views blocking removal of the attachment
|
||||
removal_blocked_by = {}
|
||||
|
||||
for attachment in self.browse(cr, uid, ids, context=context):
|
||||
# in-document URLs are html-escaped, a straight search will not
|
||||
# find them
|
||||
url = werkzeug.utils.escape(attachment.website_url)
|
||||
ids = Views.search(cr, uid, [('arch', 'like', url)], context=context)
|
||||
|
||||
if ids:
|
||||
removal_blocked_by[attachment.id] = Views.read(
|
||||
cr, uid, ids, ['name'], context=context)
|
||||
else:
|
||||
attachments_to_remove.append(attachment.id)
|
||||
if attachments_to_remove:
|
||||
self.unlink(cr, uid, attachments_to_remove, context=context)
|
||||
return removal_blocked_by
|
||||
|
||||
class res_partner(osv.osv):
|
||||
_inherit = "res.partner"
|
||||
|
||||
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
|
||||
partner = self.browse(cr, uid, ids[0], context=context)
|
||||
params = {
|
||||
'center': '%s, %s %s, %s' % (partner.street, partner.city, partner.zip, partner.country_id and partner.country_id.name_get()[0][1] or ''),
|
||||
'center': '%s, %s %s, %s' % (partner.street or '', partner.city or '', partner.zip or '', partner.country_id and partner.country_id.name_get()[0][1] or ''),
|
||||
'size': "%sx%s" % (height, width),
|
||||
'zoom': zoom,
|
||||
'sensor': 'false',
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*! http://mths.be/placeholder v2.0.7 by @mathias */
|
||||
;(function(window, document, $) {
|
||||
|
||||
// Opera Mini v7 doesn’t support placeholder although its DOM seems to indicate so
|
||||
var isOperaMini = Object.prototype.toString.call(window.operamini) == '[object OperaMini]';
|
||||
var isInputSupported = 'placeholder' in document.createElement('input') && !isOperaMini;
|
||||
var isTextareaSupported = 'placeholder' in document.createElement('textarea') && !isOperaMini;
|
||||
var prototype = $.fn;
|
||||
var valHooks = $.valHooks;
|
||||
var propHooks = $.propHooks;
|
||||
var hooks;
|
||||
var placeholder;
|
||||
|
||||
if (isInputSupported && isTextareaSupported) {
|
||||
|
||||
placeholder = prototype.placeholder = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
placeholder.input = placeholder.textarea = true;
|
||||
|
||||
} else {
|
||||
|
||||
placeholder = prototype.placeholder = function() {
|
||||
var $this = this;
|
||||
$this
|
||||
.filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
|
||||
.not('.placeholder')
|
||||
.bind({
|
||||
'focus.placeholder': clearPlaceholder,
|
||||
'blur.placeholder': setPlaceholder
|
||||
})
|
||||
.data('placeholder-enabled', true)
|
||||
.trigger('blur.placeholder');
|
||||
return $this;
|
||||
};
|
||||
|
||||
placeholder.input = isInputSupported;
|
||||
placeholder.textarea = isTextareaSupported;
|
||||
|
||||
hooks = {
|
||||
'get': function(element) {
|
||||
var $element = $(element);
|
||||
|
||||
var $passwordInput = $element.data('placeholder-password');
|
||||
if ($passwordInput) {
|
||||
return $passwordInput[0].value;
|
||||
}
|
||||
|
||||
return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value;
|
||||
},
|
||||
'set': function(element, value) {
|
||||
var $element = $(element);
|
||||
|
||||
var $passwordInput = $element.data('placeholder-password');
|
||||
if ($passwordInput) {
|
||||
return $passwordInput[0].value = value;
|
||||
}
|
||||
|
||||
if (!$element.data('placeholder-enabled')) {
|
||||
return element.value = value;
|
||||
}
|
||||
if (value == '') {
|
||||
element.value = value;
|
||||
// Issue #56: Setting the placeholder causes problems if the element continues to have focus.
|
||||
if (element != safeActiveElement()) {
|
||||
// We can't use `triggerHandler` here because of dummy text/password inputs :(
|
||||
setPlaceholder.call(element);
|
||||
}
|
||||
} else if ($element.hasClass('placeholder')) {
|
||||
clearPlaceholder.call(element, true, value) || (element.value = value);
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
// `set` can not return `undefined`; see http://jsapi.info/jquery/1.7.1/val#L2363
|
||||
return $element;
|
||||
}
|
||||
};
|
||||
|
||||
if (!isInputSupported) {
|
||||
valHooks.input = hooks;
|
||||
propHooks.value = hooks;
|
||||
}
|
||||
if (!isTextareaSupported) {
|
||||
valHooks.textarea = hooks;
|
||||
propHooks.value = hooks;
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// Look for forms
|
||||
$(document).delegate('form', 'submit.placeholder', function() {
|
||||
// Clear the placeholder values so they don't get submitted
|
||||
var $inputs = $('.placeholder', this).each(clearPlaceholder);
|
||||
setTimeout(function() {
|
||||
$inputs.each(setPlaceholder);
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
|
||||
// Clear placeholder values upon page reload
|
||||
$(window).bind('beforeunload.placeholder', function() {
|
||||
$('.placeholder').each(function() {
|
||||
this.value = '';
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function args(elem) {
|
||||
// Return an object of element attributes
|
||||
var newAttrs = {};
|
||||
var rinlinejQuery = /^jQuery\d+$/;
|
||||
$.each(elem.attributes, function(i, attr) {
|
||||
if (attr.specified && !rinlinejQuery.test(attr.name)) {
|
||||
newAttrs[attr.name] = attr.value;
|
||||
}
|
||||
});
|
||||
return newAttrs;
|
||||
}
|
||||
|
||||
function clearPlaceholder(event, value) {
|
||||
var input = this;
|
||||
var $input = $(input);
|
||||
if (input.value == $input.attr('placeholder') && $input.hasClass('placeholder')) {
|
||||
if ($input.data('placeholder-password')) {
|
||||
$input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id'));
|
||||
// If `clearPlaceholder` was called from `$.valHooks.input.set`
|
||||
if (event === true) {
|
||||
return $input[0].value = value;
|
||||
}
|
||||
$input.focus();
|
||||
} else {
|
||||
input.value = '';
|
||||
$input.removeClass('placeholder');
|
||||
input == safeActiveElement() && input.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setPlaceholder() {
|
||||
var $replacement;
|
||||
var input = this;
|
||||
var $input = $(input);
|
||||
var id = this.id;
|
||||
if (input.value == '') {
|
||||
if (input.type == 'password') {
|
||||
if (!$input.data('placeholder-textinput')) {
|
||||
try {
|
||||
$replacement = $input.clone().attr({ 'type': 'text' });
|
||||
} catch(e) {
|
||||
$replacement = $('<input>').attr($.extend(args(this), { 'type': 'text' }));
|
||||
}
|
||||
$replacement
|
||||
.removeAttr('name')
|
||||
.data({
|
||||
'placeholder-password': $input,
|
||||
'placeholder-id': id
|
||||
})
|
||||
.bind('focus.placeholder', clearPlaceholder);
|
||||
$input
|
||||
.data({
|
||||
'placeholder-textinput': $replacement,
|
||||
'placeholder-id': id
|
||||
})
|
||||
.before($replacement);
|
||||
}
|
||||
$input = $input.removeAttr('id').hide().prev().attr('id', id).show();
|
||||
// Note: `$input[0] != input` now!
|
||||
}
|
||||
$input.addClass('placeholder');
|
||||
$input[0].value = $input.attr('placeholder');
|
||||
} else {
|
||||
$input.removeClass('placeholder');
|
||||
}
|
||||
}
|
||||
|
||||
function safeActiveElement() {
|
||||
// Avoid IE9 `document.activeElement` of death
|
||||
// https://github.com/mathiasbynens/jquery-placeholder/pull/99
|
||||
try {
|
||||
return document.activeElement;
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
}(this, document, jQuery));
|
|
@ -0,0 +1,27 @@
|
|||
#website-top-navbar a.btn-link {
|
||||
color: #ad1d28;
|
||||
}
|
||||
.oe_product section {
|
||||
background: rgba(16, 138, 147, 0.75) !important;
|
||||
}
|
||||
.oe_product section .text-info {
|
||||
color: #DFD6F5;
|
||||
}
|
||||
ul.wizard li {
|
||||
background: #debb27 !important;
|
||||
}
|
||||
ul.wizard .chevron:before {
|
||||
border-left: 10px solid #debb27 !important;
|
||||
}
|
||||
ul.wizard .text-primary {
|
||||
color: #ad1d28 !important;
|
||||
}
|
||||
ul.wizard .text-success {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before {
|
||||
color: #79D5DB !important;
|
||||
}
|
||||
.input-group-addon .fa {
|
||||
color: #444;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#website-top-navbar button.btn-primary {
|
||||
color: #fff;
|
||||
border: 2px solid #0061c2;
|
||||
}
|
||||
#website-top-navbar a.btn-link {
|
||||
color: #fff;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue