[MERGE] upstream
bzr revid: fme@openerp.com-20140227101725-7egd9xq859jasnhj
This commit is contained in:
commit
2868e9a6ac
|
@ -729,8 +729,7 @@ class account_journal(osv.osv):
|
|||
'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
|
||||
'entry_posted': fields.boolean('Autopost Created Moves', help='Check this box to automatically post entries of this journal. Note that legally, some entries may be automatically posted when the source document is validated (Invoices), whatever the status of this field.'),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
|
||||
'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
|
||||
|
||||
'allow_date':fields.boolean('Check Date in Period', help= 'If checked, the entry won\'t be created if the entry date is not included into the selected period'),
|
||||
'profit_account_id' : fields.many2one('account.account', 'Profit Account'),
|
||||
'loss_account_id' : fields.many2one('account.account', 'Loss Account'),
|
||||
'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1),
|
||||
|
|
|
@ -495,6 +495,7 @@ class account_bank_statement(osv.osv):
|
|||
ctx = (context or {}).copy()
|
||||
ctx['journal_id'] = self.browse(cr, uid, ids[0], context=context).journal_id.id
|
||||
return {
|
||||
'name': _('Journal Items'),
|
||||
'view_type':'form',
|
||||
'view_mode':'tree',
|
||||
'res_model':'account.move.line',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
<field name="journal_id" invisible="1"/>
|
||||
<field name="period_id" invisible="1" groups="account.group_account_user"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<field name="user_id"/>
|
||||
<field name="user_id" string="Responsible"/>
|
||||
<field name="date_due"/>
|
||||
<field name="origin"/>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
|
@ -251,7 +251,7 @@
|
|||
<group>
|
||||
<group>
|
||||
<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="user_id" string="Responsible" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="payment_term" widget="selection"/>
|
||||
</group>
|
||||
|
|
|
@ -588,7 +588,7 @@
|
|||
<field name="partner_id" on_change="onchange_partner_id(partner_id)" domain="[
|
||||
'&',
|
||||
'|',('parent_id','=',False),('is_company','=',True),
|
||||
'|',('customer','=',True),('supplier','=',True)]"/>
|
||||
'|',('customer','=',True),('supplier','=',True)]" context="{'default_supplier': 1}"/>
|
||||
<field name="type" on_change="onchange_type(partner_id, type)"/>
|
||||
<field name="account_id" options='{"no_open":True}' domain="[('journal_id','=',parent.journal_id), ('company_id', '=', parent.company_id)]"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]"/>
|
||||
|
@ -1270,12 +1270,11 @@
|
|||
<field name="model">account.move</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Account Entry" version="7.0">
|
||||
<header>
|
||||
<button name="button_validate" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/>
|
||||
<button name="button_cancel" states="posted" string="Cancel Entry" type="object" groups="account.group_account_invoice"/>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet string="Journal Entries" >
|
||||
<header>
|
||||
<button name="button_validate" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/>
|
||||
<button name="button_cancel" states="posted" string="Cancel Entry" type="object" groups="account.group_account_invoice"/>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<label for="name" class="oe_edit_only" attrs="{'invisible':[('name','=','/')]}"/>
|
||||
<h1>
|
||||
<field name="name" readonly="True" attrs="{'invisible':[('name','=','/')]}"/>
|
||||
|
@ -1384,7 +1383,6 @@
|
|||
<field name="narration" colspan="4" placeholder="Add an internal note..." nolabel="1" height="50"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -1453,7 +1451,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"/>
|
||||
|
||||
|
@ -2263,7 +2261,7 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="journal_id" on_change="onchange_journal_id(journal_id)" widget="selection" domain="[('type', '=', 'cash')]" />
|
||||
<field name="user_id" readonly="1" string="Responsible"/>
|
||||
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}" string="Responsible"/>
|
||||
<field name='company_id' widget="selection" groups="base.group_multi_company" />
|
||||
</group>
|
||||
<group>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<label for="fiscalyear"/>
|
||||
<div>
|
||||
<field name="fiscalyear" on_change="onchange_fiscalyear(fiscalyear)" class="oe_inline"/>
|
||||
<label align="0.7" string="(If you do not select Fiscal year it will take all open fiscal years)" class="oe_inline"/>
|
||||
<label align="0.7" string="(If you do not select a specific fiscal year, all open fiscal years will be selected.)" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="target_move"/>
|
||||
<label for="period_from" string="Periods"/>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<label for="period_id"/>
|
||||
<div>
|
||||
<field name="period_id" class="oe_inline"/>
|
||||
<label string="(If you do not select period it will take all open periods)" class="oe_inline"/>
|
||||
<label string="(If you do not select a specific period, all open periods will be selected)" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="target_move"/>
|
||||
</group>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='line_id']/tree/field[@name='analytic_account_id']" position="replace">
|
||||
<xpath expr="/form/notebook/page/field[@name='line_id']/tree/field[@name='analytic_account_id']" position="replace">
|
||||
<field name="analytics_id" context="{'journal_id':parent.journal_id}" groups="analytic.group_analytic_accounting"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/sheet/notebook/page/field[@name='line_id']/form/notebook/page/group/group/field[@name='analytic_account_id']" position="replace">
|
||||
<xpath expr="/form/notebook/page/field[@name='line_id']/form/notebook/page/group/group/field[@name='analytic_account_id']" position="replace">
|
||||
<field name="analytics_id" context="{'journal_id':parent.journal_id}" groups="analytic.group_analytic_accounting"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
@ -352,6 +352,7 @@ class account_asset_asset(osv.osv):
|
|||
context = {}
|
||||
context.update({'search_default_asset_id': ids, 'default_asset_id': ids})
|
||||
return {
|
||||
'name': _('Journal Items'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move.line',
|
||||
|
|
|
@ -8,19 +8,19 @@ msgstr ""
|
|||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
|
||||
"PO-Revision-Date: 2010-09-29 11:28+0000\n"
|
||||
"Last-Translator: OpenERP Administrators <Unknown>\n"
|
||||
"PO-Revision-Date: 2014-02-18 16:35+0000\n"
|
||||
"Last-Translator: Boško Stojaković <bluesoft83@gmail.com>\n"
|
||||
"Language-Team: Bosnian <bs@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-01-28 06:28+0000\n"
|
||||
"X-Generator: Launchpad (build 16914)\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-19 05:23+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: account_cancel
|
||||
#: view:account.invoice:0
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "Otkaži"
|
||||
|
||||
#~ msgid "Account Cancel"
|
||||
#~ msgstr "Računovodstvo - povrat"
|
||||
|
|
|
@ -26,7 +26,7 @@ class account_journal(osv.osv):
|
|||
|
||||
_columns = {
|
||||
'allow_check_writing': fields.boolean('Allow Check writing', help='Check this if the journal is to be used for writing checks.'),
|
||||
'use_preprint_check': fields.boolean('Use Preprinted Check'),
|
||||
'use_preprint_check': fields.boolean('Use Preprinted Check', help='Check if you use a preformated sheet for check'),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,14 +8,14 @@ msgstr ""
|
|||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
|
||||
"PO-Revision-Date: 2011-11-11 15:21+0000\n"
|
||||
"Last-Translator: Fabien (Open ERP) <fp@tinyerp.com>\n"
|
||||
"PO-Revision-Date: 2014-02-16 20:38+0000\n"
|
||||
"Last-Translator: Harri Luuppala <Unknown>\n"
|
||||
"Language-Team: Finnish <fi@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-01-28 05:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16914)\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-17 05:38+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: account_followup
|
||||
#: model:email.template,subject:account_followup.email_template_account_followup_default
|
||||
|
@ -55,12 +55,12 @@ msgstr ""
|
|||
#: view:account_followup.followup.line:0
|
||||
#: field:account_followup.followup.line,manual_action:0
|
||||
msgid "Manual Action"
|
||||
msgstr ""
|
||||
msgstr "Manuaalinen toimenpide"
|
||||
|
||||
#. module: account_followup
|
||||
#: field:account_followup.sending.results,needprinting:0
|
||||
msgid "Needs Printing"
|
||||
msgstr ""
|
||||
msgstr "Odottaa tulostusta"
|
||||
|
||||
#. module: account_followup
|
||||
#: view:res.partner:0
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Populate Statement:" version="7.0">
|
||||
<group>
|
||||
<field name="lines"/>
|
||||
<field name="lines" nolabel="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="populate_statement" string="ADD" type="object" class="oe_highlight"/>
|
||||
<button name="populate_statement" string="Ok" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
</footer>
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<field name="tax_amount" nolabel="1"/>
|
||||
<div class="oe_subtotal_footer_separator">
|
||||
<label for="amount"/>
|
||||
<button type="object" icon="terp-stock_format-scientific" name="compute_tax" class="oe_link oe_edit_only" string="(Update)" attrs="{'invisible': [('state','!=','draft')]}"/>
|
||||
<button type="object" name="compute_tax" class="oe_link oe_edit_only" string="(Update)" attrs="{'invisible': [('state','!=','draft')]}"/>
|
||||
</div>
|
||||
<field name="amount" class="oe_subtotal_footer_separator" nolabel="1"/>
|
||||
</group>
|
||||
|
|
|
@ -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'}
|
||||
|
||||
|
|
|
@ -253,7 +253,7 @@ class account_analytic_account(osv.osv):
|
|||
def check_recursion(self, cr, uid, ids, context=None, parent=None):
|
||||
return super(account_analytic_account, self)._check_recursion(cr, uid, ids, context=context, parent=parent)
|
||||
|
||||
_order = 'name asc'
|
||||
_order = 'code, name asc'
|
||||
_constraints = [
|
||||
(check_recursion, 'Error! You cannot create recursive analytic accounts.', ['parent_id']),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# Japanese translation for openobject-addons
|
||||
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
|
||||
"PO-Revision-Date: 2014-02-21 02:29+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Japanese <ja@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-22 06:39+0000\n"
|
||||
"X-Generator: Launchpad (build 16926)\n"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: view:account.analytic.account:0
|
||||
msgid "or view"
|
||||
msgstr "または参照"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: view:account.analytic.account:0
|
||||
msgid "Nothing to invoice, create"
|
||||
msgstr ""
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: view:account.analytic.account:0
|
||||
msgid "expenses"
|
||||
msgstr "経費"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: model:ir.model,name:analytic_contract_hr_expense.model_account_analytic_account
|
||||
msgid "Analytic Account"
|
||||
msgstr "分析勘定"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: code:addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py:144
|
||||
#, python-format
|
||||
msgid "Expenses to Invoice of %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: code:addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py:136
|
||||
#, python-format
|
||||
msgid "Expenses of %s"
|
||||
msgstr "%sの経費"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: field:account.analytic.account,expense_invoiced:0
|
||||
#: field:account.analytic.account,expense_to_invoice:0
|
||||
#: field:account.analytic.account,remaining_expense:0
|
||||
msgid "unknown"
|
||||
msgstr "不明"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: field:account.analytic.account,est_expenses:0
|
||||
msgid "Estimation of Expenses to Invoice"
|
||||
msgstr "請求対象経費見込"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: field:account.analytic.account,charge_expenses:0
|
||||
msgid "Charge Expenses"
|
||||
msgstr "経費請求"
|
||||
|
||||
#. module: analytic_contract_hr_expense
|
||||
#: view:account.analytic.account:0
|
||||
msgid "⇒ Invoice"
|
||||
msgstr "⇒ 請求"
|
|
@ -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,
|
||||
|
|
|
@ -8,7 +8,7 @@ from werkzeug.exceptions import BadRequest
|
|||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import http
|
||||
from openerp.http import request, LazyResponse
|
||||
from openerp.http import request
|
||||
from openerp.addons.web.controllers.main import db_monodb, set_cookie_and_redirect, login_and_redirect
|
||||
from openerp.modules.registry import RegistryManager
|
||||
from openerp.tools.translate import _
|
||||
|
@ -25,7 +25,7 @@ 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;
|
||||
|
@ -75,7 +75,7 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
|||
providers = self.list_providers()
|
||||
|
||||
response = super(OAuthLogin, self).web_login(*args, **kw)
|
||||
if isinstance(response, LazyResponse):
|
||||
if response.is_qweb:
|
||||
error = request.params.get('oauth_error')
|
||||
if error == '1':
|
||||
error = _("Sign up is not allowed on this database.")
|
||||
|
@ -86,9 +86,9 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
|
|||
else:
|
||||
error = None
|
||||
|
||||
response.params['values']['providers'] = providers
|
||||
response.qcontext['providers'] = providers
|
||||
if error:
|
||||
response.params['values']['error'] = error
|
||||
response.qcontext['error'] = error
|
||||
|
||||
return response
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import werkzeug
|
|||
import openerp
|
||||
from openerp.addons.auth_signup.res_users import SignupError
|
||||
from openerp import http
|
||||
from openerp.http import request, LazyResponse
|
||||
from openerp.http import request
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
@ -34,8 +34,7 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
|
|||
@http.route()
|
||||
def web_login(self, *args, **kw):
|
||||
response = super(AuthSignupHome, self).web_login(*args, **kw)
|
||||
if isinstance(response, LazyResponse):
|
||||
response.params['values'].update(self.get_auth_signup_config())
|
||||
response.qcontext.update(self.get_auth_signup_config())
|
||||
return response
|
||||
|
||||
@http.route('/web/signup', type='http', auth='public', website=True, multilang=True)
|
||||
|
@ -52,9 +51,7 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
|
|||
except (SignupError, AssertionError), e:
|
||||
qcontext['error'] = _(e.message)
|
||||
|
||||
def callback(template, values):
|
||||
return request.registry['ir.ui.view'].render(request.cr, request.uid, template, values)
|
||||
return LazyResponse(callback, template='auth_signup.signup', values=qcontext)
|
||||
return request.render('auth_signup.signup', qcontext)
|
||||
|
||||
@http.route('/web/reset_password', type='http', auth='public', website=True, multilang=True)
|
||||
def web_auth_reset_password(self, *args, **kw):
|
||||
|
@ -80,9 +77,7 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
|
|||
qcontext['error'] = _("Could not reset your password")
|
||||
_logger.exception('error when resetting password')
|
||||
|
||||
def callback(template, values):
|
||||
return request.registry['ir.ui.view'].render(request.cr, request.uid, template, values)
|
||||
return LazyResponse(callback, template='auth_signup.reset_password', values=qcontext)
|
||||
return request.render('auth_signup.reset_password', qcontext)
|
||||
|
||||
def get_auth_signup_config(self):
|
||||
"""retrieve the module config (which features are enabled) for the login page"""
|
||||
|
@ -117,6 +112,10 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
|
|||
request.cr.commit()
|
||||
|
||||
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 _
|
||||
|
||||
|
@ -237,7 +237,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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<record id="bank_swift_field" model="res.partner.bank.type.field">
|
||||
<field name="name">bank_bic</field>
|
||||
<field name="bank_type_id" ref="bank_iban"/>
|
||||
<field eval="True" name="required"/>
|
||||
<field eval="False" name="required"/>
|
||||
<field eval="False" name="readonly"/>
|
||||
</record>
|
||||
<record id="bank_country_field" model="res.partner.bank.type.field">
|
||||
|
|
|
@ -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,10 +34,11 @@ 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
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -776,7 +777,7 @@ class calendar_event(osv.Model):
|
|||
else:
|
||||
result[event] = ""
|
||||
return result
|
||||
|
||||
|
||||
def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None):
|
||||
if not isinstance(ids, list):
|
||||
ids = [ids]
|
||||
|
@ -789,7 +790,7 @@ class calendar_event(osv.Model):
|
|||
data.update(update_data)
|
||||
self.write(cr, uid, ids, data, context=context)
|
||||
return True
|
||||
|
||||
|
||||
def _tz_get(self, cr, uid, context=None):
|
||||
return [(x.lower(), x) for x in pytz.all_timezones]
|
||||
|
||||
|
@ -802,7 +803,7 @@ class calendar_event(osv.Model):
|
|||
},
|
||||
}
|
||||
_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 +845,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 +872,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 +888,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")
|
||||
|
||||
|
@ -1353,15 +1353,55 @@ 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'''
|
||||
''' 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 len(str(ids).split('-')) == 1:
|
||||
|
@ -1383,31 +1423,14 @@ 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)
|
||||
|
||||
# set end_date for calendar searching
|
||||
|
@ -1473,7 +1496,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):
|
||||
|
@ -1518,6 +1540,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
|
||||
|
@ -1531,7 +1554,7 @@ class calendar_event(osv.Model):
|
|||
ids_to_unlink = []
|
||||
|
||||
# One time moved to google_Calendar, we can specify, if not in google, and not rec or get_inst = 0, we delete it
|
||||
for event_id in ids:
|
||||
for event_id in ids:
|
||||
if unlink_level == 1 and len(str(event_id).split('-')) == 1: # if ID REAL
|
||||
if self.browse(cr, uid, event_id).recurrent_id:
|
||||
ids_to_exclure.append(event_id)
|
||||
|
|
|
@ -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"/>
|
||||
|
@ -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>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<field name="name">Meeting Types Tree</field>
|
||||
<field name="model">calendar.event.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Meeting Types" editable="bottom">
|
||||
<tree string="Meeting Types">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
@ -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,7 +1,7 @@
|
|||
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({
|
||||
|
@ -11,8 +11,8 @@ openerp.calendar = function(instance) {
|
|||
get_next_notif: function() {
|
||||
var self= this;
|
||||
this.rpc("/calendar/notify")
|
||||
.then(
|
||||
function(result) {
|
||||
.then(
|
||||
function(result) {
|
||||
_.each(result, function(res) {
|
||||
setTimeout(function() {
|
||||
//If notification not already displayed, we add button and action on it
|
||||
|
@ -21,25 +21,25 @@ 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);
|
||||
});
|
||||
|
@ -48,7 +48,7 @@ openerp.calendar = function(instance) {
|
|||
},
|
||||
check_notifications: function() {
|
||||
var self= this;
|
||||
self.get_next_notif();
|
||||
self.get_next_notif();
|
||||
setInterval(function(){
|
||||
self.get_next_notif();
|
||||
}, 5 * 60 * 1000 );
|
||||
|
@ -58,7 +58,7 @@ openerp.calendar = function(instance) {
|
|||
show_application: function() {
|
||||
this._super();
|
||||
this.check_notifications();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
@ -77,7 +77,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 +91,8 @@ openerp.calendar = function(instance) {
|
|||
|
||||
var reload_page = function(){
|
||||
return location.replace(action_url);
|
||||
}
|
||||
};
|
||||
reload_page();
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -108,7 +106,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 +119,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 +132,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
|
||||
|
||||
|
||||
|
|
|
@ -120,14 +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], 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):
|
||||
|
@ -140,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 = {
|
||||
|
|
|
@ -49,9 +49,7 @@
|
|||
}
|
||||
</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new opportunity.
|
||||
</p><p>
|
||||
<p>
|
||||
OpenERP helps you keep track of your sales pipeline to follow
|
||||
up potential sales and better forecast your future revenues.
|
||||
</p><p>
|
||||
|
@ -65,9 +63,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 +73,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 +116,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>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<field name="name">Direct Sales</field>
|
||||
<field name="code">DM</field>
|
||||
<field name="use_leads">True</field>
|
||||
<field name="alias_name">info</field>
|
||||
<field name="alias_name">sales</field>
|
||||
<field name="member_ids" eval="[(4, ref('base.user_root'))]"/>
|
||||
</record>
|
||||
|
||||
|
@ -56,6 +56,14 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
</record>
|
||||
|
||||
<!--default alias for leads-->
|
||||
<record id="mail_alias_lead_info" model="mail.alias">
|
||||
<field name="alias_name">info</field>
|
||||
<field name="alias_model_id" ref="model_crm_lead"/>
|
||||
<field name="alias_user_id" ref="base.user_root"/>
|
||||
<field name="alias_parent_model_id" ref="model_crm_case_section"/>
|
||||
</record>
|
||||
|
||||
<!-- notify all employees of module installation -->
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
@ -950,6 +951,14 @@ class crm_lead(format_address, osv.osv):
|
|||
default['stage_id'] = self._get_default_stage_id(cr, uid, local_context)
|
||||
return super(crm_lead, self).copy(cr, uid, id, default, context=context)
|
||||
|
||||
def get_empty_list_help(self, cr, uid, help, context=None):
|
||||
context['empty_list_help_model'] = 'crm.case.section'
|
||||
context['empty_list_help_id'] = context.get('default_section_id', None)
|
||||
context['empty_list_help_document_name'] = _("opportunity")
|
||||
if context.get('default_type') == 'lead':
|
||||
context['empty_list_help_document_name'] = _("lead")
|
||||
return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context)
|
||||
|
||||
# ----------------------------------------
|
||||
# Mail Gateway
|
||||
# ----------------------------------------
|
||||
|
|
|
@ -146,6 +146,7 @@
|
|||
<!-- CRM-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_lead_create" model="mail.message.subtype">
|
||||
<field name="name">Lead Created</field>
|
||||
<field name="hidden" eval="True"/>
|
||||
<field name="res_model">crm.lead</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Lead created</field>
|
||||
|
@ -171,6 +172,7 @@
|
|||
<!-- Salesteam-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_salesteam_lead" model="mail.message.subtype">
|
||||
<field name="name">Lead Created</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="res_model">crm.case.section</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="parent_id" eval="ref('mt_lead_create')"/>
|
||||
|
@ -178,18 +180,21 @@
|
|||
</record>
|
||||
<record id="mt_salesteam_lead_stage" model="mail.message.subtype">
|
||||
<field name="name">Opportunity Stage Changed</field>
|
||||
<field name="sequence">11</field>
|
||||
<field name="res_model">crm.case.section</field>
|
||||
<field name="parent_id" eval="ref('mt_lead_stage')"/>
|
||||
<field name="relation_field">section_id</field>
|
||||
</record>
|
||||
<record id="mt_salesteam_lead_won" model="mail.message.subtype">
|
||||
<field name="name">Opportunity Won</field>
|
||||
<field name="sequence">12</field>
|
||||
<field name="res_model">crm.case.section</field>
|
||||
<field name="parent_id" eval="ref('mt_lead_won')"/>
|
||||
<field name="relation_field">section_id</field>
|
||||
</record>
|
||||
<record id="mt_salesteam_lead_lost" model="mail.message.subtype">
|
||||
<field name="name">Opportunity Lost</field>
|
||||
<field name="sequence">13</field>
|
||||
<field name="res_model">crm.case.section</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="parent_id" eval="ref('mt_lead_lost')"/>
|
||||
|
|
|
@ -44,9 +44,7 @@
|
|||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new opportunity.
|
||||
</p><p>
|
||||
<p>
|
||||
OpenERP helps you keep track of your sales pipeline to follow
|
||||
up potential sales and better forecast your future revenues.
|
||||
</p><p>
|
||||
|
|
|
@ -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'}"/>
|
||||
|
|
|
@ -264,14 +264,16 @@ class crm_phonecall(osv.osv):
|
|||
Open meeting's calendar view to schedule a meeting on current phonecall.
|
||||
:return dict: dictionary value for created meeting view
|
||||
"""
|
||||
partner_ids = []
|
||||
phonecall = self.browse(cr, uid, ids[0], context)
|
||||
if phonecall.partner_id and phonecall.partner_id.email:
|
||||
partner_ids.append(phonecall.partner_id.id)
|
||||
res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'calendar', 'action_calendar_event', context)
|
||||
res['context'] = {
|
||||
'default_phonecall_id': phonecall.id,
|
||||
'default_partner_id': phonecall.partner_id and phonecall.partner_id.id or False,
|
||||
'default_partner_ids': partner_ids,
|
||||
'default_user_id': uid,
|
||||
'default_email_from': phonecall.email_from,
|
||||
'default_state': 'open',
|
||||
'default_name': phonecall.name,
|
||||
}
|
||||
return res
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
<field name="view_mode">tree,calendar</field>
|
||||
<field name="view_id" ref="crm_case_inbound_phone_tree_view"/>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{'default_state': 'done'}</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_phonecalls_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
|
@ -106,7 +106,7 @@
|
|||
<field name="view_mode">tree,calendar</field>
|
||||
<field name="view_id" ref="crm_case_phone_tree_view"/>
|
||||
<field name="domain">[('state','!=','done')]</field>
|
||||
<field name="context" eval="'{\'default_state\':\'open\'}'"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_phonecalls_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
|
||||
|
@ -66,7 +67,42 @@ class crm_configuration(osv.TransientModel):
|
|||
'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
|
||||
implied_group='base.group_multi_salesteams',
|
||||
help="""Allows you to use Sales Teams to manage your leads and opportunities."""),
|
||||
'alias_prefix': fields.char('Default Alias Name for Leads'),
|
||||
'alias_domain' : fields.char('Alias Domain'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'alias_domain': lambda self, cr, uid, context: self.pool['mail.alias']._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
def _find_default_lead_alias_id(self, cr, uid, context=None):
|
||||
alias_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'crm.mail_alias_lead_info')
|
||||
if not alias_id:
|
||||
alias_ids = self.pool['mail.alias'].search(
|
||||
cr, uid, [
|
||||
('alias_model_id.model', '=', 'crm.lead'),
|
||||
('alias_force_thread_id', '=', False),
|
||||
('alias_parent_model_id.model', '=', 'crm.case.section'),
|
||||
('alias_parent_thread_id', '=', False),
|
||||
('alias_defaults', '=', '{}')
|
||||
], context=context)
|
||||
alias_id = alias_ids and alias_ids[0] or False
|
||||
return alias_id
|
||||
|
||||
def get_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
alias_name = False
|
||||
alias_id = self._find_default_lead_alias_id(cr, uid, context=context)
|
||||
if alias_id:
|
||||
alias_name = self.pool['mail.alias'].browse(cr, uid, alias_id, context=context).alias_name
|
||||
return {'alias_prefix': alias_name}
|
||||
|
||||
def set_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
mail_alias = self.pool['mail.alias']
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
alias_id = self._find_default_lead_alias_id(cr, uid, context=context)
|
||||
if not alias_id:
|
||||
create_ctx = dict(context, alias_model_name='crm.lead', alias_parent_model_name='crm.case.section')
|
||||
alias_id = self.pool['mail.alias'].create(cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx)
|
||||
else:
|
||||
mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context)
|
||||
return True
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<field name="model">sale.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.view_sale_config_settings"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<div name="config_sale" position="before">
|
||||
<separator string="After-Sale Services"/>
|
||||
<group>
|
||||
|
@ -26,11 +27,31 @@
|
|||
<group>
|
||||
<label for="id" string="Manage Sales Teams"/>
|
||||
<div>
|
||||
<field name="group_multi_salesteams" class="oe_inline"/>
|
||||
<label for="group_multi_salesteams"/>
|
||||
<div>
|
||||
<field name="group_multi_salesteams" class="oe_inline"/>
|
||||
<label for="group_multi_salesteams"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
<xpath expr="//group[@name='On Mail Client']" position="before">
|
||||
<group name="default_alias">
|
||||
<label for="id" string="Leads Email Alias"/>
|
||||
<div attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<div>
|
||||
<field name="alias_prefix" class="oe_inline" attrs="{'required': [('alias_domain', '!=', False)]}"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
<p>
|
||||
All emails sent to this address and processed by the mailgateway
|
||||
will create a new lead.
|
||||
</p>
|
||||
</div>
|
||||
</group>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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':{}}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# Finnish translation for openobject-addons
|
||||
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
|
||||
"PO-Revision-Date: 2014-02-18 20:10+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Finnish <fi@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-19 05:23+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: event_sale
|
||||
#: model:ir.model,name:event_sale.model_product_product
|
||||
msgid "Product"
|
||||
msgstr "Tuote"
|
||||
|
||||
#. module: event_sale
|
||||
#: help:product.product,event_ok:0
|
||||
msgid ""
|
||||
"Determine if a product needs to create automatically an event registration "
|
||||
"at the confirmation of a sales order line."
|
||||
msgstr ""
|
||||
|
||||
#. module: event_sale
|
||||
#: help:sale.order.line,event_id:0
|
||||
msgid ""
|
||||
"Choose an event and it will automatically create a registration for this "
|
||||
"event."
|
||||
msgstr ""
|
||||
"Valitse tapahtuma ja se luo automaattisesti rekisteröinnin kyseiseen "
|
||||
"tapahtumaan."
|
||||
|
||||
#. module: event_sale
|
||||
#: model:event.event,name:event_sale.event_technical_training
|
||||
msgid "Technical training in Grand-Rosiere"
|
||||
msgstr ""
|
||||
|
||||
#. module: event_sale
|
||||
#: help:product.product,event_type_id:0
|
||||
msgid ""
|
||||
"Select event types so when we use this product in sales order lines, it will "
|
||||
"filter events of this type only."
|
||||
msgstr ""
|
||||
"Valitse tapahtumatyypit, joiden avulla myyntitilausrivillä voidaan suodattaa "
|
||||
"vain tämän tyyppiset tapahtumat."
|
||||
|
||||
#. module: event_sale
|
||||
#: field:product.product,event_type_id:0
|
||||
msgid "Type of Event"
|
||||
msgstr "Tapahtumatyyppi"
|
||||
|
||||
#. module: event_sale
|
||||
#: field:sale.order.line,event_ok:0
|
||||
msgid "event_ok"
|
||||
msgstr ""
|
||||
|
||||
#. module: event_sale
|
||||
#: field:product.product,event_ok:0
|
||||
msgid "Event Subscription"
|
||||
msgstr "Tapahtumaan rekisteröityminen"
|
||||
|
||||
#. module: event_sale
|
||||
#: field:sale.order.line,event_type_id:0
|
||||
msgid "Event Type"
|
||||
msgstr "Tapahtumatyyppi"
|
||||
|
||||
#. module: event_sale
|
||||
#: model:product.template,name:event_sale.event_product_product_template
|
||||
msgid "Technical Training"
|
||||
msgstr "Tekninen koulutus"
|
||||
|
||||
#. module: event_sale
|
||||
#: code:addons/event_sale/event_sale.py:88
|
||||
#, python-format
|
||||
msgid "The registration %s has been created from the Sales Order %s."
|
||||
msgstr "Rekisteröinti %s on luotu myyntitilaukselta %s."
|
||||
|
||||
#. module: event_sale
|
||||
#: field:sale.order.line,event_id:0
|
||||
msgid "Event"
|
||||
msgstr "Tapahtuma"
|
||||
|
||||
#. module: event_sale
|
||||
#: model:ir.model,name:event_sale.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Myyntitilausrivi"
|
|
@ -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()
|
||||
|
|
|
@ -62,19 +62,8 @@ class fleet_vehicle_cost(osv.Model):
|
|||
res[record.id] = _('Unknown')
|
||||
return res
|
||||
|
||||
def _cost_name_get_fnc(self, cr, uid, ids, name, unknow_none, context=None):
|
||||
res = {}
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
name = record.vehicle_id.name
|
||||
if record.cost_subtype_id.name:
|
||||
name += ' / '+ record.cost_subtype_id.name
|
||||
if record.date:
|
||||
name += ' / '+ record.date
|
||||
res[record.id] = name
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.function(_cost_name_get_fnc, type="char", string='Name', store=True),
|
||||
'name': fields.related('vehicle_id', 'name', type="char", string='Name', store=True),
|
||||
'vehicle_id': fields.many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log'),
|
||||
'cost_subtype_id': fields.many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost'),
|
||||
'amount': fields.float('Total Price'),
|
||||
|
|
|
@ -832,6 +832,9 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
fleet.vehicle.cost
|
||||
-->
|
||||
<record model='ir.actions.act_window' id='fleet_vehicle_service_types_act'>
|
||||
<field name="name">Service Types</field>
|
||||
<field name="res_model">fleet.service.type</field>
|
||||
|
@ -848,6 +851,17 @@
|
|||
|
||||
<menuitem action="fleet_vehicle_service_types_act" parent="fleet_configuration" id="fleet_vehicle_service_types_menu" groups="base.group_no_one"/>
|
||||
|
||||
<record model='ir.ui.view' id='fleet_vehicle_cost_tree'>
|
||||
<field name="name">fleet.vehicle.cost.tree</field>
|
||||
<field name="model">fleet.vehicle.cost</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Vehicle Costs">
|
||||
<field name="vehicle_id"/>
|
||||
<field name="cost_subtype_id"/>
|
||||
<field name="date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model='ir.ui.view' id='fleet_vehicle_costs_report'>
|
||||
<field name="name">fleet.vehicle.cost.graph</field>
|
||||
|
@ -939,31 +953,5 @@
|
|||
</record>
|
||||
|
||||
<menuitem action="fleet_vehicle_costs_act" parent="fleet_vehicles" id="fleet_vehicle_costs_menu" groups="group_fleet_manager"/>
|
||||
<!--
|
||||
<record model='ir.ui.view' id='fleet_hr_employee_form'>
|
||||
<field name="name">fleet.hr.employee.form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form" />
|
||||
<field name="arch" type="xml">
|
||||
<notebook position="inside">
|
||||
<page string="Vehicle">
|
||||
<group>
|
||||
<field name="vehicle_id" widget="many2many_tags"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="fleet.vehicle.model" id="citroen">
|
||||
<field name="name">Citroen</field>
|
||||
</record>
|
||||
|
||||
<record model="fleet.vehicle" id="stw_vehicle">
|
||||
<field name="name">240BTN</field>
|
||||
<field name="model_id" ref="citroen" />
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
@ -433,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)
|
||||
|
@ -487,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,
|
||||
|
@ -537,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:
|
||||
|
@ -554,8 +557,12 @@ class gamification_challenge(osv.Model):
|
|||
'completeness': goal.completeness,
|
||||
'state': goal.state,
|
||||
})
|
||||
if goal.state != 'reached':
|
||||
all_reached = False
|
||||
if goal_ids:
|
||||
res_lines.append(line_data)
|
||||
if all_reached:
|
||||
return []
|
||||
return res_lines
|
||||
|
||||
##### Reporting #####
|
||||
|
@ -620,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')
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv
|
||||
from challenge import MAX_VISIBILITY_RANKING
|
||||
|
||||
|
@ -39,10 +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.generate_goals_from_challenge(cr, uid, challenge_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):
|
||||
|
@ -54,10 +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.generate_goals_from_challenge(cr, uid, challenge_ids, 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):
|
||||
|
@ -130,8 +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.generate_goals_from_challenge(cr, uid, challenge_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
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * gamification_sale_crm
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-02-13 15:09+0000\n"
|
||||
"PO-Revision-Date: 2014-02-13 15:09+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_lead_delay_close
|
||||
msgid "Days to Close a Deal"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_new_leads
|
||||
msgid "New Leads"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_call
|
||||
msgid "calls"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_lead_delay_open
|
||||
msgid "Time to Qualify a Lead"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_sale_order_created
|
||||
msgid "New Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_customer_refunds
|
||||
msgid "invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_customer_refunds
|
||||
msgid "Customer Refunds"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.challenge,name:gamification_sale_crm.challenge_crm_sale
|
||||
msgid "Monthly Sales Targets"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_tot_customer_refunds
|
||||
msgid "Total Customer Refunds"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_new_opportunities
|
||||
msgid "opportunities"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_lead_delay_close
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_lead_delay_open
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_new_opportunities
|
||||
msgid "New Opportunities"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_call
|
||||
msgid "Logged Calls"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_paid_sale_order
|
||||
msgid "Paid Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.challenge,name:gamification_sale_crm.challenge_crm_marketing
|
||||
msgid "Lead Acquisition"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_tot_paid_sale_order
|
||||
msgid "Total Paid Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_new_leads
|
||||
msgid "leads"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_paid_sale_order
|
||||
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_sale_order_created
|
||||
msgid "orders"
|
||||
msgstr ""
|
||||
|
||||
#. module: gamification_sale_crm
|
||||
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_tot_invoices
|
||||
msgid "Total Invoiced"
|
||||
msgstr ""
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|
@ -36,21 +37,185 @@ from dateutil import parser
|
|||
import pytz
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import osv
|
||||
from collections import namedtuple
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Meta(type):
|
||||
""" This Meta class allow to define class as a structure, and so instancied variable
|
||||
in __init__ to avoid to have side effect alike 'static' variable """
|
||||
def __new__(typ, name, parents, attrs):
|
||||
methods = dict((k, v) for k, v in attrs.iteritems()
|
||||
if callable(v))
|
||||
attrs = dict((k, v) for k, v in attrs.iteritems()
|
||||
if not callable(v))
|
||||
|
||||
def init(self, **kw):
|
||||
for k, v in attrs.iteritems():
|
||||
setattr(self, k, v)
|
||||
for k, v in kw.iteritems():
|
||||
assert k in attrs
|
||||
setattr(self, k, v)
|
||||
|
||||
methods['__init__'] = init
|
||||
methods['__getitem__'] = getattr
|
||||
return type.__new__(typ, name, parents, methods)
|
||||
|
||||
class Struct(object):
|
||||
__metaclass__ = Meta
|
||||
|
||||
class OpenerpEvent(Struct):
|
||||
event = False
|
||||
found = False
|
||||
event_id = False
|
||||
isRecurrence = False
|
||||
isInstance = False
|
||||
update = False
|
||||
status = False
|
||||
attendee_id = False
|
||||
synchro = False
|
||||
|
||||
class GmailEvent(Struct):
|
||||
event = False
|
||||
found = False
|
||||
isRecurrence = False
|
||||
isInstance = False
|
||||
update = False
|
||||
status = False
|
||||
|
||||
class SyncEvent(object):
|
||||
def __init__(self):
|
||||
self.OE = OpenerpEvent()
|
||||
self.GG = GmailEvent()
|
||||
self.OP = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self,key)
|
||||
|
||||
def compute_OP(self):
|
||||
#If event are already in Gmail and in OpenERP
|
||||
if self.OE.found and self.GG.found:
|
||||
#If the event has been deleted from one side, we delete on other side !
|
||||
if self.OE.status != self.GG.status:
|
||||
self.OP = Delete((self.OE.status and "OE") or (self.GG.status and "GG"),
|
||||
'The event has been deleted from one side, we delete on other side !' )
|
||||
#If event is not deleted !
|
||||
elif self.OE.status and self.GG.status:
|
||||
if self.OE.update.split('.')[0] != self.GG.update.split('.')[0]:
|
||||
if self.OE.update < self.GG.update:
|
||||
tmpSrc = 'GG'
|
||||
elif self.OE.update > self.GG.update:
|
||||
tmpSrc = 'OE'
|
||||
assert tmpSrc in ['GG','OE']
|
||||
|
||||
|
||||
#if self.OP.action == None:
|
||||
if self[tmpSrc].isRecurrence:
|
||||
if self[tmpSrc].status:
|
||||
self.OP = Update(tmpSrc, 'Only need to update, because i\'m active')
|
||||
else:
|
||||
self.OP = Exclude(tmpSrc, 'Need to Exclude (Me = First event from recurrence) from recurrence')
|
||||
|
||||
elif self[tmpSrc].isInstance:
|
||||
self.OP= Update(tmpSrc, 'Only need to update, because already an exclu');
|
||||
else:
|
||||
self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event');
|
||||
#end-if self.OP.action == None:
|
||||
|
||||
else:
|
||||
if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
|
||||
self.OP = Update('OE','Event already updated by another user, but not synchro with my google calendar')
|
||||
#import ipdb; ipdb.set_trace();
|
||||
else:
|
||||
self.OP = NothingToDo("",'Not update needed')
|
||||
else:
|
||||
self.OP = NothingToDo("", "Both are already deleted");
|
||||
|
||||
# New in openERP... Create on create_events of synchronize function
|
||||
elif self.OE.found and not self.GG.found:
|
||||
#Has been deleted from gmail
|
||||
if self.OE.status:
|
||||
self.OP = Delete('OE', 'Removed from GOOGLE')
|
||||
else:
|
||||
self.OP = NothingToDo("","Already Deleted in gmail and unlinked in OpenERP")
|
||||
elif self.GG.found and not self.OE.found:
|
||||
tmpSrc = 'GG'
|
||||
if not self.GG.status and not self.GG.isInstance:
|
||||
# don't need to make something... because event has been created and deleted before the synchronization
|
||||
self.OP = NothingToDo("", 'Nothing to do... Create and Delete directly')
|
||||
else:
|
||||
if self.GG.isInstance:
|
||||
if self[tmpSrc].status:
|
||||
self.OP = Exclude(tmpSrc, 'Need to create the new exclu')
|
||||
else:
|
||||
self.OP = Exclude(tmpSrc, 'Need to copy and Exclude')
|
||||
else:
|
||||
self.OP = Create(tmpSrc, 'New EVENT CREATE from GMAIL')
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
myPrint = "---- A SYNC EVENT ---"
|
||||
myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
|
||||
myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
|
||||
myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name)
|
||||
myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False))
|
||||
myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
|
||||
myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
|
||||
myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
|
||||
myPrint += "\n Synchro OE: %10s " % (self.OE.synchro)
|
||||
myPrint += "\n Update OE: %10s " % (self.OE.update)
|
||||
myPrint += "\n Update GG: %10s " % (self.GG.update)
|
||||
myPrint += "\n Status OE:%5s vs GG: %5s" % (self.OE.status, self.GG.status)
|
||||
if (self.OP is None):
|
||||
myPrint += "\n Action %s" % "---!!!---NONE---!!!---"
|
||||
else:
|
||||
myPrint += "\n Action %s" % type(self.OP).__name__
|
||||
myPrint += "\n Source %s" % (self.OP.src)
|
||||
myPrint += "\n comment %s" % (self.OP.info)
|
||||
return myPrint
|
||||
|
||||
|
||||
class SyncOperation(object):
|
||||
def __init__(self, src,info, **kw):
|
||||
self.src = src
|
||||
self.info = info
|
||||
for k,v in kw.items():
|
||||
setattr(self,k,v)
|
||||
|
||||
def __str__(self):
|
||||
return 'in__STR__'
|
||||
|
||||
class Create(SyncOperation):
|
||||
pass
|
||||
class Update(SyncOperation):
|
||||
pass
|
||||
class Delete(SyncOperation):
|
||||
pass
|
||||
class NothingToDo(SyncOperation):
|
||||
pass
|
||||
class Exclude(SyncOperation):
|
||||
pass
|
||||
|
||||
|
||||
class google_calendar(osv.AbstractModel):
|
||||
STR_SERVICE = 'calendar'
|
||||
_name = 'google.%s' % STR_SERVICE
|
||||
|
||||
def generate_data(self, cr, uid, event, context=None):
|
||||
def generate_data(self, cr, uid, event, context=None):
|
||||
if event.allday:
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) , context=context).isoformat('T').split('T')[0]
|
||||
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration), context=context).isoformat('T').split('T')[0]
|
||||
type = 'date'
|
||||
vstype = 'dateTime'
|
||||
else:
|
||||
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
|
||||
type = 'dateTime'
|
||||
vstype = 'date'
|
||||
attendee_list = []
|
||||
|
||||
for attendee in event.attendee_ids:
|
||||
|
@ -64,10 +229,12 @@ class google_calendar(osv.AbstractModel):
|
|||
"description": event.description or '',
|
||||
"start":{
|
||||
type:start_date,
|
||||
vstype:None,
|
||||
'timeZone':'UTC'
|
||||
},
|
||||
"end":{
|
||||
type:end_date,
|
||||
vstype:None,
|
||||
'timeZone':'UTC'
|
||||
},
|
||||
"attendees":attendee_list,
|
||||
|
@ -80,10 +247,13 @@ class google_calendar(osv.AbstractModel):
|
|||
if not event.active:
|
||||
data["state"] = "cancelled"
|
||||
|
||||
if not self.get_need_synchro_attendee(cr,uid,context=context):
|
||||
data.pop("attendees")
|
||||
|
||||
return data
|
||||
|
||||
def create_an_event(self, cr, uid,event, context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
data = self.generate_data(cr, uid,event, context=context)
|
||||
|
||||
|
@ -94,7 +264,7 @@ class google_calendar(osv.AbstractModel):
|
|||
return gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context)
|
||||
|
||||
def delete_an_event(self, cr, uid,event_id, context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
params = {
|
||||
'access_token' : self.get_token(cr,uid,context)
|
||||
|
@ -108,12 +278,14 @@ class google_calendar(osv.AbstractModel):
|
|||
if not token:
|
||||
token = self.get_token(cr,uid,context)
|
||||
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
params = {
|
||||
'fields': 'items,nextPageToken',
|
||||
'access_token' : token,
|
||||
'maxResults':1000
|
||||
'maxResults':1000,
|
||||
'timeMin': self.get_start_time_to_synchro(cr,uid,context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
|
||||
|
||||
}
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
||||
|
@ -134,7 +306,7 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
def update_to_google(self, cr, uid, oe_event, google_event, context):
|
||||
calendar_event = self.pool['calendar.event']
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'],'id,updated', self.get_token(cr,uid,context))
|
||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
||||
|
@ -148,10 +320,10 @@ class google_calendar(osv.AbstractModel):
|
|||
calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date':update_date})
|
||||
|
||||
if context['curr_attendee']:
|
||||
self.pool.get('calendar.attendee').write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date},context)
|
||||
self.pool['calendar.attendee'].write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date},context)
|
||||
|
||||
def update_an_event(self, cr, uid,event, context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
data = self.generate_data(cr, uid,event, context=context)
|
||||
|
||||
|
@ -164,7 +336,7 @@ class google_calendar(osv.AbstractModel):
|
|||
return response
|
||||
|
||||
def update_recurrent_event_exclu(self, cr, uid,instance_id,event_ori_google_id,event_new, context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
data = self.generate_data(cr, uid,event_new, context=context)
|
||||
|
||||
|
@ -186,7 +358,7 @@ class google_calendar(osv.AbstractModel):
|
|||
calendar_event = self.pool['calendar.event']
|
||||
res_partner_obj = self.pool['res.partner']
|
||||
calendar_attendee_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool.get('res.users')
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context).partner_id.id
|
||||
attendee_record = []
|
||||
partner_record = [(4,myPartnerID)]
|
||||
|
@ -203,14 +375,16 @@ class google_calendar(osv.AbstractModel):
|
|||
|
||||
if google_attendee.get('found',False):
|
||||
continue
|
||||
attendee_id = res_partner_obj.search(cr, uid,[('email', '=', google_attendee['email'])], context=context)
|
||||
if not attendee_id:
|
||||
attendee_id = [res_partner_obj.create(cr, uid,{'email': google_attendee['email'],'Customer': False, 'name': google_attendee.get("displayName",False) or google_attendee['email'] }, context=context)]
|
||||
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
|
||||
partner_record.append((4, attendee.get('id')))
|
||||
attendee['partner_id'] = attendee.pop('id')
|
||||
attendee['state'] = google_attendee['responseStatus']
|
||||
attendee_record.append((0, 0, attendee))
|
||||
if self.get_need_synchro_attendee(cr,uid,context=context):
|
||||
attendee_id = res_partner_obj.search(cr, uid,[('email', '=', google_attendee['email'])], context=context)
|
||||
if not attendee_id:
|
||||
attendee_id = [res_partner_obj.create(cr, uid,{'email': google_attendee['email'],'customer': False, 'name': google_attendee.get("displayName",False) or google_attendee['email'] }, context=context)]
|
||||
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
|
||||
partner_record.append((4, attendee.get('id')))
|
||||
attendee['partner_id'] = attendee.pop('id')
|
||||
attendee['state'] = google_attendee['responseStatus']
|
||||
attendee_record.append((0, 0, attendee))
|
||||
|
||||
UTC = pytz.timezone('UTC')
|
||||
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
|
||||
if single_event_dict['start'].get('dateTime',False) and single_event_dict['end'].get('dateTime',False):
|
||||
|
@ -244,7 +418,7 @@ class google_calendar(osv.AbstractModel):
|
|||
'location':single_event_dict.get('location',False),
|
||||
'class':single_event_dict.get('visibility','public'),
|
||||
'oe_update_date':update_date,
|
||||
# 'google_internal_event_id': single_event_dict.get('id',False),
|
||||
# 'google_internal_event_id': single_event_dict.get('id',False),
|
||||
})
|
||||
|
||||
if single_event_dict.get("recurrence",False):
|
||||
|
@ -261,16 +435,17 @@ class google_calendar(osv.AbstractModel):
|
|||
res = calendar_event.create(cr, uid, result, context=context)
|
||||
|
||||
if context['curr_attendee']:
|
||||
self.pool.get('calendar.attendee').write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date,'google_internal_event_id': single_event_dict.get('id',False)},context)
|
||||
self.pool['calendar.attendee'].write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date,'google_internal_event_id': single_event_dict.get('id',False)},context)
|
||||
return res
|
||||
|
||||
def synchronize_events(self, cr, uid, ids, context=None):
|
||||
gc_obj = self.pool.get('google.calendar')
|
||||
gc_obj = self.pool['google.calendar']
|
||||
|
||||
# Create all new events from OpenERP into Gmail, if that is not recurrent event
|
||||
self.create_new_events(cr, uid, context=context)
|
||||
|
||||
self.bind_recurring_events_to_google(cr, uid, context)
|
||||
cr.commit()
|
||||
|
||||
|
||||
res = self.update_events(cr, uid, context)
|
||||
|
||||
|
@ -279,10 +454,9 @@ class google_calendar(osv.AbstractModel):
|
|||
"url" : ''
|
||||
}
|
||||
|
||||
def create_new_events(self, cr, uid, context):
|
||||
gc_pool = self.pool.get('google.calendar')
|
||||
|
||||
calendar_event = self.pool['calendar.event']
|
||||
def create_new_events(self, cr, uid, context=None):
|
||||
gc_pool = self.pool['google.calendar']
|
||||
ev_obj = self.pool['calendar.event']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
|
||||
|
@ -290,75 +464,73 @@ class google_calendar(osv.AbstractModel):
|
|||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
|
||||
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '=', False)], context=context_norecurrent)
|
||||
my_att_ids = att_obj.search(cr, uid,[ ('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '=', False),
|
||||
'|',
|
||||
('event_id.date_deadline','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
], context=context_norecurrent)
|
||||
|
||||
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
|
||||
if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0:
|
||||
response = self.create_an_event(cr,uid,att.event_id,context=context)
|
||||
update_date = datetime.strptime(response['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
|
||||
calendar_event.write(cr, uid, att.event_id.id, {'oe_update_date':update_date})
|
||||
ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date':update_date})
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date':update_date})
|
||||
cr.commit()
|
||||
return True
|
||||
cr.commit()
|
||||
|
||||
def get_empty_synchro_summarize(self) :
|
||||
return {
|
||||
#OPENERP
|
||||
'OE_event' : False,
|
||||
'OE_found' : False,
|
||||
'OE_event_id' : False,
|
||||
'OE_isRecurrence':False,
|
||||
'OE_isInstance':False,
|
||||
'OE_update':False,
|
||||
'OE_status':False,
|
||||
'OE_attendee_id': False,
|
||||
'OE_synchro':False,
|
||||
def bind_recurring_events_to_google(self, cr, uid, context):
|
||||
ev_obj = self.pool['calendar.event']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
|
||||
|
||||
#GOOGLE
|
||||
'GG_event' : False,
|
||||
'GG_found' : False,
|
||||
'GG_isRecurrence':False,
|
||||
'GG_isInstance':False,
|
||||
'GG_update':False,
|
||||
'GG_status':False,
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
context_norecurrent['active_test'] = False
|
||||
|
||||
#TO_DO_IN_GOOGLE
|
||||
'td_action':'', # create, update, delete, None
|
||||
#If 'td_action' in (create , update),
|
||||
# If td_source == OE
|
||||
# We create in google the event based on OpenERP
|
||||
# If td_source == GG
|
||||
# We create in OpenERP the event based on Gmail
|
||||
#
|
||||
#If 'td_action' in (delete),
|
||||
# If td_source == OE
|
||||
# We delete in OpenERP the event
|
||||
# If td_source == GG
|
||||
# We delete in Gmail the event
|
||||
# If td_source == ALL
|
||||
# We delete in openERP AND in Gmail the event
|
||||
'td_source': '', # OE, GG, ALL
|
||||
'td_comment':''
|
||||
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '=', False)], context=context_norecurrent)
|
||||
|
||||
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
|
||||
if att.event_id.recurrent_id and att.event_id.recurrent_id > 0:
|
||||
new_google_internal_event_id = False
|
||||
source_event_record = ev_obj.browse(cr, uid, att.event_id.recurrent_id, context)
|
||||
source_attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',source_event_record.id)], context=context)
|
||||
source_attendee_record = att_obj.browse(cr, uid, source_attendee_record_id, context)[0]
|
||||
|
||||
if att.event_id.recurrent_id_date and source_event_record.allday and source_attendee_record.google_internal_event_id:
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.split(' ')[0].replace('-','')
|
||||
elif att.event_id.recurrent_id_date and source_attendee_record.google_internal_event_id:
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.replace('-','').replace(' ','T').replace(':','') + 'Z'
|
||||
|
||||
}
|
||||
if new_google_internal_event_id:
|
||||
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
|
||||
res = self.update_recurrent_event_exclu(cr, uid,new_google_internal_event_id, source_attendee_record.google_internal_event_id,att.event_id, context=context)
|
||||
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
|
||||
cr.commit()
|
||||
|
||||
def update_events(self, cr, uid, context):
|
||||
def update_events(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
calendar_event = self.pool['calendar.event']
|
||||
user_obj = self.pool['res.users']
|
||||
att_obj = self.pool['calendar.attendee']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
|
||||
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
|
||||
|
||||
context_novirtual = context.copy()
|
||||
context_novirtual['virtual_id'] = False
|
||||
context_novirtual['active_test'] = False
|
||||
|
||||
all_event_from_google = self.get_event_dict(cr,uid,context=context)
|
||||
all_new_event_from_google = all_event_from_google.copy()
|
||||
|
||||
all_event_from_google = self.get_event_dict(cr, uid, context=context)
|
||||
|
||||
# Select all events from OpenERP which have been already synchronized in gmail
|
||||
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '!=', False)], context=context_novirtual)
|
||||
my_att_ids = att_obj.search(cr, uid,[ ('partner_id', '=', myPartnerID),
|
||||
('google_internal_event_id', '!=', False),
|
||||
'|',
|
||||
('event_id.date_deadline','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
('event_id.end_date','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
|
||||
], context=context_novirtual)
|
||||
event_to_synchronize = {}
|
||||
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
|
||||
event = att.event_id
|
||||
|
@ -369,18 +541,19 @@ class google_calendar(osv.AbstractModel):
|
|||
event_to_synchronize[base_event_id] = {}
|
||||
|
||||
if att.google_internal_event_id not in event_to_synchronize[base_event_id]:
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id] = self.get_empty_synchro_summarize()
|
||||
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_attendee_id'] = att.id
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_event'] = event
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_found'] = True
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_event_id'] = event.id
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_isRecurrence'] = event.recurrency
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_isInstance'] = bool(event.recurrent_id and event.recurrent_id > 0)
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_update'] = event.oe_update_date
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_status'] = event.active
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_synchro'] = att.oe_synchro_date
|
||||
event_to_synchronize[base_event_id][att.google_internal_event_id] = SyncEvent()
|
||||
|
||||
ev_to_sync = event_to_synchronize[base_event_id][att.google_internal_event_id]
|
||||
|
||||
ev_to_sync.OE.attendee_id = att.id
|
||||
ev_to_sync.OE.event = event
|
||||
ev_to_sync.OE.found = True
|
||||
ev_to_sync.OE.event_id = event.id
|
||||
ev_to_sync.OE.isRecurrence = event.recurrency
|
||||
ev_to_sync.OE.isInstance = bool(event.recurrent_id and event.recurrent_id > 0)
|
||||
ev_to_sync.OE.update = event.oe_update_date
|
||||
ev_to_sync.OE.status = event.active
|
||||
ev_to_sync.OE.synchro = att.oe_synchro_date
|
||||
|
||||
for event in all_event_from_google.values():
|
||||
event_id = event.get('id')
|
||||
|
@ -390,98 +563,28 @@ class google_calendar(osv.AbstractModel):
|
|||
event_to_synchronize[base_event_id] = {}
|
||||
|
||||
if event_id not in event_to_synchronize[base_event_id]:
|
||||
event_to_synchronize[base_event_id][event_id] = self.get_empty_synchro_summarize()
|
||||
|
||||
event_to_synchronize[base_event_id][event_id]['GG_event'] = event
|
||||
event_to_synchronize[base_event_id][event_id]['GG_found'] = True
|
||||
event_to_synchronize[base_event_id][event_id]['GG_isRecurrence'] = bool(event.get('recurrence',''))
|
||||
event_to_synchronize[base_event_id][event_id]['GG_isInstance'] = bool(event.get('recurringEventId',0))
|
||||
event_to_synchronize[base_event_id][event_id]['GG_update'] = event.get('updated',None) # if deleted, no date without browse event
|
||||
if event_to_synchronize[base_event_id][event_id]['GG_update']:
|
||||
event_to_synchronize[base_event_id][event_id]['GG_update'] =event_to_synchronize[base_event_id][event_id]['GG_update'].replace('T',' ').replace('Z','')
|
||||
event_to_synchronize[base_event_id][event_id]['GG_status'] = (event.get('status') != 'cancelled')
|
||||
|
||||
event_to_synchronize[base_event_id][event_id] = SyncEvent()
|
||||
|
||||
ev_to_sync = event_to_synchronize[base_event_id][event_id]
|
||||
|
||||
ev_to_sync.GG.event = event
|
||||
ev_to_sync.GG.found = True
|
||||
ev_to_sync.GG.isRecurrence = bool(event.get('recurrence',''))
|
||||
ev_to_sync.GG.isInstance = bool(event.get('recurringEventId',0))
|
||||
ev_to_sync.GG.update = event.get('updated',None) # if deleted, no date without browse event
|
||||
if ev_to_sync.GG.update:
|
||||
ev_to_sync.GG.update = ev_to_sync.GG.update.replace('T',' ').replace('Z','')
|
||||
ev_to_sync.GG.status = (event.get('status') != 'cancelled')
|
||||
|
||||
######################
|
||||
# PRE-PROCESSING #
|
||||
######################
|
||||
|
||||
for base_event in event_to_synchronize:
|
||||
for current_event in event_to_synchronize[base_event]:
|
||||
event = event_to_synchronize[base_event][current_event]
|
||||
|
||||
#If event are already in Gmail and in OpenERP
|
||||
if event['OE_found'] and event['GG_found']:
|
||||
#If the event has been deleted from one side, we delete on other side !
|
||||
if event['OE_status'] != event['GG_status']:
|
||||
event['td_action'] = "DELETE"
|
||||
event['td_source'] = (event['OE_status'] and "OE") or (event['GG_status'] and "GG")
|
||||
#If event is not deleted !
|
||||
elif event['OE_status'] and event['GG_status']:
|
||||
if event['OE_update'].split('.')[0] != event['GG_update'].split('.')[0]:
|
||||
if event['OE_update'] < event['GG_update']:
|
||||
event['td_source'] = 'GG'
|
||||
elif event['OE_update'] > event['GG_update']:
|
||||
event['td_source'] = 'OE'
|
||||
|
||||
|
||||
if event['td_action'] != "None":
|
||||
if event['%s_isRecurrence' % event['td_source']]:
|
||||
if event['%s_status' % event['td_source']]:
|
||||
event['td_action'] = "UPDATE"
|
||||
event['td_comment'] = 'Only need to update, because i\'m active'
|
||||
else:
|
||||
event['td_action'] = "EXCLUDE"
|
||||
event['td_comment'] = 'Need to Exclude (Me = First event from recurrence) from recurrence'
|
||||
|
||||
elif event['%s_isInstance' % event['td_source']]:
|
||||
event['td_action'] = "UPDATE"
|
||||
event['td_comment'] = 'Only need to update, because already an exclu'
|
||||
else:
|
||||
event['td_action'] = "UPDATE"
|
||||
event['td_comment'] = 'Simply Update... I\'m a single event'
|
||||
|
||||
else:
|
||||
if not event['OE_synchro'] or event['OE_synchro'].split('.')[0] < event['OE_update'].split('.')[0]:
|
||||
event['td_source'] = 'OE'
|
||||
event['td_action'] = "UPDATE"
|
||||
event['td_comment'] = 'Event already updated by another user, but not synchro with my google calendar'
|
||||
|
||||
else:
|
||||
event['td_action'] = "None"
|
||||
event['td_comment'] = 'Not update needed'
|
||||
else:
|
||||
event['td_action'] = "None"
|
||||
event['td_comment'] = "Both are already deleted"
|
||||
# New in openERP... Create on create_events of synchronize function
|
||||
elif event['OE_found'] and not event['GG_found']:
|
||||
#Has been deleted from gmail
|
||||
if event['OE_status']:
|
||||
event['td_source'] = 'OE'
|
||||
event['td_action'] = 'DELETE'
|
||||
event['td_comment'] = 'Removed from GOOGLE ?'
|
||||
else:
|
||||
event['td_action'] = "None"
|
||||
event['td_comment'] = "Already Deleted in gmail and unlinked in OpenERP"
|
||||
elif event['GG_found'] and not event['OE_found']:
|
||||
event['td_source'] = 'GG'
|
||||
if not event['GG_status'] and not event['GG_isInstance']:
|
||||
# don't need to make something... because event has been created and deleted before the synchronization
|
||||
event['td_action'] = 'None'
|
||||
event['td_comment'] = 'Nothing to do... Create and Delete directly'
|
||||
|
||||
else:
|
||||
if event['GG_isInstance']:
|
||||
if event['%s_status' % event['td_source']]:
|
||||
event['td_action'] = "EXCLUDE"
|
||||
event['td_comment'] = 'Need to create the new exclu'
|
||||
else:
|
||||
event['td_action'] = "EXCLUDE"
|
||||
event['td_comment'] = 'Need to copy and Exclude'
|
||||
else:
|
||||
event['td_action'] = "CREATE"
|
||||
event['td_comment'] = 'New EVENT CREATE from GMAIL'
|
||||
|
||||
event_to_synchronize[base_event][current_event].compute_OP()
|
||||
#print event_to_synchronize[base_event]
|
||||
#print "========================================================"
|
||||
|
||||
######################
|
||||
# DO ACTION #
|
||||
######################
|
||||
|
@ -489,105 +592,62 @@ class google_calendar(osv.AbstractModel):
|
|||
event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].iteritems(),key=operator.itemgetter(0))
|
||||
for current_event in event_to_synchronize[base_event]:
|
||||
cr.commit()
|
||||
event = current_event[1]
|
||||
#############
|
||||
### DEBUG ###
|
||||
#############
|
||||
# if event['td_action'] and event['td_action'] != 'None':
|
||||
# print " Real Event %s (%s)" % (current_event[0],event['OE_event_id'])
|
||||
# print " Found OE:%5s vs GG: %5s" % (event['OE_found'],event['GG_found'])
|
||||
# print " Recurrence OE:%5s vs GG: %5s" % (event['OE_isRecurrence'],event['GG_isRecurrence'])
|
||||
# print " Instance OE:%5s vs GG: %5s" % (event['OE_isInstance'],event['GG_isInstance'])
|
||||
# print " Synchro OE: %10s " % (event['OE_synchro'])
|
||||
# print " Update OE: %10s " % (event['OE_update'])
|
||||
# print " Update GG: %10s " % (event['GG_update'])
|
||||
# print " Status OE:%5s vs GG: %5s" % (event['OE_status'],event['GG_status'])
|
||||
# print " Action %s" % (event['td_action'])
|
||||
# print " Source %s" % (event['td_source'])
|
||||
# print " comment %s" % (event['td_comment'])
|
||||
event = current_event[1] # event is an Sync Event !
|
||||
|
||||
actToDo = event.OP
|
||||
actSrc = event.OP.src
|
||||
|
||||
context['curr_attendee'] = event.get('OE_attendee_id',False)
|
||||
# if not isinstance(actToDo, NothingToDo):
|
||||
# print event
|
||||
|
||||
context['curr_attendee'] = event.OE.attendee_id
|
||||
|
||||
actToDo = event['td_action']
|
||||
actSrc = event['td_source']
|
||||
if not actToDo:
|
||||
raise ("#!? WHAT I NEED TO DO ????")
|
||||
else:
|
||||
if actToDo == 'None':
|
||||
continue
|
||||
elif actToDo == 'CREATE':
|
||||
context_tmp = context.copy()
|
||||
context_tmp['NewMeeting'] = True
|
||||
if actSrc == 'GG':
|
||||
res = self.update_from_google(cr, uid, False, event['GG_event'], "create", context=context_tmp)
|
||||
event['OE_event_id'] = res
|
||||
meeting = calendar_event.browse(cr,uid,res,context=context)
|
||||
attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',res)], context=context)
|
||||
self.pool.get('calendar.attendee').write(cr,uid,attendee_record_id, {'oe_synchro_date':meeting.oe_update_date,'google_internal_event_id': event['GG_event']['id']},context=context_tmp)
|
||||
elif actSrc == 'OE':
|
||||
raise "Should be never here, creation for OE is done before update !"
|
||||
#TODO Add to batch
|
||||
elif actToDo == 'UPDATE':
|
||||
if actSrc == 'GG':
|
||||
self.update_from_google(cr, uid, event['OE_event'], event['GG_event'], 'write', context)
|
||||
elif actSrc == 'OE':
|
||||
self.update_to_google(cr, uid, event['OE_event'], event['GG_event'], context)
|
||||
elif actToDo == 'EXCLUDE' :
|
||||
if actSrc == 'OE':
|
||||
self.delete_an_event(cr,uid,current_event[0],context=context)
|
||||
elif actSrc == 'GG':
|
||||
new_google_event_id = event['GG_event']['id'].split('_')[1]
|
||||
if 'T' in new_google_event_id:
|
||||
new_google_event_id = new_google_event_id.replace('T','')[:-1]
|
||||
if isinstance(actToDo, NothingToDo):
|
||||
continue
|
||||
elif isinstance(actToDo, Create):
|
||||
context_tmp = context.copy()
|
||||
context_tmp['NewMeeting'] = True
|
||||
if actSrc == 'GG':
|
||||
res = self.update_from_google(cr, uid, False, event.GG.event, "create", context=context_tmp)
|
||||
event.OE.event_id = res
|
||||
meeting = calendar_event.browse(cr,uid,res,context=context)
|
||||
attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',res)], context=context)
|
||||
self.pool['calendar.attendee'].write(cr, uid, attendee_record_id, {'oe_synchro_date':meeting.oe_update_date, 'google_internal_event_id':event.GG.event['id']}, context=context_tmp)
|
||||
elif actSrc == 'OE':
|
||||
raise "Should be never here, creation for OE is done before update !"
|
||||
#TODO Add to batch
|
||||
elif isinstance(actToDo, Update):
|
||||
if actSrc == 'GG':
|
||||
self.update_from_google(cr, uid, event.OE.event, event.GG.event, 'write', context)
|
||||
elif actSrc == 'OE':
|
||||
self.update_to_google(cr, uid, event.OE.event, event.GG.event, context)
|
||||
elif isinstance(actToDo, Exclude):
|
||||
if actSrc == 'OE':
|
||||
self.delete_an_event(cr,uid,current_event[0],context=context)
|
||||
elif actSrc == 'GG':
|
||||
new_google_event_id = event.GG.event['id'].split('_')[1]
|
||||
if 'T' in new_google_event_id:
|
||||
new_google_event_id = new_google_event_id.replace('T','')[:-1]
|
||||
else:
|
||||
new_google_event_id = new_google_event_id + "000000"
|
||||
|
||||
if event.GG.status:
|
||||
parent_event = {}
|
||||
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id , new_google_event_id)
|
||||
res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
|
||||
else:
|
||||
new_google_event_id = new_google_event_id + "000000"
|
||||
|
||||
if event['GG_status']:
|
||||
parent_event = {}
|
||||
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].get('OE_event_id') , new_google_event_id)
|
||||
res = self.update_from_google(cr, uid, parent_event, event['GG_event'], "copy", context)
|
||||
else:
|
||||
if event_to_synchronize[base_event][0][1].get('OE_event_id'):
|
||||
parent_oe_id = event_to_synchronize[base_event][0][1].get('OE_event_id')
|
||||
calendar_event.unlink(cr,uid,"%s-%s" % (parent_oe_id,new_google_event_id),unlink_level=1,context=context)
|
||||
if event_to_synchronize[base_event][0][1].OE.event_id:
|
||||
parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
|
||||
calendar_event.unlink(cr,uid,"%s-%s" % (parent_oe_id,new_google_event_id),unlink_level=1,context=context)
|
||||
|
||||
elif actToDo == 'DELETE':
|
||||
if actSrc == 'GG':
|
||||
self.delete_an_event(cr,uid,current_event[0],context=context)
|
||||
elif actSrc == 'OE':
|
||||
calendar_event.unlink(cr,uid,event['OE_event_id'],unlink_level=0,context=context)
|
||||
elif isinstance(actToDo, Delete):
|
||||
if actSrc == 'GG':
|
||||
self.delete_an_event(cr,uid,current_event[0],context=context)
|
||||
elif actSrc == 'OE':
|
||||
calendar_event.unlink(cr,uid,event.OE.event_id,unlink_level=0,context=context)
|
||||
return True
|
||||
|
||||
def bind_recurring_events_to_google(self, cr, uid, context):
|
||||
calendar_event = self.pool['calendar.event']
|
||||
att_obj = self.pool.get('calendar.attendee')
|
||||
user_obj = self.pool['res.users']
|
||||
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
|
||||
|
||||
context_norecurrent = context.copy()
|
||||
context_norecurrent['virtual_id'] = False
|
||||
context_norecurrent['active_test'] = False
|
||||
|
||||
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '=', False)], context=context_norecurrent)
|
||||
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
|
||||
if att.event_id.recurrent_id and att.event_id.recurrent_id > 0:
|
||||
new_google_internal_event_id = False
|
||||
source_event_record = calendar_event.browse(cr, uid, att.event_id.recurrent_id, context)
|
||||
source_attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',source_event_record.id)], context=context)
|
||||
source_attendee_record = att_obj.browse(cr, uid, source_attendee_record_id, context)
|
||||
if source_attendee_record:
|
||||
source_attendee_record = source_attendee_record[0]
|
||||
|
||||
if att.event_id.recurrent_id_date and source_event_record.allday and source_attendee_record.google_internal_event_id:
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.split(' ')[0].replace('-','')
|
||||
elif event.recurrent_id_date and source_attendee_record.google_internal_event_id:
|
||||
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.replace('-','').replace(' ','T').replace(':','') + 'Z'
|
||||
|
||||
if new_google_internal_event_id:
|
||||
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
|
||||
res = self.update_recurrent_event_exclu(cr,uid,new_google_internal_event_id,source_attendee_record.google_internal_event_id,att.event_id,context=context)
|
||||
att_obj.write(cr, uid, [att.event_id.id], {'google_internal_event_id': new_google_internal_event_id})
|
||||
|
||||
|
||||
def check_and_sync(self, cr, uid, oe_event, google_event, context):
|
||||
if datetime.strptime(oe_event.oe_update_date,"%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'],"%Y-%m-%dT%H:%M:%S.%fz"):
|
||||
self.update_to_google(cr, uid, oe_event, google_event, context)
|
||||
|
@ -595,7 +655,7 @@ class google_calendar(osv.AbstractModel):
|
|||
self.update_from_google(cr, uid, oe_event, google_event, 'write', context)
|
||||
|
||||
def get_sequence(self,cr,uid,instance_id,context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
params = {
|
||||
'fields': 'sequence',
|
||||
|
@ -613,7 +673,7 @@ class google_calendar(osv.AbstractModel):
|
|||
#################################
|
||||
|
||||
def get_token(self,cr,uid,context=None):
|
||||
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
|
||||
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
|
||||
|
||||
if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < (datetime.now() + timedelta(minutes=1)):
|
||||
self.do_refresh_token(cr,uid,context=context)
|
||||
|
@ -622,20 +682,20 @@ class google_calendar(osv.AbstractModel):
|
|||
return current_user.google_calendar_token
|
||||
|
||||
def do_refresh_token(self,cr,uid,context=None):
|
||||
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
|
||||
gs_pool = self.pool.get('google.service')
|
||||
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
|
||||
gs_pool = self.pool['google.service']
|
||||
|
||||
refresh = current_user.google_calendar_rtoken
|
||||
all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken,self.STR_SERVICE,context=context)
|
||||
all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken, self.STR_SERVICE, context=context)
|
||||
|
||||
vals = {}
|
||||
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
|
||||
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
|
||||
|
||||
self.pool.get('res.users').write(cr,SUPERUSER_ID,uid,vals,context=context)
|
||||
self.pool['res.users'].write(cr,SUPERUSER_ID,uid,vals,context=context)
|
||||
|
||||
def need_authorize(self,cr,uid,context=None):
|
||||
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
|
||||
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
|
||||
return current_user.google_calendar_rtoken == False
|
||||
|
||||
def get_calendar_scope(self,RO=False):
|
||||
|
@ -643,22 +703,30 @@ class google_calendar(osv.AbstractModel):
|
|||
return 'https://www.googleapis.com/auth/calendar%s' % (readonly)
|
||||
|
||||
def authorize_google_uri(self,cr,uid,from_url='http://www.openerp.com',context=None):
|
||||
url = self.pool.get('google.service')._get_authorize_uri(cr,uid,from_url,self.STR_SERVICE,scope=self.get_calendar_scope(),context=context)
|
||||
url = self.pool['google.service']._get_authorize_uri(cr,uid,from_url,self.STR_SERVICE,scope=self.get_calendar_scope(),context=context)
|
||||
return url
|
||||
|
||||
def can_authorize_google(self,cr,uid,context=None):
|
||||
return self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager')
|
||||
|
||||
def set_all_tokens(self,cr,uid,authorization_code,context=None):
|
||||
gs_pool = self.pool.get('google.service')
|
||||
gs_pool = self.pool['google.service']
|
||||
all_token = gs_pool._get_google_token_json(cr, uid, authorization_code,self.STR_SERVICE,context=context)
|
||||
|
||||
vals = {}
|
||||
vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token')
|
||||
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
|
||||
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
|
||||
self.pool.get('res.users').write(cr,SUPERUSER_ID,uid,vals,context=context)
|
||||
self.pool['res.users'].write(cr,SUPERUSER_ID,uid,vals,context=context)
|
||||
|
||||
def get_start_time_to_synchro(self, cr, uid, context=None) :
|
||||
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
|
||||
number_of_week = 13
|
||||
return datetime.now()-timedelta(weeks=number_of_week)
|
||||
|
||||
def get_need_synchro_attendee(self, cr, uid, context=None):
|
||||
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
|
||||
return True
|
||||
|
||||
class res_users(osv.Model):
|
||||
_inherit = 'res.users'
|
||||
|
@ -718,7 +786,6 @@ class calendar_attendee(osv.Model):
|
|||
# If attendees are updated, we need to specify that next synchro need an action
|
||||
# Except if it come from an update_from_google
|
||||
if not context.get('curr_attendee', False) and not context.get('NewMeeting', False):
|
||||
self.pool.get('calendar.event').write(cr, uid, ref, {'oe_update_date':datetime.now()},context)
|
||||
|
||||
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)
|
||||
self.pool['calendar.event'].write(cr, uid, ref, {'oe_update_date':datetime.now()},context)
|
||||
|
||||
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)
|
123
addons/hr/hr.py
123
addons/hr/hr.py
|
@ -21,16 +21,16 @@
|
|||
|
||||
import logging
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.modules.module import get_module_resource
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class hr_employee_category(osv.osv):
|
||||
class hr_employee_category(osv.Model):
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
if not ids:
|
||||
|
@ -73,9 +73,9 @@ class hr_employee_category(osv.osv):
|
|||
]
|
||||
|
||||
|
||||
class hr_job(osv.osv):
|
||||
class hr_job(osv.Model):
|
||||
|
||||
def _no_of_employee(self, cr, uid, ids, name, args, context=None):
|
||||
def _get_nbr_employees(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
for job in self.browse(cr, uid, ids, context=context):
|
||||
nb_employees = len(job.employee_ids or [])
|
||||
|
@ -93,59 +93,81 @@ class hr_job(osv.osv):
|
|||
return res
|
||||
|
||||
_name = "hr.job"
|
||||
_description = "Job Description"
|
||||
_inherit = ['mail.thread']
|
||||
_description = "Job Position"
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
_columns = {
|
||||
'name': fields.char('Job Name', size=128, required=True, select=True),
|
||||
'expected_employees': fields.function(_no_of_employee, string='Total Forecasted Employees',
|
||||
'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees',
|
||||
help='Expected number of employees for this job position after new recruitment.',
|
||||
store = {
|
||||
'hr.job': (lambda self,cr,uid,ids,c=None: ids, ['no_of_recruitment'], 10),
|
||||
'hr.employee': (_get_job_position, ['job_id'], 10),
|
||||
}, type='integer',
|
||||
multi='no_of_employee'),
|
||||
'no_of_employee': fields.function(_no_of_employee, string="Current Number of Employees",
|
||||
multi='_get_nbr_employees'),
|
||||
'no_of_employee': fields.function(_get_nbr_employees, string="Current Number of Employees",
|
||||
help='Number of employees currently occupying this job position.',
|
||||
store = {
|
||||
'hr.employee': (_get_job_position, ['job_id'], 10),
|
||||
}, type='integer',
|
||||
multi='no_of_employee'),
|
||||
'no_of_recruitment': fields.integer('Expected in Recruitment', help='Number of new employees you expect to recruit.'),
|
||||
multi='_get_nbr_employees'),
|
||||
'no_of_recruitment': fields.integer('Expected New Employees', help='Number of new employees you expect to recruit.'),
|
||||
'no_of_hired_employee': fields.integer('Hired Employees', help='Number of hired employees for this job position during recruitment phase.'),
|
||||
'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees', groups='base.group_user'),
|
||||
'description': fields.text('Job Description'),
|
||||
'requirements': fields.text('Requirements'),
|
||||
'department_id': fields.many2one('hr.department', 'Department'),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
'state': fields.selection([('open', 'No Recruitment'), ('recruit', 'Recruitement in Progress')], 'Status', readonly=True, required=True,
|
||||
help="By default 'In position', set it to 'In Recruitment' if recruitment process is going on for this job position."),
|
||||
'state': fields.selection([('open', 'Recruitment Closed'), ('recruit', 'Recruitment in Progress')],
|
||||
string='Status', readonly=True, required=True,
|
||||
track_visibility='always',
|
||||
help="By default 'Closed', set it to 'In Recruitment' if recruitment process is going on for this job position."),
|
||||
'write_date': fields.datetime('Update Date', readonly=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=c),
|
||||
'no_of_recruitment': 0,
|
||||
'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=ctx),
|
||||
'state': 'open',
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
|
||||
('hired_employee_check', "CHECK ( no_of_hired_employee <= no_of_recruitment )", "Number of hired employee must be less than expected number of employee in recruitment."),
|
||||
]
|
||||
|
||||
|
||||
def on_change_expected_employee(self, cr, uid, ids, no_of_recruitment, no_of_employee, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
return {'value': {'expected_employees': no_of_recruitment + no_of_employee}}
|
||||
|
||||
def job_recruitement(self, cr, uid, ids, *args):
|
||||
for job in self.browse(cr, uid, ids):
|
||||
def set_recruit(self, cr, uid, ids, context=None):
|
||||
for job in self.browse(cr, uid, ids, context=context):
|
||||
no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment
|
||||
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
|
||||
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment}, context=context)
|
||||
return True
|
||||
|
||||
def job_open(self, cr, uid, ids, *args):
|
||||
self.write(cr, uid, ids, {'state': 'open', 'no_of_recruitment': 0})
|
||||
def set_open(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {
|
||||
'state': 'open',
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0
|
||||
}, context=context)
|
||||
return True
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default.update({
|
||||
'employee_ids': [],
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0,
|
||||
})
|
||||
if 'name' in default:
|
||||
job = self.browse(cr, uid, id, context=context)
|
||||
default['name'] = _("%s (copy)") % (job.name)
|
||||
return super(hr_job, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
# ----------------------------------------
|
||||
# Compatibility methods
|
||||
# ----------------------------------------
|
||||
_no_of_employee = _get_nbr_employees # v7 compatibility
|
||||
job_open = set_open # v7 compatibility
|
||||
job_recruitment = set_recruit # v7 compatibility
|
||||
|
||||
|
||||
class hr_employee(osv.osv):
|
||||
_name = "hr.employee"
|
||||
|
@ -227,27 +249,46 @@ class hr_employee(osv.osv):
|
|||
'color': 0,
|
||||
}
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
create_ctx = dict(context, mail_create_nolog=True)
|
||||
employee_id = super(hr_employee, self).create(cr, uid, data, context=create_ctx)
|
||||
def _broadcast_welcome(self, cr, uid, employee_id, context=None):
|
||||
""" Broadcast the welcome message to all users in the employee company. """
|
||||
employee = self.browse(cr, uid, employee_id, context=context)
|
||||
partner_ids = []
|
||||
_model, group_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_user')
|
||||
if employee.user_id:
|
||||
res_users = self.pool['res.users']
|
||||
# send a copy to every user of the company
|
||||
# TODO: post to the `Whole Company` mail.group when we'll be able to link to the employee record
|
||||
_model, group_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_user')
|
||||
user_ids = res_users.search(cr, uid, [('company_id', '=', employee.user_id.company_id.id),
|
||||
('groups_id', 'in', group_id)])
|
||||
partner_ids = list(set(u.partner_id.id for u in res_users.browse(cr, uid, user_ids, context=context)))
|
||||
company_id = employee.user_id.company_id.id
|
||||
elif employee.company_id:
|
||||
company_id = employee.company_id.id
|
||||
elif employee.job_id:
|
||||
company_id = employee.job_id.company_id.id
|
||||
elif employee.department_id:
|
||||
company_id = employee.department_id.company_id.id
|
||||
else:
|
||||
partner_ids = []
|
||||
self.message_post(cr, uid, [employee_id],
|
||||
company_id = self.pool['res.company']._company_default_get(cr, uid, 'hr.employee', context=context)
|
||||
res_users = self.pool['res.users']
|
||||
user_ids = res_users.search(
|
||||
cr, SUPERUSER_ID, [
|
||||
('company_id', '=', company_id),
|
||||
('groups_id', 'in', group_id)
|
||||
], context=context)
|
||||
partner_ids = list(set(u.partner_id.id for u in res_users.browse(cr, SUPERUSER_ID, user_ids, context=context)))
|
||||
self.message_post(
|
||||
cr, uid, [employee_id],
|
||||
body=_('Welcome to %s! Please help him/her take the first steps with OpenERP!') % (employee.name),
|
||||
partner_ids=partner_ids,
|
||||
subtype='mail.mt_comment', context=context
|
||||
)
|
||||
return True
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if context.get("mail_broadcast"):
|
||||
context['mail_create_nolog'] = True
|
||||
|
||||
employee_id = super(hr_employee, self).create(cr, uid, data, context=context)
|
||||
|
||||
if context.get("mail_broadcast"):
|
||||
self._broadcast_welcome(cr, uid, employee_id, context=context)
|
||||
return employee_id
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</group>
|
||||
<group string="Position">
|
||||
<field name="department_id" on_change="onchange_department_id(department_id)"/>
|
||||
<field name="job_id" options='{"no_open": True}' domain="[('state','!=','old')]" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
|
||||
<field name="job_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id"/>
|
||||
</group>
|
||||
|
@ -333,8 +333,8 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Job" version="7.0">
|
||||
<header>
|
||||
<button name="job_recruitement" string="Launch Recruitement" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="job_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_recruit" string="Launch Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
|
||||
</header>
|
||||
<sheet>
|
||||
|
@ -342,20 +342,20 @@
|
|||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" class="oe_inline"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group name="job_data">
|
||||
<field name="no_of_employee" groups="base.group_user"/>
|
||||
<field name="no_of_recruitment" on_change="on_change_expected_employee(no_of_recruitment,no_of_employee)"/>
|
||||
<field name="expected_employees" groups="base.group_user"/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
</group>
|
||||
<div class="oe_right" name="buttons"/>
|
||||
<group name="employee_data">
|
||||
<field name="department_id" class="oe_inline"/>
|
||||
<label for="no_of_employee"/>no_of_recruitment
|
||||
<div>
|
||||
<field name="no_of_employee" class="oe_inline"/>
|
||||
<p><field name="no_of_recruitment" groups="base.group_user" colspan="0" class="oe_inline" style="padding-top: 1px"/> new employee(s) expected</p>
|
||||
</div>
|
||||
</group>
|
||||
<div>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</div>
|
||||
<div>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="requirements"/>
|
||||
<field name="requirements"/>
|
||||
</div>
|
||||
|
@ -378,6 +378,7 @@
|
|||
<field name="no_of_employee"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="expected_employees"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
@ -389,34 +390,18 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Jobs">
|
||||
<field name="name" string="Job"/>
|
||||
<filter icon="terp-camera_test" domain="[('state','=','open')]" string="In Position" help="In Position"/>
|
||||
<filter icon="terp-personal+" domain="[('state','=','recruit')]" string="In Recruitment" help="In Recruitment"/>
|
||||
<filter domain="[('state','=','open')]" string="In Position"/>
|
||||
<filter domain="[('state','=','recruit')]" string="In Recruitment" name="in_recruitment"/>
|
||||
<field name="department_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
<filter string="Department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_employee_form" model="ir.ui.view">
|
||||
<field name="name">hr.job.employee.form</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Job" version="7.0">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
</group>
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_hr_job">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
|
@ -441,7 +426,6 @@
|
|||
</record>
|
||||
|
||||
<menuitem name="Recruitment" id="base.menu_crm_case_job_req_main" parent="menu_hr_root" groups="base.group_hr_user"/>
|
||||
<menuitem parent="hr.menu_hr_configuration" id="menu_hr_job" action="action_hr_job" sequence="6"/>
|
||||
|
||||
<!-- hr.department -->
|
||||
<record id="view_department_form" model="ir.ui.view">
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<group name="recruitment_grp">
|
||||
<label for="id" string="Talent Management"/>
|
||||
<div name="recruitment">
|
||||
<div>
|
||||
<div name="hr_recruitment">
|
||||
<field name="module_hr_recruitment" class="oe_inline"/>
|
||||
<label for="module_hr_recruitment"/>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
name: HR Officer
|
||||
login: hro
|
||||
password: hro
|
||||
email: hro@example.com
|
||||
-
|
||||
I added groups for HR Officer.
|
||||
-
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
- state == 'open'
|
||||
- no_of_recruitment == 0
|
||||
-
|
||||
Now, Recruitement is started so I start recruitement of Job Postion of "Developer" Profile.
|
||||
Now, Recruitment is started so I start recruitment of Job Postion of "Developer" Profile.
|
||||
-
|
||||
!python {model: hr.job}: |
|
||||
self.job_recruitement(cr, uid, [ref('job_developer')])
|
||||
self.job_recruitment(cr, uid, [ref('job_developer')])
|
||||
-
|
||||
I check 'state' and number of 'Expected in Recruitment' after initiating the recruitment
|
||||
-
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="employee_id" on_change="onchange_employee_id(employee_id)"/>
|
||||
<field name="job_id" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
|
||||
<field name="job_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type_id"/>
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * hr_gamification
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-02-13 15:10+0000\n"
|
||||
"PO-Revision-Date: 2014-02-13 15:10+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:hr.employee:0
|
||||
msgid "Grant a Badge"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.actions.act_window,help:hr_gamification.goals_menu_groupby_action2
|
||||
msgid "<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to create a goal. \n"
|
||||
" </p>\n"
|
||||
" <p>\n"
|
||||
" A goal is defined by a user and a goal type.\n"
|
||||
" Goals can be created automatically by using challenges.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.ui.menu,name:hr_gamification.menu_hr_gamification
|
||||
msgid "Engagement"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:gamification.badge.user.wizard:0
|
||||
msgid "Reward Employee with"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: code:addons/hr_gamification/wizard/grant_badge.py:45
|
||||
#, python-format
|
||||
msgid "You can send badges only to employees linked to a user."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:gamification.badge.user.wizard:0
|
||||
#: model:ir.actions.act_window,name:hr_gamification.action_reward_wizard
|
||||
msgid "Reward Employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: field:gamification.badge.user,employee_id:0
|
||||
#: field:gamification.badge.user.wizard,employee_id:0
|
||||
#: model:ir.model,name:hr_gamification.model_hr_employee
|
||||
msgid "Employee"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.model,name:hr_gamification.model_gamification_badge
|
||||
msgid "Gamification badge"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.model,name:hr_gamification.model_gamification_badge_user_wizard
|
||||
msgid "gamification.badge.user.wizard"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:hr.employee:0
|
||||
msgid "to reward this employee for a good action"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.actions.act_window,name:hr_gamification.goals_menu_groupby_action2
|
||||
#: model:ir.ui.menu,name:hr_gamification.gamification_goal_menu_hr
|
||||
msgid "Goals History"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: field:hr.employee,badge_ids:0
|
||||
msgid "Employee Badges"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: code:addons/hr_gamification/wizard/grant_badge.py:48
|
||||
#, python-format
|
||||
msgid "You can not send a badge to yourself"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:gamification.badge.user.wizard:0
|
||||
msgid "Describe what they did and why it matters (will be public)"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:hr.employee:0
|
||||
msgid "Click to grant this employee his first badge"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.actions.act_window,help:hr_gamification.challenge_list_action2
|
||||
msgid "<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to create a challenge. \n"
|
||||
" </p>\n"
|
||||
" <p>\n"
|
||||
" Assign a list of goals to chosen users to evaluate them.\n"
|
||||
" The challenge can use a period (weekly, monthly...) for automatic creation of goals.\n"
|
||||
" The goals are created for the specified users or member of the group.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:hr.employee:0
|
||||
msgid "Goals"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:gamification.badge.user.wizard:0
|
||||
msgid "What are you thank for?"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: constraint:gamification.badge.user:0
|
||||
msgid "The selected employee does not correspond to the selected user."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: field:hr.employee,has_badges:0
|
||||
msgid "Has Badges"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:hr.employee:0
|
||||
msgid "Badges are rewards of good work. Give them to people you believe deserve it."
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:hr.employee:0
|
||||
msgid "Received Badges"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: field:hr.employee,goal_ids:0
|
||||
msgid "Employee HR Goals"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.model,name:hr_gamification.model_gamification_badge_user
|
||||
msgid "Gamification user badge"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:gamification.badge:0
|
||||
msgid "Granted Employees"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.actions.act_window,name:hr_gamification.challenge_list_action2
|
||||
#: model:ir.ui.menu,name:hr_gamification.gamification_challenge_menu_hr
|
||||
msgid "Challenges"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: code:addons/hr_gamification/wizard/grant_badge.py:45
|
||||
#: code:addons/hr_gamification/wizard/grant_badge.py:48
|
||||
#, python-format
|
||||
msgid "Warning!"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:gamification.badge.user.wizard:0
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: view:gamification.badge.user.wizard:0
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#. module: hr_gamification
|
||||
#: model:ir.ui.menu,name:hr_gamification.gamification_badge_menu_hr
|
||||
msgid "Badges"
|
||||
msgstr ""
|
||||
|
|
@ -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
|
||||
|
|
|
@ -37,13 +37,14 @@ You can define the different phases of interviews and easily rate the applicant
|
|||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg'],
|
||||
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg','static/src/img/down1.png'],
|
||||
'depends': [
|
||||
'decimal_precision',
|
||||
'hr',
|
||||
'survey',
|
||||
'calendar',
|
||||
'fetchmail',
|
||||
'web_kanban_gauge',
|
||||
],
|
||||
'data': [
|
||||
'wizard/hr_recruitment_create_partner_job_view.xml',
|
||||
|
@ -58,7 +59,11 @@ You can define the different phases of interviews and easily rate the applicant
|
|||
'hr_recruitment_data.xml',
|
||||
],
|
||||
'demo': ['hr_recruitment_demo.xml'],
|
||||
'js': [
|
||||
'static/src/js/job_position.js',
|
||||
],
|
||||
'test': ['test/recruitment_process.yml'],
|
||||
'css':['static/src/css/job_position.css'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
|
|
|
@ -19,12 +19,10 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import tools
|
||||
|
||||
from datetime import datetime
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html2plaintext
|
||||
|
||||
|
||||
AVAILABLE_PRIORITIES = [
|
||||
('', ''),
|
||||
|
@ -246,7 +244,8 @@ class hr_applicant(osv.Model):
|
|||
if job_id:
|
||||
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
|
||||
department_id = job_record and job_record.department_id and job_record.department_id.id or False
|
||||
return {'value': {'department_id': department_id}}
|
||||
user_id = job_record and job_record.user_id and job_record.user_id.id or False
|
||||
return {'value': {'department_id': department_id, 'user_id': user_id}}
|
||||
|
||||
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
|
||||
if not stage_id:
|
||||
|
@ -331,19 +330,12 @@ class hr_applicant(osv.Model):
|
|||
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
|
||||
return value
|
||||
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context):
|
||||
domain = ['&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)]
|
||||
return {
|
||||
'name': _('Attachments'),
|
||||
'domain': domain,
|
||||
'res_model': 'ir.attachment',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_id': False,
|
||||
'view_mode': 'tree,form',
|
||||
'view_type': 'form',
|
||||
'limit': 80,
|
||||
'context': "{'default_res_model': '%s'}" % (self._name)
|
||||
}
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
|
||||
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
|
||||
action = self.pool.get(model).read(cr, uid, action_id, context=context)
|
||||
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
|
||||
action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)])
|
||||
return action
|
||||
|
||||
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
|
||||
recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
|
||||
|
@ -364,7 +356,7 @@ class hr_applicant(osv.Model):
|
|||
val = msg.get('from').split('<')[0]
|
||||
defaults = {
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'partner_name':val,
|
||||
'partner_name': val,
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
|
@ -378,13 +370,20 @@ class hr_applicant(osv.Model):
|
|||
def create(self, cr, uid, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
context['mail_create_nolog'] = True
|
||||
if vals.get('department_id') and not context.get('default_department_id'):
|
||||
context['default_department_id'] = vals.get('department_id')
|
||||
|
||||
if vals.get('job_id') or context.get('default_job_id'):
|
||||
job_id = vals.get('job_id') or context.get('default_job_id')
|
||||
vals.update(self.onchange_job(cr, uid, [], job_id, context=context)['value'])
|
||||
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
|
||||
applicant = self.browse(cr, uid, obj_id, context=context)
|
||||
if applicant.job_id:
|
||||
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('Applicant <b>created</b>'), subtype="hr_recruitment.mt_job_new_applicant", context=context)
|
||||
name = applicant.partner_name if applicant.partner_name else applicant.name
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [applicant.job_id.id],
|
||||
body=_('New application from %s') % name,
|
||||
subtype="hr_recruitment.mt_job_applicant_new", context=context)
|
||||
return obj_id
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
|
@ -404,6 +403,15 @@ class hr_applicant(osv.Model):
|
|||
else:
|
||||
res = super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
# post processing: if job changed, post a message on the job
|
||||
if vals.get('job_id'):
|
||||
for applicant in self.browse(cr, uid, ids, context=None):
|
||||
name = applicant.partner_name if applicant.partner_name else applicant.name
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [vals['job_id']],
|
||||
body=_('New application from %s') % name,
|
||||
subtype="hr_recruitment.mt_job_applicant_new", context=context)
|
||||
|
||||
# post processing: if stage changed, post a message in the chatter
|
||||
if vals.get('stage_id'):
|
||||
stage = self.pool['hr.recruitment.stage'].browse(cr, uid, vals['stage_id'], context=context)
|
||||
|
@ -444,7 +452,8 @@ class hr_applicant(osv.Model):
|
|||
address_id = self.pool.get('res.partner').address_get(cr, uid, [applicant.partner_id.id], ['contact'])['contact']
|
||||
contact_name = self.pool.get('res.partner').name_get(cr, uid, [applicant.partner_id.id])[0][1]
|
||||
if applicant.job_id and (applicant.partner_name or contact_name):
|
||||
applicant.job_id.write({'no_of_recruitment': applicant.job_id.no_of_recruitment - 1})
|
||||
applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1}, context=context)
|
||||
create_ctx = dict(context, mail_broadcast=True)
|
||||
emp_id = hr_employee.create(cr, uid, {'name': applicant.partner_name or contact_name,
|
||||
'job_id': applicant.job_id.id,
|
||||
'address_home_id': address_id,
|
||||
|
@ -452,8 +461,12 @@ class hr_applicant(osv.Model):
|
|||
'address_id': applicant.company_id and applicant.company_id.partner_id and applicant.company_id.partner_id.id or False,
|
||||
'work_email': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.email or False,
|
||||
'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False,
|
||||
})
|
||||
}, context=create_ctx)
|
||||
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
|
||||
self.pool['hr.job'].message_post(
|
||||
cr, uid, [applicant.job_id.id],
|
||||
body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
|
||||
subtype="hr_recruitment.mt_job_applicant_hired", context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Warning!'), _('You must define an Applied Job and a Contact Name for this applicant.'))
|
||||
|
||||
|
@ -490,16 +503,37 @@ class hr_job(osv.osv):
|
|||
_inherit = "hr.job"
|
||||
_name = "hr.job"
|
||||
_inherits = {'mail.alias': 'alias_id'}
|
||||
|
||||
def _get_attached_docs(self, cr, uid, ids, field_name, arg, context=None):
|
||||
res = {}
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
for job_id in ids:
|
||||
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', '=', job_id)], context=context)
|
||||
res[job_id] = attachment_obj.search(
|
||||
cr, uid, [
|
||||
'|',
|
||||
'&', ('res_model', '=', 'hr.job'), ('res_id', '=', job_id),
|
||||
'&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)
|
||||
], context=context)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"),
|
||||
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True,
|
||||
help="Email alias for this job position. New emails will automatically "
|
||||
"create new applicants for this job position."),
|
||||
'address_id': fields.many2one('res.partner', 'Job Location', help="Address where employees are working"),
|
||||
'application_ids': fields.one2many('hr.applicant', 'job_id', 'Applications'),
|
||||
'manager_id': fields.related('department_id', 'manager_id', type='many2one', string='Department Manager', relation='hr.employee', readonly=True, store=True),
|
||||
'document_ids': fields.function(_get_attached_docs, type='one2many', relation='ir.attachment', string='Applications'),
|
||||
'user_id': fields.many2one('res.users', 'Recruitment Responsible', track_visibility='onchange'),
|
||||
'color': fields.integer('Color Index'),
|
||||
}
|
||||
|
||||
def _address_get(self, cr, uid, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return user.company_id.partner_id.id
|
||||
|
||||
_defaults = {
|
||||
'address_id': _address_get
|
||||
}
|
||||
|
@ -541,6 +575,18 @@ class hr_job(osv.osv):
|
|||
'nodestroy': True,
|
||||
}
|
||||
|
||||
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
|
||||
#open attachments of job and related applicantions.
|
||||
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
|
||||
action = self.pool.get(model).read(cr, uid, action_id, context=context)
|
||||
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', 'in', ids)], context=context)
|
||||
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
|
||||
action['domain'] = str(['|', '&', ('res_model', '=', 'hr.job'), ('res_id', 'in', ids), '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)])
|
||||
return action
|
||||
|
||||
def action_set_no_of_recruitment(self, cr, uid, id, value, context=None):
|
||||
return self.write(cr, uid, [id], {'no_of_recruitment': value}, context=context)
|
||||
|
||||
|
||||
class applicant_category(osv.osv):
|
||||
""" Category of applicant """
|
||||
|
|
|
@ -495,13 +495,9 @@
|
|||
<field name="alias_name">jobs</field>
|
||||
<field name="alias_model_id" ref="model_hr_applicant"/>
|
||||
<field name="alias_user_id" ref="base.user_root"/>
|
||||
<field name="alias_parent_model_id" ref="model_hr_job"/>
|
||||
</record>
|
||||
|
||||
<!-- Job-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_job_new_applicant" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
</record>
|
||||
<!-- Applicant-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">New Applicant</field>
|
||||
|
@ -515,12 +511,35 @@
|
|||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<record id="mt_applicant_employee" model="mail.message.subtype">
|
||||
<record id="mt_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Applicant hired</field>
|
||||
</record>
|
||||
<!-- Job-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_job_applicant_new" model="mail.message.subtype">
|
||||
<field name="name">Applicant Created</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_new')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_stage_changed" model="mail.message.subtype">
|
||||
<field name="name">Applicant Stage Changed</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_stage_changed')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
<record id="mt_job_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" eval="ref('mt_applicant_hired')"/>
|
||||
<field name="relation_field">job_id</field>
|
||||
</record>
|
||||
|
||||
<!-- Applicant Categories(Tag) -->
|
||||
<record id="tag_applicant_reserve" model="hr.applicant_category">
|
||||
<field name="name">Reserve</field>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
######################## JOB OPPORTUNITIES (menu) ###########################
|
||||
<record model="ir.actions.act_window" id="crm_case_categ0_act_job">
|
||||
<field name="name">Applications</field>
|
||||
|
@ -9,16 +8,15 @@
|
|||
<field name="view_mode">kanban,tree,form,graph,calendar</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="view_crm_case_jobs_filter"/>
|
||||
<field name="context">{'empty_list_help_model': 'hr.job'}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new job applicant.
|
||||
<p>
|
||||
OpenERP helps you track applicants in the recruitment
|
||||
process and follow up all operations: meetings, interviews, etc.
|
||||
</p><p>
|
||||
OpenERP helps you track applicants in the recruitment process
|
||||
and follow up all operations: meetings, interviews, etc.
|
||||
Candidates and their cv's are automatically created when they
|
||||
apply for a job. If you install the document management modules,
|
||||
all resumes are indexed automatically, so that you can easily
|
||||
search through their content in the recruitment menu.
|
||||
Applicants and their attached CV are created automatically when an email is sent.
|
||||
If you install the document management modules, all resumes are indexed automatically,
|
||||
so that you can easily search through their content.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -58,15 +56,10 @@
|
|||
sequence="1"/>
|
||||
|
||||
<!-- ALL JOBS REQUESTS -->
|
||||
|
||||
<menuitem parent="base.menu_crm_case_job_req_main" id="hr.menu_hr_job_position" action="action_hr_job" sequence="1"/>
|
||||
<menuitem
|
||||
name="Applications"
|
||||
parent="base.menu_crm_case_job_req_main"
|
||||
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="1"/>
|
||||
|
||||
|
||||
<menuitem parent="hr.menu_hr_configuration" id="hr.menu_hr_job" action="hr.action_hr_job" sequence="2"/>
|
||||
|
||||
|
||||
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="2"/>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</record>
|
||||
|
||||
|
||||
<!-- Jobs -->
|
||||
<!-- Applicants -->
|
||||
<record model="ir.ui.view" id="crm_case_tree_view_job">
|
||||
<field name="name">Applicants</field>
|
||||
<field name="model">hr.applicant</field>
|
||||
|
@ -91,10 +91,10 @@
|
|||
<label for="partner_name" class="oe_edit_only"/>
|
||||
<h2 style="display: inline-block;">
|
||||
<field name="partner_name" class="oe_inline"/>
|
||||
<button string="Create Employee" name="create_employee_from_applicant" type="object"
|
||||
class="oe_link oe_inline" style="margin-left: 8px;"
|
||||
attrs="{'invisible': [('emp_id', '!=', False)]}"/>
|
||||
</h2>
|
||||
<button string="Create Employee" name="create_employee_from_applicant" type="object"
|
||||
class="oe_link oe_inline" style="margin-left: 8px;"
|
||||
attrs="{'invisible': [('emp_id', '!=', False)]}"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
|
@ -307,46 +307,190 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- HR Job -->
|
||||
<record model="ir.actions.act_window" id="action_hr_job_applications">
|
||||
<field name="name">Applications</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="view_mode">kanban,tree,form,graph,calendar</field>
|
||||
<field name="context">{'search_default_job_id': [active_id], 'default_job_id': active_id, 'empty_list_help_model': 'hr.job'}</field>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
OpenERP helps you track applicants in the recruitment
|
||||
process and follow up all operations: meetings, interviews, etc.
|
||||
</p><p>
|
||||
Applicants and their attached CV are created automatically when an email is sent.
|
||||
If you install the document management modules, all resumes are indexed automatically,
|
||||
so that you can easily search through their content.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Jobs -->
|
||||
<record id="view_job_filter_recruitment" model="ir.ui.view">
|
||||
<field name="name">Job</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_job_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="department_id" positon="after">
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_job_survey" model="ir.ui.view">
|
||||
<field name="name">hr.job.form1</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="job_data" position="inside">
|
||||
<group name="employee_data" position="inside">
|
||||
<label for="survey_id" groups="base.group_user"/>
|
||||
<div groups="base.group_user">
|
||||
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
|
||||
<button string="Print Interview" name="action_print_survey" type="object" attrs="{'invisible':[('survey_id','=',False)]}" class="oe_inline oe_link"/>
|
||||
</div>
|
||||
<label for="address_id"/>
|
||||
<div>
|
||||
<field name="address_id" context="{'show_address': 1}"/>
|
||||
<span class="oe_grey">(empty = remote work)</span>
|
||||
</div>
|
||||
</group>
|
||||
<field name="expected_employees" position="after">
|
||||
<label for="survey_id" groups="base.group_user"/>
|
||||
<div groups="base.group_user">
|
||||
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
|
||||
<button class="oe_inline"
|
||||
string="Interview"
|
||||
name="action_print_survey" type="object"
|
||||
attrs="{'invisible':[('survey_id','=',False)]}"/>
|
||||
</div>
|
||||
</field>
|
||||
<xpath expr="//group[@name='job_data']" position="after">
|
||||
<group name="group_alias"
|
||||
attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_name" string="Email Alias"/>
|
||||
<div name="alias_def">
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<label for="alias_name" string="Specific Email Address" attrs="{'invisible': [('alias_domain', '=', False)]}" help ="Define a specific contact address for this job position. If you keep it empty, the default email address will be used which is in human resources settings"/>
|
||||
<div name="alias_def" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
string="Email Alias" required="0"/>
|
||||
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="alias_contact" class="oe_inline" string="Accept Emails From"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<field name="user_id" class="oe_inline"/>
|
||||
</xpath>
|
||||
<div name="buttons" position="inside">
|
||||
<button string="Applications" name="%(action_hr_job_applications)d" context="{'default_user_id': user_id}" type="action"/>
|
||||
<button string="Documents" name="action_get_attachment_tree_view" type="object"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_kanban" model="ir.ui.view">
|
||||
<field name="name">hr.job.kanban</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban version="7.0" class="oe_background_grey">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="color"/>
|
||||
<field name="application_ids"/>
|
||||
<field name="document_ids"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="survey_id"/>
|
||||
<field name="state"/>
|
||||
<field name="user_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_job oe_kanban_card oe_kanban_global_click">
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban oe_custom">
|
||||
<span class="oe_e">í</span>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<t t-if="widget.view.is_action_enabled('edit')">
|
||||
<li><a type="edit">Edit...</a></li>
|
||||
</t>
|
||||
<t t-if="widget.view.is_action_enabled('delete')">
|
||||
<li><a type="delete">Delete</a></li>
|
||||
</t>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class = "oe_kanban_content">
|
||||
<t t-if="record.user_id.raw_value">
|
||||
<img t-att-src="kanban_image('res.users', 'image_medium', record.user_id.raw_value[0])" t-att-title="record.user_id.value" class="oe_kanban_avatar oe_job_avatar"/>
|
||||
</t>
|
||||
<t t-if="record.user_id.raw_value === false">
|
||||
<img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_avatar oe_job_avatar"/>
|
||||
</t>
|
||||
<div class="oe_job_detail">
|
||||
<div class="oe_job oe_name oe_kanban_ellipsis">
|
||||
<field name="name"/>
|
||||
</div>
|
||||
<div class="oe_job oe_department oe_kanban_ellipsis">
|
||||
<field name="department_id"/>
|
||||
<span t-if="record.manager_id.value" class="oe_manager_name">
|
||||
(<t t-esc="record.manager_id.value"/>)
|
||||
</span>
|
||||
</div>
|
||||
<div class="oe_job_alias oe_kanban_ellipsis" t-if=" record.alias_id.value and record.state.raw_value == 'recruit'">
|
||||
<span class="oe_e">%%</span><small><field name="alias_id"/></small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<t t-if="record.state.raw_value == 'recruit'">
|
||||
<div class="oe_applications">
|
||||
<a name="%(action_hr_job_applications)d" type="action">
|
||||
<span t-if="record.application_ids.raw_value.length gt 1"><t t-esc="record.application_ids.raw_value.length"/> Applications</span>
|
||||
<span t-if="record.application_ids.raw_value.length lt 2"><t t-esc="record.application_ids.raw_value.length"/> Application</span>
|
||||
</a>
|
||||
<br/>
|
||||
<a t-if="record.document_ids.raw_value.length gt 0" name="action_get_attachment_tree_view" type="object">
|
||||
<span t-if="record.document_ids.raw_value.length gt 1"><t t-esc="record.document_ids.raw_value.length"/> Documents</span>
|
||||
<span t-if="record.document_ids.raw_value.length lt 2"><t t-esc="record.document_ids.raw_value.length"/> Document</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oe_job_justgage">
|
||||
<field state="recruit" name="no_of_hired_employee" widget="gauge"
|
||||
style="width:160px; height: 120px;"
|
||||
options="{
|
||||
'max_field': 'no_of_recruitment',
|
||||
'label': 'Hired Employees',
|
||||
'on_change': 'action_set_no_of_recruitment',
|
||||
'on_click_label': 'employee(s) to recruit',
|
||||
'force_set': False,
|
||||
'gauge_value_field': 'no_of_recruitment',
|
||||
}">
|
||||
Hired Employees
|
||||
</field>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="record.state.raw_value == 'open'">
|
||||
<div class="oe_start_recruitment">
|
||||
<p><b>click here</b>, To start the recruitment</p>
|
||||
<img src="/hr_recruitment/static/src/img/down1.png"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="oe_launch_recruitment">
|
||||
<a t-if="record.state.raw_value == 'open'" data-name="job_recruitment" data-type="object" class="oe_kanban_action">Launch Recruitment</a>
|
||||
<a t-if="record.state.raw_value == 'recruit'" data-name="job_open" data-type="object" class="oe_kanban_action">Recruitment Done</a>
|
||||
<a t-if="record.survey_id.raw_value"> | </a>
|
||||
<a t-if="record.survey_id.raw_value" data-name="action_print_survey" data-type="object" class="oe_kanban_action">Print Interview</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- hr related job position menu action -->
|
||||
<record model="ir.actions.act_window" id="action_hr_job">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{'search_default_in_recruitment': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click here to create a new job or remove the filter on "In Recruitment" to recruit for an on hold job.
|
||||
</p>
|
||||
<p>
|
||||
Define job position profile and manage recruitment in a context of a particular job: print interview survey, define number of expected new employees, and manage its recruitment pipe
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Stage Tree View -->
|
||||
<record model="ir.ui.view" id="hr_recruitment_stage_tree">
|
||||
<field name="name">hr.recruitment.stage.tree</field>
|
||||
|
|
|
@ -8,14 +8,14 @@ msgstr ""
|
|||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
|
||||
"PO-Revision-Date: 2012-02-11 15:26+0000\n"
|
||||
"Last-Translator: 开阖软件 Jeff Wang <jeff@osbzr.com>\n"
|
||||
"PO-Revision-Date: 2014-02-20 15:53+0000\n"
|
||||
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
|
||||
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-01-28 06:29+0000\n"
|
||||
"X-Generator: Launchpad (build 16914)\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-21 05:48+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: help:hr.applicant,active:0
|
||||
|
@ -33,24 +33,24 @@ msgstr "必备条件"
|
|||
#. module: hr_recruitment
|
||||
#: view:hr.applicant:0
|
||||
msgid "Application Summary"
|
||||
msgstr ""
|
||||
msgstr "申请摘要"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: view:hr.applicant:0
|
||||
msgid "Start Interview"
|
||||
msgstr ""
|
||||
msgstr "开始面试"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: view:hr.applicant:0
|
||||
msgid "Mobile:"
|
||||
msgstr ""
|
||||
msgstr "手机:"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: help:hr.recruitment.stage,fold:0
|
||||
msgid ""
|
||||
"This stage is not visible, for example in status bar or kanban view, when "
|
||||
"there are no records in that stage to display."
|
||||
msgstr ""
|
||||
msgstr "此阶段是不可见的。例如:状态栏或看板视图中,在该阶段中 不存在可显示的记录。"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: model:hr.recruitment.degree,name:hr_recruitment.degree_graduate
|
||||
|
@ -99,7 +99,7 @@ msgstr "暂停的职位"
|
|||
#: view:hr.applicant:0
|
||||
#: field:hr.applicant,message_unread:0
|
||||
msgid "Unread Messages"
|
||||
msgstr ""
|
||||
msgstr "未读消息"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: field:hr.applicant,company_id:0
|
||||
|
@ -157,7 +157,7 @@ msgstr ""
|
|||
#: model:ir.actions.act_window,name:hr_recruitment.crm_case_categ0_act_job
|
||||
#: model:ir.ui.menu,name:hr_recruitment.menu_crm_case_categ0_act_job
|
||||
msgid "Applications"
|
||||
msgstr ""
|
||||
msgstr "求职申请"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: field:hr.applicant,day_open:0
|
||||
|
@ -167,12 +167,12 @@ msgstr "开启天数"
|
|||
#. module: hr_recruitment
|
||||
#: field:hr.applicant,emp_id:0
|
||||
msgid "employee"
|
||||
msgstr ""
|
||||
msgstr "雇员"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: field:hr.config.settings,fetchmail_applicants:0
|
||||
msgid "Create applicants from an incoming email account"
|
||||
msgstr ""
|
||||
msgstr "从电子邮件帐号接收的邮件创建求职申请"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: view:hr.recruitment.report:0
|
||||
|
@ -184,7 +184,7 @@ msgstr "天数"
|
|||
#: view:hr.recruitment.partner.create:0
|
||||
#: model:ir.actions.act_window,name:hr_recruitment.action_hr_recruitment_partner_create
|
||||
msgid "Create Contact"
|
||||
msgstr ""
|
||||
msgstr "新建联系人"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: view:hr.applicant:0
|
||||
|
@ -808,7 +808,7 @@ msgstr "生效"
|
|||
#: view:hr.recruitment.report:0
|
||||
#: field:hr.recruitment.report,nbr:0
|
||||
msgid "# of Applications"
|
||||
msgstr ""
|
||||
msgstr "个申请"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: model:ir.actions.act_window,help:hr_recruitment.hr_recruitment_stage_act
|
||||
|
@ -1251,7 +1251,7 @@ msgstr "开始"
|
|||
#. module: hr_recruitment
|
||||
#: view:board.board:0
|
||||
msgid "Applications to be Processed"
|
||||
msgstr ""
|
||||
msgstr "待处理的申请"
|
||||
|
||||
#. module: hr_recruitment
|
||||
#: view:hr.applicant:0
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
||||
# Copyright (C) 2004-Today OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -19,9 +19,11 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
class hr_applicant_settings(osv.osv_memory):
|
||||
|
||||
class hr_applicant_settings(osv.TransientModel):
|
||||
_name = 'hr.config.settings'
|
||||
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
|
||||
|
||||
|
@ -32,6 +34,44 @@ class hr_applicant_settings(osv.osv_memory):
|
|||
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
|
||||
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
|
||||
help='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
|
||||
'and create automatically application documents in the system.'),
|
||||
'and create automatically application documents in the system.',
|
||||
deprecated='Will be removed with OpenERP v8, not applicable anymore. Use aliases instead.'),
|
||||
'alias_prefix': fields.char('Default Alias Name for Jobs'),
|
||||
'alias_domain': fields.char('Alias Domain'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'alias_domain': lambda self, cr, uid, context: self.pool['mail.alias']._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
|
||||
}
|
||||
|
||||
def _find_default_job_alias_id(self, cr, uid, context=None):
|
||||
alias_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'hr_recruitment.mail_alias_jobs')
|
||||
if not alias_id:
|
||||
alias_ids = self.pool['mail.alias'].search(
|
||||
cr, uid, [
|
||||
('alias_model_id.model', '=', 'hr.applicant'),
|
||||
('alias_force_thread_id', '=', False),
|
||||
('alias_parent_model_id.model', '=', 'hr.job'),
|
||||
('alias_parent_thread_id', '=', False),
|
||||
('alias_defaults', '=', '{}')
|
||||
], context=context)
|
||||
alias_id = alias_ids and alias_ids[0] or False
|
||||
return alias_id
|
||||
|
||||
def get_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
alias_name = False
|
||||
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
|
||||
if alias_id:
|
||||
alias_name = self.pool['mail.alias'].browse(cr, uid, alias_id, context=context).alias_name
|
||||
return {'alias_prefix': alias_name}
|
||||
|
||||
def set_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
|
||||
if not alias_id:
|
||||
create_ctx = dict(context, alias_model_name='hr.applicant', alias_parent_model_name='hr.job')
|
||||
alias_id = self.pool['mail.alias'].create(cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx)
|
||||
else:
|
||||
mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context)
|
||||
return True
|
||||
|
|
|
@ -19,6 +19,14 @@
|
|||
<label for="module_document"/>
|
||||
</div>
|
||||
</div>
|
||||
<xpath expr="//div[@name='hr_recruitment']" position="after">
|
||||
<div attrs="{'invisible': ['|',('module_hr_recruitment','=',False),('alias_domain', '=', False)]}">
|
||||
<label string="Default job email address"/>
|
||||
<field name="alias_prefix" class="oe_inline" attrs="{'required': [('alias_domain', '!=', False)]}"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
job_position.css: job_position.sass
|
||||
sass --trace -t expanded job_position.sass job_position.css
|
|
@ -0,0 +1,86 @@
|
|||
.openerp .oe_kanban_job{
|
||||
width: 355px;
|
||||
min-height: 165px !important;
|
||||
}
|
||||
.openerp .oe_job_alias{
|
||||
margin: 3px;
|
||||
}
|
||||
.openerp .oe_job_detail{
|
||||
height: 70px;
|
||||
width: 308px
|
||||
}
|
||||
.openerp .oe_job_alias .oe_e {
|
||||
font-size: 30px;
|
||||
line-height: 6px;
|
||||
vertical-align: top;
|
||||
margin-right: 3px;
|
||||
color: white;
|
||||
text-shadow: 0px 0px 2px black;
|
||||
float: left;
|
||||
}
|
||||
.openerp .oe_job {
|
||||
font-size: 112%;
|
||||
position: inline;
|
||||
margin: 3px 3px;
|
||||
color: #4c4c4c;
|
||||
height: 16px;
|
||||
}
|
||||
.openerp img.oe_job_avatar {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 295px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.openerp .oe_launch_recruitment{
|
||||
float: left;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: 10px;
|
||||
}
|
||||
.openerp div.oe_applications {
|
||||
position: absolute;;
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.openerp .oe_applications > a > span:hover{
|
||||
margin: 4px 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.openerp .oe_name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.openerp .oe_manager_name {
|
||||
width: 135px;
|
||||
font-size: 11px;
|
||||
color: gray;
|
||||
}
|
||||
.openerp .oe_job_justgage {
|
||||
float: right;
|
||||
margin-top: -40px;
|
||||
margin-right: -58px;
|
||||
width:200px;
|
||||
height:130px;
|
||||
}
|
||||
.openerp .oe_department {
|
||||
width: 350px;
|
||||
}
|
||||
.openerp .oe_start_recruitment {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.openerp .oe_start_recruitment p {
|
||||
font-size: 14px;
|
||||
color: gray;
|
||||
padding-left: 50px;
|
||||
}
|
||||
.openerp .oe_start_recruitment img {
|
||||
margin-top: -22px;
|
||||
width: 32px;
|
||||
height: 34px;
|
||||
float: left;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.openerp .oe_job_messages{
|
||||
margin-top: 40px !important;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
.openerp
|
||||
.oe_kanban_job
|
||||
width: 355px
|
||||
min-height: 165px !important
|
||||
.oe_job_alias
|
||||
margin: 3px
|
||||
.oe_job_detail
|
||||
height: 70px
|
||||
width: 308px
|
||||
.oe_job_alias .oe_e
|
||||
font-size: 30px
|
||||
line-height: 6px
|
||||
vertical-align: top
|
||||
margin-right: 3px
|
||||
color: white
|
||||
text-shadow: 0px 0px 2px black
|
||||
float: left
|
||||
.oe_job
|
||||
font-size: 112%
|
||||
position: inline
|
||||
margin: 3px 3px
|
||||
color: #4c4c4c
|
||||
height: 16px
|
||||
img.oe_job_avatar
|
||||
position: absolute
|
||||
width: 24px
|
||||
height: 24px
|
||||
margin-left: 295px
|
||||
margin-top: -5px
|
||||
.oe_launch_recruitment
|
||||
float: left
|
||||
position: absolute
|
||||
bottom: 3px
|
||||
left: 10px
|
||||
div.oe_applications
|
||||
position: absolute
|
||||
margin-top: 16px
|
||||
font-size: 14px
|
||||
.oe_applications > a > span:hover
|
||||
margin: 4px 0
|
||||
text-decoration: underline
|
||||
.oe_name
|
||||
font-size: 14px
|
||||
font-weight: bold
|
||||
.oe_manager_name
|
||||
width: 135px
|
||||
font-size: 11px
|
||||
color: gray
|
||||
.oe_job_justgage
|
||||
float: right
|
||||
margin-top: -40px
|
||||
margin-right: -58px
|
||||
width: 200px
|
||||
height: 130px
|
||||
.oe_department
|
||||
width: 350px
|
||||
.oe_start_recruitment
|
||||
padding-top: 10px
|
||||
p
|
||||
font-size: 14px
|
||||
color: gray
|
||||
padding-left: 50px
|
||||
img
|
||||
margin-top: -22px
|
||||
width: 32px
|
||||
height: 34px
|
||||
float: left
|
||||
padding-left: 12px
|
||||
.oe_job_messages
|
||||
margin-top: 40px !important
|
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1,13 @@
|
|||
openerp.hr_recruitment = function (openerp) {
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'hr.job') {
|
||||
this.$('.oe_applications a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
|
@ -7,14 +7,14 @@ msgstr ""
|
|||
"Project-Id-Version: OpenERP Server 6.0dev\n"
|
||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
|
||||
"PO-Revision-Date: 2012-11-01 08:13+0000\n"
|
||||
"Last-Translator: 盈通 ccdos <ccdos@intoerp.com>\n"
|
||||
"PO-Revision-Date: 2014-02-20 15:57+0000\n"
|
||||
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-01-28 06:13+0000\n"
|
||||
"X-Generator: Launchpad (build 16914)\n"
|
||||
"X-Launchpad-Export-Date: 2014-02-21 05:48+0000\n"
|
||||
"X-Generator: Launchpad (build 16916)\n"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: field:hr.analytic.timesheet,sheet_id:0
|
||||
|
@ -33,7 +33,7 @@ msgstr "服务"
|
|||
#: field:hr.timesheet.report,quantity:0
|
||||
#: field:timesheet.report,quantity:0
|
||||
msgid "Time"
|
||||
msgstr ""
|
||||
msgstr "时间"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: help:hr.config.settings,timesheet_max_difference:0
|
||||
|
@ -92,7 +92,7 @@ msgstr "# 成本"
|
|||
#. module: hr_timesheet_sheet
|
||||
#: field:hr_timesheet_sheet.sheet,message_unread:0
|
||||
msgid "Unread Messages"
|
||||
msgstr ""
|
||||
msgstr "未读消息"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: view:hr.timesheet.report:0
|
||||
|
@ -133,7 +133,7 @@ msgstr "日期到"
|
|||
#. module: hr_timesheet_sheet
|
||||
#: view:hr_timesheet_sheet.sheet:0
|
||||
msgid "to"
|
||||
msgstr ""
|
||||
msgstr "到"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: model:process.node,note:hr_timesheet_sheet.process_node_invoiceonwork0
|
||||
|
@ -145,7 +145,7 @@ msgstr "根据计工单"
|
|||
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:398
|
||||
#, python-format
|
||||
msgid "You cannot modify an entry in a confirmed timesheet."
|
||||
msgstr ""
|
||||
msgstr "你不能修改已确认的时间表条目"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: view:hr.timesheet.report:0
|
||||
|
@ -188,13 +188,13 @@ msgstr "拒绝"
|
|||
#: view:hr_timesheet_sheet.sheet:0
|
||||
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet
|
||||
msgid "Timesheet Activities"
|
||||
msgstr ""
|
||||
msgstr "时间表活动"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:38
|
||||
#, python-format
|
||||
msgid "Please create an employee and associate it with this user."
|
||||
msgstr ""
|
||||
msgstr "请创建员工信息并关联到此用户"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:402
|
||||
|
@ -202,7 +202,7 @@ msgstr ""
|
|||
#, python-format
|
||||
msgid ""
|
||||
"You cannot enter an attendance date outside the current timesheet dates."
|
||||
msgstr ""
|
||||
msgstr "无法录入当前时间表以外的出勤日期"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:205
|
||||
|
@ -226,7 +226,7 @@ msgstr ""
|
|||
#. module: hr_timesheet_sheet
|
||||
#: field:hr_timesheet_sheet.sheet,message_ids:0
|
||||
msgid "Messages"
|
||||
msgstr ""
|
||||
msgstr "消息"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: help:hr_timesheet_sheet.sheet,state:0
|
||||
|
@ -252,7 +252,7 @@ msgstr ""
|
|||
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:38
|
||||
#, python-format
|
||||
msgid "Error!"
|
||||
msgstr ""
|
||||
msgstr "错误!"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: field:hr.config.settings,timesheet_max_difference:0
|
||||
|
@ -307,7 +307,7 @@ msgstr "员工计工单"
|
|||
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:215
|
||||
#, python-format
|
||||
msgid "Invalid Action!"
|
||||
msgstr ""
|
||||
msgstr "无效的动作!"
|
||||
|
||||
#. module: hr_timesheet_sheet
|
||||
#: view:hr.timesheet.report:0
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import commands
|
||||
import logging
|
||||
import simplejson
|
||||
import os
|
||||
|
@ -11,7 +12,7 @@ import math
|
|||
import md5
|
||||
import openerp.addons.hw_proxy.controllers.main as hw_proxy
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock
|
||||
from Queue import Queue, Empty
|
||||
|
||||
try:
|
||||
|
@ -39,6 +40,7 @@ class EscposDriver(Thread):
|
|||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.queue = Queue()
|
||||
self.lock = Lock()
|
||||
self.status = {'status':'connecting', 'messages':[]}
|
||||
|
||||
def connected_usb_devices(self):
|
||||
|
@ -47,6 +49,13 @@ class EscposDriver(Thread):
|
|||
if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
|
||||
connected.append(device)
|
||||
return connected
|
||||
|
||||
def lockedstart(self):
|
||||
self.lock.acquire()
|
||||
if not self.isAlive():
|
||||
self.daemon = True
|
||||
self.start()
|
||||
self.lock.release()
|
||||
|
||||
def get_escpos_printer(self):
|
||||
try:
|
||||
|
@ -86,7 +95,9 @@ class EscposDriver(Thread):
|
|||
_logger.warning('ESC/POS Device Disconnected: '+message)
|
||||
|
||||
def run(self):
|
||||
self.queue = Queue()
|
||||
if not escpos:
|
||||
_logger.error('ESC/POS cannot initialize, please verify system dependencies.')
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
timestamp, task, data = self.queue.get(True)
|
||||
|
@ -105,6 +116,8 @@ class EscposDriver(Thread):
|
|||
elif task == 'cashbox':
|
||||
if timestamp >= time.time() - 12:
|
||||
self.open_cashbox(printer)
|
||||
elif task == 'printstatus':
|
||||
self.print_status(printer)
|
||||
elif task == 'status':
|
||||
pass
|
||||
|
||||
|
@ -113,10 +126,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 +168,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 +190,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 +279,8 @@ class EscposDriver(Thread):
|
|||
driver = EscposDriver()
|
||||
|
||||
hw_proxy.drivers['escpos'] = driver
|
||||
|
||||
driver.push_task('printstatus')
|
||||
|
||||
class EscposProxy(hw_proxy.Proxy):
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
|
@ -0,0 +1,548 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="1600px"
|
||||
height="1200px"
|
||||
id="svg2985"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
sodipodi:docname="posbox_doc_schema.svg"
|
||||
inkscape:export-filename="/home/fva/Code/openerp/posbox_doc_schema.png"
|
||||
inkscape:export-xdpi="67.5"
|
||||
inkscape:export-ydpi="67.5">
|
||||
<defs
|
||||
id="defs2987">
|
||||
<marker
|
||||
inkscape:stockid="DotL"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="DotL"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path6730"
|
||||
d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
|
||||
transform="scale(0.8) translate(7.4, 1)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="DotM"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="DotM"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path6733"
|
||||
d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
|
||||
transform="scale(0.4) translate(7.4, 1)" />
|
||||
</marker>
|
||||
<pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#Strips1_3"
|
||||
id="pattern6512"
|
||||
patternTransform="matrix(1.4070053,-1.4070053,10.196067,10.196067,0,0)" />
|
||||
<pattern
|
||||
inkscape:stockid="Stripes 1:3"
|
||||
id="Strips1_3"
|
||||
patternTransform="translate(0,0) scale(10,10)"
|
||||
height="1"
|
||||
width="4"
|
||||
patternUnits="userSpaceOnUse"
|
||||
inkscape:collect="always">
|
||||
<rect
|
||||
id="rect4622"
|
||||
height="2"
|
||||
width="1"
|
||||
y="-0.5"
|
||||
x="0"
|
||||
style="fill:black;stroke:none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.45254834"
|
||||
inkscape:cx="679.01909"
|
||||
inkscape:cy="813.9637"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata2990">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<g
|
||||
id="g7313">
|
||||
<g
|
||||
transform="translate(0,35.355339)"
|
||||
id="g6566">
|
||||
<g
|
||||
id="g6514"
|
||||
transform="translate(-10.9375,-57.8125)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6489"
|
||||
d="m 905,179.59375 -34.8125,31.34375 -118.78125,0 c -5.54,0 -10,4.46 -10,10 l 0,102.5 -157.5,0 c -16.62,0 -30,13.38 -30,30 l 0,632.3125 -27.4375,24.9375 33.25,44.2813 c 0.10609,0.1442 0.2039,0.2952 0.3125,0.4374 l 2.125,2.8126 0.21875,-0.125 c 5.44486,5.6076 13.06148,9.0937 21.53125,9.0937 l 463.43755,0 c 16.6199,0 30,-13.38 30,-30 l 0,-683.75 c 0,-10.90688 -5.7475,-20.40588 -14.4063,-25.65625 l -41.125,-31 -27.3125,26.65625 -45.28125,0 0,-102.5 c 0,-2.89877 -1.22955,-5.48931 -3.1875,-7.3125 l 0.25,-0.375 L 905,179.59375 z"
|
||||
style="opacity:0.25;fill:url(#pattern6512);fill-opacity:1;stroke:none" />
|
||||
<g
|
||||
transform="translate(-2.2097087,75.130096)"
|
||||
id="g3823">
|
||||
<g
|
||||
id="g3847">
|
||||
<rect
|
||||
ry="5.274591"
|
||||
y="106.16312"
|
||||
x="711.8349"
|
||||
height="151.06096"
|
||||
width="194.27489"
|
||||
id="rect3843"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8.48901749;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
rx="5.274591" />
|
||||
<rect
|
||||
rx="5.274591"
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3845"
|
||||
width="175.41872"
|
||||
height="151.06096"
|
||||
x="721.263"
|
||||
y="106.16312"
|
||||
ry="5.274591" />
|
||||
</g>
|
||||
<rect
|
||||
rx="5.274591"
|
||||
ry="5.274591"
|
||||
y="869.83844"
|
||||
x="746.01172"
|
||||
height="110.99157"
|
||||
width="131.81378"
|
||||
id="rect3765"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8.48901749;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8.48901749;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect6618"
|
||||
width="125.18465"
|
||||
height="53.539146"
|
||||
x="938.25635"
|
||||
y="571.52777"
|
||||
ry="5.274591"
|
||||
rx="5.274591" />
|
||||
<rect
|
||||
rx="34.723557"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect2993"
|
||||
width="509.11688"
|
||||
height="730.677"
|
||||
x="525.61603"
|
||||
y="217.12157"
|
||||
ry="34.723557" />
|
||||
<rect
|
||||
rx="21.787001"
|
||||
ry="21.787001"
|
||||
y="235.37366"
|
||||
x="543.0672"
|
||||
height="694.12708"
|
||||
width="474.21454"
|
||||
id="rect3763"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425566;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425554;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3767"
|
||||
width="160.64433"
|
||||
height="9.1270761"
|
||||
x="562.84052"
|
||||
y="740.37366"
|
||||
rx="5.3000002"
|
||||
ry="5.3000002" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999997"
|
||||
y="667.30597"
|
||||
x="828.00549"
|
||||
height="9.1270761"
|
||||
width="73.434502"
|
||||
id="rect3769"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="313.75256"
|
||||
x="693.65527"
|
||||
height="9.1270761"
|
||||
width="160.64433"
|
||||
id="rect3771"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425554;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<g
|
||||
id="g3783">
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="245.39903"
|
||||
x="962.35583"
|
||||
height="271.93509"
|
||||
width="43.971706"
|
||||
id="rect3773"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425554;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="374.20874"
|
||||
x="952.03491"
|
||||
height="7.0710678"
|
||||
width="21.213203"
|
||||
id="rect3775"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3777"
|
||||
width="21.213203"
|
||||
height="7.0710678"
|
||||
x="992.86823"
|
||||
y="373.3754"
|
||||
rx="5.3000002"
|
||||
ry="5.3000002" />
|
||||
<rect
|
||||
transform="matrix(0,-1,1,0,0,0)"
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="981.60596"
|
||||
x="-260.01752"
|
||||
height="7.0710678"
|
||||
width="21.213203"
|
||||
id="rect3779"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3781"
|
||||
width="21.213203"
|
||||
height="7.0710678"
|
||||
x="-526.6842"
|
||||
y="981.60596"
|
||||
rx="5.3000002"
|
||||
ry="5.3000002"
|
||||
transform="matrix(0,-1,1,0,0,0)" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="m 958.1875,822.25 c -9.01707,3.59482 -15.375,12.41301 -15.375,22.71875 0,13.50563 10.93187,24.46875 24.4375,24.46875 13.50563,0 24.46875,-10.96312 24.46875,-24.46875 0,-10.17788 -6.2141,-18.91637 -15.0625,-22.59375 l 0,8.8125 c 4.63325,2.9517 7.6875,8.02222 7.6875,13.78125 0,9.11223 -7.65608,16.5 -17.09375,16.5 -9.43767,0 -17.0625,-7.38777 -17.0625,-16.5 0,-5.88681 3.20248,-11.04837 8,-13.96875 l 0,-8.75 z"
|
||||
id="path3792" />
|
||||
<g
|
||||
id="g3818"
|
||||
transform="translate(0,-1.767767)">
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999992"
|
||||
y="879.66095"
|
||||
x="943.13324"
|
||||
height="8.7558908"
|
||||
width="46.827415"
|
||||
id="rect3794"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3798"
|
||||
width="46.827415"
|
||||
height="8.7558908"
|
||||
x="943.13324"
|
||||
y="895.68982"
|
||||
rx="5.2999992"
|
||||
ry="5.3000002" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999992"
|
||||
y="910.22754"
|
||||
x="943.13324"
|
||||
height="8.7558908"
|
||||
width="46.827415"
|
||||
id="rect3800"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999992"
|
||||
y="963.05292"
|
||||
x="-845.58075"
|
||||
height="8.7558908"
|
||||
width="32.390652"
|
||||
id="rect3812"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.99400008;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
transform="matrix(0,-1,1,0,0,0)" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g6559"
|
||||
transform="translate(0,-7.7339804)">
|
||||
<path
|
||||
transform="matrix(1,0,0,0.85333333,0,117.03125)"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
sodipodi:ry="10.546875"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:cx="1110.1562"
|
||||
id="path6539"
|
||||
style="fill:#00fd28;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:#ff0d0d;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path6541"
|
||||
sodipodi:cx="1110.1562"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:ry="10.546875"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
transform="matrix(1,0,0,0.85333333,0,141.44531)" />
|
||||
<path
|
||||
transform="matrix(1,0,0,0.85333333,0,165.85937)"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
sodipodi:ry="10.546875"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:cx="1110.1562"
|
||||
id="path6543"
|
||||
style="fill:#00fd28;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:#00fd28;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path6545"
|
||||
sodipodi:cx="1110.1562"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:ry="10.546875"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
transform="matrix(1,0,0,0.85333333,0,190.27344)" />
|
||||
<path
|
||||
transform="matrix(1,0,0,0.85333333,0,214.6875)"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
sodipodi:ry="10.546875"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:cx="1110.1562"
|
||||
id="path6547"
|
||||
style="fill:#fff90d;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
</g>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6598"
|
||||
y="178.8759"
|
||||
x="514.01447"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="178.8759"
|
||||
x="514.01447"
|
||||
id="tspan6600"
|
||||
sodipodi:role="line">2A Power</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="724.2699"
|
||||
y="127.21583"
|
||||
id="text6602"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6604"
|
||||
x="724.2699"
|
||||
y="127.21583"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">SD Card</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6606"
|
||||
y="626.60999"
|
||||
x="319.89322"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="626.60999"
|
||||
x="319.89322"
|
||||
id="tspan6608"
|
||||
sodipodi:role="line">HDMI</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="761.83496"
|
||||
y="1097.278"
|
||||
id="text6610"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6612"
|
||||
x="761.83496"
|
||||
y="1097.278"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">USB</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6614"
|
||||
y="1097.278"
|
||||
x="596.10681"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="1097.278"
|
||||
x="596.10681"
|
||||
id="tspan6616"
|
||||
sodipodi:role="line">RJ45</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="1135.8894"
|
||||
y="660.90717"
|
||||
id="text6620"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6622"
|
||||
x="1135.8894"
|
||||
y="660.90717"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">Composite</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="774.18842"
|
||||
x="1135.8894"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="774.18842"
|
||||
x="1135.8894"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">Audio</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="1135.8894"
|
||||
y="774.18842"
|
||||
id="text6628"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6630"
|
||||
x="1135.8894"
|
||||
y="774.18842"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">Audio</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6632"
|
||||
y="891.37592"
|
||||
x="1212.4519"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="891.37592"
|
||||
x="1212.4519"
|
||||
id="tspan6634"
|
||||
sodipodi:role="line">Ready</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="1212.4519"
|
||||
y="940.45667"
|
||||
id="text6636"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6638"
|
||||
x="1212.4519"
|
||||
y="940.45667"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">Power</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6640"
|
||||
y="986.97278"
|
||||
x="1212.4519"
|
||||
style="font-size:36.98903275px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="986.97278"
|
||||
x="1212.4519"
|
||||
id="tspan6642"
|
||||
sodipodi:role="line">Network</tspan></text>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6663"
|
||||
d="m 437.5,614.15728 54.6875,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 601.86625,199.79103 0,54.6875"
|
||||
id="path7295"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7297"
|
||||
d="m 1113.1075,650.87603 -54.6875,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1113.1075,761.81353 -76.5625,0"
|
||||
id="path7299"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1192.657,876.26632 -54.6875,0"
|
||||
id="path7301"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7303"
|
||||
d="m 1192.657,927.08963 -54.6875,-27.62136"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1192.657,974.04593 -54.6875,0"
|
||||
id="path7305"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7307"
|
||||
d="m 1192.657,974.04593 -54.6875,-24.85922"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1192.657,974.04593 -54.6875,-49.16602"
|
||||
id="path7309"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 630.03792,1053.883 0,-32.0313"
|
||||
id="path7311"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,452 @@
|
|||
====================
|
||||
PosBox Documentation
|
||||
====================
|
||||
|
||||
Posbox Setup Guide
|
||||
==================
|
||||
|
||||
.. image:: _images/posbox_setup.png
|
||||
:width: 100%
|
||||
:align: center
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Before you start setting up your PosPox make sure you have everything.
|
||||
You will need :
|
||||
|
||||
- The PosBox
|
||||
- A 2A Power adapter
|
||||
- A computer or tablet with an up to date Firefox, Chrome or Safari web
|
||||
browser (we recommend Chrome)
|
||||
- A running SaaS or V8.0 OpenERP Instance with the Point of Sale
|
||||
Installed.
|
||||
- A local network set up with DHCP ( this is the default setting )
|
||||
- A RJ45 Ethrnet Cable
|
||||
- An Epson USB TM-T20 Printer or another compatible printer.
|
||||
- A Honeywell Eclipse USB Barcode Scanner or another compatible scanner.
|
||||
- An Epson compatible cash drawer.
|
||||
|
||||
Step By Step Setup Guide
|
||||
------------------------
|
||||
|
||||
.. image:: _images/posbox_doc_schema.png
|
||||
:width: 100%
|
||||
:align: center
|
||||
|
||||
Power the PosBox.
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Plug the PosBox to the 2A Power Adapter, a bright red status led should
|
||||
light up.
|
||||
|
||||
Connect it to the Local Network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Connect the PosBox to the Local Network with the RJ45 Cable. Make sure
|
||||
You connect the PosBox to the same Network as your POS device. Once
|
||||
connected a bright yellow status led should light up, and a green status
|
||||
led should flash.
|
||||
|
||||
Connect the USB Printer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Put a paper roll in the printer, power it up, then connect it to one of
|
||||
the PosBox's USB port
|
||||
|
||||
Connect the cash drawer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The cash drawer should be connected to the printer with the RJ25 cable
|
||||
|
||||
Connect the USB Barcode Scanner
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Connect the usb barcode scanner to one of the PosBox's USB port. The
|
||||
Barcode Scanner should immediately light up and emit a loud beep.
|
||||
|
||||
Configure the USB Barcode Scanner
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The barcode scanner should be configured in QWERTY and emit a new line /
|
||||
return symbol after each scan. This is most likely the default
|
||||
configuration of your barcode scanner.
|
||||
|
||||
Make sure the PosBox is ready
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once powered, The PosBox needs less than a minute to boot. Once the
|
||||
PosBox is ready, it should print a status receipt with its IP Adress.
|
||||
Also, the last status led, just after the red power status led, should
|
||||
be permanently lit green.
|
||||
|
||||
Setup the Point of Sale
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Make sure to activate the 'Print via Proxy', 'Remote Scanning' or
|
||||
'Cashdrawer' options in the Point of Sale configuration. If you are
|
||||
using firefox, you must manually specify the PosBox's IP address which
|
||||
was printed on the status receipt.
|
||||
|
||||
If you are running multiple Point of Sales on the same PosBox, make sure
|
||||
that only one of them has Remote Scanning activated.
|
||||
|
||||
If you manually specify the PosBox's IP address, you must configure your
|
||||
network to make sure the PosBox's IP address doesn't change. Please
|
||||
refer to your Router documentation.
|
||||
|
||||
Launch the Point of Sale.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you didn't spcecify the PosBox's IP address in the config, the POS
|
||||
will need some time to perform a network scan to find the PosBox. This
|
||||
is only done once.
|
||||
|
||||
The Point of Sale is now connected to the PosBox and your Hardware
|
||||
should be ready to use.
|
||||
|
||||
Multi-Pos Configuration
|
||||
-----------------------
|
||||
The advised way to setup a multi Point-of-Sale shop is to have one PosBox
|
||||
per Point-of-Sale. In this case it is mandatory to manually specify the
|
||||
IP address of each PosBox in each Point of Sale. You must also configure
|
||||
your network to make sure the PosBox's IP address doesn't change. Please
|
||||
refer to your Router documentation.
|
||||
|
||||
PosBoxless Setup Guide
|
||||
======================
|
||||
|
||||
.. image:: _images/posboxless_setup.png
|
||||
:width: 100%
|
||||
:align: center
|
||||
|
||||
If you are running your Point of Sale on a debian-based linux
|
||||
distribution, you do not need the PosBox as you can run its software
|
||||
locally. However the installation process is not foolproof. You'll need
|
||||
at least to know how to install and run openerp. You may also run into
|
||||
issues specific to your distribution or to your particular setup and
|
||||
hardware configuration.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
- A debian based linux distribution (Debian, Ubuntu, Mint, etc.)
|
||||
- A running OpenERP server (version trunk or saas-3 or 8.0 or later)
|
||||
- You must uninstall any esc-pos printer driver as it will conflict
|
||||
with OpenERP's built-in driver.
|
||||
|
||||
Step By Step Setup Guide
|
||||
------------------------
|
||||
|
||||
Extra dependencies
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The driver modules requires the installation of new python modules:
|
||||
|
||||
::
|
||||
|
||||
$ sudo pip install pyserial
|
||||
$ sudo pip install --pre pyusb
|
||||
|
||||
Database Setup
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You must create a database called ``posbox`` with the modules
|
||||
``hw_proxy``, ``hw_escpos``, and ``hw_scanner`` installed.
|
||||
|
||||
Access Rights
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The drivers need raw access to the printer and barcode scanner devices.
|
||||
Doing so requires a bit system administration. First we are going to
|
||||
create a group that has haccess to usb devices:
|
||||
|
||||
::
|
||||
|
||||
$ sudo groupadd usbusers
|
||||
|
||||
Then we add the user who will run the OpenERP server to ``usbusers``
|
||||
|
||||
::
|
||||
|
||||
$ sudo useradd -G usbusers USERNAME
|
||||
|
||||
Then we need to create a udev rule that will automatically allow members
|
||||
of ``usbusers`` to access raw usb devices. To do so create a file called
|
||||
``99-usbusers.rule`` in the ``/etc/udev/rules.d/`` directory with the
|
||||
following content:
|
||||
|
||||
::
|
||||
|
||||
SUBSYSTEM=="usb", GROUP="usbusers", MODE="0660"
|
||||
SUBSYSTEMS=="usb", GROUP="usbusers", MODE="0660"
|
||||
|
||||
Then you need to reboot your machine.
|
||||
|
||||
Start the local OpenERP Installl
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We must launch the OpenERP server on the port ``8069`` with the correct
|
||||
database settings:
|
||||
|
||||
::
|
||||
|
||||
$ ./server/openerp-server --addons-path=addons,web/addons --db-filter='^posbox$' \
|
||||
--xmlrpc-port=8069 -d posbox
|
||||
|
||||
Check that everything works
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Plug all your hardware to your machine's USB ports, and go to
|
||||
``http://localhost/hw_proxy/status`` refresh the page a few times and
|
||||
see if all your devices are indicated as *Connected*. Possible source of
|
||||
errors are: The paths on the distribution differ from the paths expected
|
||||
by the drivers, another process has grabbed exclusive access to the
|
||||
devices, the udev rules do not apply or a superceeded by others.
|
||||
|
||||
Automatically Start OpenERP
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You must now make sure that this OpenERP install is automatically
|
||||
started after boot. There are various ways to do so, and how to do it
|
||||
depends on your particular setup. We use *systemd* on the PosBox, but
|
||||
*upstart* or *sysvinit* are other options.
|
||||
|
||||
Setup the Point of Sale
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The IP Adress field in the POS Config must be either ``127.0.0.1``
|
||||
or ``localhost``. You can also leave it empty.
|
||||
|
||||
PosBox Technical Documentation
|
||||
==============================
|
||||
|
||||
Technical Overview
|
||||
------------------
|
||||
|
||||
The PosBox Hardware
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The PosBox's Hardware is based on a model B Raspberry Pi, a popular
|
||||
open-source micro-computer. The Raspberry Pi is powered with a 2A
|
||||
micro-usb power adapter. 2A is needed to give enough power to the
|
||||
barcode scanners. And we recommend Samsung power adapters for their
|
||||
availability and reliability ( but beware of counterfeits ). The
|
||||
Raspberry is protected by a ModMyPi Raspberry Pi Case. The Software is
|
||||
installed on a 8Gb Class 10 or Higher SD Card. The SD Card's class is
|
||||
important to ensure good performances. All this hardware is easily
|
||||
available worldwide from independant vendors.
|
||||
|
||||
Compatible Peripherals
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Printers:
|
||||
|
||||
- Epson TM-T20
|
||||
- Support for Other Esc-Pos compatible printers can be easily added
|
||||
on a case-by-case basis, please see the *Unsupported Printers*
|
||||
chapter of this manual.
|
||||
|
||||
- Barcode Scannners:
|
||||
|
||||
- Metapace S61
|
||||
- Honeywell Eclipse or Voyager 95x0 Series.
|
||||
- Most other barcode scanners should work out of the box. Some
|
||||
barcode scanners need more power than the PosBox can provide and
|
||||
must be plugged in a self-powered USB HUB. This is the case for
|
||||
Datalogic Barcode Scanners.
|
||||
|
||||
- Cash Drawers:
|
||||
|
||||
- As the cash drawers are connected to the printer, All Epson
|
||||
compatible cash drawers should work out of the Box.
|
||||
|
||||
The PosBox Software
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The PosBox runs a Raspbian Linux distribution, a Debian derivative
|
||||
optimized for the Raspberry Pi. It also runs a barebones install of
|
||||
OpenERP which provides the webserver and the drivers. The printer &
|
||||
scanner drivers are implemented as openerp modules. Those modules are
|
||||
named ``hw_proxy``, ``hw_escpos``, ``hw_scanner`` and are the only
|
||||
modules installed and running. OpenERP is only used for the framework it
|
||||
provides. No business data is processed or stored on the PosBox. The
|
||||
OpenERP install is a full bazaar clone of the ``trunk`` branch, and can
|
||||
thus be updated trough the usual means.
|
||||
|
||||
We use systemd to manage the Openerp server. Systemd makes sure OpenERP
|
||||
starts up at boot and is always up and running. Its systemd unit file is
|
||||
called ``openerp.service``, and can be found in
|
||||
``/etc/systemd/system/openerp.service``. The systemd version used is
|
||||
quite old and thus ``journalctl`` is not available. All logs can be
|
||||
found in instead ``/var/logs/syslog``
|
||||
|
||||
We removed all graphical software from the default install to reduce to
|
||||
image size but nothing prevents you from reinstalling them.
|
||||
|
||||
Accessing the PosBox
|
||||
--------------------
|
||||
|
||||
Local Access
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If you plug a QWERTY USB keyboard into one of the PosBox's USB ports,
|
||||
and if you connect a computer monitor to the *HDMI* port of the PosBox,
|
||||
you can use it as a small UNIX computer and perform various
|
||||
administration tasks.
|
||||
|
||||
Once the PosBox is ready press ``ALT-F2`` to access the login prompt.
|
||||
The login is ``pi`` and the password ``admin``. The OpenERP install is
|
||||
in the ``~/openerp`` directory along with a few scripts to help with
|
||||
debugging and administration.
|
||||
|
||||
Remote Access
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
If you have the PosBox's IP address and a SSH client you can access the
|
||||
PosBox's system remotely. The login / password are ``pi``/``admin``
|
||||
|
||||
Getting Unsupported Printers to Work
|
||||
------------------------------------
|
||||
|
||||
The PosBox should be able to print to any ESC-POS printer, not just the
|
||||
Epson TM-T20. If You have such a printer, you can activate it with the
|
||||
following steps:
|
||||
|
||||
- Get local or remote access to the PosBox.
|
||||
- Plug in your printer
|
||||
- type ``lsusb`` in a prompt
|
||||
- Find your printer in the list of connected USB devices
|
||||
- Find your printer's vendor id: It consists of two hexadecimal numbers
|
||||
separated by a colon.
|
||||
- Edit ``~/openerp/addons/hw_escpos/escpos/supported_devices.py`` and
|
||||
add an entry for your printer.
|
||||
- Restar The PosBox.
|
||||
- If everything works properly you can send your printer's name and
|
||||
vendor ID to ``support@openerp.com`` and we'll add it to the list of
|
||||
supported devices.
|
||||
|
||||
Updating The PosBox Software
|
||||
----------------------------
|
||||
|
||||
The best way to update the PosBox software is to download a new version
|
||||
of the image and flash the SD-Card with it. This operation is described
|
||||
in details on the following tutorial
|
||||
``http://elinux.org/RPi_Easy_SD_Card_Setup``, just replace the standard
|
||||
raspberry pi image to the latest one found at
|
||||
``http://nightly.openerp.com/trunk/posbox/``
|
||||
|
||||
Troubleshoot
|
||||
============
|
||||
|
||||
The POS cannot connect to the PosBox.
|
||||
-------------------------------------
|
||||
|
||||
- The easiest way to make sure the PosBox is properly set-up is to turn
|
||||
it on with the printer plugged in as it will print a receipt
|
||||
indicating any error if encountered or the PosBox's IP adress in case
|
||||
of success. If no receipt is printed, check the following steps:
|
||||
- Make sure the PosBox is powered on, indicated by a brightly lid red
|
||||
status LED.
|
||||
- Make sure the PosBox is ready, this is indicated by a brightly lid
|
||||
green status LED just above the red power status LED. The PosBox
|
||||
should be ready one minute after it is powered on.
|
||||
- Make sure the PosBox is connected to the Network. This is indicated
|
||||
by a brightly lid yellow status LED.
|
||||
- Make sure the PosBox is connected to the same network as your POS
|
||||
device. Both the device and the posbox should be visible in the list
|
||||
of connected devices on your network router.
|
||||
- Make sure that your LAN is set up with DHCP, and gives ip Address in
|
||||
the range 192.168.0.X, 192.168.1.X, 10.0.0.X. If you cannot setup
|
||||
your LAN that way, you must manually set up your PosBox's ip-address.
|
||||
See the relevant paragraph in the Setup chapter of this documentation
|
||||
- If you have specified the PosBox's IP address in the config, make
|
||||
sure it correspond to the printed on the PosBox's status receipt.
|
||||
- Make sure that the POS is not loaded over HTTPS.
|
||||
- A bug in Firefox's HTTP implementation prevents the autodiscovery
|
||||
from working reliably. When using Firefox you should manually set up
|
||||
the PosBox's ip address in the POS config.
|
||||
|
||||
The Barcode Scanner is not working
|
||||
----------------------------------
|
||||
|
||||
- The PosBox needs a 2A power supply to work with some barcode
|
||||
scanners. If you are not using the provided power supply, make sure
|
||||
the one you use has enough power.
|
||||
- Some barcode scanners will need more than 2A and will not work, or
|
||||
will work unreliably, even with the provided power supply. In those
|
||||
case you can plug the barcode scanner in a self-powered USB Hub.
|
||||
- Some poorly built barcode scanners do not advertise themselves as
|
||||
barcode scanners but as a usb keyboard instead, and will not be
|
||||
recognized by the PosBox.
|
||||
- The barcode scanner must be configured in US QWERTY and emit a linefeed
|
||||
after each codebar.
|
||||
|
||||
The Barcode Scanner is not working reliably
|
||||
-------------------------------------------
|
||||
|
||||
- Make sure that no more than one device with 'Scan via Proxy' enabled
|
||||
are connected to the PosBox at the same time.
|
||||
|
||||
Printing the receipt takes too much time.
|
||||
-----------------------------------------
|
||||
|
||||
- A small delay before the first print is expected, as the PosBox will
|
||||
do some preprocessing to speed up the next printings. If you suffer
|
||||
delays afterwards it is most likely due to poor network connection
|
||||
between the POS and the PosBox.
|
||||
|
||||
Some characters are not correctly printed on the receipt.
|
||||
---------------------------------------------------------
|
||||
|
||||
- The PosBox does not support all languages and characters. It
|
||||
currently supports latin and cyrillic based scripts, with basic
|
||||
japanese support.
|
||||
|
||||
The Printer is Offline
|
||||
----------------------
|
||||
|
||||
- The PosBox only supports EPSON TM-T20 printers. Make sure the printer
|
||||
is connected, powered, has enough paper and has its lid closed, and
|
||||
does is not in an error status. If the error persists, please contact
|
||||
support.
|
||||
|
||||
The Cashdrawer does not open.
|
||||
-----------------------------
|
||||
|
||||
- The cashdrawer should be connected to the printer and should be
|
||||
activated in the POS Configuration
|
||||
|
||||
Credits
|
||||
=======
|
||||
The PosBox project was developped by Frédéric van der Essen with the
|
||||
kind help of Gary Malherbe, Fabien Meghazi, Nicolas Wisniewsky,
|
||||
Dimitri Del Marmol and Antony Lesuisse.
|
||||
|
||||
This development would not have been possible without the Indiegogo
|
||||
campaign and those who contributed to it. Special thanks goes to the
|
||||
partners who backed the campaign with founding partner bundles:
|
||||
|
||||
- Camptocamp
|
||||
- BHC
|
||||
- openBig
|
||||
- Eeezee-IT
|
||||
- Solarsis LDA
|
||||
- ACSONE
|
||||
- Vauxoo
|
||||
- Ekomurz
|
||||
- Datalp
|
||||
- Dao Systems
|
||||
- Eggs Solutions
|
||||
- OpusVL
|
||||
|
||||
And also the partners who've backed the development with the Founding
|
||||
PosBox Bundle:
|
||||
|
||||
- Willow IT
|
||||
- E\. Akhalwaya & Sons
|
||||
- Multibase
|
||||
- Mindesa
|
||||
- bpso.biz
|
||||
- Shine IT.
|
||||
|
Binary file not shown.
|
@ -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:
|
||||
|
@ -124,10 +129,6 @@ class Scanner(Thread):
|
|||
self.set_status('error',str(e))
|
||||
return None
|
||||
|
||||
@http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='none', cors='*')
|
||||
def is_scanner_connected(self):
|
||||
return self.get_device() != None
|
||||
|
||||
def get_barcode(self):
|
||||
""" Returns a scanned barcode. Will wait at most 5 seconds to get a barcode, and will
|
||||
return barcode scanned in the past if they are not older than 5 seconds and have not
|
||||
|
@ -135,6 +136,8 @@ class Scanner(Thread):
|
|||
busy reading another barcode
|
||||
"""
|
||||
|
||||
self.lockedstart()
|
||||
|
||||
while True:
|
||||
try:
|
||||
timestamp, barcode = self.barcodes.get(True, 5)
|
||||
|
@ -144,8 +147,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 +211,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
|
|
|
@ -54,6 +54,7 @@
|
|||
<!-- Discussion subtype for messaging / Chatter -->
|
||||
<record id="mt_comment" model="mail.message.subtype">
|
||||
<field name="name">Discussions</field>
|
||||
<field name="sequence" eval="0"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -154,6 +154,12 @@ class mail_alias(osv.Model):
|
|||
sequence = (sequence + 1) if sequence else 2
|
||||
return new_name
|
||||
|
||||
def _clean_and_make_unique(self, cr, uid, name, context=None):
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
name = remove_accents(name).lower().split('@')[0]
|
||||
name = re.sub(r'[^\w+.]+', '-', name)
|
||||
return self._find_unique(cr, uid, name, context=context)
|
||||
|
||||
def migrate_to_alias(self, cr, child_model_name, child_table_name, child_model_auto_init_fct,
|
||||
alias_model_name, alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={},
|
||||
alias_generate_name=False, context=None):
|
||||
|
@ -199,7 +205,7 @@ class mail_alias(osv.Model):
|
|||
alias_vals['alias_parent_thread_id'] = obj_data['id']
|
||||
alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
|
||||
alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
|
||||
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id})
|
||||
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id}, context={'mail_notrack': True})
|
||||
_logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])
|
||||
|
||||
# Finally attempt to reinstate the missing constraint
|
||||
|
@ -227,11 +233,7 @@ class mail_alias(osv.Model):
|
|||
model_name = context.get('alias_model_name')
|
||||
parent_model_name = context.get('alias_parent_model_name')
|
||||
if vals.get('alias_name'):
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
|
||||
alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
|
||||
alias_name = self._find_unique(cr, uid, alias_name, context=context)
|
||||
vals['alias_name'] = alias_name
|
||||
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
|
||||
if model_name:
|
||||
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0]
|
||||
vals['alias_model_id'] = model_id
|
||||
|
@ -240,6 +242,12 @@ class mail_alias(osv.Model):
|
|||
vals['alias_parent_model_id'] = model_id
|
||||
return super(mail_alias, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
""""give uniqe alias name if given alias name is allready assigned"""
|
||||
if vals.get('alias_name'):
|
||||
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
|
||||
return super(mail_alias, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def open_document(self, cr, uid, ids, context=None):
|
||||
alias = self.browse(cr, uid, ids, context=context)[0]
|
||||
if not alias.alias_model_id or not alias.alias_force_thread_id:
|
||||
|
|
|
@ -29,6 +29,8 @@ class mail_message_subtype(osv.osv):
|
|||
on the Wall. """
|
||||
_name = 'mail.message.subtype'
|
||||
_description = 'Message subtypes'
|
||||
_order = 'sequence, id'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Message Type', required=True, translate=True,
|
||||
help='Message subtype gives a more precise type on the message, '\
|
||||
|
@ -50,7 +52,11 @@ class mail_message_subtype(osv.osv):
|
|||
help="Model the subtype applies to. If False, this subtype applies to all models."),
|
||||
'default': fields.boolean('Default',
|
||||
help="Activated by default when subscribing."),
|
||||
'sequence': fields.integer('Sequence', help="Used to order subtypes."),
|
||||
'hidden': fields.boolean('Hidden', help="Hide the subtype in the follower options")
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'default': True,
|
||||
'sequence': 1,
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<tree string="Subtype">
|
||||
<field name="name"/>
|
||||
<field name="sequence"/>
|
||||
<field name="res_model"/>
|
||||
<field name="default"/>
|
||||
</tree>
|
||||
|
@ -22,12 +23,18 @@
|
|||
<form string="Email message" version="7.0">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="res_model"/>
|
||||
<field name="default"/>
|
||||
<field name="description"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="relation_field"/>
|
||||
<group string='Description'>
|
||||
<field name="name"/>
|
||||
<field name="sequence"/>
|
||||
<field name="res_model"/>
|
||||
<field name="description"/>
|
||||
<field name="default"/>
|
||||
<field name="hidden"/>
|
||||
</group>
|
||||
<group string='Auto subscription'>
|
||||
<field name="parent_id"/>
|
||||
<field name="relation_field"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue