[MERGE] from trunk
bzr revid: chm@openerp.com-20140307104233-fqz4jwzf5yj6jtm2
This commit is contained in:
commit
23eca80556
|
@ -114,7 +114,7 @@ class account_invoice(osv.osv):
|
|||
#we check if the invoice is partially reconciled and if there are other invoices
|
||||
#involved in this partial reconciliation (and we sum these invoices)
|
||||
for line in aml.reconcile_partial_id.line_partial_ids:
|
||||
if line.invoice:
|
||||
if line.invoice and invoice.type == line.invoice.type:
|
||||
nb_inv_in_partial_rec += 1
|
||||
#store the max invoice id as for this invoice we will make a balance instead of a simple division
|
||||
max_invoice_id = max(max_invoice_id, line.invoice.id)
|
||||
|
|
|
@ -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" 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"/>
|
||||
|
||||
|
|
|
@ -1453,7 +1453,7 @@
|
|||
<act_window
|
||||
id="act_account_move_to_account_move_line_open"
|
||||
name="Journal Items"
|
||||
context="{'search_default_journal_id': active_id, 'default_journal_id': active_id}"
|
||||
context="{'search_default_move_id': active_id, 'default_move_id': active_id}"
|
||||
res_model="account.move.line"
|
||||
src_model="account.move"/>
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ openerp.account.quickadd = function (instance) {
|
|||
start:function(){
|
||||
var tmp = this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
var defs = [];
|
||||
this.$el.parent().prepend(QWeb.render("AccountMoveLineQuickAdd", {widget: this}));
|
||||
|
||||
this.$el.parent().find('.oe_account_select_journal').change(function() {
|
||||
|
@ -41,11 +42,17 @@ openerp.account.quickadd = function (instance) {
|
|||
self.$el.parent().find('.oe_account_select_period').removeAttr('disabled');
|
||||
});
|
||||
var mod = new instance.web.Model("account.move.line", self.dataset.context, self.dataset.domain);
|
||||
mod.call("default_get", [['journal_id','period_id'],self.dataset.context]).then(function(result) {
|
||||
defs.push(mod.call("default_get", [['journal_id','period_id'],self.dataset.context]).then(function(result) {
|
||||
self.current_period = result['period_id'];
|
||||
self.current_journal = result['journal_id'];
|
||||
});
|
||||
return tmp;
|
||||
}));
|
||||
defs.push(mod.call("list_journals", []).then(function(result) {
|
||||
self.journals = result;
|
||||
}));
|
||||
defs.push(mod.call("list_periods", []).then(function(result) {
|
||||
self.periods = result;
|
||||
}));
|
||||
return $.when(tmp, defs);
|
||||
},
|
||||
do_search: function(domain, context, group_by) {
|
||||
var self = this;
|
||||
|
@ -53,34 +60,27 @@ openerp.account.quickadd = function (instance) {
|
|||
this.last_context = context;
|
||||
this.last_group_by = group_by;
|
||||
this.old_search = _.bind(this._super, this);
|
||||
var mod = new instance.web.Model("account.move.line", context, domain);
|
||||
return $.when(mod.call("list_journals", []).then(function(result) {
|
||||
self.journals = result;
|
||||
}),mod.call("list_periods", []).then(function(result) {
|
||||
self.periods = result;
|
||||
})).then(function () {
|
||||
var o;
|
||||
self.$el.parent().find('.oe_account_select_journal').children().remove().end();
|
||||
self.$el.parent().find('.oe_account_select_journal').append(new Option('', ''));
|
||||
for (var i = 0;i < self.journals.length;i++){
|
||||
o = new Option(self.journals[i][1], self.journals[i][0]);
|
||||
if (self.journals[i][0] === self.current_journal){
|
||||
self.current_journal_type = self.journals[i][2];
|
||||
self.current_journal_currency = self.journals[i][3];
|
||||
self.current_journal_analytic = self.journals[i][4];
|
||||
$(o).attr('selected',true);
|
||||
}
|
||||
self.$el.parent().find('.oe_account_select_journal').append(o);
|
||||
var o;
|
||||
self.$el.parent().find('.oe_account_select_journal').children().remove().end();
|
||||
self.$el.parent().find('.oe_account_select_journal').append(new Option('', ''));
|
||||
for (var i = 0;i < self.journals.length;i++){
|
||||
o = new Option(self.journals[i][1], self.journals[i][0]);
|
||||
if (self.journals[i][0] === self.current_journal){
|
||||
self.current_journal_type = self.journals[i][2];
|
||||
self.current_journal_currency = self.journals[i][3];
|
||||
self.current_journal_analytic = self.journals[i][4];
|
||||
$(o).attr('selected',true);
|
||||
}
|
||||
self.$el.parent().find('.oe_account_select_period').children().remove().end();
|
||||
self.$el.parent().find('.oe_account_select_period').append(new Option('', ''));
|
||||
for (var i = 0;i < self.periods.length;i++){
|
||||
o = new Option(self.periods[i][1], self.periods[i][0]);
|
||||
self.$el.parent().find('.oe_account_select_period').append(o);
|
||||
}
|
||||
self.$el.parent().find('.oe_account_select_period').val(self.current_period).attr('selected',true);
|
||||
return self.search_by_journal_period();
|
||||
});
|
||||
self.$el.parent().find('.oe_account_select_journal').append(o);
|
||||
}
|
||||
self.$el.parent().find('.oe_account_select_period').children().remove().end();
|
||||
self.$el.parent().find('.oe_account_select_period').append(new Option('', ''));
|
||||
for (var i = 0;i < self.periods.length;i++){
|
||||
o = new Option(self.periods[i][1], self.periods[i][0]);
|
||||
self.$el.parent().find('.oe_account_select_period').append(o);
|
||||
}
|
||||
self.$el.parent().find('.oe_account_select_period').val(self.current_period).attr('selected',true);
|
||||
return self.search_by_journal_period();
|
||||
},
|
||||
search_by_journal_period: function() {
|
||||
var self = this;
|
||||
|
@ -93,7 +93,9 @@ openerp.account.quickadd = function (instance) {
|
|||
self.last_context["journal_type"] = self.current_journal_type;
|
||||
self.last_context["currency"] = self.current_journal_currency;
|
||||
self.last_context["analytic_journal_id"] = self.current_journal_analytic;
|
||||
return self.old_search(new instance.web.CompoundDomain(self.last_domain, domain), self.last_context, self.last_group_by);
|
||||
var compound_domain = new instance.web.CompoundDomain(self.last_domain, domain);
|
||||
self.dataset.domain = compound_domain.eval();
|
||||
return self.old_search(compound_domain, self.last_context, self.last_group_by);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -664,13 +664,20 @@ class account_analytic_account(osv.osv):
|
|||
|
||||
partner_payment_term = contract.partner_id.property_payment_term and contract.partner_id.property_payment_term.id or False
|
||||
|
||||
currency_id = False
|
||||
if contract.pricelist_id:
|
||||
currency_id = contract.pricelist_id.currency_id.id
|
||||
elif contract.partner_id.property_product_pricelist:
|
||||
currency_id = contract.partner_id.property_product_pricelist.currency_id.id
|
||||
elif contract.company_id:
|
||||
currency_id = contract.company_id.currency_id.id
|
||||
|
||||
inv_data = {
|
||||
'reference': contract.code or False,
|
||||
'account_id': contract.partner_id.property_account_receivable.id,
|
||||
'type': 'out_invoice',
|
||||
'partner_id': contract.partner_id.id,
|
||||
'currency_id': contract.partner_id.property_product_pricelist.id or False,
|
||||
'currency_id': currency_id,
|
||||
'journal_id': len(journal_ids) and journal_ids[0] or False,
|
||||
'date_invoice': contract.recurring_next_date,
|
||||
'origin': contract.name,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -911,9 +911,10 @@ class account_voucher(osv.osv):
|
|||
if context.get('payment_expected_currency') and currency_id != context.get('payment_expected_currency'):
|
||||
vals['value']['amount'] = 0
|
||||
amount = 0
|
||||
res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
|
||||
for key in res.keys():
|
||||
vals[key].update(res[key])
|
||||
if partner_id:
|
||||
res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
|
||||
for key in res.keys():
|
||||
vals[key].update(res[key])
|
||||
return vals
|
||||
|
||||
def button_proforma_voucher(self, cr, uid, ids, context=None):
|
||||
|
@ -965,7 +966,7 @@ class account_voucher(osv.osv):
|
|||
res = {}
|
||||
if not partner_id:
|
||||
return res
|
||||
res = {'account_id':False}
|
||||
res = {}
|
||||
partner_pool = self.pool.get('res.partner')
|
||||
journal_pool = self.pool.get('account.journal')
|
||||
if pay_now == 'pay_later':
|
||||
|
@ -977,7 +978,8 @@ class account_voucher(osv.osv):
|
|||
account_id = partner.property_account_payable.id
|
||||
else:
|
||||
account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
|
||||
res['account_id'] = account_id
|
||||
if account_id:
|
||||
res['account_id'] = account_id
|
||||
return {'value':res}
|
||||
|
||||
def _sel_context(self, cr, uid, voucher_id, context=None):
|
||||
|
@ -1366,6 +1368,7 @@ class account_voucher(osv.osv):
|
|||
move_pool = self.pool.get('account.move')
|
||||
move_line_pool = self.pool.get('account.move.line')
|
||||
for voucher in self.browse(cr, uid, ids, context=context):
|
||||
local_context = dict(context, force_company=voucher.journal_id.company_id.id)
|
||||
if voucher.move_id:
|
||||
continue
|
||||
company_currency = self._get_company_currency(cr, uid, voucher.id, context)
|
||||
|
@ -1380,7 +1383,7 @@ class account_voucher(osv.osv):
|
|||
# Get the name of the account_move just created
|
||||
name = move_pool.browse(cr, uid, move_id, context=context).name
|
||||
# Create the first line of the voucher
|
||||
move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, context), context)
|
||||
move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, local_context), local_context)
|
||||
move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
|
||||
line_total = move_line_brw.debit - move_line_brw.credit
|
||||
rec_list_ids = []
|
||||
|
@ -1392,9 +1395,9 @@ class account_voucher(osv.osv):
|
|||
line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
|
||||
|
||||
# Create the writeoff line if needed
|
||||
ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
|
||||
ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, local_context)
|
||||
if ml_writeoff:
|
||||
move_line_pool.create(cr, uid, ml_writeoff, context)
|
||||
move_line_pool.create(cr, uid, ml_writeoff, local_context)
|
||||
# We post the voucher.
|
||||
self.write(cr, uid, [voucher.id], {
|
||||
'move_id': move_id,
|
||||
|
@ -1605,7 +1608,11 @@ class account_bank_statement(osv.osv):
|
|||
bank_st_line_obj = self.pool.get('account.bank.statement.line')
|
||||
st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
|
||||
if st_line.voucher_id:
|
||||
voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
|
||||
voucher_obj.write(cr, uid, [st_line.voucher_id.id],
|
||||
{'number': next_number,
|
||||
'date': st_line.date,
|
||||
'period_id': st_line.statement_id.period_id.id},
|
||||
context=context)
|
||||
if st_line.voucher_id.state == 'cancel':
|
||||
voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
|
||||
voucher_obj.signal_proforma_voucher(cr, uid, [st_line.voucher_id.id])
|
||||
|
|
|
@ -73,8 +73,14 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="type" invisible="True"/>
|
||||
<field name="partner_id" domain="[('customer','=',True)]" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" string="Customer" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": True}'/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
<field name="partner_id" domain="[('customer','=',True)]" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" string="Customer" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": True}'/>
|
||||
<field name="account_id"
|
||||
domain="[('type','in', ['liquidity', 'receivable'])]"/>
|
||||
<field name="pay_now" on_change="onchange_payment(pay_now, journal_id, partner_id)" required="1"/>
|
||||
<field name="date_due" attrs="{'invisible':[('pay_now','=','pay_now')]}"/>
|
||||
<field name="reference"
|
||||
attrs="{'invisible':[('pay_now','!=','pay_now')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="journal_id" domain="[('type','in',['sale','sale_refund'])]" widget="selection" on_change="onchange_journal(journal_id, line_cr_ids, tax_id, partner_id, date, amount, type, company_id, context)" groups="account.group_account_user"/>
|
||||
|
@ -112,15 +118,6 @@
|
|||
</div>
|
||||
<field name="amount" class="oe_subtotal_footer_separator" nolabel="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="pay_now" on_change="onchange_payment(pay_now, journal_id, partner_id)" required="1"/>
|
||||
<field name="date_due" attrs="{'invisible':[('pay_now','=','pay_now')]}"/>
|
||||
<field name="account_id"
|
||||
attrs="{'invisible':[('pay_now','!=','pay_now')]}"
|
||||
domain="[('type','=','liquidity')]"/>
|
||||
<field name="reference"
|
||||
attrs="{'invisible':[('pay_now','!=','pay_now')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Journal Items" attrs="{'invisible': [('state','!=','posted')]}">
|
||||
|
@ -224,11 +221,11 @@
|
|||
<h1><label for="number" string="Purchase Receipt"/> <field name="number" class="oe_inline" readonly="1"/></h1>
|
||||
|
||||
<field name="pay_now" invisible="1"/>
|
||||
<field name="account_id" domain="[('type','=','other')]" invisible="True"/>
|
||||
<field name="type" invisible="True"/>
|
||||
<group>
|
||||
<group>
|
||||
<field name="partner_id" domain="[('supplier','=',True)]" string="Supplier" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}" />
|
||||
<field name="account_id" domain="[('type', 'in', ['liquidity', 'payable'])]"/>
|
||||
<field name="name" colspan="2"/>
|
||||
<field name="reference"/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
|
|
|
@ -94,7 +94,7 @@ class account_statement_from_invoice_lines(osv.osv_memory):
|
|||
'account_id': result['value'].get('account_id', statement.journal_id.default_credit_account_id.id),
|
||||
'company_id': statement.company_id.id,
|
||||
'currency_id': statement.currency.id,
|
||||
'date': line.date,
|
||||
'date': statement.date,
|
||||
'amount': sign*amount,
|
||||
'payment_rate': result['value']['payment_rate'],
|
||||
'payment_rate_currency_id': result['value']['payment_rate_currency_id'],
|
||||
|
@ -119,7 +119,7 @@ class account_statement_from_invoice_lines(osv.osv_memory):
|
|||
'statement_id': statement_id,
|
||||
'ref': line.ref,
|
||||
'voucher_id': voucher_id,
|
||||
'date': time.strftime('%Y-%m-%d'),
|
||||
'date': statement.date,
|
||||
}, context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
|
|
@ -268,10 +268,14 @@ def log_fct(cr, uid_orig, model, method, fct_src, *args, **kw):
|
|||
new_values = get_data(cr, uid_orig, pool, res_ids, model, method)
|
||||
elif method == 'read':
|
||||
res = fct_src(cr, uid_orig, model.model, method, *args, **kw)
|
||||
if isinstance(res, dict):
|
||||
records = [res]
|
||||
else:
|
||||
records = res
|
||||
# build the res_ids and the old_values dict. Here we don't use get_data() to
|
||||
# avoid performing an additional read()
|
||||
res_ids = []
|
||||
for record in res:
|
||||
for record in records:
|
||||
res_ids.append(record['id'])
|
||||
old_values[(model.id, record['id'])] = {'value': record, 'text': record}
|
||||
# log only the fields read
|
||||
|
@ -279,7 +283,9 @@ def log_fct(cr, uid_orig, model, method, fct_src, *args, **kw):
|
|||
elif method == 'unlink':
|
||||
res_ids = args[0]
|
||||
old_values = get_data(cr, uid_orig, pool, res_ids, model, method)
|
||||
res = fct_src(cr, uid_orig, model.model, method, *args, **kw)
|
||||
# process_data first as fct_src will unlink the record
|
||||
self.process_data(cr, uid_orig, pool, res_ids, model, method, old_values, new_values, field_list)
|
||||
return fct_src(cr, uid_orig, model.model, method, *args, **kw)
|
||||
else: # method is write, action or workflow action
|
||||
res_ids = []
|
||||
if args:
|
||||
|
@ -322,7 +328,7 @@ def get_data(cr, uid, pool, res_ids, model, method):
|
|||
data = {}
|
||||
resource_pool = pool[model.model]
|
||||
# read all the fields of the given resources in super admin mode
|
||||
for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids):
|
||||
for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids, resource_pool._all_columns):
|
||||
values = {}
|
||||
values_text = {}
|
||||
resource_id = resource['id']
|
||||
|
@ -456,7 +462,9 @@ 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:
|
||||
name = pool[model.model].name_get(cr, uid, [resource_id])[0][1]
|
||||
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,
|
||||
|
|
|
@ -15,6 +15,7 @@ from openerp.tools.translate import _
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
#----------------------------------------------------------
|
||||
# helpers
|
||||
#----------------------------------------------------------
|
||||
|
@ -25,16 +26,20 @@ def fragment_to_query_string(func):
|
|||
return """<html><head><script>
|
||||
var l = window.location;
|
||||
var q = l.hash.substring(1);
|
||||
var r = '/' + l.search;
|
||||
var r = l.pathname + l.search;
|
||||
if(q.length !== 0) {
|
||||
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
|
||||
#----------------------------------------------------------
|
||||
|
@ -88,6 +93,7 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
|||
|
||||
return response
|
||||
|
||||
|
||||
class OAuthController(http.Controller):
|
||||
|
||||
@http.route('/auth_oauth/signin', type='http', auth='none')
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010-2012 OpenERP SA (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2014 OpenERP SA (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -30,7 +30,9 @@ Allow users to sign up through OAuth2 Provider.
|
|||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['auth_oauth', 'auth_signup'],
|
||||
'data': [],
|
||||
'data': [
|
||||
'views/auth_oauth_signup.xml',
|
||||
],
|
||||
'js': [],
|
||||
'css': [],
|
||||
'qweb': [],
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- vim:et:si:ts=2:sts=2:sw=2 -->
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="auth_oauth_signup.signup" inherit_id="auth_signup.signup" name="OAuth Signup buttons">
|
||||
<xpath expr="//button[@type='submit']" position="before">
|
||||
<div class="pull-right">
|
||||
<div t-foreach="providers or []" t-as="p">
|
||||
<a t-att-href="p['auth_link']" class="btn btn-link">
|
||||
<i t-att-class="p['css_class']"/>
|
||||
<t t-esc="p['body']"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
|
@ -30,13 +30,19 @@ from openerp.tools import exception_to_unicode
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Home(openerp.addons.web.controllers.main.Home):
|
||||
class AuthSignup(openerp.addons.web.controllers.main.Home):
|
||||
|
||||
@http.route()
|
||||
def web_login(self, *args, **kw):
|
||||
mode = request.params.get('mode')
|
||||
qcontext = request.params.copy()
|
||||
super_response = None
|
||||
if request.httprequest.method != 'POST' or mode not in ('reset', 'signup'):
|
||||
# Default behavior is to try to login, which in reset or signup mode in a non-sense.
|
||||
super_response = super(AuthSignup, self).web_login(*args, **kw)
|
||||
response = webmain.render_bootstrap_template(request.session.db, 'auth_signup.signup', qcontext, lazy=True)
|
||||
if isinstance(super_response, LazyResponse):
|
||||
response.params['values'].update(super_response.params['values'])
|
||||
token = qcontext.get('token', None)
|
||||
token_infos = None
|
||||
if token:
|
||||
|
@ -59,11 +65,10 @@ class Home(openerp.addons.web.controllers.main.Home):
|
|||
}
|
||||
qcontext.update(config)
|
||||
|
||||
if 'error' in qcontext or mode not in ('reset', 'signup') or (not token and not config[mode]):
|
||||
response = super(Home, self).web_login(*args, **kw)
|
||||
if isinstance(response, LazyResponse):
|
||||
response.params['values'].update(config)
|
||||
return response
|
||||
if 'error' in request.params or mode not in ('reset', 'signup') or (not token and not config[mode]):
|
||||
if isinstance(super_response, LazyResponse):
|
||||
super_response.params['values'].update(config)
|
||||
return super_response
|
||||
|
||||
if request.httprequest.method == 'GET':
|
||||
if token_infos:
|
||||
|
@ -76,22 +81,28 @@ class Home(openerp.addons.web.controllers.main.Home):
|
|||
res_users.reset_password(request.cr, openerp.SUPERUSER_ID, login)
|
||||
qcontext['message'] = _("An email has been sent with credentials to reset your password")
|
||||
response.params['template'] = 'web.login'
|
||||
except Exception:
|
||||
qcontext['error'] = _("Could not reset your password")
|
||||
except Exception, e:
|
||||
qcontext['error'] = exception_to_unicode(e) or _("Could not reset your password")
|
||||
_logger.exception('error when resetting password')
|
||||
else:
|
||||
values = dict((key, qcontext.get(key)) for key in ('login', 'name', 'password'))
|
||||
try:
|
||||
self._signup_with_values(token, values)
|
||||
request.cr.commit()
|
||||
redirect = request.params.get('redirect')
|
||||
if not redirect:
|
||||
redirect = '/web?' + request.httprequest.query_string
|
||||
return http.redirect_with_hash(redirect)
|
||||
except SignupError, e:
|
||||
qcontext['error'] = exception_to_unicode(e)
|
||||
return super(Home, self).web_login(*args, **kw)
|
||||
|
||||
return response
|
||||
|
||||
def _signup_with_values(self, token, values):
|
||||
request.registry['res.users'].signup(request.cr, openerp.SUPERUSER_ID, values, token)
|
||||
db, login, password = request.registry['res.users'].signup(request.cr, openerp.SUPERUSER_ID, values, token)
|
||||
request.cr.commit() # as authenticate will use its own cursor we need to commit the current transaction
|
||||
uid = request.session.authenticate(db, login, password)
|
||||
if not uid:
|
||||
raise SignupError(_('Authentification Failed.'))
|
||||
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -25,7 +25,7 @@ from urlparse import urljoin
|
|||
|
||||
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, ustr
|
||||
from ast import literal_eval
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
@ -236,7 +236,12 @@ class res_users(osv.Model):
|
|||
# create a copy of the template user (attached to a specific partner_id if given)
|
||||
values['active'] = True
|
||||
context = dict(context or {}, no_reset_password=True)
|
||||
return self.copy(cr, uid, template_user_id, values, context=context)
|
||||
try:
|
||||
with cr.savepoint():
|
||||
return self.copy(cr, uid, template_user_id, values, context=context)
|
||||
except Exception, e:
|
||||
# copy may failed if asked login is not available.
|
||||
raise SignupError(ustr(e))
|
||||
|
||||
def reset_password(self, cr, uid, login, context=None):
|
||||
""" retrieve the user corresponding to login (login or email),
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<data>
|
||||
<template id="auth_signup.login" inherit_id="web.login" name="Sign up - Reset Password">
|
||||
<xpath expr="//button[@type='submit']" position="before">
|
||||
<a t-if="signup" t-attf-href="?mode=signup{{ '&debug' if debug else '' }}" class="btn btn-link pull-right">Sign up</a>
|
||||
<a t-if="reset" t-attf-href="?mode=reset{{ '&debug' if debug else '' }}" class="btn btn-link pull-right">Reset Password</a>
|
||||
<a t-if="signup" t-attf-href="?{{ keep_query('*', mode='signup') }}" class="btn btn-link pull-right">Sign up</a>
|
||||
<a t-if="reset" t-attf-href="?{{ keep_query('*', mode='reset') }}" class="btn btn-link pull-right">Reset Password</a>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -29,21 +29,21 @@
|
|||
|
||||
<t t-set="reset_without_token" t-value="mode == 'reset' and not token"/>
|
||||
|
||||
<form class="oe_signup_form" role="form" t-attf-action="/web/login{{ '?debug' if debug else '' }}" method="post">
|
||||
<form class="oe_signup_form" role="form" method="post">
|
||||
<t t-call="web.database_select"/>
|
||||
|
||||
<div class="form-group field-login">
|
||||
<label for="login" class="control-label">Your Email</label>
|
||||
<input type="email" name="login" t-att-value="login" id="login" class="form-control" autofocus="autofocus"
|
||||
required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"/>
|
||||
<input type="hidden" name="login" t-att-value="login" t-if="mode == 'reset' and token"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group field-name" t-if="not reset_without_token">
|
||||
<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" autofocus="autofocus" t-att-disabled="'disabled' if mode == 'reset' and token 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"
|
||||
t-att-autofocus="'autofocus' if reset_without_token else None"
|
||||
required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"/>
|
||||
<input type="hidden" name="login" t-att-value="login" t-if="mode == 'reset' and token"/>
|
||||
required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"
|
||||
t-att-autofocus="'autofocus' if mode != 'reset' and login else None"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group field-password" t-if="not reset_without_token">
|
||||
|
@ -68,7 +68,7 @@
|
|||
<input type="hidden" name="mode" t-att-value="mode"/>
|
||||
<input type="hidden" name="token" t-att-value="token"/>
|
||||
<div class="clearfix oe_login_buttons">
|
||||
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
|
||||
<a t-attf-href="?{{ keep_query('*', mode='login') }}" class="btn btn-link pull-right">Back to Login</a>
|
||||
<button type="submit" class="btn btn-primary pull-left">
|
||||
<t t-if="mode == 'signup'">Sign up</t>
|
||||
<t t-if="mode == 'reset'">Reset password</t>
|
||||
|
|
|
@ -275,6 +275,13 @@ class base_action_rule(osv.osv):
|
|||
if action.filter_id:
|
||||
domain = eval(action.filter_id.domain)
|
||||
ctx.update(eval(action.filter_id.context))
|
||||
if 'lang' not in ctx:
|
||||
# Filters might be language-sensitive, attempt to reuse creator lang
|
||||
# as we are usually running this as super-user in background
|
||||
[filter_meta] = action.filter_id.perm_read()
|
||||
user_id = filter_meta['write_uid'] and filter_meta['write_uid'][0] or \
|
||||
filter_meta['create_uid'][0]
|
||||
ctx['lang'] = self.pool['res.users'].browse(cr, uid, user_id).lang
|
||||
record_ids = model.search(cr, uid, domain, context=ctx)
|
||||
|
||||
# determine when action should occur for the records
|
||||
|
|
|
@ -41,7 +41,9 @@ class base_config_settings(osv.osv_memory):
|
|||
help="""This installs the module google_docs."""),
|
||||
'module_google_calendar': fields.boolean('Allow the users to synchronize their calendar with Google Calendar',
|
||||
help="""This installs the module google_calendar."""),
|
||||
'font': fields.many2one('res.font', string="Report Font", help="Set the font into the report header, it will be used as default font in the RML reports of the user company"),
|
||||
'font': fields.many2one('res.font', string="Report Font", domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))],
|
||||
help="Set the font into the report header, it will be used as default font in the RML reports of the user company"),
|
||||
|
||||
}
|
||||
|
||||
_defaults= {
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
<label for="font" />
|
||||
<div>
|
||||
<div>
|
||||
<field name="font" class="oe_inline" domain="[('mode', 'in', ('normal', 'regular', 'all', 'book'))]" />
|
||||
<field name="font" class="oe_inline" />
|
||||
<button string="(reload fonts)" name="act_discover_fonts" type="object" class="oe_link"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -54,7 +54,7 @@ _ref_vat = {
|
|||
'gr': 'GR12345670',
|
||||
'hu': 'HU12345676',
|
||||
'hr': 'HR01234567896', # Croatia, contributed by Milan Tribuson
|
||||
'ie': 'IE1234567T',
|
||||
'ie': 'IE1234567FA',
|
||||
'it': 'IT12345670017',
|
||||
'lt': 'LT123456715',
|
||||
'lu': 'LU12345613',
|
||||
|
@ -190,6 +190,34 @@ class res_partner(osv.osv):
|
|||
return check == int(num[8])
|
||||
return False
|
||||
|
||||
def _ie_check_char(self, vat):
|
||||
vat = vat.zfill(8)
|
||||
extra = 0
|
||||
if vat[7] not in ' W':
|
||||
if vat[7].isalpha():
|
||||
extra = 9 * (ord(vat[7]) - 64)
|
||||
else:
|
||||
# invalid
|
||||
return -1
|
||||
checksum = extra + sum((8-i) * int(x) for i, x in enumerate(vat[:7]))
|
||||
return 'WABCDEFGHIJKLMNOPQRSTUV'[checksum % 23]
|
||||
|
||||
def check_vat_ie(self, vat):
|
||||
""" Temporary Ireland VAT validation to support the new format
|
||||
introduced in January 2013 in Ireland, until upstream is fixed.
|
||||
TODO: remove when fixed upstream"""
|
||||
if len(vat) not in (8, 9) or not vat[2:7].isdigit():
|
||||
return False
|
||||
if len(vat) == 8:
|
||||
# Normalize pre-2013 numbers: final space or 'W' not significant
|
||||
vat += ' '
|
||||
if vat[:7].isdigit():
|
||||
return vat[7] == self._ie_check_char(vat[:7] + vat[8])
|
||||
elif vat[1] in (string.ascii_uppercase + '+*'):
|
||||
# Deprecated format
|
||||
# See http://www.revenue.ie/en/online/third-party-reporting/reporting-payment-details/faqs.html#section3
|
||||
return vat[7] == self._ie_check_char(vat[2:7] + vat[0] + vat[8])
|
||||
return False
|
||||
|
||||
# Mexican VAT verification, contributed by <moylop260@hotmail.com>
|
||||
# and Panos Christeas <p_christ@hol.gr>
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import hashlib
|
||||
import pytz
|
||||
import re
|
||||
import time
|
||||
import openerp
|
||||
import openerp.service.report
|
||||
import uuid
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil import parser
|
||||
from dateutil import rrule
|
||||
|
@ -34,7 +34,6 @@ from openerp import tools, SUPERUSER_ID
|
|||
from openerp.osv import fields, osv
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.translate import _
|
||||
from openerp import http
|
||||
from openerp.http import request
|
||||
from operator import itemgetter
|
||||
|
||||
|
@ -62,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)
|
||||
|
@ -75,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):
|
||||
|
@ -333,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
|
||||
|
@ -358,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
|
||||
"""
|
||||
|
||||
|
@ -383,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,
|
||||
|
@ -406,7 +400,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
|||
'min_duration': min_duration,
|
||||
'max_duration': max_duration,
|
||||
'rrule': rule
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
|
@ -432,83 +426,86 @@ 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)
|
||||
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:
|
||||
cron = self.pool.get('ir.cron').browse(cr,uid,cron[0],context=context)
|
||||
cron = self.pool.get('ir.cron').browse(cr, uid, cron[0], context=context)
|
||||
else:
|
||||
raise ("Cron for " + self._name + " not identified :( !")
|
||||
|
||||
if cron.interval_type=="weeks":
|
||||
if cron.interval_type == "weeks":
|
||||
cron_interval = cron.interval_number * 7 * 24 * 60 * 60
|
||||
elif cron.interval_type=="days":
|
||||
cron_interval = cron.interval_number * 24 * 60 * 60
|
||||
elif cron.interval_type=="hours":
|
||||
elif cron.interval_type == "days":
|
||||
cron_interval = cron.interval_number * 24 * 60 * 60
|
||||
elif cron.interval_type == "hours":
|
||||
cron_interval = cron.interval_number * 60 * 60
|
||||
elif cron.interval_type=="minutes":
|
||||
elif cron.interval_type == "minutes":
|
||||
cron_interval = cron.interval_number * 60
|
||||
elif cron.interval_type=="seconds":
|
||||
cron_interval = cron.interval_number
|
||||
elif cron.interval_type == "seconds":
|
||||
cron_interval = cron.interval_number
|
||||
|
||||
if not cron_interval:
|
||||
raise ("Cron delay for " + self._name + " can not be calculated :( !")
|
||||
|
||||
all_events = self.get_next_potential_limit_alarm(cr,uid,cron_interval,notif=False,context=context)
|
||||
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'];
|
||||
curEvent = self.pool.get('calendar.event').browse(cr,uid,event,context=context)
|
||||
for event in all_events: # .values()
|
||||
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,cron_interval,notif=False,context=context)
|
||||
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, cron_interval, notif=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
self.do_mail_reminder(cr,uid,alert,context=context)
|
||||
self.do_mail_reminder(cr, uid, alert, context=context)
|
||||
|
||||
if not bFound: # if it's the first alarm for this recurrent event
|
||||
bFound = True
|
||||
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');
|
||||
LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,cron_interval,notif=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, cron_interval, notif=False, context=context)
|
||||
if LastFound:
|
||||
for alert in LastFound:
|
||||
self.do_mail_reminder(cr,uid,alert,context=context)
|
||||
self.do_mail_reminder(cr, uid, alert, context=context)
|
||||
|
||||
def get_next_notif(self,cr,uid,context=None):
|
||||
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 = []
|
||||
all_events = self.get_next_potential_limit_alarm(cr,uid,ajax_check_every_seconds,partner_id=partner.id,mail=False,context=context)
|
||||
|
||||
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'];
|
||||
curEvent = self.pool.get('calendar.event').browse(cr,uid,event,context=context)
|
||||
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)
|
||||
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.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))
|
||||
if not bFound: #if it's the first alarm for this recurrent event
|
||||
bFound = True
|
||||
if bFound and not LastFound: #if the precedent event had alarm but not this one, we can stop the search fot this event
|
||||
all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
|
||||
if not bFound: # if it's the first alarm for this recurrent event
|
||||
bFound = True
|
||||
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))
|
||||
return all_notif
|
||||
all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
|
||||
return all_notif
|
||||
|
||||
def do_mail_reminder(self, cr, uid, alert, context=None):
|
||||
if context is None:
|
||||
|
@ -519,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
|
||||
|
||||
|
@ -797,12 +794,12 @@ class calendar_event(osv.Model):
|
|||
'location': {
|
||||
'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
|
||||
},
|
||||
'date': {
|
||||
'date': {
|
||||
'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
|
||||
},
|
||||
}
|
||||
_columns = {
|
||||
'id': fields.integer('ID', readonly=True),
|
||||
'id': fields.integer('ID', readonly=True),
|
||||
'state': fields.selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', readonly=True, track_visibility='onchange'),
|
||||
'name': fields.char('Meeting Subject', required=True, states={'done': [('readonly', True)]}),
|
||||
'is_attendee': fields.function(_compute, string='Attendee', type="boolean", multi='attendee'),
|
||||
|
@ -844,6 +841,7 @@ class calendar_event(osv.Model):
|
|||
'attendee_ids': fields.one2many('calendar.attendee', 'event_id', 'Attendees', ondelete='cascade'),
|
||||
'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}),
|
||||
'alarm_ids': fields.many2many('calendar.alarm', string='Reminders', ondelete="restrict"),
|
||||
|
||||
}
|
||||
_defaults = {
|
||||
'end_type': 'count',
|
||||
|
@ -870,11 +868,9 @@ class calendar_event(osv.Model):
|
|||
]
|
||||
|
||||
def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
|
||||
|
||||
"""Returns duration and/or end date based on values passed
|
||||
@param ids: List of calendar event's IDs.
|
||||
@param start_date: Starting date
|
||||
@param duration: Duration between start date and end date
|
||||
@param end_date: Ending Datee
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
@ -888,14 +884,14 @@ class calendar_event(osv.Model):
|
|||
value['duration'] = duration
|
||||
|
||||
if allday: # For all day event
|
||||
start = datetime.strptime(start_date.split(' ')[0].split('T')[0], "%Y-%m-%d")
|
||||
duration = 24.0
|
||||
value['duration'] = duration
|
||||
start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
|
||||
user = self.pool['res.users'].browse(cr, uid, uid)
|
||||
tz = pytz.timezone(user.tz) if user.tz else pytz.utc
|
||||
start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone
|
||||
start = start.astimezone(pytz.utc) # convert start back to utc
|
||||
value['date'] = start.strftime("%Y-%m-%d") + ' 00:00:00'
|
||||
|
||||
value['duration'] = 24.0
|
||||
value['date'] = datetime.strftime(start, "%Y-%m-%d %H:%M:%S")
|
||||
else:
|
||||
start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
@ -979,7 +975,7 @@ class calendar_event(osv.Model):
|
|||
}
|
||||
return res
|
||||
|
||||
def get_search_fields(self,browse_event,order_fields,r_date=None):
|
||||
def get_search_fields(self, browse_event, order_fields, r_date=None):
|
||||
sort_fields = {}
|
||||
for ord in order_fields:
|
||||
if ord == 'id' and r_date:
|
||||
|
@ -989,17 +985,17 @@ class calendar_event(osv.Model):
|
|||
'If we sort on FK, we obtain a browse_record, so we need to sort on name_get'
|
||||
if type(browse_event[ord]) is openerp.osv.orm.browse_record:
|
||||
name_get = browse_event[ord].name_get()
|
||||
if len(name_get) and len(name_get[0])>=2:
|
||||
if len(name_get) and len(name_get[0]) >= 2:
|
||||
sort_fields[ord] = name_get[0][1]
|
||||
|
||||
return sort_fields
|
||||
|
||||
def get_recurrent_ids(self, cr, uid, event_id, domain, order=None, context=None):
|
||||
|
||||
"""Gives virtual event ids for recurring events
|
||||
"""Gives virtual event ids for recurring events
|
||||
This method gives ids of dates that comes between start date and end date of calendar views
|
||||
|
||||
@param order: The fields (comma separated, format "FIELD {DESC|ASC}") on which the events should be sorted
|
||||
|
||||
@param order: The fields (comma separated, format "FIELD {DESC|ASC}") on which the events should be sorted
|
||||
"""
|
||||
|
||||
if not context:
|
||||
|
@ -1024,7 +1020,7 @@ class calendar_event(osv.Model):
|
|||
for ev in self.browse(cr, uid, ids_to_browse, context=context):
|
||||
if not ev.recurrency or not ev.rrule:
|
||||
result.append(ev.id)
|
||||
result_data.append(self.get_search_fields(ev,order_fields))
|
||||
result_data.append(self.get_search_fields(ev, order_fields))
|
||||
continue
|
||||
|
||||
rdates = self.get_recurrent_date_by_event(cr, uid, ev, context=context)
|
||||
|
@ -1070,7 +1066,7 @@ class calendar_event(osv.Model):
|
|||
|
||||
if [True for item in new_pile if not item]:
|
||||
continue
|
||||
result_data.append(self.get_search_fields(ev,order_fields,r_date=r_date))
|
||||
result_data.append(self.get_search_fields(ev, order_fields, r_date=r_date))
|
||||
|
||||
if order_fields:
|
||||
def comparer(left, right):
|
||||
|
@ -1081,7 +1077,7 @@ class calendar_event(osv.Model):
|
|||
return 0
|
||||
|
||||
sort_params = [key.split()[0] if key[-4:].lower() != 'desc' else '-%s' % key.split()[0] for key in (order or self._order).split(',')]
|
||||
comparers = [ ((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
|
||||
comparers = [((itemgetter(col[1:]), -1) if col[0] == '-' else (itemgetter(col), 1)) for col in sort_params]
|
||||
ids = [r['id'] for r in sorted(result_data, cmp=comparer)]
|
||||
|
||||
if isinstance(event_id, (str, int, long)):
|
||||
|
@ -1089,7 +1085,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.
|
||||
|
@ -1123,7 +1118,7 @@ class calendar_event(osv.Model):
|
|||
data['end_date_new'] = ''.join((re.compile('\d')).findall(data.get('end_date'))) + 'T235959Z'
|
||||
|
||||
return (data.get('end_type') == 'count' and (';COUNT=' + str(data.get('count'))) or '') +\
|
||||
((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '')
|
||||
((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '')
|
||||
|
||||
freq = data.get('rrule_type', False) # day/week/month/year
|
||||
res = ''
|
||||
|
@ -1219,12 +1214,12 @@ class calendar_event(osv.Model):
|
|||
res.update(self.check_partners_email(cr, uid, value[0][2], context=context))
|
||||
return res
|
||||
|
||||
def onchange_rec_day(self,cr,uid,id,date,mo,tu,we,th,fr,sa,su):
|
||||
def onchange_rec_day(self, cr, uid, id, date, mo, tu, we, th, fr, sa, su):
|
||||
""" set the start date according to the first occurence of rrule"""
|
||||
rrule_obj = self._get_empty_rrule_data()
|
||||
rrule_obj.update({
|
||||
'byday':True,
|
||||
'rrule_type':'weekly',
|
||||
'byday': True,
|
||||
'rrule_type': 'weekly',
|
||||
'mo': mo,
|
||||
'tu': tu,
|
||||
'we': we,
|
||||
|
@ -1232,12 +1227,11 @@ class calendar_event(osv.Model):
|
|||
'fr': fr,
|
||||
'sa': sa,
|
||||
'su': su,
|
||||
'interval':1
|
||||
'interval': 1
|
||||
})
|
||||
str_rrule = self.compute_rule_string(rrule_obj)
|
||||
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' } }
|
||||
|
||||
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.
|
||||
|
@ -1251,12 +1245,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':
|
||||
{
|
||||
'title': _('Email addresses not found'),
|
||||
'message': warning_msg,
|
||||
}
|
||||
}
|
||||
return {'warning': {
|
||||
'title': _('Email addresses not found'),
|
||||
'message': warning_msg,
|
||||
}}
|
||||
|
||||
# ----------------------------------------
|
||||
# OpenChatter
|
||||
|
@ -1353,17 +1345,52 @@ class calendar_event(osv.Model):
|
|||
res = super(calendar_event, self).copy(cr, uid, calendar_id2real_id(id), default, context)
|
||||
return res
|
||||
|
||||
def _detach_one_event(self, cr, uid, id, values=dict(), context=None):
|
||||
real_event_id = calendar_id2real_id(id)
|
||||
data = self.read(cr, uid, id, ['date', 'date_deadline', 'rrule', 'duration'])
|
||||
|
||||
if data.get('rrule'):
|
||||
data.update(
|
||||
values,
|
||||
recurrent_id=real_event_id,
|
||||
recurrent_id_date=data.get('date'),
|
||||
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'))
|
||||
)
|
||||
|
||||
#do not copy the id
|
||||
if data.get('id'):
|
||||
del(data['id'])
|
||||
new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
|
||||
return new_id
|
||||
|
||||
def open_after_detach_event(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
new_id = self._detach_one_event(cr, uid, ids[0], context=context)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'calendar.event',
|
||||
'view_mode': 'form',
|
||||
'res_id': new_id,
|
||||
'target': 'current',
|
||||
'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'''
|
||||
for field in field_names:
|
||||
if field not in ['name', 'message_follower_ids','oe_update_date']:
|
||||
return False
|
||||
return True
|
||||
if field in ['date', 'active']:
|
||||
return True
|
||||
return False
|
||||
|
||||
context = context or {}
|
||||
|
||||
if isinstance(ids, (str,int, long)):
|
||||
if isinstance(ids, (str, int, long)):
|
||||
if len(str(ids).split('-')) == 1:
|
||||
ids = [int(ids)]
|
||||
else:
|
||||
|
@ -1383,30 +1410,13 @@ class calendar_event(osv.Model):
|
|||
# if we are setting the recurrency flag to False or if we are only changing fields that
|
||||
# should be only updated on the real ID and not on the virtual (like message_follower_ids):
|
||||
# then set real ids to be updated.
|
||||
if not values.get('recurrency', True) or _only_changes_to_apply_on_real_ids(values.keys()):
|
||||
if not values.get('recurrency', True) or not _only_changes_to_apply_on_real_ids(values.keys()):
|
||||
ids.append(real_event_id)
|
||||
continue
|
||||
|
||||
#if edit one instance of a reccurrent id
|
||||
data = self.read(cr, uid, event_id, ['date', 'date_deadline', 'rrule', 'duration'])
|
||||
if data.get('rrule'):
|
||||
data.update(
|
||||
values,
|
||||
recurrent_id=real_event_id,
|
||||
recurrent_id_date=data.get('date'),
|
||||
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'))
|
||||
)
|
||||
|
||||
#do not copy the id
|
||||
if data.get('id'):
|
||||
del(data['id'])
|
||||
new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
|
||||
context.update({'active_id': new_id, 'active_ids': [new_id]})
|
||||
continue
|
||||
else:
|
||||
data = self.read(cr, uid, event_id, ['date', 'date_deadline', 'rrule', 'duration'])
|
||||
if data.get('rrule'):
|
||||
new_id = self._detach_one_event(cr, uid, event_id, values, context=None)
|
||||
|
||||
res = super(calendar_event, self).write(cr, uid, ids, values, context=context)
|
||||
|
||||
|
@ -1473,7 +1483,6 @@ class calendar_event(osv.Model):
|
|||
if context is None:
|
||||
context = {}
|
||||
fields2 = fields and fields[:] or None
|
||||
|
||||
EXTRAFIELDS = ('class', 'user_id', 'duration', 'date', 'rrule', 'vtimezone')
|
||||
for f in EXTRAFIELDS:
|
||||
if fields and (f not in fields):
|
||||
|
@ -1506,7 +1515,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:
|
||||
|
@ -1518,6 +1527,7 @@ class calendar_event(osv.Model):
|
|||
for k in EXTRAFIELDS:
|
||||
if (k in r) and (fields and (k not in fields)):
|
||||
del r[k]
|
||||
|
||||
if isinstance(ids, (str, int, long)):
|
||||
return result and result[0] or False
|
||||
return result
|
||||
|
@ -1564,7 +1574,7 @@ class mail_message(osv.Model):
|
|||
|
||||
def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None):
|
||||
if doc_model == 'calendar.event':
|
||||
order = context.get('order', self._order)
|
||||
order = context.get('order', self._order)
|
||||
for virtual_id in self.pool[doc_model].get_recurrent_ids(cr, uid, doc_dict.keys(), [], order=order, context=context):
|
||||
doc_dict.setdefault(virtual_id, doc_dict[get_real_ids(virtual_id)])
|
||||
return super(mail_message, self)._find_allowed_model_wise(cr, uid, doc_model, doc_dict, context=context)
|
||||
|
@ -1596,26 +1606,27 @@ class ir_http(osv.AbstractModel):
|
|||
|
||||
def _auth_method_calendar(self):
|
||||
token = request.params['token']
|
||||
db = request.params['db']
|
||||
db = request.params['db']
|
||||
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
error_message = False
|
||||
with registry.cursor() as cr:
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token)])
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token', '=', token)])
|
||||
if not attendee_id:
|
||||
error_message = """Invalid Invitation Token."""
|
||||
elif request.session.uid and request.session.login != 'anonymous':
|
||||
# if valid session but user is not match
|
||||
attendee = attendee_pool.browse(cr, openerp.SUPERUSER_ID, attendee_id[0])
|
||||
user = registry.get('res.users').browse(cr, openerp.SUPERUSER_ID, request.session.uid)
|
||||
if attendee.partner_id.id != user.partner_id.id:
|
||||
error_message = """Invitation cannot be forwarded via email. This event/meeting belongs to %s and you are logged in as %s. Please ask organizer to add you.""" % (attendee.email, user.email)
|
||||
if attendee.partner_id.id != user.partner_id.id:
|
||||
error_message = """Invitation cannot be forwarded via email. This event/meeting belongs to %s and you are logged in as %s. Please ask organizer to add you.""" % (attendee.email, user.email)
|
||||
|
||||
if error_message:
|
||||
raise BadRequest(error_message)
|
||||
return True
|
||||
|
||||
|
||||
class invite_wizard(osv.osv_memory):
|
||||
_inherit = 'mail.wizard.invite'
|
||||
|
||||
|
|
|
@ -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;">
|
||||
Where
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="1" style="vertical-align:top;">
|
||||
<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>
|
||||
% 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 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;">
|
||||
What
|
||||
</div>
|
||||
% 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;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${object.event_id.description}
|
||||
</div>
|
||||
<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;">
|
||||
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Duration
|
||||
</div>
|
||||
% 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;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
|
||||
</div>
|
||||
% 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>
|
||||
<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>
|
||||
|
@ -261,11 +269,11 @@
|
|||
<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">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>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.date, 'dayname')}</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
|
||||
|
@ -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;">
|
||||
Where
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="1" style="vertical-align:top;">
|
||||
<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>
|
||||
% 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 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;">
|
||||
What
|
||||
</div>
|
||||
% 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;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${object.event_id.description}
|
||||
</div>
|
||||
<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;">
|
||||
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Duration
|
||||
</div>
|
||||
% 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;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
|
||||
</div>
|
||||
% 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>
|
||||
<tr style=" height: 30px;">
|
||||
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
<div>
|
||||
|
@ -341,12 +357,12 @@
|
|||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</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>
|
||||
|
@ -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;">
|
||||
Where
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="1" style="vertical-align:top;">
|
||||
<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>
|
||||
% 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 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;">
|
||||
What
|
||||
</div>
|
||||
% 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;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${object.event_id.description}
|
||||
</div>
|
||||
<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;">
|
||||
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Duration
|
||||
</div>
|
||||
% 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;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
|
||||
</div>
|
||||
% 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>
|
||||
<tr style=" height: 30px;">
|
||||
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
<div>
|
||||
|
|
|
@ -6,20 +6,7 @@
|
|||
-->
|
||||
|
||||
<!--For Meetings -->
|
||||
<record id="res_partner_another" model="res.partner">
|
||||
<field name="name">Arshaw</field>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="customer" eval="False"/>
|
||||
<field name="email">fullcalendar@example.com</field>
|
||||
</record>
|
||||
<record id="res_user_another" model="res.users">
|
||||
<field name="name" >Second Demo User</field>
|
||||
<field name="login" >Second Demo User</field>
|
||||
<field name="partner_id" ref="res_partner_another"/>
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
</record>
|
||||
|
||||
<record id="cal_contact_1" model="calendar.contacts">
|
||||
<record id="cal_contact_1" model="calendar.contacts">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
|
@ -33,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"/>
|
||||
|
@ -105,8 +92,8 @@
|
|||
|
||||
<record id="calendar_event_7" model="calendar.event">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="res_user_another"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('res_partner_another'),ref('base.res_partner_8')])]"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('base.res_partner_7')])]"/>
|
||||
<field name="name">Presentation of the new Calendar</field>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
|
||||
<field eval="time.strftime('%Y-%m-16 6:00:00')" name="date"/>
|
||||
|
@ -114,18 +101,7 @@
|
|||
<field eval="8.5" name="duration"/>
|
||||
<field name="state">draft</field>
|
||||
</record>
|
||||
|
||||
<record id="calendar_event_8" model="calendar.event">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="user_id" ref="res_user_another"/>
|
||||
<field name="partner_ids" eval="[(6,0,[ref('res_partner_another'),ref('base.partner_root')])]"/>
|
||||
<field name="name">Discuss about the module : Calendar </field>
|
||||
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
|
||||
<field eval="time.strftime('%Y-%m-16 6:00:00')" name="date"/>
|
||||
<field eval="time.strftime('%Y-%m-16 18:30:00')" name="date_deadline"/>
|
||||
<field eval="8.5" name="duration"/>
|
||||
<field name="state">draft</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -51,21 +51,26 @@
|
|||
</div>
|
||||
<notebook>
|
||||
<page string="Meeting Details">
|
||||
<group attrs="{'invisible': [('recurrency','==',False)]}" class="oe_edit_only ">
|
||||
<p class='alert alert-warning'> This event is linked to a recurrence...<br/>
|
||||
<button type="object" name="open_after_detach_event" string="Update only this instance" help="Click here to update only this instance and not all recurrences. " class="oe_link"/>
|
||||
</p>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="date" string="Starting at" on_change="onchange_dates(date, duration, False, allday)"/>
|
||||
<label for="duration"/>
|
||||
<div>
|
||||
<field name="duration" widget="float_time"
|
||||
on_change="onchange_dates(date,duration,False,allday)"
|
||||
on_change="onchange_dates(date, duration, False, allday)"
|
||||
class="oe_inline" attrs="{'invisible': [('allday','=',True)]}"/>
|
||||
<label string="hours" attrs="{'invisible': [('allday','=',True)]}"/>
|
||||
(<field name="allday" on_change="onchange_dates(date,False,False,allday)" class="oe_inline"/>
|
||||
(<field name="allday" class="oe_inline"/>
|
||||
<label for="allday" string="All Day?"/>)
|
||||
</div>
|
||||
<field name="date_deadline" groups="base.group_no_one"
|
||||
attrs="{'invisible': ['|', ('allday','=',True), ('duration','<', 24)]}"
|
||||
on_change="onchange_dates(date,False,date_deadline)"/>
|
||||
/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="categ_ids" widget="many2many_tags"/>
|
||||
|
@ -121,10 +126,10 @@
|
|||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="class"/>
|
||||
<field name="show_as"/>
|
||||
<field name="rrule" invisible="1" readonly="0" />
|
||||
<field name="rrule" invisible="1" readonly="0" />
|
||||
<field name="recurrent_id" invisible="1" />
|
||||
</group>
|
||||
</group>
|
||||
|
@ -136,7 +141,7 @@
|
|||
<tree string="Invitation details" editable="top" create="false" delete="false">
|
||||
<field name="partner_id" />
|
||||
<field name="state" />
|
||||
<field name="email" widget="email"/>
|
||||
<field name="email" widget="email"/>
|
||||
|
||||
<button name="do_tentative" states="needsAction,declined,accepted" string="Uncertain" type="object" icon="terp-crm" />
|
||||
<button name="do_accept" string="Accept" states="needsAction,tentative,declined" type="object" icon="gtk-apply"/>
|
||||
|
@ -176,7 +181,7 @@
|
|||
<field name="date" string="Start" />
|
||||
<field name="duration" string="Duration" widget="float_time" attrs="{'invisible': [('allday','=',True)]}"/>
|
||||
<field name="allday" class="oe_inline" attrs="{'invisible': [('allday','=',False)]}"/>
|
||||
<field name="partner_ids" widget="many2manyattendee" string="Attendees"/>
|
||||
<field name="partner_ids" widget="many2manyattendee" string="Attendees"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="categ_ids" widget="many2many_tags"/>
|
||||
|
@ -213,7 +218,7 @@
|
|||
<field name="name">Meetings Calendar</field>
|
||||
<field name="model">calendar.event</field>
|
||||
<field name="priority" eval="2"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Meetings" date_start="date" date_stop="date_deadline" date_delay="duration" all_day="allday"
|
||||
display="[name]" color="color_partner_id" attendee="partner_ids" avatar_model="res.partner"
|
||||
use_contacts="True" event_open_popup="%(calendar.view_calendar_event_form_popup)s">
|
||||
|
@ -248,14 +253,14 @@
|
|||
<field name="user_id"/>
|
||||
<field name="show_as"/>
|
||||
<field name="class" string="Privacy"/>
|
||||
<filter icon="terp-go-today" string="My Events" domain="[('user_id','=',uid)]" help="My Events"/>
|
||||
<filter icon="terp-go-today" string="My Events" domain="[('user_id','=',uid)]" help="My Events"/>
|
||||
<filter string="My Meetings" help="My Meetings" name="mymeetings" context='{"mymeetings": 1}'/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<separator/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Responsible" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Availability" icon="terp-camera_test" domain="[]" context="{'group_by':'show_as'}"/>
|
||||
<filter string="Privacy" icon="terp-locked" domain="[]" context="{'group_by':'class'}"/>
|
||||
<filter string="Privacy" icon="terp-locked" domain="[]" context="{'group_by':'class'}"/>
|
||||
<filter string="Event Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}" help="Start Date of Event by Month"/>
|
||||
</group>
|
||||
</search>
|
||||
|
@ -267,8 +272,7 @@
|
|||
<field name="res_model">calendar.event</field>
|
||||
<field name="view_mode">calendar,tree,form,gantt</field>
|
||||
<field name="view_id" ref="view_calendar_event_calendar"/>
|
||||
<field name="search_view_id" ref="view_calendar_event_search"/>
|
||||
<field name="context">{"search_default_mymeetings": 1}</field>
|
||||
<field name="search_view_id" ref="view_calendar_event_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to schedule a new meeting.
|
||||
|
@ -313,7 +317,7 @@
|
|||
<field name="name">Meetings</field>
|
||||
<field name="res_model">calendar.event</field>
|
||||
<field name="view_mode">form,calendar,tree,gantt</field>
|
||||
<field name="view_id" ref="view_calendar_event_form"/>
|
||||
<field name="view_id" ref="view_calendar_event_form"/>
|
||||
</record>
|
||||
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class calendar_contacts(osv.osv):
|
|||
|
||||
_columns = {
|
||||
'user_id': fields.many2one('res.users','Me'),
|
||||
'partner_id': fields.many2one('res.partner','Employee',required=True, domain=[('customer','=',True)]),
|
||||
'partner_id': fields.many2one('res.partner','Employee',required=True, domain=[]),
|
||||
'active':fields.boolean('active'),
|
||||
}
|
||||
_defaults = {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<field name="res_model">calendar.contacts</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="domain">[('user_id','=',uid)]</field>
|
||||
<field name="view_id" ref="view_calendar_contacts" />
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
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):
|
||||
|
||||
@http.route('/calendar/meeting/accept', type='http', auth="calendar")
|
||||
def accept(self, db, token, action, id,**kwargs):
|
||||
def accept(self, db, token, action, id, **kwargs):
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
with registry.cursor() as cr:
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token),('state','!=', 'accepted')])
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token', '=', token), ('state', '!=', 'accepted')])
|
||||
if attendee_id:
|
||||
attendee_pool.do_accept(cr, openerp.SUPERUSER_ID, attendee_id)
|
||||
return self.view(db, token, action, id, view='form')
|
||||
|
@ -25,7 +23,7 @@ class meeting_invitation(http.Controller):
|
|||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
with registry.cursor() as cr:
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token),('state','!=', 'declined')])
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token', '=', token), ('state', '!=', 'declined')])
|
||||
if attendee_id:
|
||||
attendee_pool.do_decline(cr, openerp.SUPERUSER_ID, attendee_id)
|
||||
return self.view(db, token, action, id, view='form')
|
||||
|
@ -36,13 +34,13 @@ 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 = attendee_pool.search_read(cr, openerp.SUPERUSER_ID, [('access_token','=',token)],[])
|
||||
|
||||
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:
|
||||
attendee_data['current_attendee'] = attendee[0]
|
||||
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in webmain.manifest_list('js', db=db))
|
||||
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list('css',db=db))
|
||||
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list('css', db=db))
|
||||
|
||||
return webmain.html_template % {
|
||||
'js': js,
|
||||
|
@ -50,7 +48,7 @@ class meeting_invitation(http.Controller):
|
|||
'modules': simplejson.dumps(webmain.module_boot(db)),
|
||||
'init': "s.calendar.event('%s', '%s', '%s', '%s' , '%s');" % (db, action, id, 'form', json.dumps(attendee_data)),
|
||||
}
|
||||
|
||||
|
||||
# Function used, in RPC to check every 5 minutes, if notification to do for an event or not
|
||||
@http.route('/calendar/notify', type='json', auth="none")
|
||||
def notify(self):
|
||||
|
@ -58,15 +56,14 @@ class meeting_invitation(http.Controller):
|
|||
uid = request.session.uid
|
||||
context = request.session.context
|
||||
with registry.cursor() as cr:
|
||||
res = registry.get("calendar.alarm_manager").get_next_notif(cr,uid,context=context)
|
||||
res = registry.get("calendar.alarm_manager").get_next_notif(cr, uid, context=context)
|
||||
return res
|
||||
|
||||
|
||||
@http.route('/calendar/notify_ack', type='json', auth="none")
|
||||
def notify_ack(self, type=''):
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.session.db)
|
||||
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,18 +1,21 @@
|
|||
openerp.calendar = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
var QWeb = instance.web.qweb;
|
||||
instance.calendar = {}
|
||||
|
||||
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(
|
||||
function(result) {
|
||||
.done(
|
||||
function(result) {
|
||||
_.each(result, function(res) {
|
||||
setTimeout(function() {
|
||||
//If notification not already displayed, we add button and action on it
|
||||
|
@ -21,44 +24,56 @@ openerp.calendar = function(instance) {
|
|||
res.message += QWeb.render("notify_footer");
|
||||
a = self.do_notify(res.title,res.message,true);
|
||||
|
||||
$(".link2event").on('click', function() {
|
||||
$(".link2event").on('click', function() {
|
||||
self.rpc("/web/action/load", {
|
||||
action_id: "calendar.action_calendar_event_notify",
|
||||
}).then( function(r) {
|
||||
}).then( function(r) {
|
||||
r.res_id = res.event_id;
|
||||
return self.action_manager.do_action(r);
|
||||
});
|
||||
return self.action_manager.do_action(r);
|
||||
});
|
||||
});
|
||||
a.element.find(".link2recall").on('click',function() {
|
||||
a.element.find(".link2recall").on('click',function() {
|
||||
self.get_notif_box(this).find('.ui-notify-close').trigger("click");
|
||||
});
|
||||
a.element.find(".link2showed").on('click',function() {
|
||||
});
|
||||
a.element.find(".link2showed").on('click',function() {
|
||||
self.get_notif_box(this).find('.ui-notify-close').trigger("click");
|
||||
self.rpc("/calendar/notify_ack");
|
||||
});
|
||||
}
|
||||
//If notification already displayed in the past, we remove the css attribute which hide this notification
|
||||
else if (self.get_notif_box($.find(".eid_"+res.event_id)).attr("style") !== ""){
|
||||
self.get_notif_box($.find(".eid_"+res.event_id)).attr("style","");
|
||||
self.get_notif_box($.find(".eid_"+res.event_id)).attr("style","");
|
||||
}
|
||||
},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.get_next_notif();
|
||||
self.intervalNotif = setInterval(function(){
|
||||
self.get_next_notif();
|
||||
}, 5 * 60 * 1000 );
|
||||
}, 5 * 60 * 1000 );
|
||||
},
|
||||
|
||||
//Override the show_application of addons/web/static/src/js/chrome.js
|
||||
show_application: function() {
|
||||
this._super();
|
||||
this.check_notifications();
|
||||
}
|
||||
},
|
||||
//Override addons/web/static/src/js/chrome.js
|
||||
on_logout: function() {
|
||||
this._super();
|
||||
clearInterval(self.intervalNotif);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
@ -77,7 +92,6 @@ openerp.calendar = function(instance) {
|
|||
if(instance.session.session_is_valid(self.db) && instance.session.username != "anonymous") {
|
||||
self.redirect_meeting_view(self.db,self.action,self.id,self.view);
|
||||
} else {
|
||||
alert('in anonymous or null ');
|
||||
self.open_invitation_form(self.attendee_data);
|
||||
}
|
||||
},
|
||||
|
@ -92,9 +106,8 @@ openerp.calendar = function(instance) {
|
|||
|
||||
var reload_page = function(){
|
||||
return location.replace(action_url);
|
||||
}
|
||||
};
|
||||
reload_page();
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -108,7 +121,7 @@ openerp.calendar = function(instance) {
|
|||
});
|
||||
},
|
||||
map_tag: function(value){
|
||||
return _.map(value, function(el) {return {name: el[1], id:el[0], state: el[2]};})
|
||||
return _.map(value, function(el) {return {name: el[1], id:el[0], state: el[2]};});
|
||||
},
|
||||
get_render_data: function(ids){
|
||||
var self = this;
|
||||
|
@ -121,7 +134,7 @@ openerp.calendar = function(instance) {
|
|||
if (! self.get("effective_readonly")) {
|
||||
var tag_element = self.tags.tagElements();
|
||||
_.each(data,function(value, key){
|
||||
$(tag_element[key]).find(".custom-edit").addClass(data[key][2])
|
||||
$(tag_element[key]).find(".custom-edit").addClass(data[key][2]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +147,7 @@ openerp.calendar = function(instance) {
|
|||
instance.session.session_bind(instance.session.origin).done(function () {
|
||||
new instance.calendar.invitation(null,db,action,id,view,attendee_data).appendTo($("body").addClass('openerp'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -33,12 +33,14 @@
|
|||
!python {model: calendar.event}: |
|
||||
ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': True} )
|
||||
before = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False})
|
||||
self.write(cr, uid,[ids[1]], {'name':'New Name','recurrency' : True}, context={'virtual_id': True})
|
||||
# We start by detach the event
|
||||
newid = self._detach_one_event(cr, uid,ids[1])
|
||||
self.write(cr, uid,[newid], {'name':'New Name','recurrency' : True}, context={'virtual_id': True})
|
||||
after = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False})
|
||||
assert len(after) == len(before)+1, 'Wrong number of events found, after to have moved a virtual event'
|
||||
new_id = list(set(after)-set(before))[0]
|
||||
new_id = list(set(after)-set(before))[0]
|
||||
new_event = self.browse(cr,uid,new_id,context=context)
|
||||
assert new_event.recurrent_id == before[0], 'Recurrent_id not correctly passed to the new event'
|
||||
assert new_event.recurrent_id == before[0], 'Recurrent_id not correctly passed to the new event'
|
||||
-
|
||||
Now I will make All day event and test it
|
||||
-
|
||||
|
@ -58,7 +60,6 @@
|
|||
duration: 1
|
||||
interval: days
|
||||
type: notification
|
||||
|
||||
-
|
||||
Now I will assign this reminder to all day event
|
||||
-
|
||||
|
|
|
@ -84,9 +84,12 @@
|
|||
idval = '%d-%s' % (ref('calendar_event_sprintreview0'), '20110425124700')
|
||||
self.write(cr, uid, [idval], {'description': 'Review code of the module: sync_google_calendar.'})
|
||||
-
|
||||
I check whether the record is edited perfectly or not.
|
||||
I check whether that all the records of this recurrence has been edited.
|
||||
-
|
||||
!python {model: calendar.event}: |
|
||||
meeting_ids = self.search(cr, uid, [('recurrent_id', '=', ref('calendar_event_sprintreview0')), ('recurrent_id_date','=','2011-04-25 12:47:00')], context)
|
||||
assert meeting_ids, 'Meeting is not edited !'
|
||||
meeting_ids = self.search(cr, uid, [('recurrent_id', '=', ref('calendar_event_sprintreview0'))], context)
|
||||
meetings = self.browse(cr, uid, meeting_ids, context)
|
||||
for meeting in meetings:
|
||||
assert meeting.description == 'Review code of the module: sync_google_calendar.', 'Description not changed for id: %s' %meeting.id
|
||||
|
||||
|
||||
|
|
|
@ -23,15 +23,13 @@ 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'
|
||||
_columns = {
|
||||
'phonecall_id': fields.many2one ('crm.phonecall', 'Phonecall'),
|
||||
'opportunity_id': fields.many2one ('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"),
|
||||
'phonecall_id': fields.many2one('crm.phonecall', 'Phonecall'),
|
||||
'opportunity_id': fields.many2one('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
|
@ -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:
|
||||
|
|
|
@ -120,13 +120,14 @@ class crm_case_section(osv.osv):
|
|||
month_begin = date.today().replace(day=1)
|
||||
section_result = [{
|
||||
'value': 0,
|
||||
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
|
||||
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B %Y'),
|
||||
} for i in range(self._period_number - 1, -1, -1)]
|
||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
for group in group_obj:
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern)
|
||||
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
|
||||
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
|
||||
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field, 0)}
|
||||
return section_result
|
||||
|
||||
def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None):
|
||||
|
@ -139,13 +140,18 @@ class crm_case_section(osv.osv):
|
|||
month_begin = date.today().replace(day=1)
|
||||
date_begin = month_begin - relativedelta.relativedelta(months=self._period_number - 1)
|
||||
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1])
|
||||
date_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)), ('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
|
||||
lead_pre_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
|
||||
('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
|
||||
('type', '=', 'lead')]
|
||||
opp_pre_domain = [('date_deadline', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
('date_deadline', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
('type', '=', 'opportunity')]
|
||||
for id in ids:
|
||||
res[id] = dict()
|
||||
lead_domain = date_domain + [('type', '=', 'lead'), ('section_id', '=', id)]
|
||||
lead_domain = lead_pre_domain + [('section_id', '=', id)]
|
||||
opp_domain = opp_pre_domain + [('section_id', '=', id)]
|
||||
res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context)
|
||||
opp_domain = date_domain + [('type', '=', 'opportunity'), ('section_id', '=', id)]
|
||||
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date'], 'planned_revenue', 'create_date', context=context)
|
||||
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
|
|
|
@ -65,9 +65,9 @@
|
|||
<record id="action_report_crm_lead_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Leads Analysis</field>
|
||||
<field name="res_model">crm.lead.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{"search_default_month":1}</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="crm.view_report_crm_lead_graph_two"/>
|
||||
<field name="domain">[('type','=', 'lead'),('section_id', '=', active_id)]</field>
|
||||
<field name="help">Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.</field>
|
||||
</record>
|
||||
|
@ -75,9 +75,8 @@
|
|||
<record id="action_report_crm_opportunity_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Opportunities Analysis</field>
|
||||
<field name="res_model">crm.lead.report</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{"search_default_month":1}</field>
|
||||
<field name="view_mode">tree,graph</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="crm.view_report_crm_opportunity_graph"/>
|
||||
<field name="domain">[('type','=', 'opportunity'), ('section_id', '=', active_id)]</field>
|
||||
<field name="help">Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline.</field>
|
||||
</record>
|
||||
|
@ -119,14 +118,14 @@
|
|||
<a name="%(crm_case_form_view_salesteams_lead)d" type="action">Leads</a>
|
||||
<a name="%(action_report_crm_lead_salesteam)d" type="action" class="oe_sparkline_bar_link">
|
||||
<field name="monthly_open_leads" widget="sparkline_bar"
|
||||
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': 'Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
|
||||
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': ' Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oe_salesteams_opportunities">
|
||||
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
|
||||
<a name="%(action_report_crm_opportunity_salesteam)d" type="action">
|
||||
<field name="monthly_planned_revenue" widget="sparkline_bar"
|
||||
options="{'height': '20px', 'barWidth': '4', 'barSpacing': '1', 'delayIn': '3000', 'tooltip_suffix': 'Opportunities'}">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field>
|
||||
options="{'height': '20px', 'barWidth': '4', 'barSpacing': '1', 'delayIn': '3000', 'tooltip_suffix': ' (Planned Revenue)'}">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -330,6 +330,7 @@ class crm_lead(format_address, osv.osv):
|
|||
'phone': partner.phone,
|
||||
'mobile': partner.mobile,
|
||||
'fax': partner.fax,
|
||||
'zip': partner.zip,
|
||||
}
|
||||
return {'value': values}
|
||||
|
||||
|
|
|
@ -343,6 +343,7 @@
|
|||
help="Leads that are assigned to any sales teams I am member of"/>
|
||||
<filter string="Dead" name="dead"
|
||||
domain="[('probability', '=', '0'), ('stage_id.fold', '=', True)]"/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator />
|
||||
<filter string="Available for mass mailing"
|
||||
name='not_opt_out' domain="[('opt_out', '=', False)]"
|
||||
|
@ -562,6 +563,7 @@
|
|||
<filter string="My Team(s)"
|
||||
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
|
||||
help="Opportunities that are assigned to any sales teams I am member of"/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<group expand="0" string="Group By..." colspan="16">
|
||||
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
|
|
|
@ -171,6 +171,7 @@
|
|||
<filter icon="terp-gtk-go-back-rtl" string="To Do" name="current" domain="[('state','=','open')]"/>
|
||||
<separator/>
|
||||
<filter string="Unassigned Phonecalls" icon="terp-personal-" domain="[('user_id','=',False)]" help="Unassigned Phonecalls"/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter string="Phone Calls Assigned to Me or My Team(s)" icon="terp-personal+" domain="['|', ('section_id.user_id','=',uid), ('user_id', '=', uid)]"
|
||||
help="Phone Calls Assigned to the current user or with a team having the current user as team leader"/>
|
||||
|
|
|
@ -89,10 +89,6 @@ class crm_lead_report(osv.osv):
|
|||
id,
|
||||
c.date_deadline,
|
||||
|
||||
to_char(c.create_date, 'YYYY') as creation_year,
|
||||
to_char(c.create_date, 'MM') as creation_month,
|
||||
to_char(c.create_date, 'YYYY-MM-DD') as creation_day,
|
||||
|
||||
to_char(c.date_open, 'YYYY-MM-DD') as opening_date,
|
||||
to_char(c.date_closed, 'YYYY-mm-dd') as date_closed,
|
||||
|
||||
|
|
|
@ -15,6 +15,17 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_report_crm_lead_graph_two" model="ir.ui.view">
|
||||
<field name="name">crm.lead.report.graph.two</field>
|
||||
<field name="model">crm.lead.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Leads Analysis" type="pivot" stacked="True">
|
||||
<field name="create_date" type="row"/>
|
||||
<field name="user_id" type="col"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_report_crm_opportunity_graph" model="ir.ui.view">
|
||||
<field name="name">crm.opportunity.report.graph</field>
|
||||
<field name="model">crm.lead.report</field>
|
||||
|
@ -23,7 +34,7 @@
|
|||
<field name="date_deadline" type="row"/>
|
||||
<field name="user_id" type="col"/>
|
||||
<field name="stage_id" type="col"/>
|
||||
<field name="probable_revenue" type="measure"/>
|
||||
<field name="planned_revenue" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -84,7 +95,7 @@
|
|||
<filter string="Creation date (week)" icon="terp-go-year"
|
||||
domain="[]" context="{'group_by':'create_date:week'}"/>
|
||||
<filter string="Creation date (month)" icon="terp-go-year"
|
||||
domain="[]" context="{'group_by':'create_date:month'}"/>
|
||||
domain="[]" context="{'group_by':'create_date:month'}" name="month"/>
|
||||
<filter string="Creation date (year)" icon="terp-go-year"
|
||||
domain="[]" context="{'group_by':'create_date:year'}"/>
|
||||
<separator orientation="vertical" />
|
||||
|
|
|
@ -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)
|
||||
-
|
||||
|
|
|
@ -46,12 +46,12 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
results = []
|
||||
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')])
|
||||
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')])
|
||||
for id in ids:
|
||||
results.append(id)
|
||||
email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
|
||||
if email:
|
||||
ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
|
||||
ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')])
|
||||
for id in ids:
|
||||
results.append(id)
|
||||
return list(set(results))
|
||||
|
@ -69,11 +69,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 +99,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 +123,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, [], team_id, 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 +144,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)
|
||||
|
@ -186,11 +190,13 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
|||
_columns = {
|
||||
'user_ids': fields.many2many('res.users', string='Salesmen'),
|
||||
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
|
||||
'deduplicate': fields.boolean('Apply deduplication', help='Merge with existing leads/opportunities of each partner'),
|
||||
'deduplicate': fields.boolean('Apply deduplication', help='Merge with existing leads/opportunities of each partner'),
|
||||
'action': fields.selection([
|
||||
('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 +272,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">
|
||||
|
|
|
@ -140,8 +140,8 @@
|
|||
</group>
|
||||
<group col="2">
|
||||
<separator string="Categorization" colspan="2"/>
|
||||
<field name="type_id" widget="selection" readonly="1"/>
|
||||
<field name="channel_id" widget="selection" readonly="1"/>
|
||||
<field name="type_id" readonly="1"/>
|
||||
<field name="channel_id" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="Details" />
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
else{
|
||||
parent.inner_widget.views[parent.inner_widget.active_view].controller.reload();
|
||||
}
|
||||
parent.do_action({ type: 'ir.actions.act_window_close' });
|
||||
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');
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
};
|
||||
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
|
||||
|
@ -70,6 +72,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 +85,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 +169,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 +231,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 +404,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,
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<h3>Body</h3>
|
||||
<field name="body_html" width="250" height="450"
|
||||
placeholder="Rich-text/HTML content of the message (placeholders may be used here)"/>
|
||||
<field name="attachment_ids" nolabel="1" widget="many2many_binary"/>
|
||||
<field name="attachment_ids" widget="many2many_binary"/>
|
||||
</page>
|
||||
<page string="Advanced Settings">
|
||||
<group>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
<div>Use template
|
||||
<!--FIX: To avoid css issue of many2one field in footer temporary used oe_form (BUG:1152464)-->
|
||||
<field name="template_id" nolabel="1" class='oe_inline'
|
||||
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)" domain="[('model_id.model','=',model)]"/>
|
||||
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)" domain="[('model_id.model','=',model)]"
|
||||
context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
|
||||
</div>
|
||||
<button icon="/email_template/static/src/img/email_template_save.png"
|
||||
type="object" name="save_as_template" string="Save as new template" class="oe_link"
|
||||
|
|
|
@ -219,6 +219,7 @@ class event_event(osv.osv):
|
|||
]
|
||||
|
||||
def onchange_event_type(self, cr, uid, ids, type_event, context=None):
|
||||
values = {}
|
||||
if type_event:
|
||||
type_info = self.pool.get('event.type').browse(cr,uid,type_event,context)
|
||||
dic ={
|
||||
|
@ -228,7 +229,8 @@ class event_event(osv.osv):
|
|||
'seats_min': type_info.default_registration_min,
|
||||
'seats_max': type_info.default_registration_max,
|
||||
}
|
||||
return {'value': dic}
|
||||
values.update(dic)
|
||||
return values
|
||||
|
||||
def onchange_start_date(self, cr, uid, ids, date_begin=False, date_end=False, context=None):
|
||||
res = {'value':{}}
|
||||
|
|
|
@ -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'}"/>
|
||||
|
|
|
@ -187,7 +187,7 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
|
|||
for server in self.browse(cr, uid, ids, context=context):
|
||||
_logger.info('start checking for new emails on %s server %s', server.type, server.name)
|
||||
context.update({'fetchmail_server_id': server.id, 'server_type': server.type})
|
||||
count = 0
|
||||
count, failed = 0, 0
|
||||
imap_server = False
|
||||
pop_server = False
|
||||
if server.type == 'imap':
|
||||
|
@ -196,20 +196,26 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
|
|||
imap_server.select()
|
||||
result, data = imap_server.search(None, '(UNSEEN)')
|
||||
for num in data[0].split():
|
||||
res_id = None
|
||||
result, data = imap_server.fetch(num, '(RFC822)')
|
||||
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
|
||||
data[0][1],
|
||||
save_original=server.original,
|
||||
strip_attachments=(not server.attach),
|
||||
context=context)
|
||||
imap_server.store(num, '-FLAGS', '\\Seen')
|
||||
try:
|
||||
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
|
||||
data[0][1],
|
||||
save_original=server.original,
|
||||
strip_attachments=(not server.attach),
|
||||
context=context)
|
||||
except Exception:
|
||||
_logger.exception('Failed to process mail from %s server %s.', server.type, server.name)
|
||||
failed += 1
|
||||
if res_id and server.action_id:
|
||||
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", server.object_id.model)})
|
||||
imap_server.store(num, '+FLAGS', '\\Seen')
|
||||
cr.commit()
|
||||
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids': [res_id], 'active_model': context.get("thread_model", server.object_id.model)})
|
||||
imap_server.store(num, '+FLAGS', '\\Seen')
|
||||
cr.commit()
|
||||
count += 1
|
||||
_logger.info("fetched/processed %s email(s) on %s server %s", count, server.type, server.name)
|
||||
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", count, server.type, server.name, (count - failed), failed)
|
||||
except Exception:
|
||||
_logger.exception("Failed to fetch mail from %s server %s.", server.type, server.name)
|
||||
_logger.exception("General failure when trying to fetch mail from %s server %s.", server.type, server.name)
|
||||
finally:
|
||||
if imap_server:
|
||||
imap_server.close()
|
||||
|
@ -222,18 +228,23 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
|
|||
for num in range(1, numMsgs + 1):
|
||||
(header, msges, octets) = pop_server.retr(num)
|
||||
msg = '\n'.join(msges)
|
||||
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
|
||||
msg,
|
||||
save_original=server.original,
|
||||
strip_attachments=(not server.attach),
|
||||
context=context)
|
||||
res_id = None
|
||||
try:
|
||||
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
|
||||
msg,
|
||||
save_original=server.original,
|
||||
strip_attachments=(not server.attach),
|
||||
context=context)
|
||||
except Exception:
|
||||
_logger.exception('Failed to process mail from %s server %s.', server.type, server.name)
|
||||
failed += 1
|
||||
if res_id and server.action_id:
|
||||
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", server.object_id.model)})
|
||||
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids': [res_id], 'active_model': context.get("thread_model", server.object_id.model)})
|
||||
pop_server.dele(num)
|
||||
cr.commit()
|
||||
_logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name)
|
||||
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", numMsgs, server.type, server.name, (numMsgs - failed), failed)
|
||||
except Exception:
|
||||
_logger.exception("Failed to fetch mail from %s server %s.", server.type, server.name)
|
||||
_logger.exception("General failure when trying to fetch mail from %s server %s.", server.type, server.name)
|
||||
finally:
|
||||
if pop_server:
|
||||
pop_server.quit()
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_span12">
|
||||
<h2 class="oe_slogan">Drive Engagement with Gamification</h2>
|
||||
<h3 class="oe_slogan">Leverage natural desire for competition</h3>
|
||||
<p class="oe_mt32">
|
||||
Reinforce good habits and improve win rates with real-time recognition and rewards inspired by <a href="http://en.wikipedia.org/wiki/Gamification">game mechanics</a>. Align teams around clear business objectives with challenges, personal objectives and team leader boards.
|
||||
</p>
|
||||
<div class="oe_span4 oe_centered">
|
||||
<h3>Leaderboards</h3>
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture" src="crm_game_01.png">
|
||||
</div>
|
||||
<p>
|
||||
Promote leaders and competition amongst sales team with performance ratios.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span4 oe_centered">
|
||||
<h3>Personnal Objectives</h3>
|
||||
<div class="oe_row_img">
|
||||
<img class="oe_picture" src="crm_game_02.png">
|
||||
</div>
|
||||
<p>
|
||||
Assign clear goals to users to align them with the company objectives.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span4 oe_centered">
|
||||
<h3>Visual Information</h3>
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture" src="crm_game_03.png">
|
||||
</div>
|
||||
<p>
|
||||
See in an glance the progress of each user.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Create custom Challenges</h2>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">
|
||||
Use predefined goals to generate easily your own challenges. Assign it to a team or individual users. Receive feedback as often as needed: daily, weekly... Repeat it automatically to compare progresses through time.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture oe_screenshot" src="crm_sc_05.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Motivate with Badges</h2>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture" src="crm_linkedin.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">
|
||||
Inspire achievement with recognition of coworker's good work by rewarding badges. These can be deserved manually or upon completion of challenges. Add fun to the competition with rare badges.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Adapt to any module</h2>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">
|
||||
Create goals linked to any module. The evaluation system is very flexible and can be used for many different tasks : sales evaluation, creation of events, project completion or even helping new users to complete their profile.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture oe_screenshot" src="crm_sc_02.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
File diff suppressed because it is too large
Load Diff
|
@ -387,6 +387,7 @@ class gamification_challenge(osv.Model):
|
|||
end_date = challenge.end_date
|
||||
|
||||
for line in challenge.line_ids:
|
||||
# FIXME: allow to restrict to a subset of users
|
||||
for user in challenge.user_ids:
|
||||
|
||||
goal_obj = self.pool.get('gamification.goal')
|
||||
|
@ -400,8 +401,9 @@ class gamification_challenge(osv.Model):
|
|||
# resume canceled goals
|
||||
domain.append(('state', '=', 'canceled'))
|
||||
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
|
||||
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
|
||||
goal_obj.update(cr, uid, canceled_goal_ids, context=context)
|
||||
if canceled_goal_ids:
|
||||
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
|
||||
goal_obj.update(cr, uid, canceled_goal_ids, context=context)
|
||||
|
||||
# skip to next user
|
||||
continue
|
||||
|
@ -431,7 +433,7 @@ class gamification_challenge(osv.Model):
|
|||
##### JS utilities #####
|
||||
|
||||
def _get_serialized_challenge_lines(self, cr, uid, challenge, user_id=False, restrict_goal_ids=False, restrict_top=False, context=None):
|
||||
"""Return a serialised version of the goals information
|
||||
"""Return a serialised version of the goals information if the user has not completed every goal
|
||||
|
||||
:challenge: browse record of challenge to compute
|
||||
:user_id: res.users id of the user retrieving progress (False if no distinction, only for ranking challenges)
|
||||
|
@ -485,6 +487,7 @@ class gamification_challenge(osv.Model):
|
|||
(start_date, end_date) = start_end_date_for_period(challenge.period)
|
||||
|
||||
res_lines = []
|
||||
all_reached = True
|
||||
for line in challenge.line_ids:
|
||||
line_data = {
|
||||
'name': line.definition_id.name,
|
||||
|
@ -516,13 +519,6 @@ class gamification_challenge(osv.Model):
|
|||
domain.append(('user_id', '=', user_id))
|
||||
sorting = goal_obj._order
|
||||
limit = 1
|
||||
# initialise in case search returns no results
|
||||
line_data.update({
|
||||
'id': 0,
|
||||
'current': 0,
|
||||
'completeness': 0,
|
||||
'state': 'draft',
|
||||
})
|
||||
else:
|
||||
line_data.update({
|
||||
'own_goal_id': False,
|
||||
|
@ -542,6 +538,8 @@ class gamification_challenge(osv.Model):
|
|||
'completeness': goal.completeness,
|
||||
'state': goal.state,
|
||||
})
|
||||
if goal.state != 'reached':
|
||||
all_reached = False
|
||||
else:
|
||||
ranking += 1
|
||||
if user_id and goal.user_id.id == user_id:
|
||||
|
@ -559,7 +557,12 @@ class gamification_challenge(osv.Model):
|
|||
'completeness': goal.completeness,
|
||||
'state': goal.state,
|
||||
})
|
||||
res_lines.append(line_data)
|
||||
if goal.state != 'reached':
|
||||
all_reached = False
|
||||
if goal_ids:
|
||||
res_lines.append(line_data)
|
||||
if all_reached:
|
||||
return []
|
||||
return res_lines
|
||||
|
||||
##### Reporting #####
|
||||
|
@ -624,22 +627,28 @@ class gamification_challenge(osv.Model):
|
|||
return self.write(cr, uid, challenge.id, {'last_report_date': fields.date.today()}, context=context)
|
||||
|
||||
##### Challenges #####
|
||||
# TODO in trunk, remove unused parameter user_id
|
||||
def accept_challenge(self, cr, uid, challenge_ids, context=None, user_id=None):
|
||||
"""The user accept the suggested challenge"""
|
||||
user_id = user_id or uid
|
||||
return self._accept_challenge(cr, uid, uid, challenge_ids, context=context)
|
||||
|
||||
def _accept_challenge(self, cr, uid, user_id, challenge_ids, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, user_id, context=context)
|
||||
message = "%s has joined the challenge" % user.name
|
||||
self.message_post(cr, uid, challenge_ids, body=message, context=context)
|
||||
self.message_post(cr, SUPERUSER_ID, challenge_ids, body=message, context=context)
|
||||
self.write(cr, SUPERUSER_ID, challenge_ids, {'invited_user_ids': [(3, user_id)], 'user_ids': [(4, user_id)]}, context=context)
|
||||
return self.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
|
||||
return self.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
|
||||
|
||||
# TODO in trunk, remove unused parameter user_id
|
||||
def discard_challenge(self, cr, uid, challenge_ids, context=None, user_id=None):
|
||||
"""The user discard the suggested challenge"""
|
||||
user_id = user_id or uid
|
||||
return self._discard_challenge(cr, uid, uid, challenge_ids, context=context)
|
||||
|
||||
def _discard_challenge(self, cr, uid, user_id, challenge_ids, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, user_id, context=context)
|
||||
message = "%s has refused the challenge" % user.name
|
||||
self.message_post(cr, SUPERUSER_ID, challenge_ids, body=message, context=context)
|
||||
return self.write(cr, uid, challenge_ids, {'invited_user_ids': (3, user_id)}, context=context)
|
||||
return self.write(cr, SUPERUSER_ID, challenge_ids, {'invited_user_ids': (3, user_id)}, context=context)
|
||||
|
||||
def reply_challenge_wizard(self, cr, uid, challenge_id, context=None):
|
||||
result = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'challenge_wizard')
|
||||
|
|
|
@ -163,7 +163,8 @@ class gamification_goal(osv.Model):
|
|||
string="Challenge",
|
||||
type='many2one',
|
||||
relation='gamification.challenge',
|
||||
store=True),
|
||||
store=True, readonly=True,
|
||||
help="Challenge that generated the goal, assign challenge to users to generate goals with a value in this field."),
|
||||
'start_date': fields.date('Start Date'),
|
||||
'end_date': fields.date('End Date'), # no start and end = always active
|
||||
'target_goal': fields.float('To Reach',
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv
|
||||
from challenge import MAX_VISIBILITY_RANKING
|
||||
|
||||
|
@ -39,9 +40,10 @@ class res_users_gamification_group(osv.Model):
|
|||
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
|
||||
|
||||
challenge_obj = self.pool.get('gamification.challenge')
|
||||
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
||||
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
||||
if challenge_ids:
|
||||
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
|
||||
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
|
||||
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
|
||||
return write_res
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
|
@ -53,9 +55,10 @@ class res_users_gamification_group(osv.Model):
|
|||
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
|
||||
|
||||
challenge_obj = self.pool.get('gamification.challenge')
|
||||
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
||||
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
||||
if challenge_ids:
|
||||
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
|
||||
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
|
||||
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
|
||||
return write_res
|
||||
|
||||
# def get_goals_todo_info(self, cr, uid, context=None):
|
||||
|
@ -83,13 +86,15 @@ class res_users_gamification_group(osv.Model):
|
|||
challenge_ids = challenge_obj.search(cr, uid, [('user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
|
||||
for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
|
||||
# serialize goals info to be able to use it in javascript
|
||||
all_goals_info.append({
|
||||
'id': challenge.id,
|
||||
'name': challenge.name,
|
||||
'visibility_mode': challenge.visibility_mode,
|
||||
'currency': user.company_id.currency_id.id,
|
||||
'lines': challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context),
|
||||
})
|
||||
lines = challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context)
|
||||
if lines:
|
||||
all_goals_info.append({
|
||||
'id': challenge.id,
|
||||
'name': challenge.name,
|
||||
'visibility_mode': challenge.visibility_mode,
|
||||
'currency': user.company_id.currency_id.id,
|
||||
'lines': lines,
|
||||
})
|
||||
|
||||
return all_goals_info
|
||||
|
||||
|
@ -126,7 +131,8 @@ class res_groups_gamification_group(osv.Model):
|
|||
user_ids += [id for command in vals['users'] if command[0] == 6 for id in command[2]]
|
||||
|
||||
challenge_obj = self.pool.get('gamification.challenge')
|
||||
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', ids)], context=context)
|
||||
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', ids)], context=context)
|
||||
if challenge_ids:
|
||||
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
|
||||
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
|
||||
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
|
||||
return write_res
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<group string="Reference">
|
||||
<field name="definition_id" on_change="on_change_definition_id(definition_id)" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="challenge_id" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="challenge_id" />
|
||||
</group>
|
||||
<group string="Schedule">
|
||||
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
|
@ -280,8 +280,8 @@
|
|||
|
||||
<!-- menus in settings - technical feature required -->
|
||||
<menuitem id="gamification_menu" name="Gamification Tools" parent="base.menu_administration" groups="base.group_no_one" />
|
||||
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="0"/>
|
||||
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="10"/>
|
||||
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="0"/>
|
||||
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="10"/>
|
||||
<menuitem id="gamification_definition_menu" parent="gamification_menu" action="goal_definition_list_action" sequence="20"/>
|
||||
<menuitem id="gamification_badge_menu" parent="gamification_menu" action="badge_list_action" sequence="30"/>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<field name="user_id" eval="ref('base.user_demo')" />
|
||||
<field name="line_id" eval="ref('line_crm_sale1')" />
|
||||
<field name="start_date" eval="time.strftime('%Y-%m-01')" />
|
||||
<field name="end_date" eval="time.strftime('%Y-%m-31')" />
|
||||
<field name="end_date" eval="(DateTime.today().replace(day=1)+relativedelta(months=1, days=-1)).strftime('%Y-%m-%d')" />
|
||||
<field name="target_goal">2000</field>
|
||||
<field name="state">inprogress</field>
|
||||
</record>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,4 +37,5 @@ Badge received are displayed on the user profile.
|
|||
'views/gamification.xml',
|
||||
],
|
||||
'js': ['static/src/js/gamification.js'],
|
||||
'auto_install': True,
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<field name="view_type">form</field>
|
||||
<field name="name">Goals History</field>
|
||||
<field name="view_mode">tree,kanban</field>
|
||||
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_type': True}</field>
|
||||
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_definition': True}</field>
|
||||
<field name="domain">[('challenge_id.category', '=', 'hr')]</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
|
|
|
@ -370,7 +370,8 @@ class hr_holidays(osv.osv):
|
|||
'date': record.date_from,
|
||||
'end_date': record.date_to,
|
||||
'date_deadline': record.date_to,
|
||||
'state': 'open', # to block that meeting date in the calendar
|
||||
'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:
|
||||
|
|
|
@ -74,8 +74,8 @@
|
|||
<label for="number_of_days_temp" string="Duration"/>
|
||||
<div>
|
||||
<group col="3" attrs="{'invisible': [('type', '=', 'add')]}">
|
||||
<field name="date_from" nolabel="1" on_change="onchange_date_from(date_to, date_from)" required="1" class="oe_inline"/><label string="-" class="oe_inline"/>
|
||||
<field name="date_to" nolabel="1" on_change="onchange_date_to(date_to, date_from)" required="1" class="oe_inline"/>
|
||||
<field name="date_from" nolabel="1" on_change="onchange_date_from(date_to, date_from)" attrs="{'required':[('type', '=', 'remove')]}" class="oe_inline"/><label string="-" class="oe_inline"/>
|
||||
<field name="date_to" nolabel="1" on_change="onchange_date_to(date_to, date_from)" attrs="{'required':[('type', '=', 'remove')]}" class="oe_inline"/>
|
||||
</group>
|
||||
<div>
|
||||
<field name="number_of_days_temp" class="oe_inline"/> days
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import commands
|
||||
import logging
|
||||
import simplejson
|
||||
import os
|
||||
|
@ -11,7 +12,7 @@ import math
|
|||
import md5
|
||||
import openerp.addons.hw_proxy.controllers.main as hw_proxy
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock
|
||||
from Queue import Queue, Empty
|
||||
|
||||
try:
|
||||
|
@ -39,6 +40,7 @@ class EscposDriver(Thread):
|
|||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.queue = Queue()
|
||||
self.lock = Lock()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
|
||||
def connected_usb_devices(self):
|
||||
|
@ -47,6 +49,13 @@ class EscposDriver(Thread):
|
|||
if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
|
||||
connected.append(device)
|
||||
return connected
|
||||
|
||||
def lockedstart(self):
|
||||
self.lock.acquire()
|
||||
if not self.isAlive():
|
||||
self.daemon = True
|
||||
self.start()
|
||||
self.lock.release()
|
||||
|
||||
def get_escpos_printer(self):
|
||||
try:
|
||||
|
@ -65,7 +74,7 @@ class EscposDriver(Thread):
|
|||
self.push_task('status')
|
||||
return self.status
|
||||
|
||||
def open_cashbox(printer):
|
||||
def open_cashbox(self,printer):
|
||||
printer.cashdraw(2)
|
||||
printer.cashdraw(5)
|
||||
|
||||
|
@ -86,6 +95,9 @@ class EscposDriver(Thread):
|
|||
_logger.warning('ESC/POS Device Disconnected: '+message)
|
||||
|
||||
def run(self):
|
||||
if not escpos:
|
||||
_logger.error('ESC/POS cannot initialize, please verify system dependencies.')
|
||||
return
|
||||
self.queue = Queue()
|
||||
while True:
|
||||
try:
|
||||
|
@ -103,8 +115,10 @@ class EscposDriver(Thread):
|
|||
self.print_receipt_body(printer,data)
|
||||
printer.cut()
|
||||
elif task == 'cashbox':
|
||||
if timestamp >= time.time() * 12:
|
||||
if timestamp >= time.time() - 12:
|
||||
self.open_cashbox(printer)
|
||||
elif task == 'printstatus':
|
||||
self.print_status(printer)
|
||||
elif task == 'status':
|
||||
pass
|
||||
|
||||
|
@ -113,10 +127,31 @@ class EscposDriver(Thread):
|
|||
_logger.error(e);
|
||||
|
||||
def push_task(self,task, data = None):
|
||||
if not self.isAlive():
|
||||
self.start()
|
||||
self.lockedstart()
|
||||
self.queue.put((time.time(),task,data))
|
||||
|
||||
def print_status(self,eprint):
|
||||
localips = ['0.0.0.0','127.0.0.1','127.0.1.1']
|
||||
ips = [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
|
||||
ips = [ ip for ip in ips if ip not in localips ]
|
||||
eprint.text('\n\n')
|
||||
eprint.set(align='center',type='b',height=2,width=2)
|
||||
eprint.text('PosBox Status\n')
|
||||
eprint.text('\n')
|
||||
eprint.set(align='center')
|
||||
|
||||
if len(ips) == 0:
|
||||
eprint.text('ERROR: Could not connect to LAN\n\nPlease check that the PosBox is correc-\ntly connected with a network cable,\n that the LAN is setup with DHCP, and\nthat network addresses are available')
|
||||
elif len(ips) == 1:
|
||||
eprint.text('IP Address\n'+ips[0]+'\n')
|
||||
else:
|
||||
eprint.text('IP Addresses\n')
|
||||
for ip in ips:
|
||||
eprint.text(ip+'\n')
|
||||
|
||||
eprint.text('\n\n')
|
||||
eprint.cut()
|
||||
|
||||
def print_receipt_body(self,eprint,receipt):
|
||||
|
||||
def check(string):
|
||||
|
@ -134,7 +169,6 @@ class EscposDriver(Thread):
|
|||
else:
|
||||
return str(amount)
|
||||
|
||||
|
||||
def printline(left, right='', width=40, ratio=0.5, indent=0):
|
||||
lwidth = int(width * ratio)
|
||||
rwidth = width - lwidth
|
||||
|
@ -157,6 +191,7 @@ class EscposDriver(Thread):
|
|||
|
||||
# Receipt Header
|
||||
if receipt['company']['logo']:
|
||||
eprint.set(align='center')
|
||||
eprint.print_base64_image(receipt['company']['logo'])
|
||||
eprint.text('\n')
|
||||
else:
|
||||
|
@ -245,6 +280,8 @@ class EscposDriver(Thread):
|
|||
driver = EscposDriver()
|
||||
|
||||
hw_proxy.drivers['escpos'] = driver
|
||||
|
||||
driver.push_task('printstatus')
|
||||
|
||||
class EscposProxy(hw_proxy.Proxy):
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import os
|
|||
import time
|
||||
from os import listdir
|
||||
from os.path import join
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock
|
||||
from select import select
|
||||
from Queue import Queue, Empty
|
||||
|
||||
|
@ -26,6 +26,7 @@ except ImportError:
|
|||
class Scanner(Thread):
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.lock = Lock()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
self.input_dir = '/dev/input/by-id/'
|
||||
self.barcodes = Queue()
|
||||
|
@ -86,6 +87,12 @@ class Scanner(Thread):
|
|||
57:(" "," "),
|
||||
}
|
||||
|
||||
def lockedstart(self):
|
||||
self.lock.acquire()
|
||||
if not self.isAlive():
|
||||
self.start()
|
||||
self.lock.release()
|
||||
|
||||
def set_status(self, status, message = None):
|
||||
if status == self.status['status']:
|
||||
if message != None and message != self.status['messages'][-1]:
|
||||
|
@ -102,8 +109,6 @@ class Scanner(Thread):
|
|||
elif status == 'disconnected' and message:
|
||||
_logger.warning('Disconnected Barcode Scanner: '+message)
|
||||
|
||||
|
||||
|
||||
def get_device(self):
|
||||
try:
|
||||
if not evdev:
|
||||
|
@ -135,6 +140,8 @@ class Scanner(Thread):
|
|||
busy reading another barcode
|
||||
"""
|
||||
|
||||
self.lockedstart()
|
||||
|
||||
while True:
|
||||
try:
|
||||
timestamp, barcode = self.barcodes.get(True, 5)
|
||||
|
@ -144,8 +151,7 @@ class Scanner(Thread):
|
|||
return ''
|
||||
|
||||
def get_status(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
self.lockedstart()
|
||||
return self.status
|
||||
|
||||
def run(self):
|
||||
|
@ -209,7 +215,6 @@ hw_proxy.drivers['scanner'] = s
|
|||
class ScannerDriver(hw_proxy.Proxy):
|
||||
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
|
||||
def scanner(self):
|
||||
if not s.isAlive():
|
||||
s.start()
|
||||
return s.get_barcode()
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_im_message,im.message,model_im_message,,1,0,1,0
|
||||
access_im_user,im.user,model_im_user,,1,1,1,0
|
||||
access_im_session,im.session,model_im_session,,1,0,0,0
|
||||
access_im_session,im.session,model_im_session,,1,1,1,0
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -160,8 +160,8 @@ class mail_mail(osv.Model):
|
|||
fragment['message_id'] = mail.mail_message_id.id
|
||||
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
|
||||
|
|
|
@ -199,7 +199,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
|
||||
|
||||
|
@ -369,10 +369,11 @@ class mail_thread(osv.AbstractModel):
|
|||
track_ctx = dict(context)
|
||||
if 'lang' not in track_ctx:
|
||||
track_ctx['lang'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).lang
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
|
||||
if tracked_fields:
|
||||
initial_values = {thread_id: dict((item, False) for item in tracked_fields)}
|
||||
self.message_track(cr, uid, [thread_id], tracked_fields, initial_values, context=track_ctx)
|
||||
if not context.get('mail_notrack'):
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
|
||||
if tracked_fields:
|
||||
initial_values = {thread_id: dict((item, False) for item in tracked_fields)}
|
||||
self.message_track(cr, uid, [thread_id], tracked_fields, initial_values, context=track_ctx)
|
||||
return thread_id
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
|
@ -393,7 +394,11 @@ class mail_thread(osv.AbstractModel):
|
|||
result = super(mail_thread, self).write(cr, uid, ids, values, context=context)
|
||||
self.message_auto_subscribe(cr, uid, ids, values.keys(), context=context, values=values)
|
||||
|
||||
# Perform the tracking
|
||||
if not context.get('mail_notrack'):
|
||||
# Perform the tracking
|
||||
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=context)
|
||||
else:
|
||||
tracked_fields = None
|
||||
if tracked_fields:
|
||||
self.message_track(cr, uid, ids, tracked_fields, initial_values, context=track_ctx)
|
||||
return result
|
||||
|
@ -414,6 +419,9 @@ class mail_thread(osv.AbstractModel):
|
|||
return res
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
# avoid tracking multiple temporary changes during copy
|
||||
context = dict(context or {}, mail_notrack=True)
|
||||
|
||||
default = default or {}
|
||||
default['message_ids'] = []
|
||||
default['message_follower_ids'] = []
|
||||
|
@ -725,13 +733,15 @@ class mail_thread(osv.AbstractModel):
|
|||
# Private message: should not contain any thread_id
|
||||
if not model and thread_id:
|
||||
if assert_model:
|
||||
assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message), received %s.' % thread_id
|
||||
_warn('posting a message without model should be with a null res_id (private message), received %s, resetting thread_id' % thread_id)
|
||||
if thread_id:
|
||||
raise ValueError('Routing: posting a message without model should be with a null res_id (private message), received %s.' % thread_id)
|
||||
_warn('posting a message without model should be with a null res_id (private message), received %s resetting thread_id' % thread_id)
|
||||
thread_id = 0
|
||||
# Private message: should have a parent_id (only answers)
|
||||
if not model and not message_dict.get('parent_id'):
|
||||
if assert_model:
|
||||
assert message_dict.get('parent_id'), 'Routing: posting a message without model should be with a parent_id (private mesage).'
|
||||
if not message_dict.get('parent_id'):
|
||||
raise ValueError('Routing: posting a message without model should be with a parent_id (private mesage).')
|
||||
_warn('posting a message without model should be with a parent_id (private mesage), skipping')
|
||||
return ()
|
||||
|
||||
|
@ -760,7 +770,10 @@ class mail_thread(osv.AbstractModel):
|
|||
# New Document: check model accepts the mailgateway
|
||||
if not thread_id and model and not hasattr(model_pool, 'message_new'):
|
||||
if assert_model:
|
||||
assert hasattr(model_pool, 'message_new'), 'Model %s does not accept document creation, crashing' % model
|
||||
if not hasattr(model_pool, 'message_new'):
|
||||
raise ValueError(
|
||||
'Model %s does not accept document creation, crashing' % model
|
||||
)
|
||||
_warn('model %s does not accept document creation, skipping' % model)
|
||||
return ()
|
||||
|
||||
|
@ -821,8 +834,11 @@ class mail_thread(osv.AbstractModel):
|
|||
to which this mail should be attached. Only used if the message
|
||||
does not reply to an existing thread and does not match any mail alias.
|
||||
:return: list of [model, thread_id, custom_values, user_id, alias]
|
||||
|
||||
:raises: ValueError, TypeError
|
||||
"""
|
||||
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
|
||||
if not isinstance(message, Message):
|
||||
raise TypeError('message must be an email.message.Message at this point')
|
||||
mail_msg_obj = self.pool['mail.message']
|
||||
fallback_model = model
|
||||
|
||||
|
@ -940,9 +956,11 @@ class mail_thread(osv.AbstractModel):
|
|||
return [route]
|
||||
|
||||
# AssertionError if no routes found and if no bounce occured
|
||||
assert False, \
|
||||
"No possible route found for incoming message from %s to %s (Message-Id %s:)." \
|
||||
"Create an appropriate mail.alias or force the destination model." % (email_from, email_to, message_id)
|
||||
raise ValueError(
|
||||
'No possible route found for incoming message from %s to %s (Message-Id %s:). '
|
||||
'Create an appropriate mail.alias or force the destination model.' %
|
||||
(email_from, email_to, message_id)
|
||||
)
|
||||
|
||||
def message_route_process(self, cr, uid, message, message_dict, routes, context=None):
|
||||
# postpone setting message_dict.partner_ids after message_post, to avoid double notifications
|
||||
|
@ -953,9 +971,11 @@ class mail_thread(osv.AbstractModel):
|
|||
context.update({'thread_model': model})
|
||||
if model:
|
||||
model_pool = self.pool[model]
|
||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
||||
(message_dict['message_id'], model)
|
||||
if not (thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new')):
|
||||
raise ValueError(
|
||||
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" %
|
||||
(message_dict['message_id'], model)
|
||||
)
|
||||
|
||||
# disabled subscriptions during message_new/update to avoid having the system user running the
|
||||
# email gateway become a follower of all inbound messages
|
||||
|
@ -965,7 +985,8 @@ class mail_thread(osv.AbstractModel):
|
|||
else:
|
||||
thread_id = model_pool.message_new(cr, user_id, message_dict, custom_values, context=nosub_ctx)
|
||||
else:
|
||||
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
||||
if thread_id:
|
||||
raise ValueError("Posting a message without model should be with a null res_id, to create a private message.")
|
||||
model_pool = self.pool.get('mail.thread')
|
||||
if not hasattr(model_pool, 'message_post'):
|
||||
context['thread_model'] = model
|
||||
|
@ -1549,18 +1570,23 @@ class mail_thread(osv.AbstractModel):
|
|||
""" Add partners to the records followers. """
|
||||
if context is None:
|
||||
context = {}
|
||||
# not necessary for computation, but saves an access right check
|
||||
if not partner_ids:
|
||||
return True
|
||||
|
||||
mail_followers_obj = self.pool.get('mail.followers')
|
||||
subtype_obj = self.pool.get('mail.message.subtype')
|
||||
|
||||
user_pid = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
||||
if set(partner_ids) == set([user_pid]):
|
||||
if context.get('operation', '') != 'create':
|
||||
try:
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
try:
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
if context.get('operation', '') == 'create':
|
||||
self.check_access_rule(cr, uid, ids, 'create')
|
||||
else:
|
||||
self.check_access_rule(cr, uid, ids, 'read')
|
||||
except (osv.except_osv, orm.except_orm):
|
||||
return False
|
||||
except (osv.except_osv, orm.except_orm):
|
||||
return False
|
||||
else:
|
||||
self.check_access_rights(cr, uid, 'write')
|
||||
self.check_access_rule(cr, uid, ids, 'write')
|
||||
|
@ -1605,6 +1631,9 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
|
||||
""" Remove partners from the records followers. """
|
||||
# not necessary for computation, but saves an access right check
|
||||
if not partner_ids:
|
||||
return True
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
if set(partner_ids) == set([user_pid]):
|
||||
self.check_access_rights(cr, uid, 'read')
|
||||
|
|
|
@ -115,6 +115,9 @@
|
|||
.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_body p {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_body pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_body * {
|
||||
text-overflow:ellipsis;
|
||||
word-wrap: break-word;
|
||||
|
|
|
@ -925,20 +925,12 @@ openerp.mail = function (session) {
|
|||
},
|
||||
|
||||
on_record_clicked: function (event) {
|
||||
event.stopPropagation();
|
||||
var state = {
|
||||
'model': this.model,
|
||||
'id': this.res_id,
|
||||
'title': this.record_name
|
||||
};
|
||||
session.webclient.action_manager.do_push_state(state);
|
||||
this.do_action({
|
||||
res_model: state.model,
|
||||
res_id: state.id,
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'form']]
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
/* Call the on_compose_message on the thread of this message. */
|
||||
|
|
|
@ -197,6 +197,7 @@ openerp_mail_followers = function(session, mail) {
|
|||
// clean and display title
|
||||
var node_user_list = this.$('.oe_follower_list').empty();
|
||||
this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
|
||||
self.message_is_follower = _.indexOf(this.followers.map(function (rec) { return rec[2]['is_uid']}), true) != -1;
|
||||
// truncate number of displayed followers
|
||||
var truncated = this.followers.slice(0, this.displayed_nb);
|
||||
_(truncated).each(function (record) {
|
||||
|
@ -207,9 +208,6 @@ openerp_mail_followers = function(session, mail) {
|
|||
'is_editable': record[2]['is_editable'],
|
||||
'avatar_url': mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record[0]),
|
||||
}
|
||||
if (partner.is_uid) {
|
||||
self.message_is_follower = partner.is_uid;
|
||||
}
|
||||
$(session.web.qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
|
||||
// On mouse-enter it will show the edit_subtype pencil.
|
||||
if (partner.is_editable) {
|
||||
|
|
|
@ -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>
|
||||
|
@ -135,14 +135,14 @@
|
|||
<t t-if="widget.is_private or (widget.user_pid != partner[0])">
|
||||
<t t-if="!widget.is_private and inc==0"> and </t>
|
||||
<span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/>
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-raw="partner[1]"/></a>
|
||||
<t t-if="!widget.options.show_link" t-raw="partner[1]"/>
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-esc="partner[1]"/></a>
|
||||
<t t-if="!widget.options.show_link" t-esc="partner[1]"/>
|
||||
</span>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="widget.partner_ids.length > 3">
|
||||
<span class="oe_more">, <a><t t-raw="widget.partner_ids.length - 3"/> others...</a></span>
|
||||
<span class="oe_more">, <a><t t-esc="widget.partner_ids.length - 3"/> others...</a></span>
|
||||
<a class="oe_more_hidden"><<<</a>
|
||||
</t>
|
||||
</div>
|
||||
|
@ -150,8 +150,8 @@
|
|||
<t t-foreach='widget.recipients' t-as='recipient'>
|
||||
<label t-attf-title="Add as recipient and follower (reason: #{recipient.reason})">
|
||||
<input type="checkbox" t-att-checked="recipient.checked ? 'checked' : undefined" t-att-data="recipient.full_name"/>
|
||||
<t t-raw="recipient.name"/>
|
||||
<t t-if="recipient.email_address">(<t t-raw="recipient.email_address"/>)</t>
|
||||
<t t-esc="recipient.name"/>
|
||||
<t t-if="recipient.email_address">(<t t-esc="recipient.email_address"/>)</t>
|
||||
<t t-if="!recipient.email_address">(no email address)</t>
|
||||
</label>
|
||||
</t>
|
||||
|
@ -177,7 +177,7 @@
|
|||
<td colspan="2">
|
||||
<h2 class="oe_view_title">
|
||||
<span class="oe_view_title_text">
|
||||
<t t-raw="widget.action.name"/>
|
||||
<t t-esc="widget.action.name"/>
|
||||
</span>
|
||||
</h2>
|
||||
<t t-if="widget.action.params.header_description">
|
||||
|
@ -262,12 +262,13 @@
|
|||
<!-- message itself -->
|
||||
<div class="oe_msg_content">
|
||||
<h1 t-if="(widget.show_record_name or widget.subject) and !widget.thread_level" class="oe_msg_title">
|
||||
<a t-if="widget.options.show_link and widget.show_record_name" class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&id=#{widget.res_id}">
|
||||
<t t-raw="widget.record_name"/>
|
||||
<a t-if="widget.options.show_link and widget.show_record_name" class="oe_mail_action_model"
|
||||
t-attf-href="#action=mail.action_mail_redirect&model=#{widget.model}&res_id=#{widget.res_id}">
|
||||
<t t-esc="widget.record_name"/>
|
||||
</a>
|
||||
<span t-if="!widget.options.show_link and widget.show_record_name"><t t-raw="widget.record_name"/></span>
|
||||
<span t-if="!widget.options.show_link and widget.show_record_name"><t t-esc="widget.record_name"/></span>
|
||||
<t t-if="widget.show_record_name and widget.subject">: </t>
|
||||
<t t-if="widget.subject" t-raw="widget.subject"/>
|
||||
<t t-if="widget.subject" t-esc="widget.subject"/>
|
||||
</h1>
|
||||
<div class="oe_msg_body">
|
||||
<t t-if="widget.body_short">
|
||||
|
@ -283,8 +284,8 @@
|
|||
<t t-if="widget.attachment_ids.length > 0">
|
||||
<div class="oe_msg_attachment_list"></div>
|
||||
</t>
|
||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[2]"/></a>
|
||||
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[2]"/></span>
|
||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-esc="widget.author_id[2]"/></a>
|
||||
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-esc="widget.author_id[2]"/></span>
|
||||
<t t-if="widget.type == 'notification'">
|
||||
updated document
|
||||
<t t-if="widget.partner_ids.length > 0">
|
||||
|
@ -303,20 +304,20 @@
|
|||
<t t-if="widget.type == 'notification' or ( (widget.type == 'email' or widget.type == 'comment') and (widget.subtype or widget.partner_ids.length > 0))"
|
||||
t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
|
||||
<span t-attf-class="oe_partner_follower">
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-raw="partner[1]"/></a>
|
||||
<t t-if="!widget.options.show_link" t-raw="partner[1]"/>
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-esc="partner[1]"/></a>
|
||||
<t t-if="!widget.options.show_link" t-esc="partner[1]"/>
|
||||
</span>
|
||||
<t t-if="!partner_last">,</t>
|
||||
</t>
|
||||
<t t-if="widget.partner_ids.length > 3">
|
||||
<span t-att-title="widget.extra_partners_str">and <t t-raw="widget.extra_partners_nbr"/> more</span>
|
||||
<span t-att-title="widget.extra_partners_str">and <t t-esc="widget.extra_partners_nbr"/> more</span>
|
||||
</t>
|
||||
<t t-if="widget.type == 'notification' and widget.partner_ids.length > 0">
|
||||
notified
|
||||
</t>
|
||||
<span class='oe_subtle'>•</span>
|
||||
<span t-att-title="widget.date">
|
||||
<t t-if="widget.timerelative" t-raw="widget.timerelative"/>
|
||||
<t t-if="widget.timerelative" t-esc="widget.timerelative"/>
|
||||
<t t-if="!widget.timerelative" t-raw="widget.display_date"/>
|
||||
</span>
|
||||
<span t-if="!widget.options.readonly" class='oe_subtle'>•</span>
|
||||
|
@ -333,7 +334,7 @@
|
|||
<div class='oe_separator'></div>
|
||||
<a t-if="widget.nb_messages <= 0" class="oe_msg_fetch_more">show more message</a>
|
||||
<a t-if="widget.nb_messages === 1" class="oe_msg_fetch_more">show one more message</a>
|
||||
<a t-if="widget.nb_messages > 1" class="oe_msg_fetch_more">show <t t-raw="widget.nb_messages" /> more messages</a>
|
||||
<a t-if="widget.nb_messages > 1" class="oe_msg_fetch_more">show <t t-esc="widget.nb_messages" /> more messages</a>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
@ -353,7 +354,7 @@
|
|||
-->
|
||||
<span t-name="mail.thread.message.vote">
|
||||
<span class="oe_mail_vote_count" t-if='widget.vote_nb > 0'>
|
||||
<t t-raw='widget.vote_nb' />
|
||||
<t t-esc='widget.vote_nb' />
|
||||
<span class='oe_e'>8</span>
|
||||
</span>
|
||||
<a href='#' class="oe_msg_vote">
|
||||
|
|
|
@ -43,13 +43,13 @@
|
|||
<table class='oe_subtype'>
|
||||
<tr>
|
||||
<td width="10%"><input type="checkbox" t-att-checked="record.followed" t-att-id="'input_mail_followers_subtype_'+record.id" t-att-data-id="record.id" t-att-name="record.name" class="oe_msg_subtype_check"/></td>
|
||||
<td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
|
||||
<td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-esc="record.name"/></label></td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.followers.show_more">
|
||||
<div class="oe_partner oe_show_more">And <t t-raw="number"/> more.</div>
|
||||
<div class="oe_partner oe_show_more">And <t t-esc="number"/> more.</div>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
|
|
|
@ -394,7 +394,7 @@ class TestMailgateway(TestMail):
|
|||
'message_process: after reply, group should have 2 followers')
|
||||
|
||||
# Do: incoming email with ref holding model / res_id but that does not match any message in the thread: must raise since OpenERP saas-3
|
||||
self.assertRaises(AssertionError,
|
||||
self.assertRaises(ValueError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE, email_from='other5@gmail.com',
|
||||
to='noone@example.com', subject='spam',
|
||||
|
@ -490,14 +490,14 @@ class TestMailgateway(TestMail):
|
|||
# --------------------------------------------------
|
||||
|
||||
# Do: incoming email with model that does not accepts incoming emails must raise
|
||||
self.assertRaises(AssertionError,
|
||||
self.assertRaises(ValueError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE,
|
||||
to='noone@example.com', subject='spam', extra='', model='res.country',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail.new4@agrolait.com>')
|
||||
|
||||
# Do: incoming email without model and without alias must raise
|
||||
self.assertRaises(AssertionError,
|
||||
self.assertRaises(ValueError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE,
|
||||
to='noone@example.com', subject='spam', extra='',
|
||||
|
|
|
@ -130,7 +130,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
'wizard_id', 'attachment_id', 'Attachments'),
|
||||
'filter_id': fields.many2one('ir.filters', 'Filters'),
|
||||
}
|
||||
|
||||
#TODO change same_thread to False in trunk (Require view update)
|
||||
_defaults = {
|
||||
'composition_mode': 'comment',
|
||||
'body': lambda self, cr, uid, ctx={}: '',
|
||||
|
@ -268,6 +268,8 @@ class mail_compose_message(osv.TransientModel):
|
|||
'mail.message', 0,
|
||||
context=context)
|
||||
mail_values['attachment_ids'] = m2m_attachment_ids
|
||||
if not mail_values.get('reply_to'):
|
||||
mail_values['reply_to'] = mail_values['email_from']
|
||||
self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
|
||||
else:
|
||||
subtype = 'mail.mt_comment'
|
||||
|
@ -321,10 +323,10 @@ class mail_compose_message(osv.TransientModel):
|
|||
if email_dict.get('email_from'):
|
||||
mail_values['email_from'] = email_dict.pop('email_from')
|
||||
# replies redirection: mass mailing only
|
||||
if not wizard.same_thread:
|
||||
mail_values['reply_to'] = email_dict.pop('reply_to', None)
|
||||
else:
|
||||
if wizard.same_thread and wizard.post:
|
||||
email_dict.pop('reply_to', None)
|
||||
else:
|
||||
mail_values['reply_to'] = email_dict.pop('reply_to', None)
|
||||
mail_values.update(email_dict)
|
||||
# mass mailing without post: mail_mail values
|
||||
if mass_mail_mode and not wizard.post:
|
||||
|
@ -350,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:
|
||||
|
@ -365,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:
|
||||
|
|
|
@ -47,10 +47,10 @@
|
|||
<field name="notify"
|
||||
attrs="{'invisible':['|', ('post', '!=', True), ('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="same_thread"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
attrs="{'invisible':['|', ('composition_mode', '!=', 'mass_mail'), ('post', '=', False)]}"/>
|
||||
<field name="reply_to" placeholder="Email address te redirect replies..."
|
||||
attrs="{'invisible':['|', ('same_thread', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':[('same_thread', '!=', True)]}"/>
|
||||
attrs="{'invisible':['|', '&', ('same_thread', '=', True), ('post', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':['&', '|', ('post', '=', False), ('same_thread', '=', False), ('composition_mode', '=', 'mass_mail')]}"/>
|
||||
</group>
|
||||
<field name="body"/>
|
||||
<field name="attachment_ids" widget="many2many_binary" string="Attach a file"/>
|
||||
|
|
|
@ -48,6 +48,7 @@ class MailThread(osv.Model):
|
|||
if bounce_alias in email_to:
|
||||
bounce_match = tools.bounce_re.search(email_to)
|
||||
if bounce_match:
|
||||
bounced_model, bounced_thread_id = None, False
|
||||
bounced_mail_id = bounce_match.group(1)
|
||||
stat_ids = self.pool['mail.mail.statistics'].set_bounced(cr, uid, mail_mail_ids=[bounced_mail_id], context=context)
|
||||
for stat in self.pool['mail.mail.statistics'].browse(cr, uid, stat_ids, context=context):
|
||||
|
@ -55,7 +56,7 @@ class MailThread(osv.Model):
|
|||
bounced_thread_id = stat.res_id
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s',
|
||||
email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id)
|
||||
if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce'):
|
||||
if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce') and bounced_thread_id:
|
||||
self.pool[bounced_model].message_receive_bounce(cr, uid, [bounced_thread_id], mail_id=bounced_mail_id, context=context)
|
||||
return False
|
||||
|
||||
|
|
|
@ -168,8 +168,10 @@ class MassMailing(osv.Model):
|
|||
'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
|
||||
} for i in range(0, self._period_number)]
|
||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||
field_col_info = obj._all_columns.get(groupby_field.split(':')[0])
|
||||
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
for group in group_obj:
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT).date()
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
|
||||
timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
|
||||
section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
|
||||
return section_result
|
||||
|
|
|
@ -111,9 +111,6 @@ class procurement_order(osv.osv):
|
|||
bom_result = production_obj.action_compute(cr, uid,
|
||||
[produce_id], properties=[x.id for x in procurement.property_ids])
|
||||
production_obj.signal_button_confirm(cr, uid, [produce_id])
|
||||
if res_id:
|
||||
move_obj.write(cr, uid, [res_id],
|
||||
{'location_id': procurement.location_id.id})
|
||||
self.production_order_create_note(cr, uid, ids, context=context)
|
||||
return res
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Module to talk to EtherpadLite API."""
|
||||
|
||||
import json
|
||||
import werkzeug.urls
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
|
||||
|
@ -32,7 +32,7 @@ class EtherpadLiteClient:
|
|||
|
||||
params = arguments or {}
|
||||
params.update({'apikey': self.apiKey})
|
||||
data = werkzeug.url_encode(params, True)
|
||||
data = urllib.urlencode(params, True)
|
||||
|
||||
try:
|
||||
opener = urllib2.build_opener()
|
||||
|
|
|
@ -13,13 +13,12 @@
|
|||
border: solid 1px #ccc;
|
||||
border-radius:3px;
|
||||
text-align: center;
|
||||
line-height: 22px;
|
||||
line-height: 28px;
|
||||
overflow: hidden;
|
||||
-webkit-box-sizing: border-box;
|
||||
color: #666666;
|
||||
padding-top:-3px;
|
||||
padding-left:-1px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.oe_pad_switch:hover{
|
||||
|
|
|
@ -13,6 +13,8 @@ openerp.pad = function(instance) {
|
|||
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.render_value();
|
||||
},
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</t>
|
||||
<t t-if="! widget.get('effective_readonly')">
|
||||
<div class="oe_pad_switch_positioner oe_configured">
|
||||
<span class="oe_pad_switch oe_e">&Ntilde;</span>
|
||||
<span class="fa fa-expand oe_pad_switch"></span>
|
||||
</div>
|
||||
<div class="oe_pad_content oe_editing oe_configured">
|
||||
</div>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -474,11 +474,12 @@ class pos_session(osv.osv):
|
|||
account_move_obj = self.pool.get('account.move')
|
||||
pos_order_obj = self.pool.get('pos.order')
|
||||
for session in self.browse(cr, uid, ids, context=context):
|
||||
local_context = dict(context or {}, force_company=session.config_id.journal_id.company_id.id)
|
||||
order_ids = [order.id for order in session.order_ids if order.state == 'paid']
|
||||
|
||||
move_id = account_move_obj.create(cr, uid, {'ref' : session.name, 'journal_id' : session.config_id.journal_id.id, }, context=context)
|
||||
move_id = account_move_obj.create(cr, uid, {'ref' : session.name, 'journal_id' : session.config_id.journal_id.id, }, context=local_context)
|
||||
|
||||
pos_order_obj._create_account_move_line(cr, uid, order_ids, session, move_id, context=context)
|
||||
pos_order_obj._create_account_move_line(cr, uid, order_ids, session, move_id, context=local_context)
|
||||
|
||||
for order in session.order_ids:
|
||||
if order.state not in ('paid', 'invoiced'):
|
||||
|
@ -513,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,
|
||||
|
@ -541,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):
|
||||
|
@ -555,7 +560,11 @@ class pos_order(osv.osv):
|
|||
'journal': cash_journal.id,
|
||||
}, context=context)
|
||||
order_ids.append(order_id)
|
||||
self.signal_paid(cr, uid, [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)
|
||||
|
@ -948,22 +957,16 @@ class pos_order(osv.osv):
|
|||
# Tricky, via the workflow, we only have one id in the ids variable
|
||||
"""Create a account move line of order grouped by products or not."""
|
||||
account_move_obj = self.pool.get('account.move')
|
||||
account_move_line_obj = self.pool.get('account.move.line')
|
||||
account_period_obj = self.pool.get('account.period')
|
||||
account_tax_obj = self.pool.get('account.tax')
|
||||
user_proxy = self.pool.get('res.users')
|
||||
property_obj = self.pool.get('ir.property')
|
||||
cur_obj = self.pool.get('res.currency')
|
||||
|
||||
period = account_period_obj.find(cr, uid, context=context)[0]
|
||||
|
||||
#session_ids = set(order.session_id for order in self.browse(cr, uid, ids, context=context))
|
||||
|
||||
if session and not all(session.id == order.session_id.id for order in self.browse(cr, uid, ids, context=context)):
|
||||
raise osv.except_osv(_('Error!'), _('Selected orders do not have the same session!'))
|
||||
|
||||
current_company = user_proxy.browse(cr, uid, uid, context=context).company_id
|
||||
|
||||
grouped_data = {}
|
||||
have_to_group_by = session and session.config_id.group_by or False
|
||||
|
||||
|
@ -983,7 +986,7 @@ class pos_order(osv.osv):
|
|||
if order.state != 'paid':
|
||||
continue
|
||||
|
||||
user_company = user_proxy.browse(cr, order.user_id.id, order.user_id.id).company_id
|
||||
current_company = order.sale_journal.company_id
|
||||
|
||||
group_tax = {}
|
||||
account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context)
|
||||
|
@ -1004,6 +1007,7 @@ class pos_order(osv.osv):
|
|||
# if have_to_group_by:
|
||||
|
||||
sale_journal_id = order.sale_journal.id
|
||||
period = account_period_obj.find(cr, uid, context=dict(context or {}, company_id=current_company.id))[0]
|
||||
|
||||
# 'quantity': line.qty,
|
||||
# 'product_id': line.product_id.id,
|
||||
|
@ -1013,7 +1017,7 @@ class pos_order(osv.osv):
|
|||
'journal_id' : sale_journal_id,
|
||||
'period_id' : period,
|
||||
'move_id' : move_id,
|
||||
'company_id': user_company and user_company.id or False,
|
||||
'company_id': current_company.id,
|
||||
})
|
||||
|
||||
if data_type == 'product':
|
||||
|
@ -1054,7 +1058,10 @@ class pos_order(osv.osv):
|
|||
cur = order.pricelist_id.currency_id
|
||||
for line in order.lines:
|
||||
tax_amount = 0
|
||||
taxes = [t for t in line.product_id.taxes_id]
|
||||
taxes = []
|
||||
for t in line.product_id.taxes_id:
|
||||
if t.company_id.id == current_company.id:
|
||||
taxes.append(t)
|
||||
computed_taxes = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty)['taxes']
|
||||
|
||||
for tax in computed_taxes:
|
||||
|
|
|
@ -99,13 +99,10 @@ class pos_details_summary(report_sxw.rml_parse):
|
|||
|
||||
def _get_tax_amount(self, objects):
|
||||
res = {}
|
||||
list_ids = []
|
||||
for order in objects:
|
||||
for line in order.lines:
|
||||
if len(line.product_id.taxes_id):
|
||||
tax = line.product_id.taxes_id[0]
|
||||
res[tax.name] = (line.price_unit * line.qty * (1-(line.discount or 0.0) / 100.0)) + (tax.id in list_ids and res[tax.name] or 0)
|
||||
list_ids.append(tax.id)
|
||||
for tax in line.product_id.taxes_id:
|
||||
res[tax.name] = res.setdefault(tax.name, 0.0) + (line.price_subtotal_incl - line.price_subtotal)
|
||||
return res
|
||||
|
||||
def _get_sales_total(self, objects):
|
||||
|
|
|
@ -232,7 +232,7 @@
|
|||
<blockTable colWidths="255.0,255.0" style="Table11">
|
||||
<tr>
|
||||
<td>
|
||||
<para style="P2">[[ t[0] ]]</para>
|
||||
<para style="P2">[[ t[0].name ]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Right_9_Bold">[[ formatLang(t[1], currency_obj=company.currency_id) ]]</para>
|
||||
|
|
|
@ -162,25 +162,37 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
},
|
||||
|
||||
// find a proxy and connects to it. for options see find_proxy
|
||||
// - force_ip : only try to connect to the specified ip.
|
||||
// - port: what port to listen to (default 8069)
|
||||
// - progress(fac) : callback for search progress ( fac in [0,1] )
|
||||
autoconnect: function(options){
|
||||
var self = this;
|
||||
this.set_connection_status('connecting',{});
|
||||
var found_url = new $.Deferred();
|
||||
var success = new $.Deferred();
|
||||
this.find_proxy(options)
|
||||
.then(function(proxies){
|
||||
if(proxies.length > 0){
|
||||
self.connect(proxies[0])
|
||||
.then(function(){
|
||||
success.resolve();
|
||||
},function(){
|
||||
self.set_connection_status('disconnected');
|
||||
success.reject();
|
||||
});
|
||||
}else{
|
||||
self.set_connection_status('disconnected');
|
||||
success.reject();
|
||||
}
|
||||
|
||||
if ( options.force_ip ){
|
||||
// if the ip is forced by server config, bailout on fail
|
||||
found_url = this.try_hard_to_connect(options.force_ip, options)
|
||||
}else if( localStorage['hw_proxy_url'] ){
|
||||
// try harder when we remember a good proxy url
|
||||
found_url = this.try_hard_to_connect(localStorage['hw_proxy_url'], options)
|
||||
.then(null,function(){
|
||||
return self.find_proxy(options);
|
||||
});
|
||||
}else{
|
||||
// just find something quick
|
||||
found_url = this.find_proxy(options);
|
||||
}
|
||||
|
||||
success = found_url.then(function(url){
|
||||
return self.connect(url);
|
||||
});
|
||||
|
||||
success.fail(function(){
|
||||
self.set_connection_status('disconnected');
|
||||
});
|
||||
|
||||
return success;
|
||||
},
|
||||
|
||||
|
@ -217,10 +229,50 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
}
|
||||
},
|
||||
|
||||
// returns as a deferred a list of valid hosts urls that can be used as proxy.
|
||||
// try several time to connect to a known proxy url
|
||||
try_hard_to_connect: function(url,options){
|
||||
options = options || {};
|
||||
var port = ':' + (options.port || '8069');
|
||||
|
||||
this.set_connection_status('connecting');
|
||||
|
||||
if(url.indexOf('//') < 0){
|
||||
url = 'http://'+url;
|
||||
}
|
||||
|
||||
if(url.indexOf(':',5) < 0){
|
||||
url = url+port;
|
||||
}
|
||||
|
||||
// try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
|
||||
function try_real_hard_to_connect(url, retries, done){
|
||||
|
||||
done = done || new $.Deferred();
|
||||
|
||||
var c = $.ajax({
|
||||
url: url + '/hw_proxy/hello',
|
||||
method: 'GET',
|
||||
timeout: 1000,
|
||||
})
|
||||
.done(function(){
|
||||
done.resolve(url);
|
||||
})
|
||||
.fail(function(){
|
||||
if(retries > 0){
|
||||
try_real_hard_to_connect(url,retries-1,done);
|
||||
}else{
|
||||
done.reject();
|
||||
}
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
return try_real_hard_to_connect(url,3);
|
||||
},
|
||||
|
||||
// returns as a deferred a valid host url that can be used as proxy.
|
||||
// options:
|
||||
// - port: what port to listen to (default 8069)
|
||||
// - force_ip : limit the search to the specified ip
|
||||
// - progress(fac) : callback for search progress ( fac in [0,1] )
|
||||
find_proxy: function(options){
|
||||
options = options || {};
|
||||
|
@ -228,36 +280,17 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
var port = ':' + (options.port || '8069');
|
||||
var urls = [];
|
||||
var found = false;
|
||||
var proxies = [];
|
||||
var done = new $.Deferred();
|
||||
var parallel = 8;
|
||||
var done = new $.Deferred(); // will be resolved with the proxies valid urls
|
||||
var threads = [];
|
||||
var progress = 0;
|
||||
|
||||
this.set_connection_status('connecting');
|
||||
|
||||
if(options.force_ip){
|
||||
var url = options.force_ip;
|
||||
if(url.indexOf('//') < 0){
|
||||
url = 'http://'+url;
|
||||
}
|
||||
if(url.indexOf(':',5) < 0){
|
||||
url = url+port;
|
||||
}
|
||||
urls.push(url);
|
||||
}else{
|
||||
if(localStorage['hw_proxy_url']){
|
||||
urls.push(localStorage['hw_proxy_url']);
|
||||
}
|
||||
|
||||
urls.push('http://localhost'+port);
|
||||
|
||||
for(var i = 0; i < 256; i++){
|
||||
urls.push('http://192.168.0.'+i+port);
|
||||
urls.push('http://192.168.1.'+i+port);
|
||||
urls.push('http://192.168.2.'+i+port);
|
||||
urls.push('http://10.0.0.'+i+port);
|
||||
}
|
||||
urls.push('http://localhost'+port);
|
||||
for(var i = 0; i < 256; i++){
|
||||
urls.push('http://192.168.0.'+i+port);
|
||||
urls.push('http://192.168.1.'+i+port);
|
||||
urls.push('http://10.0.0.'+i+port);
|
||||
}
|
||||
|
||||
var prog_inc = 1/urls.length;
|
||||
|
@ -269,40 +302,39 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
}
|
||||
}
|
||||
|
||||
function thread(url,done){
|
||||
if(!url){
|
||||
function thread(done){
|
||||
var url = urls.shift();
|
||||
|
||||
done = done || new $.Deferred();
|
||||
|
||||
if( !url || found || !self.searching_for_proxy ){
|
||||
done.resolve();
|
||||
return done;
|
||||
}
|
||||
|
||||
var c = $.ajax({
|
||||
url: url + '/hw_proxy/hello',
|
||||
method: 'GET',
|
||||
timeout: 300,
|
||||
timeout: 400,
|
||||
}).done(function(){
|
||||
found = true;
|
||||
update_progress();
|
||||
proxies.push(url);
|
||||
done.resolve(url);
|
||||
})
|
||||
.fail(function(){
|
||||
update_progress();
|
||||
var next_url = urls.shift();
|
||||
if(found ||! self.searching_for_proxy || !next_url){
|
||||
done.resolve();
|
||||
}else{
|
||||
thread(next_url,done);
|
||||
}
|
||||
thread(done);
|
||||
});
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
this.searching_for_proxy = true;
|
||||
|
||||
for(var i = 0; i < Math.min(parallel,urls.length); i++){
|
||||
threads.push(thread(urls.shift(),new $.Deferred()));
|
||||
for(var i = 0, len = Math.min(parallel,urls.length); i < len; i++){
|
||||
threads.push(thread());
|
||||
}
|
||||
|
||||
var done = new $.Deferred();
|
||||
|
||||
$.when.apply($,threads).then(function(){
|
||||
var urls = [];
|
||||
for(var i = 0; i < arguments.length; i++){
|
||||
|
@ -310,7 +342,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
urls.push(arguments[i]);
|
||||
}
|
||||
}
|
||||
done.resolve(urls);
|
||||
done.resolve(urls[0]);
|
||||
});
|
||||
|
||||
return done;
|
||||
|
@ -640,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){
|
||||
|
@ -725,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',
|
||||
|
@ -735,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);
|
||||
}
|
||||
|
|
|
@ -421,62 +421,67 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
// it is therefore important to only call this method from inside a mutex
|
||||
// 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;
|
||||
_flush_order: function( order_id, options) {
|
||||
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
|
||||
// called from within a mutex.
|
||||
// this method returns a deferred that always succeeds when all orders have been tried to be sent,
|
||||
// even if none of them could actually be sent.
|
||||
_flush_all_orders: function(){
|
||||
_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);
|
||||
|
||||
options = options || {};
|
||||
|
||||
return tried_all;
|
||||
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){
|
||||
|
|
|
@ -936,12 +936,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
|
||||
document.body.addEventListener('keyup', this.hotkey_handler);
|
||||
|
||||
if( this.pos.config.iface_cashdrawer
|
||||
&& this.pos.get('selectedOrder').get('paymentLines').find( function(pl){
|
||||
return pl.cashregister.journal.type === 'cash';
|
||||
})){
|
||||
this.pos.proxy.open_cashbox();
|
||||
}
|
||||
|
||||
|
||||
this.add_action_button({
|
||||
|
@ -1145,6 +1139,13 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
return;
|
||||
}
|
||||
|
||||
if( this.pos.config.iface_cashdrawer
|
||||
&& this.pos.get('selectedOrder').get('paymentLines').find( function(pl){
|
||||
return pl.cashregister.journal.type === 'cash';
|
||||
})){
|
||||
this.pos.proxy.open_cashbox();
|
||||
}
|
||||
|
||||
if(options.invoice){
|
||||
// deactivate the validation button while we try to send the order
|
||||
this.pos_widget.action_bar.set_button_disabled('validation',true);
|
||||
|
|
|
@ -258,8 +258,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
var total = order ? order.getTotalTaxIncluded() : 0;
|
||||
var taxes = order ? total - order.getTotalTaxExcluded() : 0;
|
||||
|
||||
this.el.querySelector('.summary .total > .value').innerText = this.format_currency(total);
|
||||
this.el.querySelector('.summary .total .subentry .value').innerText = this.format_currency(taxes);
|
||||
this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
|
||||
this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -635,9 +635,9 @@
|
|||
Shop: <t t-esc="widget.pos.shop.name"/><br />
|
||||
<br />
|
||||
<t t-if="widget.pos.config.receipt_header">
|
||||
<pre>
|
||||
<div style='text-align:center'>
|
||||
<t t-esc="widget.pos.config.receipt_header" />
|
||||
</pre>
|
||||
</div>
|
||||
<br />
|
||||
</t>
|
||||
<table>
|
||||
|
@ -711,9 +711,9 @@
|
|||
</table>
|
||||
<t t-if="widget.pos.config.receipt_footer">
|
||||
<br />
|
||||
<pre>
|
||||
<div style='text-align:center'>
|
||||
<t t-esc="widget.pos.config.receipt_footer" />
|
||||
</pre>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
|
|
@ -6,12 +6,18 @@
|
|||
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));
|
||||
background-image: -webkit-linear-gradient(top, #729FCF, #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):
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue