[MERGE] merged with trunk

bzr revid: qdp-launchpad@openerp.com-20131230102802-y1ux8470wxnj4yfr
This commit is contained in:
Quentin (OpenERP) 2013-12-30 11:28:02 +01:00
commit f5718efa6e
138 changed files with 17954 additions and 1304 deletions

View File

@ -3,9 +3,15 @@
<data noupdate="1">
<record id="analytic_journal_sale" model="account.analytic.journal">
<field name="code">SAL</field>
<field name="name">Sales</field>
<field name="type">sale</field>
</record>
<record id="exp" model="account.analytic.journal">
<field name="code">PUR</field>
<field name="name">Purchases</field>
<field name="type">purchase</field>
</record>
<!--
Payment term

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-07-02 06:32+0000\n"
"PO-Revision-Date: 2013-12-19 08:06+0000\n"
"Last-Translator: Chertykov Denis <chertykov@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: 2013-09-12 05:32+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-20 05:11+0000\n"
"X-Generator: Launchpad (build 16872)\n"
#. module: account
#: model:process.transition,name:account.process_transition_supplierreconcilepaid0
@ -184,6 +184,13 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" Щелкните, чтобы добавить отчетный период.\n"
" </p><p>\n"
" Отчетный период как правило - месяц или квартал .\n"
" Обычно он соответствует периодам налоговой декларации.\n"
" </p>\n"
" "
#. module: account
#: model:ir.actions.act_window,name:account.action_view_created_invoice_dashboard

View File

@ -6,11 +6,6 @@
<field name="name">Sales</field>
<field name="type">sale</field>
</record>
<record id="exp" model="account.analytic.journal">
<field name="code">PUR</field>
<field name="name">Purchases</field>
<field name="type">purchase</field>
</record>
<record id="sit" model="account.analytic.journal">
<field name="code">START</field>
<field name="name">Miscellaneous Operation</field>

View File

@ -0,0 +1,23 @@
# Spanish (Peru) translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-12-11 21:56+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Spanish (Peru) <es_PE@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: 2013-12-12 05:15+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#. module: account_accountant
#: model:ir.actions.client,name:account_accountant.action_client_account_menu
msgid "Open Accounting Menu"
msgstr ""

View File

@ -450,6 +450,7 @@ class account_analytic_account(osv.osv):
'is_overdue_quantity' : fields.function(_is_overdue_quantity, method=True, type='boolean', string='Overdue Quantity',
store={
'account.analytic.line' : (_get_analytic_account, None, 20),
'account.analytic.account': (lambda self, cr, uid, ids, c=None: ids, ['quantity_max'], 10),
}),
'ca_invoiced': fields.function(_ca_invoiced_calc, type='float', string='Invoiced Amount',
help="Total customer invoiced amount for this account.",

View File

@ -7,19 +7,19 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2011-01-11 11:14+0000\n"
"PO-Revision-Date: 2012-08-19 10:21+0000\n"
"Last-Translator: Eric Huang <eh@cenoq.com>\n"
"PO-Revision-Date: 2013-12-29 09:12+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 06:05+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-30 04:39+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: account_chart
#: model:ir.module.module,description:account_chart.module_meta_information
msgid "Remove minimal account chart"
msgstr "移除小的會計科目表"
msgstr "移除小的會計科目表"
#. module: account_chart
#: model:ir.module.module,shortdesc:account_chart.module_meta_information

View File

@ -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: 2012-08-22 06:48+0000\n"
"Last-Translator: Eric Huang <eh@cenoq.com>\n"
"PO-Revision-Date: 2013-12-29 09:09+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.com>\n"
"Language-Team: Cenoq\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-09-12 06:35+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-30 04:39+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: account_check_writing
#: selection:res.company,check_layout:0
@ -56,13 +56,13 @@ msgstr "支票位於底部"
#. module: account_check_writing
#: model:ir.actions.act_window,name:account_check_writing.action_account_check_write
msgid "Print Check in Batch"
msgstr ""
msgstr "整批列印支票"
#. module: account_check_writing
#: code:addons/account_check_writing/wizard/account_check_batch_printing.py:59
#, python-format
msgid "One of the printed check already got a number."
msgstr ""
msgstr "已列印的支票中有一張已有號碼。"
#. module: account_check_writing
#: help:account.journal,allow_check_writing:0
@ -109,7 +109,7 @@ msgstr "原始金額"
#. module: account_check_writing
#: field:res.company,check_layout:0
msgid "Check Layout"
msgstr ""
msgstr "支票格式"
#. module: account_check_writing
#: field:account.voucher,allow_check:0
@ -131,7 +131,7 @@ msgstr "使用套表列印的支票"
#. module: account_check_writing
#: model:ir.actions.report.xml,name:account_check_writing.account_print_check_bottom
msgid "Print Check (Bottom)"
msgstr ""
msgstr "列印支票(底端)"
#. module: account_check_writing
#: model:ir.actions.act_window,help:account_check_writing.action_write_check
@ -160,7 +160,7 @@ msgstr "到期日期"
#. module: account_check_writing
#: model:ir.actions.report.xml,name:account_check_writing.account_print_check_middle
msgid "Print Check (Middle)"
msgstr ""
msgstr "列印支票(中間)"
#. module: account_check_writing
#: model:ir.model,name:account_check_writing.model_res_company
@ -171,12 +171,12 @@ msgstr "公司"
#: code:addons/account_check_writing/wizard/account_check_batch_printing.py:59
#, python-format
msgid "Error!"
msgstr ""
msgstr "錯誤!"
#. module: account_check_writing
#: help:account.check.write,check_number:0
msgid "The number of the next check number to be printed."
msgstr ""
msgstr "下一張列印的支票的號碼"
#. module: account_check_writing
#: report:account.print.check.bottom:0
@ -187,7 +187,7 @@ msgstr "截止餘額"
#. module: account_check_writing
#: model:ir.actions.report.xml,name:account_check_writing.account_print_check_top
msgid "Print Check (Top)"
msgstr ""
msgstr "列印支票(頂端)"
#. module: account_check_writing
#: report:account.print.check.bottom:0
@ -204,7 +204,7 @@ msgstr "手工憑證"
#. module: account_check_writing
#: view:account.check.write:0
msgid "or"
msgstr ""
msgstr ""
#. module: account_check_writing
#: field:account.voucher,amount_in_word:0
@ -214,22 +214,22 @@ msgstr "金額大寫"
#. module: account_check_writing
#: model:ir.model,name:account_check_writing.model_account_check_write
msgid "Prin Check in Batch"
msgstr ""
msgstr "整批列印支票"
#. module: account_check_writing
#: view:account.check.write:0
msgid "Cancel"
msgstr ""
msgstr "取消"
#. module: account_check_writing
#: field:account.check.write,check_number:0
msgid "Next Check Number"
msgstr ""
msgstr "下一個支票號碼"
#. module: account_check_writing
#: view:account.check.write:0
msgid "Check"
msgstr ""
msgstr "支票"
#~ msgid ""
#~ "The check payment form allows you to track the payment you do to your "

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-08-24 12:49+0000\n"
"Last-Translator: Eric Huang <eh@cenoq.com>\n"
"PO-Revision-Date: 2013-12-24 10:29+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 05:21+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-25 06:02+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: account_followup
#: model:email.template,subject:account_followup.email_template_account_followup_default
@ -22,12 +22,12 @@ msgstr ""
#: model:email.template,subject:account_followup.email_template_account_followup_level1
#: model:email.template,subject:account_followup.email_template_account_followup_level2
msgid "${user.company_id.name} Payment Reminder"
msgstr ""
msgstr "${user.company_id.name} 提醒付款"
#. module: account_followup
#: help:res.partner,latest_followup_level_id:0
msgid "The maximum follow-up level"
msgstr ""
msgstr "最大後續追蹤層級"
#. module: account_followup
#: view:account_followup.stat:0
@ -48,13 +48,13 @@ msgstr ""
#. module: account_followup
#: field:res.partner,payment_next_action_date:0
msgid "Next Action Date"
msgstr ""
msgstr "下個動作日期"
#. module: account_followup
#: view:account_followup.followup.line:0
#: field:account_followup.followup.line,manual_action:0
msgid "Manual Action"
msgstr ""
msgstr "手動動作"
#. module: account_followup
#: field:account_followup.sending.results,needprinting:0
@ -64,7 +64,7 @@ msgstr ""
#. module: account_followup
#: view:res.partner:0
msgid "⇾ Mark as Done"
msgstr ""
msgstr "⇾ 標為已完成"
#. module: account_followup
#: field:account_followup.followup.line,manual_action_note:0
@ -104,17 +104,17 @@ msgstr ""
#. module: account_followup
#: view:account_followup.followup.line:0
msgid "Follow-up Steps"
msgstr ""
msgstr "後續追蹤步驟"
#. module: account_followup
#: field:account_followup.print,email_body:0
msgid "Email Body"
msgstr ""
msgstr "Email內文"
#. module: account_followup
#: model:ir.actions.act_window,name:account_followup.action_account_followup_print
msgid "Send Follow-Ups"
msgstr ""
msgstr "送出後續追蹤"
#. module: account_followup
#: report:account_followup.followup.print:0
@ -133,7 +133,7 @@ msgstr ""
#. module: account_followup
#: view:res.partner:0
msgid "No Responsible"
msgstr ""
msgstr "沒有負責人"
#. module: account_followup
#: model:account_followup.followup.line,description:account_followup.demo_followup_line2
@ -209,17 +209,17 @@ msgstr "借方合計"
#. module: account_followup
#: field:res.partner,payment_next_action:0
msgid "Next Action"
msgstr ""
msgstr "下個動作"
#. module: account_followup
#: view:account_followup.followup.line:0
msgid ": Partner Name"
msgstr ""
msgstr "業務夥伴名稱"
#. module: account_followup
#: field:account_followup.followup.line,manual_action_responsible_id:0
msgid "Assign a Responsible"
msgstr ""
msgstr "指定一負責人"
#. module: account_followup
#: view:account_followup.followup:0
@ -269,7 +269,7 @@ msgstr "合夥人"
#. module: account_followup
#: sql_constraint:account_followup.followup:0
msgid "Only one follow-up per company is allowed"
msgstr ""
msgstr "一間公司僅允許一項後續追蹤"
#. module: account_followup
#: code:addons/account_followup/wizard/account_followup_print.py:254
@ -300,7 +300,7 @@ msgstr ""
#. module: account_followup
#: model:ir.actions.act_window,name:account_followup.action_customer_followup
msgid "Manual Follow-Ups"
msgstr ""
msgstr "手動後續追蹤"
#. module: account_followup
#: view:account_followup.followup.line:0
@ -356,12 +356,12 @@ msgstr "借方"
#. module: account_followup
#: model:ir.model,name:account_followup.model_account_followup_stat
msgid "Follow-up Statistics"
msgstr ""
msgstr "後續追蹤統計分析"
#. module: account_followup
#: view:res.partner:0
msgid "Send Overdue Email"
msgstr ""
msgstr "送出帳務逾期通知信"
#. module: account_followup
#: model:ir.model,name:account_followup.model_account_followup_followup_line

View File

@ -41,7 +41,7 @@
<field name="journal"/>
<field name="bank_id" domain="[('partner_id','=',partner_id)]" />
<field name="company_id" widget='selection' groups="base.group_multi_company" on_change="onchange_company_id(company_id)"/>
<field name="partner_id" widget='selection' invisible="1"/>
<field name="partner_id" invisible="1"/>
</group>
</form>
</field>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-08-29 15:27+0000\n"
"Last-Translator: Eric Huang <eh@cenoq.com>\n"
"PO-Revision-Date: 2013-12-29 09:05+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 05:48+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-30 04:39+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: account_payment
#: model:ir.actions.act_window,help:account_payment.action_payment_order_tree
@ -106,7 +106,7 @@ msgstr "到期日"
#. module: account_payment
#: view:payment.order.create:0
msgid "_Add to payment order"
msgstr ""
msgstr "新增至付款單"
#. module: account_payment
#: model:ir.actions.act_window,name:account_payment.action_account_payment_populate_statement
@ -127,7 +127,7 @@ msgstr ""
#: code:addons/account_payment/account_move_line.py:110
#, python-format
msgid "Error!"
msgstr ""
msgstr "錯誤!"
#. module: account_payment
#: report:payment.order:0
@ -148,7 +148,7 @@ msgstr "取消"
#. module: account_payment
#: model:ir.actions.act_window,name:account_payment.action_payment_order_tree_new
msgid "New Payment Order"
msgstr ""
msgstr "新增付款單"
#. module: account_payment
#: report:payment.order:0
@ -165,12 +165,12 @@ msgstr ""
#: model:ir.actions.act_window,name:account_payment.action_payment_order_tree
#: model:ir.ui.menu,name:account_payment.menu_action_payment_order_form
msgid "Payment Orders"
msgstr ""
msgstr "付款單"
#. module: account_payment
#: selection:payment.order,date_prefered:0
msgid "Directly"
msgstr ""
msgstr "直接"
#. module: account_payment
#: model:ir.actions.act_window,name:account_payment.action_payment_line_form
@ -217,7 +217,7 @@ msgstr ""
#. module: account_payment
#: view:account.bank.statement:0
msgid "Import Payment Lines"
msgstr ""
msgstr "匯入付款細項"
#. module: account_payment
#: view:payment.line:0
@ -256,17 +256,17 @@ msgstr ""
#. module: account_payment
#: field:payment.order,date_created:0
msgid "Creation Date"
msgstr ""
msgstr "建立日期"
#. module: account_payment
#: help:payment.mode,journal:0
msgid "Bank or Cash Journal for the Payment Mode"
msgstr ""
msgstr "付款模式的銀行或現金日記帳簿"
#. module: account_payment
#: selection:payment.order,date_prefered:0
msgid "Fixed date"
msgstr ""
msgstr "固定日期"
#. module: account_payment
#: field:payment.line,info_partner:0
@ -282,12 +282,12 @@ msgstr "目的帳號"
#. module: account_payment
#: view:payment.order:0
msgid "Search Payment Orders"
msgstr ""
msgstr "搜尋付款單"
#. module: account_payment
#: field:payment.line,create_date:0
msgid "Created"
msgstr ""
msgstr "已建立"
#. module: account_payment
#: view:payment.order:0
@ -394,7 +394,7 @@ msgstr "草稿"
#: view:payment.order:0
#: field:payment.order,state:0
msgid "Status"
msgstr ""
msgstr "狀態"
#. module: account_payment
#: help:payment.line,communication2:0
@ -424,7 +424,7 @@ msgstr "付款明細"
#. module: account_payment
#: model:ir.model,name:account_payment.model_account_move_line
msgid "Journal Items"
msgstr ""
msgstr "日記簿項目"
#. module: account_payment
#: help:payment.line,move_line_id:0
@ -441,7 +441,7 @@ msgstr "搜尋"
#. module: account_payment
#: field:payment.order,user_id:0
msgid "Responsible"
msgstr ""
msgstr "負責人"
#. module: account_payment
#: field:payment.line,date:0
@ -456,7 +456,7 @@ msgstr "總計:"
#. module: account_payment
#: field:payment.order,date_done:0
msgid "Execution Date"
msgstr ""
msgstr "執行日期"
#. module: account_payment
#: view:account.payment.populate.statement:0
@ -501,7 +501,7 @@ msgstr "您的參考"
#. module: account_payment
#: view:payment.order:0
msgid "Payment order"
msgstr ""
msgstr "付款單"
#. module: account_payment
#: view:payment.line:0
@ -535,7 +535,7 @@ msgstr "取消"
#. module: account_payment
#: field:payment.line,bank_id:0
msgid "Destination Bank Account"
msgstr ""
msgstr "目的銀行帳戶"
#. module: account_payment
#: view:payment.line:0
@ -548,7 +548,7 @@ msgstr "資訊"
#: model:ir.model,name:account_payment.model_payment_order
#: view:payment.order:0
msgid "Payment Order"
msgstr ""
msgstr "付款單"
#. module: account_payment
#: help:payment.line,amount:0
@ -573,7 +573,7 @@ msgstr "通訊 2"
#. module: account_payment
#: field:payment.order,date_scheduled:0
msgid "Scheduled Date"
msgstr ""
msgstr "預定日期"
#. module: account_payment
#: view:account.payment.make.payment:0
@ -584,7 +584,7 @@ msgstr "是否進行付款?"
#: view:payment.mode:0
#: field:payment.mode,journal:0
msgid "Journal"
msgstr ""
msgstr "帳簿"
#. module: account_payment
#: field:payment.mode,bank_id:0
@ -612,12 +612,12 @@ msgstr "付款"
#. module: account_payment
#: report:payment.order:0
msgid "Payment Order / Payment"
msgstr ""
msgstr "付款單 / 付款"
#. module: account_payment
#: field:payment.line,move_line_id:0
msgid "Entry line"
msgstr ""
msgstr "分錄細項"
#. module: account_payment
#: help:payment.line,communication:0
@ -640,7 +640,7 @@ msgstr "銀行帳戶"
#: view:payment.line:0
#: view:payment.order:0
msgid "Entry Information"
msgstr ""
msgstr "分錄資訊"
#. module: account_payment
#: model:ir.model,name:account_payment.model_payment_order_create
@ -673,7 +673,7 @@ msgstr ""
#: view:account.payment.populate.statement:0
#: view:payment.order.create:0
msgid "or"
msgstr ""
msgstr ""
#. module: account_payment
#: help:payment.mode,bank_id:0

File diff suppressed because it is too large Load Diff

View File

@ -7,19 +7,19 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2012-08-28 02:30+0000\n"
"Last-Translator: Eric Huang <eh@cenoq.com>\n"
"PO-Revision-Date: 2013-12-29 08:26+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 05:59+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-30 04:39+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: account_voucher
#: field:account.bank.statement.line,voucher_id:0
msgid "Reconciliation"
msgstr ""
msgstr "核銷"
#. module: account_voucher
#: model:ir.model,name:account_voucher.model_account_config_settings
@ -63,7 +63,7 @@ msgstr "計算公式 憑證上输入的金额 - 憑證行的金额合計."
#. module: account_voucher
#: view:account.voucher:0
msgid "(Update)"
msgstr ""
msgstr "(更新)"
#. module: account_voucher
#: view:account.voucher:0
@ -90,7 +90,7 @@ msgstr "三月"
#. module: account_voucher
#: field:account.voucher,message_unread:0
msgid "Unread Messages"
msgstr ""
msgstr "未讀訊息"
#. module: account_voucher
#: view:account.voucher:0
@ -100,7 +100,7 @@ msgstr "付賬"
#. module: account_voucher
#: view:account.voucher:0
msgid "Are you sure you want to cancel this receipt?"
msgstr ""
msgstr "你確定要取消這張收據嗎?"
#. module: account_voucher
#: view:account.voucher:0
@ -121,7 +121,7 @@ msgstr "依發票年份分組"
#: view:sale.receipt.report:0
#: field:sale.receipt.report,user_id:0
msgid "Salesperson"
msgstr ""
msgstr "業務人員"
#. module: account_voucher
#: view:account.voucher:0
@ -134,7 +134,7 @@ msgstr "換領券統計"
msgid ""
"You can not change the journal as you already reconciled some statement "
"lines!"
msgstr ""
msgstr "你已經核銷部分項目,所以無法變更日記帳簿!"
#. module: account_voucher
#: view:account.voucher:0
@ -145,7 +145,7 @@ msgstr "驗證"
#: model:ir.actions.act_window,name:account_voucher.action_vendor_payment
#: model:ir.ui.menu,name:account_voucher.menu_action_vendor_payment
msgid "Supplier Payments"
msgstr ""
msgstr "供應商付款"
#. module: account_voucher
#: model:ir.actions.act_window,help:account_voucher.action_purchase_receipt
@ -207,13 +207,13 @@ msgstr "備註"
#. module: account_voucher
#: field:account.voucher,message_ids:0
msgid "Messages"
msgstr ""
msgstr "訊息"
#. module: account_voucher
#: model:ir.actions.act_window,name:account_voucher.action_purchase_receipt
#: model:ir.ui.menu,name:account_voucher.menu_action_purchase_receipt
msgid "Purchase Receipts"
msgstr ""
msgstr "採購單據"
#. module: account_voucher
#: field:account.voucher.line,move_line_id:0
@ -225,7 +225,7 @@ msgstr "會計憑證行"
#: code:addons/account_voucher/account_voucher.py:1073
#, python-format
msgid "Error!"
msgstr ""
msgstr "錯誤!"
#. module: account_voucher
#: field:account.voucher.line,amount:0
@ -271,7 +271,7 @@ msgstr ""
#. module: account_voucher
#: help:account.voucher,message_unread:0
msgid "If checked new messages require your attention."
msgstr ""
msgstr "當有新訊息時通知您。"
#. module: account_voucher
#: model:ir.model,name:account_voucher.model_account_bank_statement_line
@ -294,7 +294,7 @@ msgstr "稅"
#: code:addons/account_voucher/account_voucher.py:971
#, python-format
msgid "Invalid Action!"
msgstr ""
msgstr "無效的動作!"
#. module: account_voucher
#: field:account.voucher,comment:0
@ -326,7 +326,7 @@ msgstr "付款資料"
#. module: account_voucher
#: view:account.voucher:0
msgid "(update)"
msgstr ""
msgstr "(更新)"
#. module: account_voucher
#: view:account.voucher:0
@ -395,7 +395,7 @@ msgstr "供應商換領券"
#. module: account_voucher
#: field:account.voucher,message_follower_ids:0
msgid "Followers"
msgstr ""
msgstr "關注者"
#. module: account_voucher
#: selection:account.voucher.line,type:0
@ -406,7 +406,7 @@ msgstr "借方"
#: code:addons/account_voucher/account_voucher.py:1641
#, python-format
msgid "Unable to change journal !"
msgstr ""
msgstr "無法變更日記帳簿!"
#. module: account_voucher
#: view:sale.receipt.report:0
@ -544,7 +544,7 @@ msgstr ""
#: field:account.config.settings,expense_currency_exchange_account_id:0
#: field:res.company,expense_currency_exchange_account_id:0
msgid "Loss Exchange Rate Account"
msgstr ""
msgstr "匯損科目"
#. module: account_voucher
#: view:account.voucher:0
@ -587,7 +587,7 @@ msgstr "支出明細"
#. module: account_voucher
#: view:account.voucher:0
msgid "Sale voucher"
msgstr ""
msgstr "銷售單據"
#. module: account_voucher
#: help:account.voucher,is_multi_currency:0
@ -599,7 +599,7 @@ msgstr "此字段由系統內部使用,區分該憑證是否涉及外幣"
#. module: account_voucher
#: view:account.invoice:0
msgid "Register Payment"
msgstr ""
msgstr "登記付款紀錄"
#. module: account_voucher
#: field:account.statement.from.invoice.lines,line_ids:0
@ -638,17 +638,17 @@ msgstr "應收應付"
#. module: account_voucher
#: view:account.voucher:0
msgid "Voucher Payment"
msgstr ""
msgstr "付款單據"
#. module: account_voucher
#: field:sale.receipt.report,state:0
msgid "Voucher Status"
msgstr ""
msgstr "單據狀態"
#. module: account_voucher
#: view:account.voucher:0
msgid "Are you sure to unreconcile this record?"
msgstr ""
msgstr "你是否要取消核銷此紀錄?"
#. module: account_voucher
#: field:account.voucher,company_id:0
@ -672,7 +672,7 @@ msgstr "核銷付款餘額"
#: code:addons/account_voucher/account_voucher.py:1067
#, python-format
msgid "Configuration Error !"
msgstr ""
msgstr "設置錯誤!"
#. module: account_voucher
#: view:account.voucher:0
@ -689,14 +689,14 @@ msgstr "連稅總額"
#. module: account_voucher
#: view:account.voucher:0
msgid "Purchase Voucher"
msgstr ""
msgstr "採購單據"
#. module: account_voucher
#: view:account.voucher:0
#: field:account.voucher,state:0
#: view:sale.receipt.report:0
msgid "Status"
msgstr ""
msgstr "狀態"
#. module: account_voucher
#: view:account.voucher:0
@ -707,7 +707,7 @@ msgstr "分配"
#: view:account.statement.from.invoice.lines:0
#: view:account.voucher:0
msgid "or"
msgstr ""
msgstr ""
#. module: account_voucher
#: selection:sale.receipt.report,month:0
@ -717,7 +717,7 @@ msgstr "八月"
#. module: account_voucher
#: view:account.voucher:0
msgid "Validate Payment"
msgstr ""
msgstr "認可付款"
#. module: account_voucher
#: help:account.voucher,audit:0
@ -756,12 +756,12 @@ msgstr "已付款"
#: model:ir.actions.act_window,name:account_voucher.action_sale_receipt
#: model:ir.ui.menu,name:account_voucher.menu_action_sale_receipt
msgid "Sales Receipts"
msgstr ""
msgstr "銷售收據"
#. module: account_voucher
#: field:account.voucher,message_is_follower:0
msgid "Is a Follower"
msgstr ""
msgstr "為關注者"
#. module: account_voucher
#: field:account.voucher,analytic_id:0
@ -815,7 +815,7 @@ msgstr "預付款?"
#: code:addons/account_voucher/account_voucher.py:1208
#, python-format
msgid "The invoice you are willing to pay is not valid anymore."
msgstr ""
msgstr "你要付款的發票已經不再有效。"
#. module: account_voucher
#: selection:sale.receipt.report,month:0
@ -836,12 +836,12 @@ msgstr "公司"
#. module: account_voucher
#: field:account.voucher,message_summary:0
msgid "Summary"
msgstr ""
msgstr "摘要"
#. module: account_voucher
#: field:account.voucher,active:0
msgid "Active"
msgstr ""
msgstr "啟用"
#. module: account_voucher
#: code:addons/account_voucher/account_voucher.py:1074
@ -854,14 +854,14 @@ msgstr ""
#: model:ir.actions.act_window,name:account_voucher.action_vendor_receipt
#: model:ir.ui.menu,name:account_voucher.menu_action_vendor_receipt
msgid "Customer Payments"
msgstr ""
msgstr "客戶付款"
#. module: account_voucher
#: model:ir.actions.act_window,name:account_voucher.action_sale_receipt_report_all
#: model:ir.ui.menu,name:account_voucher.menu_action_sale_receipt_report_all
#: view:sale.receipt.report:0
msgid "Sales Receipts Analysis"
msgstr ""
msgstr "銷售收據分析"
#. module: account_voucher
#: view:sale.receipt.report:0
@ -956,7 +956,7 @@ msgstr "取消"
#. module: account_voucher
#: model:ir.actions.client,name:account_voucher.action_client_invoice_menu
msgid "Open Invoicing Menu"
msgstr ""
msgstr "開啟發票開立選單"
#. module: account_voucher
#: selection:account.voucher,state:0
@ -1044,7 +1044,7 @@ msgstr "五月"
#. module: account_voucher
#: view:account.voucher:0
msgid "Sale Receipt"
msgstr ""
msgstr "銷售收據"
#. module: account_voucher
#: view:account.voucher:0
@ -1129,7 +1129,7 @@ msgstr "年份"
#: field:account.config.settings,income_currency_exchange_account_id:0
#: field:res.company,income_currency_exchange_account_id:0
msgid "Gain Exchange Rate Account"
msgstr ""
msgstr "匯益科目"
#. module: account_voucher
#: selection:account.voucher,type:0
@ -1155,7 +1155,7 @@ msgstr "預設類型"
#. module: account_voucher
#: help:account.voucher,message_ids:0
msgid "Messages and communication history"
msgstr ""
msgstr "訊息及聯絡紀錄"
#. module: account_voucher
#: model:ir.model,name:account_voucher.model_account_statement_from_invoice_lines
@ -1194,7 +1194,7 @@ msgstr "會計分錄的生效日期"
#. module: account_voucher
#: model:mail.message.subtype,name:account_voucher.mt_voucher_state_change
msgid "Status Change"
msgstr ""
msgstr "狀態變更"
#. module: account_voucher
#: selection:account.voucher,payment_option:0
@ -1241,14 +1241,14 @@ msgstr "未結餘額"
#. module: account_voucher
#: model:mail.message.subtype,description:account_voucher.mt_voucher_state_change
msgid "Status <b>changed</b>"
msgstr ""
msgstr "狀態<b>已變更</b>"
#. module: account_voucher
#: code:addons/account_voucher/account_voucher.py:1106
#: code:addons/account_voucher/account_voucher.py:1110
#, python-format
msgid "Insufficient Configuration!"
msgstr ""
msgstr "設置不夠完整!"
#. module: account_voucher
#: help:account.voucher,active:0

View File

@ -1,43 +1,28 @@
# Chinese (Traditional) translation for openobject-addons
# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-03 16:03+0000\n"
"PO-Revision-Date: 2012-08-19 10:28+0000\n"
"Last-Translator: Eric Huang <eh@cenoq.com>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-12-29 17:06+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (Traditional) <zh_TW@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: 2012-12-04 05:53+0000\n"
"X-Generator: Launchpad (build 16335)\n"
"X-Launchpad-Export-Date: 2013-12-30 04:39+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: base_crypt
#: model:ir.model,name:base_crypt.model_res_users
#. module: auth_crypt
#: field:res.users,password_crypt:0
msgid "Encrypted Password"
msgstr "已加密的密碼"
#. module: auth_crypt
#: model:ir.model,name:auth_crypt.model_res_users
msgid "Users"
msgstr ""
#~ msgid "Base - Password Encryption"
#~ msgstr "基礎 - 密碼加密"
#, python-format
#~ msgid "Please specify the password !"
#~ msgstr "請指定密碼 !"
#~ msgid "You can not have two users with the same login !"
#~ msgstr "您不能同時登入二個使用者!"
#, python-format
#~ msgid "Error"
#~ msgstr "錯誤"
#~ msgid "The chosen company is not in the allowed companies for this user"
#~ msgstr "所選的公司不是使用者被允許的公司。"
#~ msgid "res.users"
#~ msgstr "res.users"
msgstr "使用者"

View File

@ -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: 2013-09-19 09:40+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"PO-Revision-Date: 2013-12-29 09:17+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.com>\n"
"Language-Team: Chinese (Traditional) <zh_TW@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: 2013-09-20 05:38+0000\n"
"X-Generator: Launchpad (build 16765)\n"
"X-Launchpad-Export-Date: 2013-12-30 04:39+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: auth_signup
#: field:res.partner,signup_type:0
@ -66,19 +66,19 @@ msgstr ""
#. module: auth_signup
#: model:email.template,subject:auth_signup.reset_password_email
msgid "Password reset"
msgstr ""
msgstr "密碼重設"
#. module: auth_signup
#. openerp-web
#: code:addons/auth_signup/static/src/js/auth_signup.js:120
#, python-format
msgid "Please enter a password and confirm it."
msgstr ""
msgstr "請輸入密碼並確認"
#. module: auth_signup
#: view:res.users:0
msgid "Send an email to the user to (re)set their password."
msgstr ""
msgstr "寄出密碼重設電子郵件給該使用者"
#. module: auth_signup
#. openerp-web
@ -86,23 +86,23 @@ msgstr ""
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:29
#, python-format
msgid "Sign Up"
msgstr ""
msgstr "註冊"
#. module: auth_signup
#: selection:res.users,state:0
msgid "New"
msgstr ""
msgstr "新增"
#. module: auth_signup
#: code:addons/auth_signup/res_users.py:258
#, python-format
msgid "Mail sent to:"
msgstr ""
msgstr "信件已寄給:"
#. module: auth_signup
#: field:res.users,state:0
msgid "Status"
msgstr ""
msgstr "狀態"
#. module: auth_signup
#: model:email.template,body_html:auth_signup.reset_password_email
@ -122,29 +122,29 @@ msgstr ""
#: code:addons/auth_signup/static/src/js/auth_signup.js:114
#, python-format
msgid "Please enter a name."
msgstr ""
msgstr "請輸入名字"
#. module: auth_signup
#: model:ir.model,name:auth_signup.model_res_users
msgid "Users"
msgstr ""
msgstr "使用者"
#. module: auth_signup
#: field:res.partner,signup_url:0
msgid "Signup URL"
msgstr ""
msgstr "註冊網址"
#. module: auth_signup
#. openerp-web
#: code:addons/auth_signup/static/src/js/auth_signup.js:117
#, python-format
msgid "Please enter a username."
msgstr ""
msgstr "請輸入使用者名稱。"
#. module: auth_signup
#: selection:res.users,state:0
msgid "Active"
msgstr ""
msgstr "啟用"
#. module: auth_signup
#: code:addons/auth_signup/res_users.py:270
@ -159,26 +159,26 @@ msgstr ""
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:12
#, python-format
msgid "Username"
msgstr ""
msgstr "使用者名稱"
#. module: auth_signup
#. openerp-web
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:8
#, python-format
msgid "Name"
msgstr ""
msgstr "名稱"
#. module: auth_signup
#. openerp-web
#: code:addons/auth_signup/static/src/js/auth_signup.js:173
#, python-format
msgid "Please enter a username or email address."
msgstr ""
msgstr "請輸入使用者名稱或電子郵件地址"
#. module: auth_signup
#: selection:res.users,state:0
msgid "Resetting Password"
msgstr ""
msgstr "重設密碼中"
#. module: auth_signup
#. openerp-web
@ -202,7 +202,7 @@ msgstr ""
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:25
#, python-format
msgid "Log in"
msgstr ""
msgstr "登入"
#. module: auth_signup
#: field:res.partner,signup_valid:0
@ -220,7 +220,7 @@ msgstr ""
#: code:addons/auth_signup/static/src/js/auth_signup.js:173
#, python-format
msgid "Login"
msgstr ""
msgstr "登入"
#. module: auth_signup
#. openerp-web
@ -242,36 +242,36 @@ msgstr ""
#: code:addons/auth_signup/static/src/js/auth_signup.js:170
#, python-format
msgid "No database selected !"
msgstr ""
msgstr "未選定資料庫!"
#. module: auth_signup
#: view:res.users:0
msgid "Reset Password"
msgstr ""
msgstr "重設密碼"
#. module: auth_signup
#: field:base.config.settings,auth_signup_reset_password:0
msgid "Enable password reset from Login page"
msgstr ""
msgstr "於登入頁面啟用密碼重設"
#. module: auth_signup
#. openerp-web
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:30
#, python-format
msgid "Back to Login"
msgstr ""
msgstr "返回登入畫面"
#. module: auth_signup
#. openerp-web
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:22
#, python-format
msgid "Sign up"
msgstr ""
msgstr "註冊"
#. module: auth_signup
#: model:ir.model,name:auth_signup.model_res_partner
msgid "Partner"
msgstr ""
msgstr "業務夥伴"
#. module: auth_signup
#: field:res.partner,signup_token:0

View File

@ -23,6 +23,7 @@ from datetime import datetime, timedelta
import time
import logging
import openerp
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
@ -222,6 +223,7 @@ class base_action_rule(osv.osv):
def create(self, cr, uid, vals, context=None):
res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
self._register_hook(cr, [res_id])
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return res_id
def write(self, cr, uid, ids, vals, context=None):
@ -229,6 +231,7 @@ class base_action_rule(osv.osv):
ids = [ids]
super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
self._register_hook(cr, ids)
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
return True
def onchange_model_id(self, cr, uid, ids, model_id, context=None):

View File

@ -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:05+0000\n"
"PO-Revision-Date: 2011-11-07 12:53+0000\n"
"Last-Translator: Walter Cheuk <wwycheuk@gmail.com>\n"
"PO-Revision-Date: 2013-12-29 15:39+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 05:22+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-30 04:39+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: base_setup
#: view:sale.config.settings:0
@ -189,7 +189,7 @@ msgstr "客戶"
msgid ""
"When you create a new contact (person or company), you will be able to load "
"all the data from LinkedIn (photos, address, etc)."
msgstr ""
msgstr "當建立新的聯絡人時(個人或公司),你就可以從 LinkedIn 載入所有資料(照片、地址等)。"
#. module: base_setup
#: help:base.config.settings,module_multi_company:0
@ -212,7 +212,7 @@ msgstr ""
msgid ""
"You will find more options in your company details: address for the header "
"and footer, overdue payments texts, etc."
msgstr ""
msgstr "您可在貴公司的詳細資訊中找到更多選項,如表頭和頁尾的地址、付款過期通知等等。"
#. module: base_setup
#: model:ir.model,name:base_setup.model_sale_config_settings

View File

@ -7,19 +7,19 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2009-01-30 12:44+0000\n"
"Last-Translator: <>\n"
"PO-Revision-Date: 2013-12-27 04:38+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 05:19+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-28 05:19+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: base_vat
#: view:res.partner:0
msgid "Check Validity"
msgstr ""
msgstr "檢查有效性"
#. module: base_vat
#: code:addons/base_vat/base_vat.py:152
@ -28,11 +28,13 @@ msgid ""
"This VAT number does not seem to be valid.\n"
"Note: the expected format is %s"
msgstr ""
"這個統編似乎是無效的。\n"
"提醒:格式應為 %s"
#. module: base_vat
#: field:res.company,vat_check_vies:0
msgid "VIES VAT Check"
msgstr ""
msgstr "VIES 統編VAT檢查"
#. module: base_vat
#: model:ir.model,name:base_vat.model_res_company
@ -43,7 +45,7 @@ msgstr "公司"
#: code:addons/base_vat/base_vat.py:113
#, python-format
msgid "Error!"
msgstr "錯誤!"
msgstr "錯誤"
#. module: base_vat
#: help:res.partner,vat_subjected:0
@ -51,20 +53,23 @@ msgid ""
"Check this box if the partner is subjected to the VAT. It will be used for "
"the VAT legal statement."
msgstr ""
"Check this box if the partner is subjected to the VAT. It will be used for "
"the VAT legal statement.\r\n"
"如果該業務夥伴需申報(加值型)營業稅,則勾選此框。這會用在營業稅相關的稅務報告。"
#. module: base_vat
#: model:ir.model,name:base_vat.model_res_partner
msgid "Partner"
msgstr "夥伴"
msgstr "業務夥伴"
#. module: base_vat
#: help:res.company,vat_check_vies:0
msgid ""
"If checked, Partners VAT numbers will be fully validated against EU's VIES "
"service rather than via a simple format validation (checksum)."
msgstr ""
msgstr "如果勾選此框,則業務夥伴的統編會以歐盟的 VIES 做完整檢查否則僅會作基本格式檢查checksum檢查"
#. module: base_vat
#: field:res.partner,vat_subjected:0
msgid "VAT Legal Statement"
msgstr ""
msgstr "營業稅報告"

View File

@ -411,7 +411,7 @@
on_change="on_change_partner_id(partner_id)"
string="Customer"
context="{'default_name': partner_name, 'default_email': email_from, 'default_phone': phone}"/>
<field name="email_from" string="Email"/>
<field name="email_from" string="Email" widget="email"/>
<field name="phone"/>
</group>

3014
addons/crm/i18n/he.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@ import psycopg2
import openerp
from openerp import tools
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.osv.orm import except_orm
import openerp.report.interface
@ -322,7 +323,7 @@ class document_directory(osv.osv):
ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
ressource_id=vals.get('ressource_id',0)
if op=='write':
for directory in self.browse(cr, uid, ids):
for directory in self.browse(cr, SUPERUSER_ID, ids):
if not name:
name=directory.name
if not parent_id:
@ -336,7 +337,7 @@ class document_directory(osv.osv):
if len(res):
return False
if op=='create':
res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
res = self.search(cr, SUPERUSER_ID, [('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
if len(res):
return False
return True

View File

@ -31,7 +31,7 @@ class report_document_user(osv.osv):
'name': fields.char('Year', size=64,readonly=True),
'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'),
('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')],'Month',readonly=True),
'user_id':fields.integer('Owner', readonly=True),
'user_id': fields.many2one('res.users', 'Owner', readonly=True),
'user': fields.related('user_id', 'name', type='char', size=64, readonly=True),
'directory': fields.char('Directory',size=64,readonly=True),
'datas_fname': fields.char('File Name',size=64,readonly=True),

View File

@ -58,6 +58,23 @@
!python {model: ir.attachment}: |
ids = self.search(cr, uid, [('res_model', '=', 'res.country'), ('res_id', '=', ref("base.za"))])
assert ids == [ ref("attach_3rd")], ids
-
I test that I can't create duplicate directories (even when duplicates are hidden by a record rule)
-
!python {model: document.directory}: |
duplicate_detected = False
from openerp.osv.osv import except_osv
try:
demo_uid = ref('base.user_demo')
dir_vals = {
'name': 'Testing (will be deleted!)',
'parent_id': ref('document.dir_root')
}
new_dir_id = self.create(cr, demo_uid, dir_vals, context=None)
self.unlink(cr, uid, [new_dir_id], context=None)
except except_osv, e:
duplicate_detected = e.value == u'Directory name must be unique!'
assert duplicate_detected is True, 'Failed to detect duplicate directory'
-
I delete the attachments
-

View File

@ -411,7 +411,17 @@ class email_template(osv.osv):
# create a mail_mail based on values, without attachments
values = self.generate_email(cr, uid, template_id, res_id, context=context)
assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed'
del values['partner_to'] # TODO Properly use them.
# process partner_to field that is a comma separated list of partner_ids -> recipient_ids
# NOTE: only usable if force_send is True, because otherwise the value is
# not stored on the mail_mail, and therefore lost -> fixed in v8
values['recipient_ids'] = []
partner_to = values.pop('partner_to', '')
if partner_to:
# placeholders could generate '', 3, 2 due to some empty field values
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
values['recipient_ids'] += [(4, pid) for pid in self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)]
attachment_ids = values.pop('attachment_ids', [])
attachments = values.pop('attachments', [])
msg_id = mail_mail.create(cr, uid, values, context=context)
@ -420,11 +430,11 @@ class email_template(osv.osv):
# manage attachments
for attachment in attachments:
attachment_data = {
'name': attachment[0],
'datas_fname': attachment[0],
'datas': attachment[1],
'res_model': 'mail.message',
'res_id': mail.mail_message_id.id,
'name': attachment[0],
'datas_fname': attachment[0],
'datas': attachment[1],
'res_model': 'mail.message',
'res_id': mail.mail_message_id.id,
}
context.pop('default_type', None)
attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))

View File

@ -33,7 +33,7 @@ class TestServerActionsEmail(TestServerActionsBase):
'name': 'TestTemplate',
'email_from': 'myself@example.com',
'email_to': 'brigitte@example.com',
'partner_to': '[%s]' % self.test_partner_id,
'partner_to': '%s' % self.test_partner_id,
'model_id': self.res_partner_model_id,
'subject': 'About ${object.name}',
'body_html': '<p>Dear ${object.name}, your parent is ${object.parent_id and object.parent_id.name or "False"}</p>',

View File

@ -21,6 +21,7 @@
import base64
from openerp.addons.mail.tests.common import TestMail
from openerp.tools import mute_logger
class test_message_compose(TestMail):
@ -200,3 +201,44 @@ class test_message_compose(TestMail):
# Generate messsage with default email and partner on template
mail_value = mail_compose.generate_email_for_composer(cr, uid, email_template_id, uid)
self.assertEqual(set(mail_value['partner_ids']), set(send_to), 'mail.message partner_ids list created by template is incorrect')
@mute_logger('openerp.osv.orm', 'openerp.osv.orm')
def test_10_email_templating(self):
""" Tests designed for the mail.compose.message wizard updated by email_template. """
cr, uid, context = self.cr, self.uid, {}
# create the email.template on mail.group model
group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
email_template = self.registry('email.template')
email_template_id = email_template.create(cr, uid, {
'model_id': group_model_id,
'name': 'Pigs Template',
'email_from': 'Raoul Grosbedon <raoul@example.com>',
'subject': '${object.name}',
'body_html': '${object.description}',
'user_signature': True,
'email_to': 'b@b.b c@c.c',
'email_cc': 'd@d.d',
'partner_to': '${user.partner_id.id},%s,%s,-1' % (self.user_raoul.partner_id.id, self.user_bert.partner_id.id)
})
# not force send: email_recipients is not taken into account
msg_id = email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, context=context)
mail = self.mail_mail.browse(cr, uid, msg_id, context=context)
self.assertEqual(mail.subject, 'Pigs', 'email_template: send_mail: wrong subject')
self.assertEqual(mail.email_to, 'b@b.b c@c.c', 'email_template: send_mail: wrong email_to')
self.assertEqual(mail.email_cc, 'd@d.d', 'email_template: send_mail: wrong email_cc')
self.assertEqual(
set([partner.id for partner in mail.recipient_ids]),
set((self.partner_admin_id, self.user_raoul.partner_id.id, self.user_bert.partner_id.id)),
'email_template: send_mail: wrong management of partner_to')
# force send: take email_recipients into account
email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, force_send=True, context=context)
sent_emails = self._build_email_kwargs_list
email_to_lst = [
['b@b.b', 'c@c.c'], ['"Followers of Pigs" <admin@example.com>'],
['"Followers of Pigs" <raoul@raoul.fr>'], ['"Followers of Pigs" <bert@bert.fr>']]
self.assertEqual(len(sent_emails), 4, 'email_template: send_mail: 3 valid email recipients + email_to -> should send 4 emails')
for email in sent_emails:
self.assertIn(email['email_to'], email_to_lst, 'email_template: send_mail: wrong email_recipients')

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp import tools
from openerp import tools, SUPERUSER_ID
from openerp.osv import osv, fields
@ -143,9 +143,9 @@ class mail_compose_message(osv.TransientModel):
partner_ids.append(partner_id)
partner_to = rendered_values.pop('partner_to', '')
if partner_to:
for partner_id in partner_to.split(','):
if partner_id: # placeholders could generate '', 3, 2 due to some empty field values
partner_ids.append(int(partner_id))
# placeholders could generate '', 3, 2 due to some empty field values
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
return partner_ids
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None):

1912
addons/fleet/i18n/lo.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import models
import wizard

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Gamification',
'version': '1.0',
'author': 'OpenERP SA',
'category': 'Human Ressources',
'depends': ['mail', 'email_template', 'web_kanban_gauge'],
'description': """
Gamification process
====================
The Gamification module provides ways to evaluate and motivate the users of OpenERP.
The users can be evaluated using goals and numerical objectives to reach.
**Goals** are assigned through **challenges** to evaluate and compare members of a team with each others and through time.
For non-numerical achievements, **badges** can be granted to users. From a simple "thank you" to an exceptional achievement, a badge is an easy way to exprimate gratitude to a user for their good work.
Both goals and badges are flexibles and can be adapted to a large range of modules and actions. When installed, this module creates easy goals to help new users to discover OpenERP and configure their user profile.
""",
'data': [
'wizard/update_goal.xml',
'wizard/grant_badge.xml',
'views/badge.xml',
'views/challenge.xml',
'views/goal.xml',
'data/cron.xml',
'security/gamification_security.xml',
'security/ir.model.access.csv',
'data/goal_base.xml',
'data/badge.xml',
],
'installable': True,
'application': True,
'auto_install': False,
'css': ['static/src/css/gamification.css'],
'js': ['static/src/js/gamification.js',],
'qweb': ['static/src/xml/gamification.xml'],
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record forcecreate="True" id="ir_cron_check_challenge"
model="ir.cron">
<field name="name">Run Goal Challenge Checker</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
<field name="model">gamification.challenge</field>
<field name="function">_cron_update</field>
<field name="args">()</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,208 @@
<?xml version="1.0"?>
<openerp>
<!-- Mail template is done in a NOUPDATE block
so users can freely customize/delete them -->
<data noupdate="0">
<!--Email template -->
<record id="email_template_goal_reminder" model="email.template">
<field name="name">Reminder for Goal Update</field>
<field name="body_html"><![CDATA[
<header>
<strong>Reminder ${object.name}</strong>
</header>
<p>You have not updated your progress for the goal ${object.definition_id.name} (currently reached at ${object.completeness}%) for at least ${object.remind_update_delay} days. Do not forget to do it.</p>
<p>If you have not changed your score yet, you can use the button "The current value is up to date" to indicate so.</p>
]]></field>
</record>
<record id="simple_report_template" model="email.template">
<field name="name">Simple Challenge Report Progress</field>
<field name="body_html"><![CDATA[
<header>
<strong>${object.name}</strong>
</header>
<p class="oe_grey">The following message contains the current progress for the challenge ${object.name}</p>
% if object.visibility_mode == 'personal':
<table width="100%" border="1">
<tr>
<th>Goal</th>
<th>Target</th>
<th>Current</th>
<th>Completeness</th>
</tr>
% for line in ctx["challenge_lines"]:
<tr
% if line['completeness'] >= 100:
style="font-weight:bold;"
% endif
>
<td>${line['name']}</td>
<td>${line['target']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
<td>${line['current']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
<td>${line['completeness']} %</td>
</tr>
% endfor
</table>
% else:
% for line in ctx["challenge_lines"]:
<table width="100%" border="1">
<tr>
<th colspan="4">${line['name']}</th>
</tr>
<tr>
<th>#</th>
<th>Person</th>
<th>Completeness</th>
<th>Current</th>
</tr>
% for goal in line['goals']:
<tr
% if goal.completeness >= 100:
style="font-weight:bold;"
% endif
>
<td>${goal['rank']}</td>
<td>${goal['name']}</td>
<td>${goal['completeness']}%</td>
<td>${goal['current']}/${line['target']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
</tr>
% endfor
</table>
<br/><br/>
% endfor
% endif
]]></field>
</record>
</data>
<data>
<!-- goal definitions -->
<record model="gamification.goal.definition" id="definition_base_timezone">
<field name="name">Set your Timezone</field>
<field name="description">Configure your profile and specify your timezone</field>
<field name="computation_mode">count</field>
<field name="display_mode">boolean</field>
<field name="model_id" eval="ref('base.model_res_users')" />
<field name="domain">[('id','=',user.id),('partner_id.tz', '!=', False)]</field>
<field name="action_id" eval="ref('base.action_res_users_my')" />
<field name="res_id_field">user.id</field>
</record>
<record model="gamification.goal.definition" id="definition_base_company_data">
<field name="name">Set your Company Data</field>
<field name="description">Write some information about your company (specify at least a name)</field>
<field name="computation_mode">count</field>
<field name="display_mode">boolean</field>
<field name="model_id" eval="ref('base.model_res_company')" />
<field name="domain">[('user_ids', 'in', [user.id]), ('name', '=', 'Your Company')]</field>
<field name="condition">lower</field>
<field name="action_id" eval="ref('base.action_res_company_form')" />
<field name="res_id_field">user.company_id.id</field>
</record>
<record model="gamification.goal.definition" id="definition_base_company_logo">
<field name="name">Set your Company Logo</field>
<field name="computation_mode">count</field>
<field name="display_mode">boolean</field>
<field name="model_id" eval="ref('base.model_res_company')" />
<field name="domain">[('user_ids', 'in', user.id),('logo', '!=', False)]</field>
<field name="action_id" eval="ref('base.action_res_company_form')" />
<field name="res_id_field">user.company_id.id</field>
</record>
<record id="action_new_simplified_res_users" model="ir.actions.act_window">
<field name="name">Create User</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.users</field>
<field name="view_type">form</field>
<field name="target">current</field>
<field name="view_id" ref="base.view_users_simple_form"/>
<field name="context">{'default_groups_ref': ['base.group_user']}</field>
<field name="help">Create and manage users that will connect to the system. Users can be deactivated should there be a period of time during which they will/should not connect to the system. You can assign them groups in order to give them specific access to the applications they need to use in the system.</field>
</record>
<record model="gamification.goal.definition" id="definition_base_invite">
<field name="name">Invite new Users</field>
<field name="description">Create at least another user</field>
<field name="display_mode">boolean</field>
<field name="computation_mode">count</field>
<field name="model_id" eval="ref('base.model_res_users')" />
<field name="domain">[('id', '!=', user.id)]</field>
<field name="action_id" eval="ref('action_new_simplified_res_users')" />
</record>
<record model="gamification.goal.definition" id="definition_nbr_following">
<field name="name">Mail Group Following</field>
<field name="description">Follow mail groups to receive news</field>
<field name="computation_mode">python</field>
<field name="compute_code">result = pool.get('mail.followers').search(cr, uid, [('res_model', '=', 'mail.group'), ('partner_id', '=', object.user_id.partner_id.id)], count=True, context=context)</field>
<field name="action_id" eval="ref('mail.action_view_groups')" />
</record>
<!-- challenges -->
<record model="gamification.challenge" id="challenge_base_discover">
<field name="name">Complete your Profile</field>
<field name="period">once</field>
<field name="visibility_mode">personal</field>
<field name="report_message_frequency">never</field>
<field name="autojoin_group_id" eval="ref('base.group_user')" />
<field name="state">inprogress</field>
<field name="category">other</field>
</record>
<record model="gamification.challenge" id="challenge_base_configure">
<field name="name">Setup your Company</field>
<field name="period">once</field>
<field name="visibility_mode">personal</field>
<field name="report_message_frequency">never</field>
<field name="user_ids" eval="[(4, ref('base.user_root'))]" />
<field name="state">inprogress</field>
<field name="category">other</field>
</record>
<!-- lines -->
<record model="gamification.challenge.line" id="line_base_discover1">
<field name="definition_id" eval="ref('definition_base_timezone')" />
<field name="target_goal">1</field>
<field name="challenge_id" eval="ref('challenge_base_discover')" />
</record>
<record model="gamification.challenge.line" id="line_base_admin2">
<field name="definition_id" eval="ref('definition_base_company_logo')" />
<field name="target_goal">1</field>
<field name="challenge_id" eval="ref('challenge_base_configure')" />
</record>
<record model="gamification.challenge.line" id="line_base_admin1">
<field name="definition_id" eval="ref('definition_base_company_data')" />
<field name="target_goal">0</field>
<field name="challenge_id" eval="ref('challenge_base_configure')" />
</record>
<record model="gamification.challenge.line" id="line_base_admin3">
<field name="definition_id" eval="ref('definition_base_invite')" />
<field name="target_goal">1</field>
<field name="challenge_id" eval="ref('challenge_base_configure')" />
</record>
</data>
</openerp>

View File

@ -0,0 +1,86 @@
<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>

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import goal
import challenge
import res_users
import badge

View File

@ -0,0 +1,276 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from openerp.tools.translate import _
from datetime import date
import logging
_logger = logging.getLogger(__name__)
class gamification_badge_user(osv.Model):
"""User having received a badge"""
_name = 'gamification.badge.user'
_description = 'Gamification user badge'
_order = "create_date desc"
_columns = {
'user_id': fields.many2one('res.users', string="User", required=True),
'sender_id': fields.many2one('res.users', string="Sender", help="The user who has send the badge"),
'badge_id': fields.many2one('gamification.badge', string='Badge', required=True),
'comment': fields.text('Comment'),
'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
'create_date': fields.datetime('Created', readonly=True),
'create_uid': fields.many2one('res.users', string='Creator', readonly=True),
}
def _send_badge(self, cr, uid, ids, context=None):
"""Send a notification to a user for receiving a badge
Does not verify constrains on badge granting.
The users are added to the owner_ids (create badge_user if needed)
The stats counters are incremented
:param ids: list(int) of badge users that will receive the badge
"""
res = True
temp_obj = self.pool.get('email.template')
user_obj = self.pool.get('res.users')
template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_badge_received', context)
for badge_user in self.browse(cr, uid, ids, context=context):
body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.badge.user', badge_user.id, context=context)
res = user_obj.message_post(cr, uid, badge_user.user_id.id, body=body_html, context=context)
return res
def create(self, cr, uid, vals, context=None):
self.pool.get('gamification.badge').check_granting(cr, uid, badge_id=vals.get('badge_id'), context=context)
return super(gamification_badge_user, self).create(cr, uid, vals, context=context)
class gamification_badge(osv.Model):
"""Badge object that users can send and receive"""
CAN_GRANT = 1
NOBODY_CAN_GRANT = 2
USER_NOT_VIP = 3
BADGE_REQUIRED = 4
TOO_MANY = 5
_name = 'gamification.badge'
_description = 'Gamification badge'
_inherit = ['mail.thread']
def _get_owners_info(self, cr, uid, ids, name, args, context=None):
"""Return:
the list of unique res.users ids having received this badge
the total number of time this badge was granted
the total number of users this badge was granted to
"""
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
res = list(set(owner.user_id.id for owner in obj.owner_ids))
result[obj.id] = {
'unique_owner_ids': res,
'stat_count': len(obj.owner_ids),
'stat_count_distinct': len(res)
}
return result
def _get_badge_user_stats(self, cr, uid, ids, name, args, context=None):
"""Return stats related to badge users"""
result = dict.fromkeys(ids, False)
badge_user_obj = self.pool.get('gamification.badge.user')
first_month_day = date.today().replace(day=1).strftime(DF)
for bid in ids:
result[bid] = {
'stat_my': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid)], context=context, count=True),
'stat_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_date', '>=', first_month_day)], context=context, count=True),
'stat_my_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True),
'stat_my_monthly_sending': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_uid', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True)
}
return result
def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
"""Computes the number of badges remaining the user can send
0 if not allowed or no remaining
integer if limited sending
-1 if infinite (should not be displayed)
"""
result = dict.fromkeys(ids, False)
for badge in self.browse(cr, uid, ids, context=context):
if self._can_grant_badge(cr, uid, badge.id, context) != 1:
# if the user cannot grant this badge at all, result is 0
result[badge.id] = 0
elif not badge.rule_max:
# if there is no limitation, -1 is returned which means 'infinite'
result[badge.id] = -1
else:
result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
return result
_columns = {
'name': fields.char('Badge', required=True, translate=True),
'description': fields.text('Description'),
'image': fields.binary("Image", help="This field holds the image used for the badge, limited to 256x256"),
'rule_auth': fields.selection([
('everyone', 'Everyone'),
('users', 'A selected list of users'),
('having', 'People having some badges'),
('nobody', 'No one, assigned through challenges'),
],
string="Allowance to Grant",
help="Who can grant this badge",
required=True),
'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users',
string='Authorized Users',
help="Only these people can give this badge"),
'rule_auth_badge_ids': fields.many2many('gamification.badge',
'rel_badge_badge', 'badge1_id', 'badge2_id',
string='Required Badges',
help="Only the people having these badges can give this badge"),
'rule_max': fields.boolean('Monthly Limited Sending',
help="Check to set a monthly limit per person of sending this badge"),
'rule_max_number': fields.integer('Limitation Number',
help="The maximum number of time this badge can be sent per month per person."),
'stat_my_monthly_sending': fields.function(_get_badge_user_stats,
type="integer",
string='My Monthly Sending Total',
multi='badge_users',
help="The number of time the current user has sent this badge this month."),
'remaining_sending': fields.function(_remaining_sending_calc, type='integer',
string='Remaining Sending Allowed', help="If a maxium is set"),
'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
string="Reward of Challenges"),
'goal_definition_ids': fields.many2many('gamification.goal.definition', 'badge_unlocked_definition_rel',
string='Rewarded by',
help="The users that have succeeded theses goals will receive automatically the badge."),
'owner_ids': fields.one2many('gamification.badge.user', 'badge_id',
string='Owners', help='The list of instances of this badge granted to users'),
'active': fields.boolean('Active'),
'unique_owner_ids': fields.function(_get_owners_info,
string='Unique Owners',
help="The list of unique users having received this badge.",
multi='unique_users',
type="many2many", relation="res.users"),
'stat_count': fields.function(_get_owners_info, string='Total',
type="integer",
multi='unique_users',
help="The number of time this badge has been received."),
'stat_count_distinct': fields.function(_get_owners_info,
type="integer",
string='Number of users',
multi='unique_users',
help="The number of time this badge has been received by unique users."),
'stat_this_month': fields.function(_get_badge_user_stats,
type="integer",
string='Monthly total',
multi='badge_users',
help="The number of time this badge has been received this month."),
'stat_my': fields.function(_get_badge_user_stats, string='My Total',
type="integer",
multi='badge_users',
help="The number of time the current user has received this badge."),
'stat_my_this_month': fields.function(_get_badge_user_stats,
type="integer",
string='My Monthly Total',
multi='badge_users',
help="The number of time the current user has received this badge this month."),
}
_defaults = {
'rule_auth': 'everyone',
'active': True,
}
def check_granting(self, cr, uid, badge_id, context=None):
"""Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception
if not
Do not check for SUPERUSER_ID
"""
status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
if status_code == self.CAN_GRANT:
return True
elif status_code == self.NOBODY_CAN_GRANT:
raise osv.except_osv(_('Warning!'), _('This badge can not be sent by users.'))
elif status_code == self.USER_NOT_VIP:
raise osv.except_osv(_('Warning!'), _('You are not in the user allowed list.'))
elif status_code == self.BADGE_REQUIRED:
raise osv.except_osv(_('Warning!'), _('You do not have the required badges.'))
elif status_code == self.TOO_MANY:
raise osv.except_osv(_('Warning!'), _('You have already sent this badge too many time this month.'))
else:
_logger.exception("Unknown badge status code: %d" % int(status_code))
return False
def _can_grant_badge(self, cr, uid, badge_id, context=None):
"""Check if a user can grant a badge to another user
:param uid: the id of the res.users trying to send the badge
:param badge_id: the granted badge id
:return: integer representing the permission.
"""
if uid == SUPERUSER_ID:
return self.CAN_GRANT
badge = self.browse(cr, uid, badge_id, context=context)
if badge.rule_auth == 'nobody':
return self.NOBODY_CAN_GRANT
elif badge.rule_auth == 'users' and uid not in [user.id for user in badge.rule_auth_user_ids]:
return self.USER_NOT_VIP
elif badge.rule_auth == 'having':
all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', uid)], context=context)
for required_badge in badge.rule_auth_badge_ids:
if required_badge.id not in all_user_badges:
return self.BADGE_REQUIRED
if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
return self.TOO_MANY
# badge.rule_auth == 'everyone' -> no check
return self.CAN_GRANT
def check_progress(self, cr, uid, context=None):
try:
model, res_id = template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
except ValueError:
return True
badge_user_obj = self.pool.get('gamification.badge.user')
if not badge_user_obj.search(cr, uid, [('user_id', '=', uid), ('badge_id', '=', res_id)], context=context):
values = {
'user_id': uid,
'badge_id': res_id,
}
badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)
return True

View File

@ -0,0 +1,821 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from openerp.tools.translate import _
from datetime import date, datetime, timedelta
import calendar
import logging
_logger = logging.getLogger(__name__)
# display top 3 in ranking, could be db variable
MAX_VISIBILITY_RANKING = 3
def start_end_date_for_period(period, default_start_date=False, default_end_date=False):
"""Return the start and end date for a goal period based on today
:return: (start_date, end_date), datetime.date objects, False if the period is
not defined or unknown"""
today = date.today()
if period == 'daily':
start_date = today
end_date = start_date
elif period == 'weekly':
delta = timedelta(days=today.weekday())
start_date = today - delta
end_date = start_date + timedelta(days=7)
elif period == 'monthly':
month_range = calendar.monthrange(today.year, today.month)
start_date = today.replace(day=1)
end_date = today.replace(day=month_range[1])
elif period == 'yearly':
start_date = today.replace(month=1, day=1)
end_date = today.replace(month=12, day=31)
else: # period == 'once':
start_date = default_start_date # for manual goal, start each time
end_date = default_end_date
if start_date and end_date:
return (start_date.strftime(DF), end_date.strftime(DF))
else:
return (start_date, end_date)
class gamification_challenge(osv.Model):
"""Gamification challenge
Set of predifined objectives assigned to people with rules for recurrence and
rewards
If 'user_ids' is defined and 'period' is different than 'one', the set will
be assigned to the users for each period (eg: every 1st of each month if
'monthly' is selected)
"""
_name = 'gamification.challenge'
_description = 'Gamification challenge'
_inherit = 'mail.thread'
def _get_next_report_date(self, cr, uid, ids, field_name, arg, context=None):
"""Return the next report date based on the last report date and report
period.
:return: a string in DEFAULT_SERVER_DATE_FORMAT representing the date"""
res = {}
for challenge in self.browse(cr, uid, ids, context):
last = datetime.strptime(challenge.last_report_date, DF).date()
if challenge.report_message_frequency == 'daily':
next = last + timedelta(days=1)
res[challenge.id] = next.strftime(DF)
elif challenge.report_message_frequency == 'weekly':
next = last + timedelta(days=7)
res[challenge.id] = next.strftime(DF)
elif challenge.report_message_frequency == 'monthly':
month_range = calendar.monthrange(last.year, last.month)
next = last.replace(day=month_range[1]) + timedelta(days=1)
res[challenge.id] = next.strftime(DF)
elif challenge.report_message_frequency == 'yearly':
res[challenge.id] = last.replace(year=last.year + 1).strftime(DF)
# frequency == 'once', reported when closed only
else:
res[challenge.id] = False
return res
def _get_categories(self, cr, uid, context=None):
return [
('hr', 'Human Ressources / Engagement'),
('other', 'Settings / Gamification Tools'),
]
def _get_report_template(self, cr, uid, context=None):
try:
return self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'simple_report_template')[1]
except ValueError:
return False
_order = 'end_date, start_date, name, id'
_columns = {
'name': fields.char('Challenge Name', required=True, translate=True),
'description': fields.text('Description', translate=True),
'state': fields.selection([
('draft', 'Draft'),
('inprogress', 'In Progress'),
('done', 'Done'),
],
string='State', required=True, track_visibility='onchange'),
'manager_id': fields.many2one('res.users',
string='Responsible', help="The user responsible for the challenge."),
'user_ids': fields.many2many('res.users', 'user_ids',
string='Users',
help="List of users participating to the challenge"),
'autojoin_group_id': fields.many2one('res.groups',
string='Auto-subscription Group',
help='Group of users whose members will be automatically added to user_ids once the challenge is started'),
'period': fields.selection([
('once', 'Non recurring'),
('daily', 'Daily'),
('weekly', 'Weekly'),
('monthly', 'Monthly'),
('yearly', 'Yearly')
],
string='Periodicity',
help='Period of automatic goal assigment. If none is selected, should be launched manually.',
required=True),
'start_date': fields.date('Start Date',
help="The day a new challenge will be automatically started. If no periodicity is set, will use this date as the goal start date."),
'end_date': fields.date('End Date',
help="The day a new challenge will be automatically closed. If no periodicity is set, will use this date as the goal end date."),
'invited_user_ids': fields.many2many('res.users', 'invited_user_ids',
string="Suggest to users"),
'line_ids': fields.one2many('gamification.challenge.line', 'challenge_id',
string='Lines',
help="List of goals that will be set",
required=True),
'reward_id': fields.many2one('gamification.badge', string="For Every Succeding User"),
'reward_first_id': fields.many2one('gamification.badge', string="For 1st user"),
'reward_second_id': fields.many2one('gamification.badge', string="For 2nd user"),
'reward_third_id': fields.many2one('gamification.badge', string="For 3rd user"),
'reward_failure': fields.boolean('Reward Bests if not Succeeded?'),
'visibility_mode': fields.selection([
('personal', 'Individual Goals'),
('ranking', 'Leader Board (Group Ranking)'),
],
string="Display Mode", required=True),
'report_message_frequency': fields.selection([
('never', 'Never'),
('onchange', 'On change'),
('daily', 'Daily'),
('weekly', 'Weekly'),
('monthly', 'Monthly'),
('yearly', 'Yearly')
],
string="Report Frequency", required=True),
'report_message_group_id': fields.many2one('mail.group',
string='Send a copy to',
help='Group that will receive a copy of the report in addition to the user'),
'report_template_id': fields.many2one('email.template', string="Report Template", required=True),
'remind_update_delay': fields.integer('Non-updated manual goals will be reminded after',
help="Never reminded if no value or zero is specified."),
'last_report_date': fields.date('Last Report Date'),
'next_report_date': fields.function(_get_next_report_date,
type='date', string='Next Report Date', store=True),
'category': fields.selection(lambda s, *a, **k: s._get_categories(*a, **k),
string="Appears in", help="Define the visibility of the challenge through menus", required=True),
}
_defaults = {
'period': 'once',
'state': 'draft',
'visibility_mode': 'personal',
'report_message_frequency': 'never',
'last_report_date': fields.date.today,
'start_date': fields.date.today,
'manager_id': lambda s, cr, uid, c: uid,
'category': 'hr',
'reward_failure': False,
'report_template_id': lambda s, *a, **k: s._get_report_template(*a, **k),
}
def create(self, cr, uid, vals, context=None):
"""Overwrite the create method to add the user of groups"""
# add users when change the group auto-subscription
if vals.get('autojoin_group_id'):
new_group = self.pool.get('res.groups').browse(cr, uid, vals['autojoin_group_id'], context=context)
if not vals.get('user_ids'):
vals['user_ids'] = []
vals['user_ids'] += [(4, user.id) for user in new_group.users]
create_res = super(gamification_challenge, self).create(cr, uid, vals, context=context)
# subscribe new users to the challenge
if vals.get('user_ids'):
# done with browse after super to be sure catch all after orm process
challenge = self.browse(cr, uid, create_res, context=context)
self.message_subscribe_users(cr, uid, [challenge.id], [user.id for user in challenge.user_ids], context=context)
return create_res
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int,long)):
ids = [ids]
# add users when change the group auto-subscription
if vals.get('autojoin_group_id'):
new_group = self.pool.get('res.groups').browse(cr, uid, vals['autojoin_group_id'], context=context)
if not vals.get('user_ids'):
vals['user_ids'] = []
vals['user_ids'] += [(4, user.id) for user in new_group.users]
if vals.get('state') == 'inprogress':
# starting a challenge
if not vals.get('autojoin_group_id'):
# starting challenge, add users in autojoin group
if not vals.get('user_ids'):
vals['user_ids'] = []
for challenge in self.browse(cr, uid, ids, context=context):
if challenge.autojoin_group_id:
vals['user_ids'] += [(4, user.id) for user in challenge.autojoin_group_id.users]
self.generate_goals_from_challenge(cr, uid, ids, context=context)
elif vals.get('state') == 'done':
self.check_challenge_reward(cr, uid, ids, force=True, context=context)
elif vals.get('state') == 'draft':
# resetting progress
if self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', 'in', ids), ('state', 'in', ['inprogress', 'inprogress_update'])], context=context):
raise osv.except_osv("Error", "You can not reset a challenge with unfinished goals.")
write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context)
# subscribe new users to the challenge
if vals.get('user_ids'):
# done with browse after super if changes in groups
for challenge in self.browse(cr, uid, ids, context=context):
self.message_subscribe_users(cr, uid, [challenge.id], [user.id for user in challenge.user_ids], context=context)
return write_res
##### Update #####
def _cron_update(self, cr, uid, context=None, ids=False):
"""Daily cron check.
- Start planned challenges (in draft and with start_date = today)
- Create the missing goals (eg: modified the challenge to add lines)
- Update every running challenge
"""
# start planned challenges
planned_challenge_ids = self.search(cr, uid, [
('state', '=', 'draft'),
('start_date', '<=', fields.date.today())])
self.write(cr, uid, planned_challenge_ids, {'state': 'inprogress'}, context=context)
# close planned challenges
planned_challenge_ids = self.search(cr, uid, [
('state', '=', 'inprogress'),
('end_date', '>=', fields.date.today())])
self.write(cr, uid, planned_challenge_ids, {'state': 'done'}, context=context)
if not ids:
ids = self.search(cr, uid, [('state', '=', 'inprogress')], context=context)
return self._update_all(cr, uid, ids, context=context)
def _update_all(self, cr, uid, ids, context=None):
"""Update the challenges and related goals
:param list(int) ids: the ids of the challenges to update, if False will
update only challenges in progress."""
if isinstance(ids, (int,long)):
ids = [ids]
goal_obj = self.pool.get('gamification.goal')
# we use yesterday to update the goals that just ended
yesterday = date.today() - timedelta(days=1)
goal_ids = goal_obj.search(cr, uid, [
('challenge_id', 'in', ids),
'|',
('state', 'in', ('inprogress', 'inprogress_update')),
'&',
('state', 'in', ('reached', 'failed')),
'|',
('end_date', '>=', yesterday.strftime(DF)),
('end_date', '=', False)
], context=context)
# update every running goal already generated linked to selected challenges
goal_obj.update(cr, uid, goal_ids, context=context)
for challenge in self.browse(cr, uid, ids, context=context):
if challenge.autojoin_group_id:
# check in case of new users in challenge, this happens if manager removed users in challenge manually
self.write(cr, uid, [challenge.id], {'user_ids': [(4, user.id) for user in challenge.autojoin_group_id.users]}, context=context)
self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context)
# goals closed but still opened at the last report date
closed_goals_to_report = goal_obj.search(cr, uid, [
('challenge_id', '=', challenge.id),
('start_date', '>=', challenge.last_report_date),
('end_date', '<=', challenge.last_report_date)
])
if len(closed_goals_to_report) > 0:
# some goals need a final report
self.report_progress(cr, uid, challenge, subset_goal_ids=closed_goals_to_report, context=context)
if fields.date.today() == challenge.next_report_date:
self.report_progress(cr, uid, challenge, context=context)
self.check_challenge_reward(cr, uid, ids, context=context)
return True
def quick_update(self, cr, uid, challenge_id, context=None):
"""Update all the goals of a challenge, no generation of new goals"""
goal_ids = self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', '=', challenge_id)], context=context)
self.pool.get('gamification.goal').update(cr, uid, goal_ids, context=context)
return True
def action_check(self, cr, uid, ids, context=None):
"""Check a challenge
Create goals that haven't been created yet (eg: if added users)
Recompute the current value for each goal related"""
return self._update_all(cr, uid, ids=ids, context=context)
def action_report_progress(self, cr, uid, ids, context=None):
"""Manual report of a goal, does not influence automatic report frequency"""
if isinstance(ids, (int,long)):
ids = [ids]
for challenge in self.browse(cr, uid, ids, context):
self.report_progress(cr, uid, challenge, context=context)
return True
##### Automatic actions #####
def generate_goals_from_challenge(self, cr, uid, ids, context=None):
"""Generate the goals for each line and user.
If goals already exist for this line and user, the line is skipped. This
can be called after each change in the list of users or lines.
:param list(int) ids: the list of challenge concerned"""
for challenge in self.browse(cr, uid, ids, context):
(start_date, end_date) = start_end_date_for_period(challenge.period)
# if no periodicity, use challenge dates
if not start_date and challenge.start_date:
start_date = challenge.start_date
if not end_date and challenge.end_date:
end_date = challenge.end_date
for line in challenge.line_ids:
for user in challenge.user_ids:
goal_obj = self.pool.get('gamification.goal')
domain = [('line_id', '=', line.id), ('user_id', '=', user.id)]
if start_date:
domain.append(('start_date', '=', start_date))
# goal already existing for this line ?
if len(goal_obj.search(cr, uid, domain, context=context)) > 0:
# resume canceled goals
domain.append(('state', '=', 'canceled'))
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
goal_obj.update(cr, uid, canceled_goal_ids, context=context)
# skip to next user
continue
values = {
'definition_id': line.definition_id.id,
'line_id': line.id,
'user_id': user.id,
'target_goal': line.target_goal,
'state': 'inprogress',
}
if start_date:
values['start_date'] = start_date
if end_date:
values['end_date'] = end_date
if challenge.remind_update_delay:
values['remind_update_delay'] = challenge.remind_update_delay
new_goal_id = goal_obj.create(cr, uid, values, context)
goal_obj.update(cr, uid, [new_goal_id], context=context)
return True
##### 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
: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)
:restrict_goal_ids: <list(int)> compute only the results for this subset if gamification.goal ids, if False retrieve every goal of current running challenge
:restrict_top: <int> for challenge lines where visibility_mode == 'ranking', retrieve only these bests results and itself, if False retrieve all
restrict_goal_ids has priority over restrict_top
format list
# if visibility_mode == 'ranking'
{
'name': <gamification.goal.description name>,
'description': <gamification.goal.description description>,
'condition': <reach condition {lower,higher}>,
'computation_mode': <target computation {manually,count,sum,python}>,
'monetary': <{True,False}>,
'suffix': <value suffix>,
'action': <{True,False}>,
'display_mode': <{progress,boolean}>,
'target': <challenge line target>,
'own_goal_id': <gamification.goal id where user_id == uid>,
'goals': [
{
'id': <gamification.goal id>,
'rank': <user ranking>,
'user_id': <res.users id>,
'name': <res.users name>,
'state': <gamification.goal state {draft,inprogress,inprogress_update,reached,failed,canceled}>,
'completeness': <percentage>,
'current': <current value>,
}
]
},
# if visibility_mode == 'personal'
{
'id': <gamification.goal id>,
'name': <gamification.goal.description name>,
'description': <gamification.goal.description description>,
'condition': <reach condition {lower,higher}>,
'computation_mode': <target computation {manually,count,sum,python}>,
'monetary': <{True,False}>,
'suffix': <value suffix>,
'action': <{True,False}>,
'display_mode': <{progress,boolean}>,
'target': <challenge line target>,
'state': <gamification.goal state {draft,inprogress,inprogress_update,reached,failed,canceled}>,
'completeness': <percentage>,
'current': <current value>,
}
"""
goal_obj = self.pool.get('gamification.goal')
(start_date, end_date) = start_end_date_for_period(challenge.period)
res_lines = []
for line in challenge.line_ids:
line_data = {
'name': line.definition_id.name,
'description': line.definition_id.description,
'condition': line.definition_id.condition,
'computation_mode': line.definition_id.computation_mode,
'monetary': line.definition_id.monetary,
'suffix': line.definition_id.suffix,
'action': True if line.definition_id.action_id else False,
'display_mode': line.definition_id.display_mode,
'target': line.target_goal,
}
domain = [
('line_id', '=', line.id),
('state', '!=', 'draft'),
]
if restrict_goal_ids:
domain.append(('ids', 'in', restrict_goal_ids))
else:
# if no subset goals, use the dates for restriction
if start_date:
domain.append(('start_date', '=', start_date))
if end_date:
domain.append(('end_date', '=', end_date))
if challenge.visibility_mode == 'personal':
if not user_id:
raise osv.except_osv(_('Error!'),_("Retrieving progress for personal challenge without user information"))
domain.append(('user_id', '=', user_id))
sorting = goal_obj._order
limit = 1
# initialise in case search returns no results
line_data.update({
'id': 0,
'current': 0,
'completeness': 0,
'state': 'draft',
})
else:
line_data.update({
'own_goal_id': False,
'goals': [],
})
sorting = "completeness desc, current desc"
limit = False
goal_ids = goal_obj.search(cr, uid, domain, order=sorting, limit=limit, context=context)
ranking = 0
for goal in goal_obj.browse(cr, uid, goal_ids, context=context):
if challenge.visibility_mode == 'personal':
# limit=1 so only one result
line_data.update({
'id': goal.id,
'current': goal.current,
'completeness': goal.completeness,
'state': goal.state,
})
else:
ranking += 1
if user_id and goal.user_id.id == user_id:
line_data['own_goal_id'] = goal.id
elif restrict_top and ranking > restrict_top:
# not own goal, over top, skipping
continue
line_data['goals'].append({
'id': goal.id,
'user_id': goal.user_id.id,
'name': goal.user_id.name,
'rank': ranking,
'current': goal.current,
'completeness': goal.completeness,
'state': goal.state,
})
res_lines.append(line_data)
return res_lines
##### Reporting #####
def report_progress(self, cr, uid, challenge, context=None, users=False, subset_goal_ids=False):
"""Post report about the progress of the goals
:param challenge: the challenge object that need to be reported
:param users: the list(res.users) of users that are concerned by
the report. If False, will send the report to every user concerned
(goal users and group that receive a copy). Only used for challenge with
a visibility mode set to 'personal'.
:param goal_ids: the list(int) of goal ids linked to the challenge for
the report. If not specified, use the goals for the current challenge
period. This parameter can be used to produce report for previous challenge
periods.
:param subset_goal_ids: a list(int) of goal ids to restrict the report
"""
if context is None:
context = {}
temp_obj = self.pool.get('email.template')
ctx = context.copy()
if challenge.visibility_mode == 'ranking':
lines_boards = self._get_serialized_challenge_lines(cr, uid, challenge, user_id=False, restrict_goal_ids=subset_goal_ids, restrict_top=False, context=context)
ctx.update({'challenge_lines': lines_boards})
body_html = temp_obj.render_template(cr, uid, challenge.report_template_id.body_html, 'gamification.challenge', challenge.id, context=ctx)
# send to every follower of the challenge
self.message_post(cr, uid, challenge.id,
body=body_html,
context=context,
subtype='mail.mt_comment')
if challenge.report_message_group_id:
self.pool.get('mail.group').message_post(cr, uid, challenge.report_message_group_id.id,
body=body_html,
context=context,
subtype='mail.mt_comment')
else:
# generate individual reports
for user in users or challenge.user_ids:
goals = self._get_serialized_challenge_lines(cr, uid, challenge, user.id, restrict_goal_ids=subset_goal_ids, context=context)
if not goals:
continue
ctx.update({'challenge_lines': goals})
body_html = temp_obj.render_template(cr, user.id, challenge.report_template_id.body_html, 'gamification.challenge', challenge.id, context=ctx)
# send message only to users, not on the challenge
self.message_post(cr, uid, 0,
body=body_html,
partner_ids=[(4, user.partner_id.id)],
context=context,
subtype='mail.mt_comment')
if challenge.report_message_group_id:
self.pool.get('mail.group').message_post(cr, uid, challenge.report_message_group_id.id,
body=body_html,
context=context,
subtype='mail.mt_comment')
return self.write(cr, uid, challenge.id, {'last_report_date': fields.date.today()}, context=context)
##### Challenges #####
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
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.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)
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
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)
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')
id = result and result[1] or False
result = self.pool.get('ir.actions.act_window').read(cr, uid, [id], context=context)[0]
result['res_id'] = challenge_id
return result
def check_challenge_reward(self, cr, uid, ids, force=False, context=None):
"""Actions for the end of a challenge
If a reward was selected, grant it to the correct users.
Rewards granted at:
- the end date for a challenge with no periodicity
- the end of a period for challenge with periodicity
- when a challenge is manually closed
(if no end date, a running challenge is never rewarded)
"""
if isinstance(ids, (int,long)):
ids = [ids]
context = context or {}
for challenge in self.browse(cr, uid, ids, context=context):
(start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date)
yesterday = date.today() - timedelta(days=1)
if end_date == yesterday.strftime(DF) or force:
# open chatter message
message_body = _("The challenge %s is finished." % challenge.name)
# reward for everybody succeeding
rewarded_users = []
if challenge.reward_id:
for user in challenge.user_ids:
reached_goal_ids = self.pool.get('gamification.goal').search(cr, uid, [
('challenge_id', '=', challenge.id),
('user_id', '=', user.id),
('start_date', '=', start_date),
('end_date', '=', end_date),
('state', '=', 'reached')
], context=context)
if len(reached_goal_ids) == len(challenge.line_ids):
self.reward_user(cr, uid, user.id, challenge.reward_id.id, context)
rewarded_users.append(user)
if rewarded_users:
message_body += _("<br/>Reward (badge %s) for every succeeding user was sent to %s." % (challenge.reward_id.name, ", ".join([user.name for user in rewarded_users])))
else:
message_body += _("<br/>Nobody has succeeded to reach every goal, no badge is rewared for this challenge.")
# reward bests
if challenge.reward_first_id:
(first_user, second_user, third_user) = self.get_top3_users(cr, uid, challenge, context)
if first_user:
self.reward_user(cr, uid, first_user.id, challenge.reward_first_id.id, context)
message_body += _("<br/>Special rewards were sent to the top competing users. The ranking for this challenge is :")
message_body += "<br/> 1. %s - %s" % (first_user.name, challenge.reward_first_id.name)
else:
message_body += _("Nobody reached the required conditions to receive special badges.")
if second_user and challenge.reward_second_id:
self.reward_user(cr, uid, second_user.id, challenge.reward_second_id.id, context)
message_body += "<br/> 2. %s - %s" % (second_user.name, challenge.reward_second_id.name)
if third_user and challenge.reward_third_id:
self.reward_user(cr, uid, third_user.id, challenge.reward_second_id.id, context)
message_body += "<br/> 3. %s - %s" % (third_user.name, challenge.reward_third_id.name)
self.message_post(cr, uid, challenge.id, body=message_body, context=context)
return True
def get_top3_users(self, cr, uid, challenge, context=None):
"""Get the top 3 users for a defined challenge
Ranking criterias:
1. succeed every goal of the challenge
2. total completeness of each goal (can be over 100)
Top 3 is computed only for users succeeding every goal of the challenge,
except if reward_failure is True, in which case every user is
considered.
:return: ('first', 'second', 'third'), tuple containing the res.users
objects of the top 3 users. If no user meets the criterias for a rank,
it is set to False. Nobody can receive a rank is noone receives the
higher one (eg: if 'second' == False, 'third' will be False)
"""
goal_obj = self.pool.get('gamification.goal')
(start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date)
challengers = []
for user in challenge.user_ids:
all_reached = True
total_completness = 0
# every goal of the user for the running period
goal_ids = goal_obj.search(cr, uid, [
('challenge_id', '=', challenge.id),
('user_id', '=', user.id),
('start_date', '=', start_date),
('end_date', '=', end_date)
], context=context)
for goal in goal_obj.browse(cr, uid, goal_ids, context=context):
if goal.state != 'reached':
all_reached = False
if goal.definition_condition == 'higher':
# can be over 100
total_completness += 100.0 * goal.current / goal.target_goal
elif goal.state == 'reached':
# for lower goals, can not get percentage so 0 or 100
total_completness += 100
challengers.append({'user': user, 'all_reached': all_reached, 'total_completness': total_completness})
sorted_challengers = sorted(challengers, key=lambda k: (k['all_reached'], k['total_completness']), reverse=True)
if len(sorted_challengers) == 0 or (not challenge.reward_failure and not sorted_challengers[0]['all_reached']):
# nobody succeeded
return (False, False, False)
if len(sorted_challengers) == 1 or (not challenge.reward_failure and not sorted_challengers[1]['all_reached']):
# only one user succeeded
return (sorted_challengers[0]['user'], False, False)
if len(sorted_challengers) == 2 or (not challenge.reward_failure and not sorted_challengers[2]['all_reached']):
# only one user succeeded
return (sorted_challengers[0]['user'], sorted_challengers[1]['user'], False)
return (sorted_challengers[0]['user'], sorted_challengers[1]['user'], sorted_challengers[2]['user'])
def reward_user(self, cr, uid, user_id, badge_id, context=None):
"""Create a badge user and send the badge to him
:param user_id: the user to reward
:param badge_id: the concerned badge
"""
badge_user_obj = self.pool.get('gamification.badge.user')
user_badge_id = badge_user_obj.create(cr, uid, {'user_id': user_id, 'badge_id': badge_id}, context=context)
return badge_user_obj._send_badge(cr, uid, [user_badge_id], context=context)
class gamification_challenge_line(osv.Model):
"""Gamification challenge line
Predifined goal for 'gamification_challenge'
These are generic list of goals with only the target goal defined
Should only be created for the gamification_challenge object
"""
_name = 'gamification.challenge.line'
_description = 'Gamification generic goal for challenge'
_order = "sequence, id"
def on_change_definition_id(self, cr, uid, ids, definition_id=False, context=None):
goal_definition = self.pool.get('gamification.goal.definition')
if not definition_id:
return {'value': {'definition_id': False}}
goal_definition = goal_definition.browse(cr, uid, definition_id, context=context)
ret = {
'value': {
'condition': goal_definition.condition,
'definition_full_suffix': goal_definition.full_suffix
}
}
return ret
_columns = {
'name': fields.related('definition_id', 'name', string="Name"),
'challenge_id': fields.many2one('gamification.challenge',
string='Challenge',
required=True,
ondelete="cascade"),
'definition_id': fields.many2one('gamification.goal.definition',
string='Goal Definition',
required=True,
ondelete="cascade"),
'target_goal': fields.float('Target Value to Reach',
required=True),
'sequence': fields.integer('Sequence',
help='Sequence number for ordering'),
'condition': fields.related('definition_id', 'condition', type="selection",
readonly=True, string="Condition", selection=[('lower', '<='), ('higher', '>=')]),
'definition_suffix': fields.related('definition_id', 'suffix', type="char", readonly=True, string="Unit"),
'definition_monetary': fields.related('definition_id', 'monetary', type="boolean", readonly=True, string="Monetary"),
'definition_full_suffix': fields.related('definition_id', 'full_suffix', type="char", readonly=True, string="Suffix"),
}
_default = {
'sequence': 1,
}

View File

@ -0,0 +1,394 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from openerp.tools.safe_eval import safe_eval
from openerp.tools.translate import _
import logging
import time
from datetime import date, datetime, timedelta
_logger = logging.getLogger(__name__)
class gamification_goal_definition(osv.Model):
"""Goal definition
A goal definition contains the way to evaluate an objective
Each module wanting to be able to set goals to the users needs to create
a new gamification_goal_definition
"""
_name = 'gamification.goal.definition'
_description = 'Gamification goal definition'
def _get_suffix(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, '')
for goal in self.browse(cr, uid, ids, context=context):
if goal.suffix and not goal.monetary:
res[goal.id] = goal.suffix
elif goal.monetary:
# use the current user's company currency
user = self.pool.get('res.users').browse(cr, uid, uid, context)
if goal.suffix:
res[goal.id] = "%s %s" % (user.company_id.currency_id.symbol, goal.suffix)
else:
res[goal.id] = user.company_id.currency_id.symbol
else:
res[goal.id] = ""
return res
_columns = {
'name': fields.char('Goal Definition', required=True, translate=True),
'description': fields.text('Goal Description'),
'monetary': fields.boolean('Monetary Value', help="The target and current value are defined in the company currency."),
'suffix': fields.char('Suffix', help="The unit of the target and current values", translate=True),
'full_suffix': fields.function(_get_suffix, type="char", string="Full Suffix", help="The currency and suffix field"),
'computation_mode': fields.selection([
('manually', 'Recorded manually'),
('count', 'Automatic: number of records'),
('sum', 'Automatic: sum on a field'),
('python', 'Automatic: execute a specific Python code'),
],
string="Computation Mode",
help="Defined how will be computed the goals. The result of the operation will be stored in the field 'Current'.",
required=True),
'display_mode': fields.selection([
('progress', 'Progressive (using numerical values)'),
('boolean', 'Exclusive (done or not-done)'),
],
string="Displayed as", required=True),
'model_id': fields.many2one('ir.model',
string='Model',
help='The model object for the field to evaluate'),
'field_id': fields.many2one('ir.model.fields',
string='Field to Sum',
help='The field containing the value to evaluate'),
'field_date_id': fields.many2one('ir.model.fields',
string='Date Field',
help='The date to use for the time period evaluated'),
'domain': fields.char("Filter Domain",
help="Domain for filtering records. The rule can contain reference to 'user' that is a browse record of the current user, e.g. [('user_id', '=', user.id)].",
required=True),
'compute_code': fields.text('Python Code',
help="Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id."),
'condition': fields.selection([
('higher', 'The higher the better'),
('lower', 'The lower the better')
],
string='Goal Performance',
help='A goal is considered as completed when the current value is compared to the value to reach',
required=True),
'action_id': fields.many2one('ir.actions.act_window', string="Action",
help="The action that will be called to update the goal value."),
'res_id_field': fields.char("ID Field of user",
help="The field name on the user profile (res.users) containing the value for res_id for action.")
}
_defaults = {
'condition': 'higher',
'computation_mode': 'manually',
'domain': "[]",
'monetary': False,
'display_mode': 'progress',
}
def number_following(self, cr, uid, model_name="mail.thread", context=None):
"""Return the number of 'model_name' objects the user is following
The model specified in 'model_name' must inherit from mail.thread
"""
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return self.pool.get('mail.followers').search(cr, uid, [('res_model', '=', model_name), ('partner_id', '=', user.partner_id.id)], count=True, context=context)
class gamification_goal(osv.Model):
"""Goal instance for a user
An individual goal for a user on a specified time period"""
_name = 'gamification.goal'
_description = 'Gamification goal instance'
_inherit = 'mail.thread'
def _get_completion(self, cr, uid, ids, field_name, arg, context=None):
"""Return the percentage of completeness of the goal, between 0 and 100"""
res = dict.fromkeys(ids, 0.0)
for goal in self.browse(cr, uid, ids, context=context):
if goal.definition_condition == 'higher':
if goal.current >= goal.target_goal:
res[goal.id] = 100.0
else:
res[goal.id] = round(100.0 * goal.current / goal.target_goal, 2)
elif goal.current < goal.target_goal:
# a goal 'lower than' has only two values possible: 0 or 100%
res[goal.id] = 100.0
else:
res[goal.id] = 0.0
return res
def on_change_definition_id(self, cr, uid, ids, definition_id=False, context=None):
goal_definition = self.pool.get('gamification.goal.definition')
if not definition_id:
return {'value': {'definition_id': False}}
goal_definition = goal_definition.browse(cr, uid, definition_id, context=context)
return {'value': {'computation_mode': goal_definition.computation_mode, 'definition_condition': goal_definition.condition}}
_columns = {
'definition_id': fields.many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade"),
'user_id': fields.many2one('res.users', string='User', required=True),
'line_id': fields.many2one('gamification.challenge.line', string='Goal Line', ondelete="cascade"),
'challenge_id': fields.related('line_id', 'challenge_id',
string="Challenge",
type='many2one',
relation='gamification.challenge',
store=True),
'start_date': fields.date('Start Date'),
'end_date': fields.date('End Date'), # no start and end = always active
'target_goal': fields.float('To Reach',
required=True,
track_visibility='always'), # no goal = global index
'current': fields.float('Current Value', required=True, track_visibility='always'),
'completeness': fields.function(_get_completion, type='float', string='Completeness'),
'state': fields.selection([
('draft', 'Draft'),
('inprogress', 'In progress'),
('inprogress_update', 'In progress (to update)'),
('reached', 'Reached'),
('failed', 'Failed'),
('canceled', 'Canceled'),
],
string='State',
required=True,
track_visibility='always'),
'computation_mode': fields.related('definition_id', 'computation_mode', type='char', string="Computation mode"),
'remind_update_delay': fields.integer('Remind delay',
help="The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value is specified."),
'last_update': fields.date('Last Update',
help="In case of manual goal, reminders are sent if the goal as not been updated for a while (defined in challenge). Ignored in case of non-manual goal or goal not linked to a challenge."),
'definition_description': fields.related('definition_id', 'description', type='char', string='Definition Description', readonly=True),
'definition_condition': fields.related('definition_id', 'condition', type='char', string='Definition Condition', readonly=True),
'definition_suffix': fields.related('definition_id', 'full_suffix', type="char", string="Suffix", readonly=True),
'definition_display': fields.related('definition_id', 'display_mode', type="char", string="Display Mode", readonly=True),
}
_defaults = {
'current': 0,
'state': 'draft',
'start_date': fields.date.today,
}
_order = 'create_date desc, end_date desc, definition_id, id'
def _check_remind_delay(self, cr, uid, goal, context=None):
"""Verify if a goal has not been updated for some time and send a
reminder message of needed.
:return: data to write on the goal object
"""
if goal.remind_update_delay and goal.last_update:
delta_max = timedelta(days=goal.remind_update_delay)
last_update = datetime.strptime(goal.last_update, DF).date()
if date.today() - last_update > delta_max and goal.state == 'inprogress':
# generate a remind report
temp_obj = self.pool.get('email.template')
template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_goal_reminder', context)
body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.goal', goal.id, context=context)
self.message_post(cr, uid, goal.id, body=body_html, partner_ids=[goal.user_id.partner_id.id], context=context, subtype='mail.mt_comment')
return {'state': 'inprogress_update'}
return {}
def update(self, cr, uid, ids, context=None):
"""Update the goals to recomputes values and change of states
If a manual goal is not updated for enough time, the user will be
reminded to do so (done only once, in 'inprogress' state).
If a goal reaches the target value, the status is set to reached
If the end date is passed (at least +1 day, time not considered) without
the target value being reached, the goal is set as failed."""
if context is None:
context = {}
for goal in self.browse(cr, uid, ids, context=context):
towrite = {}
if goal.state in ('draft', 'canceled'):
# skip if goal draft or canceled
continue
if goal.definition_id.computation_mode == 'manually':
towrite.update(self._check_remind_delay(cr, uid, goal, context))
elif goal.definition_id.computation_mode == 'python':
# execute the chosen method
cxt = {
'self': self.pool.get('gamification.goal'),
'object': goal,
'pool': self.pool,
'cr': cr,
'context': dict(context), # copy context to prevent side-effects of eval
'uid': uid,
'result': False,
'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time
}
code = goal.definition_id.compute_code.strip()
safe_eval(code, cxt, mode="exec", nocopy=True)
# the result of the evaluated codeis put in the 'result' local variable, propagated to the context
result = cxt.get('result', False)
if result and type(result) in (float, int, long):
if result != goal.current:
towrite['current'] = result
else:
_logger.exception(_('Invalid return content from the evaluation of %s' % code))
else: # count or sum
obj = self.pool.get(goal.definition_id.model_id.model)
field_date_name = goal.definition_id.field_date_id.name
# eval the domain with user replaced by goal user object
domain = safe_eval(goal.definition_id.domain, {'user': goal.user_id})
# add temporal clause(s) to the domain if fields are filled on the goal
if goal.start_date and field_date_name:
domain.append((field_date_name, '>=', goal.start_date))
if goal.end_date and field_date_name:
domain.append((field_date_name, '<=', goal.end_date))
if goal.definition_id.computation_mode == 'sum':
field_name = goal.definition_id.field_id.name
res = obj.read_group(cr, uid, domain, [field_name], [''], context=context)
new_value = res and res[0][field_name] or 0.0
else: # computation mode = count
new_value = obj.search(cr, uid, domain, context=context, count=True)
# avoid useless write if the new value is the same as the old one
if new_value != goal.current:
towrite['current'] = new_value
# check goal target reached
if (goal.definition_condition == 'higher' and towrite.get('current', goal.current) >= goal.target_goal) or (goal.definition_condition == 'lower' and towrite.get('current', goal.current) <= goal.target_goal):
towrite['state'] = 'reached'
# check goal failure
elif goal.end_date and fields.date.today() > goal.end_date:
towrite['state'] = 'failed'
if towrite:
self.write(cr, uid, [goal.id], towrite, context=context)
return True
def action_start(self, cr, uid, ids, context=None):
"""Mark a goal as started.
This should only be used when creating goals manually (in draft state)"""
self.write(cr, uid, ids, {'state': 'inprogress'}, context=context)
return self.update(cr, uid, ids, context=context)
def action_reach(self, cr, uid, ids, context=None):
"""Mark a goal as reached.
If the target goal condition is not met, the state will be reset to In
Progress at the next goal update until the end date."""
return self.write(cr, uid, ids, {'state': 'reached'}, context=context)
def action_fail(self, cr, uid, ids, context=None):
"""Set the state of the goal to failed.
A failed goal will be ignored in future checks."""
return self.write(cr, uid, ids, {'state': 'failed'}, context=context)
def action_cancel(self, cr, uid, ids, context=None):
"""Reset the completion after setting a goal as reached or failed.
This is only the current state, if the date and/or target criterias
match the conditions for a change of state, this will be applied at the
next goal update."""
return self.write(cr, uid, ids, {'state': 'inprogress'}, context=context)
def create(self, cr, uid, vals, context=None):
"""Overwrite the create method to add a 'no_remind_goal' field to True"""
if context is None:
context = {}
context['no_remind_goal'] = True
return super(gamification_goal, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
"""Overwrite the write method to update the last_update field to today
If the current value is changed and the report frequency is set to On
change, a report is generated
"""
if context is None:
context = {}
vals['last_update'] = fields.date.today()
result = super(gamification_goal, self).write(cr, uid, ids, vals, context=context)
for goal in self.browse(cr, uid, ids, context=context):
if goal.state != "draft" and ('definition_id' in vals or 'user_id' in vals):
# avoid drag&drop in kanban view
raise osv.except_osv(_('Error!'), _('Can not modify the configuration of a started goal'))
if vals.get('current'):
if 'no_remind_goal' in context:
# new goals should not be reported
continue
if goal.challenge_id and goal.challenge_id.report_message_frequency == 'onchange':
self.pool.get('gamification.challenge').report_progress(cr, SUPERUSER_ID, goal.challenge_id, users=[goal.user_id], context=context)
return result
def get_action(self, cr, uid, goal_id, context=None):
"""Get the ir.action related to update the goal
In case of a manual goal, should return a wizard to update the value
:return: action description in a dictionnary
"""
goal = self.browse(cr, uid, goal_id, context=context)
if goal.definition_id.action_id:
# open a the action linked to the goal
action = goal.definition_id.action_id.read()[0]
if goal.definition_id.res_id_field:
current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
action['res_id'] = safe_eval(goal.definition_id.res_id_field, {'user': current_user})
# if one element to display, should see it in form mode if possible
action['views'] = [(view_id, mode) for (view_id, mode) in action['views'] if mode == 'form'] or action['views']
return action
if goal.computation_mode == 'manually':
# open a wizard window to update the value manually
action = {
'name': _("Update %s") % goal.definition_id.name,
'id': goal_id,
'type': 'ir.actions.act_window',
'views': [[False, 'form']],
'target': 'new',
'context': {'default_goal_id': goal_id, 'default_current': goal.current},
'res_model': 'gamification.goal.wizard'
}
return action
return False

View File

@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp.osv import osv
from challenge import MAX_VISIBILITY_RANKING
class res_users_gamification_group(osv.Model):
""" Update of res.users class
- if adding groups to an user, check gamification.challenge linked to
this group, and the user. This is done by overriding the write method.
"""
_name = 'res.users'
_inherit = ['res.users']
def write(self, cr, uid, ids, vals, context=None):
"""Overwrite to autosubscribe users if added to a group marked as autojoin, user will be added to challenge"""
write_res = super(res_users_gamification_group, self).write(cr, uid, ids, vals, context=context)
if vals.get('groups_id'):
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
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)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
return write_res
def create(self, cr, uid, vals, context=None):
"""Overwrite to autosubscribe users if added to a group marked as autojoin, user will be added to challenge"""
write_res = super(res_users_gamification_group, self).create(cr, uid, vals, context=context)
if vals.get('groups_id'):
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
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)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
return write_res
# def get_goals_todo_info(self, cr, uid, context=None):
def get_serialised_gamification_summary(self, cr, uid, context=None):
return self._serialised_goals_summary(cr, uid, user_id=uid, context=context)
def _serialised_goals_summary(self, cr, uid, user_id, context=None):
"""Return a serialised list of goals assigned to the user, grouped by challenge
[
{
'id': <gamification.challenge id>,
'name': <gamification.challenge name>,
'visibility_mode': <visibility {ranking,personal}>,
'currency': <res.currency id>,
'lines': [(see gamification_challenge._get_serialized_challenge_lines() format)]
},
]
"""
all_goals_info = []
challenge_obj = self.pool.get('gamification.challenge')
user = self.browse(cr, uid, uid, context=context)
challenge_ids = challenge_obj.search(cr, uid, [('user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
# serialize goals info to be able to use it in javascript
all_goals_info.append({
'id': challenge.id,
'name': challenge.name,
'visibility_mode': challenge.visibility_mode,
'currency': user.company_id.currency_id.id,
'lines': challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context),
})
return all_goals_info
def get_challenge_suggestions(self, cr, uid, context=None):
"""Return the list of challenges suggested to the user"""
challenge_info = []
challenge_obj = self.pool.get('gamification.challenge')
challenge_ids = challenge_obj.search(cr, uid, [('invited_user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
values = {
'id': challenge.id,
'name': challenge.name,
'description': challenge.description,
}
challenge_info.append(values)
return challenge_info
class res_groups_gamification_group(osv.Model):
""" Update of res.groups class
- if adding users from a group, check gamification.challenge linked to
this group, and the user. This is done by overriding the write method.
"""
_name = 'res.groups'
_inherit = 'res.groups'
# No need to overwrite create as very unlikely to be the value in the autojoin_group_id field
def write(self, cr, uid, ids, vals, context=None):
"""Overwrite to autosubscribe users if add users to a group marked as autojoin, these will be added to the challenge"""
write_res = super(res_groups_gamification_group, self).write(cr, uid, ids, vals, context=context)
if vals.get('users'):
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
user_ids = [command[1] for command in vals['users'] if command[0] == 4]
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)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
return write_res

View File

@ -0,0 +1,43 @@
<?xml version="1.0" ?>
<openerp>
<data noupdate="1">
<record model="ir.module.category" id="module_goal_category">
<field name="name">Gamification</field>
<field name="description"></field>
<field name="sequence">17</field>
</record>
<record id="group_goal_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="module_goal_category"/>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record id="goal_user_visibility" model="ir.rule">
<field name="name">User can only see his/her goals or goal from the same challenge in board visibility</field>
<field name="model_id" ref="model_gamification_goal"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
<field name="domain_force">[
'|',
('user_id','=',user.id),
'&amp;',
('challenge_id.user_ids','in',user.id),
('challenge_id.visibility_mode','=','ranking')]</field>
</record>
<record id="goal_gamification_manager_visibility" model="ir.rule">
<field name="name">Gamification Manager can see any goal</field>
<field name="model_id" ref="model_gamification_goal"/>
<field name="groups" eval="[(4, ref('group_goal_manager'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,19 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
goal_employee,"Goal Employee",model_gamification_goal,base.group_user,1,1,0,0
goal_manager,"Goal Manager",model_gamification_goal,group_goal_manager,1,1,1,1
goal_definition_employee,"Goal Definition Employee",model_gamification_goal_definition,base.group_user,1,0,0,0
goal_definition_manager,"Goal Definition Manager",model_gamification_goal_definition,group_goal_manager,1,1,1,1
challenge_employee,"Goal Challenge Employee",model_gamification_challenge,base.group_user,1,0,0,0
challenge_manager,"Goal Challenge Manager",model_gamification_challenge,group_goal_manager,1,1,1,1
challenge_line_employee,"Challenge Line Employee",model_gamification_challenge_line,base.group_user,1,0,0,0
challenge_line_manager,"Challenge Line Manager",model_gamification_challenge_line,group_goal_manager,1,1,1,1
badge_employee,"Badge Employee",model_gamification_badge,base.group_user,1,0,0,0
badge_manager,"Badge Manager",model_gamification_badge,group_goal_manager,1,1,1,1
badge_user_employee,"Badge-user Employee",model_gamification_badge_user,base.group_user,1,1,1,0
badge_user_manager,"Badge-user Manager",model_gamification_badge_user,group_goal_manager,1,1,1,1
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
2 goal_employee Goal Employee model_gamification_goal base.group_user 1 1 0 0
3 goal_manager Goal Manager model_gamification_goal group_goal_manager 1 1 1 1
4 goal_definition_employee Goal Definition Employee model_gamification_goal_definition base.group_user 1 0 0 0
5 goal_definition_manager Goal Definition Manager model_gamification_goal_definition group_goal_manager 1 1 1 1
6 challenge_employee Goal Challenge Employee model_gamification_challenge base.group_user 1 0 0 0
7 challenge_manager Goal Challenge Manager model_gamification_challenge group_goal_manager 1 1 1 1
8 challenge_line_employee Challenge Line Employee model_gamification_challenge_line base.group_user 1 0 0 0
9 challenge_line_manager Challenge Line Manager model_gamification_challenge_line group_goal_manager 1 1 1 1
10 badge_employee Badge Employee model_gamification_badge base.group_user 1 0 0 0
11 badge_manager Badge Manager model_gamification_badge group_goal_manager 1 1 1 1
12 badge_user_employee Badge-user Employee model_gamification_badge_user base.group_user 1 1 1 0
13 badge_user_manager Badge-user Manager model_gamification_badge_user group_goal_manager 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,3 @@
gamification.css: gamification.sass
sass --trace -t expanded gamification.sass gamification.css

View File

@ -0,0 +1,190 @@
@charset "UTF-8";
.openerp .oe_kanban_view .oe_kanban_card.oe_kanban_goal {
width: 230px;
min-height: 200px;
}
.openerp .oe_kanban_view .oe_kanban_card.oe_kanban_badge {
width: 250px;
min-height: 150px;
}
.openerp .oe_kanban_badge_avatars {
margin-top: 8px;
}
.openerp .oe_kanban_badge_avatars img {
width: 30px;
height: 30px;
padding-left: 0;
margin-top: 3px;
border-radius: 2px;
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.openerp .oe_kanban_goal .oe_goal_state_block {
width: 200px;
height: 130px;
margin: auto;
margin-bottom: -20px;
}
.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state {
font-size: 2.5em;
font-weight: bold;
padding-top: 30px;
}
.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state.oe_e {
font-size: 7em;
}
.openerp .oe_kanban_goal .oe_goal_state_block, .openerp .oe_kanban_goal p {
text-align: center;
}
.openerp .oe_kanban_content .oe_goal_gauge:first-child {
margin: auto;
}
.openerp .oe_kanban_content .oe_goal_gauge svg {
margin-top: -20px;
}
.openerp .oe_no_overflow {
overflow: hidden;
}
.openerp .oe_red {
color: red;
}
.openerp .oe_green {
color: green;
}
.openerp .oe_orange {
color: orange;
}
.openerp .oe_form td .oe_no_padding {
margin-left: -6px;
}
.openerp .oe_mail_wall .oe_mail_wall_aside {
margin-top: 15px;
position: relative;
display: inline-block;
vertical-align: top;
width: 280px;
border-radius: 2px;
}
.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_challenge_list {
background-color: #ededf6;
}
.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_suggestion {
background-color: #d3def1;
}
.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_suggestion ul {
padding-left: 15px;
}
.openerp .oe_mail_wall .oe_mail_wall_aside h4, .openerp .oe_mail_wall .oe_mail_wall_aside .oe_goals_list .oe_thead {
text-align: center;
padding-bottom: 15px;
}
.openerp .oe_mail_wall .oe_mail_wall_aside > div {
border-bottom: solid 5px white;
border-radius: 2px;
}
.openerp .oe_mail_wall .oe_goal {
border-bottom: solid 3px white;
padding: 5px 10px;
}
.openerp .oe_mail_wall .oe_goal .oe_update_challenge.oe_e, .openerp .oe_mail_wall .oe_goal .oe_goal_action.oe_e {
visibility: hidden;
font-size: 25px;
float: right;
position: relative;
}
.openerp .oe_mail_wall .oe_goal div:hover > .oe_update_challenge, .openerp .oe_mail_wall .oe_goal div:hover > .oe_goal_action, .openerp .oe_mail_wall .oe_goal th:hover > .oe_goal_action {
visibility: visible;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list {
padding-left: 0;
margin-top: 5px;
margin-bottom: 10px;
width: 100%;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress div {
display: inline-block;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress::before {
content: "•";
padding-right: 4px;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached::before {
content: "✓";
padding-right: 0;
margin-left: -2px;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached .oe_cell {
text-decoration: line-through;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current {
font-size: 150%;
font-weight: bold;
min-width: 50px;
padding: 0 5px;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_outer_box {
display: inline-block;
position: relative;
z-index: 0;
vertical-align: middle;
width: 100%;
border: solid 1px rgba(0, 0, 0, 0.03);
border-radius: 2px;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_outer_box.oe_no_progress {
border: none;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_progress_background {
background-color: white;
position: absolute;
height: 100%;
width: 100%;
z-index: -2;
top: 0;
left: 0;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_progress {
background-color: #d4e9de;
position: absolute;
height: 100%;
width: 0;
z-index: -1;
top: 0;
left: 0;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_thead {
font-weight: normal;
padding: 5px;
text-align: center;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell {
padding: 3px 0;
}
.openerp .oe_mail_wall .oe_table.oe_goals_list .col0 {
font-size: 200%;
font-weight: bold;
width: 25px;
text-align: center;
}
.openerp .oe_mail_wall .oe_table.oe_goals_list .col1 {
padding: 0 5px;
}
.openerp .oe_mail_wall .oe_table.oe_goals_list .col2 {
width: auto;
}
.openerp .oe_mail_wall .oe_user_avatar {
width: 24px;
padding-right: 5px;
}
.openerp .oe_mail_wall .oe_mail {
display: inline-block;
}
.openerp .oe_mail_wall .oe_table {
display: table;
}
.openerp .oe_mail_wall .oe_row {
display: table-row;
}
.openerp .oe_mail_wall .oe_cell {
display: table-cell;
vertical-align: middle;
}

View File

@ -0,0 +1,181 @@
@charset "utf-8"
.openerp
// Kanban views
.oe_kanban_view
.oe_kanban_card.oe_kanban_goal
width: 230px
min-height: 200px
.oe_kanban_card.oe_kanban_badge
width: 250px
min-height: 150px
.oe_kanban_badge_avatars
margin-top: 8px
img
width: 30px
height: 30px
padding-left: 0
margin-top: 3px
border-radius: 2px
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2)
.oe_kanban_goal
.oe_goal_state_block
width: 200px
height: 130px
margin: auto
margin-bottom: -20px
.oe_goal_state
font-size: 2.5em
font-weight: bold
padding-top: 30px
.oe_goal_state.oe_e
font-size: 7em
.oe_goal_state_block,p
text-align: center
.oe_kanban_content
.oe_goal_gauge:first-child
margin: auto /* avoid margin-right: 16px */
.oe_goal_gauge
svg
margin-top: -20px
.oe_no_overflow
overflow: hidden
.oe_red
color: red
.oe_green
color: green
.oe_orange
color: orange
// compensate padding from .openerp .oe_form td.oe_form_group_cell + .oe_form_group_cell
.oe_form td .oe_no_padding
margin-left: -6px
.oe_mail_wall
.oe_mail_wall_aside
margin-top: 15px
position: relative
display: inline-block
vertical-align: top
width: 280px
border-radius: 2px
.oe_gamification_challenge_list
background-color: #EDEDF6
.oe_gamification_suggestion
background-color: rgb(211, 222, 241)
ul
padding-left: 15px
h4, .oe_goals_list .oe_thead
text-align: center
padding-bottom: 15px
.oe_mail_wall_aside > div
border-bottom: solid 5px white
border-radius: 2px
.oe_goal
border-bottom: solid 3px white
padding: 5px 10px
.oe_update_challenge.oe_e, .oe_goal_action.oe_e
visibility: hidden
font-size: 25px
float: right
position: relative
div:hover > .oe_update_challenge, div:hover > .oe_goal_action, th:hover > .oe_goal_action
visibility: visible
.oe_goals_list
padding-left: 0
margin-top: 5px
margin-bottom: 10px
width: 100%
.oe_no_progress
div
display: inline-block
.oe_no_progress::before
content: ""
padding-right: 4px
.oe_no_progress.oe_goal_reached::before
content: ""
padding-right: 0
margin-left: -2px
.oe_no_progress.oe_goal_reached
.oe_cell
text-decoration: line-through
.oe_cell.oe_goal_current
font-size: 150%
font-weight: bold
min-width: 50px
padding: 0 5px
.oe_goal_outer_box
display: inline-block
position: relative
z-index: 0
vertical-align: middle
width: 100%
border: solid 1px rgba(0,0,0,0.03)
border-radius: 2px
.oe_goal_outer_box.oe_no_progress
border: none
.oe_goal_progress_background
background-color: white
position: absolute
height: 100%
width: 100%
z-index: -2
top: 0
left: 0
.oe_goal_progress
background-color: rgb(212, 233, 222)
position: absolute
height: 100%
width: 0
z-index: -1
top: 0
left: 0
.oe_thead
font-weight: normal
padding: 5px
text-align: center
.oe_cell
padding: 3px 0
.oe_table.oe_goals_list
.col0
font-size: 200%
font-weight: bold
width: 25px
text-align: center
.col1
padding: 0 5px
.col2
width: auto
.oe_user_avatar
width: 24px
padding-right: 5px
.oe_mail
display: inline-block
.oe_table
display: table
.oe_row
display: table-row
.oe_cell
display: table-cell
vertical-align: middle

View File

@ -0,0 +1,148 @@
openerp.gamification = function(instance) {
var QWeb = instance.web.qweb;
instance.gamification.Sidebar = instance.web.Widget.extend({
template: 'gamification.UserWallSidebar',
init: function (parent, action) {
var self = this;
this._super(parent, action);
this.deferred = $.Deferred();
this.goals_info = {};
this.challenge_suggestions = {};
$(document).off('keydown.klistener');
},
events: {
// update a challenge and related goals
'click a.oe_update_challenge': function(event) {
var self = this;
var challenge_id = parseInt(event.currentTarget.id, 10);
var goals_updated = new instance.web.Model('gamification.challenge').call('quick_update', [challenge_id]);
$.when(goals_updated).done(function() {
self.get_goal_todo_info();
});
},
// action to modify a goal
'click a.oe_goal_action': function(event) {
var self = this;
var goal_id = parseInt(event.currentTarget.id, 10);
var goal_action = new instance.web.Model('gamification.goal').call('get_action', [goal_id]).then(function(res) {
goal_action['action'] = res;
});
$.when(goal_action).done(function() {
var action = self.do_action(goal_action.action);
$.when(action).done(function () {
new instance.web.Model('gamification.goal').call('update', [[goal_id]]).then(function(res) {
self.get_goal_todo_info();
});
});
});
},
// get more info about a challenge request
'click a.oe_challenge_reply': function(event) {
var self = this;
var challenge_id = parseInt(event.currentTarget.id, 10);
var challenge_action = new instance.web.Model('gamification.challenge').call('reply_challenge_wizard', [challenge_id]).then(function(res) {
challenge_action['action'] = res;
});
$.when(challenge_action).done(function() {
self.do_action(challenge_action.action).done(function () {
self.get_goal_todo_info();
});
});
},
'click .oe_goal h4': function(event) {
var self = this;
this.kkeys = [];
$(document).on('keydown.klistener', function(event) {
if ("37,38,39,40,65,66".indexOf(event.keyCode) < 0) {
$(document).off('keydown.klistener');
} else {
self.kkeys.push(event.keyCode);
if (self.kkeys.toString().indexOf("38,38,40,40,37,39,37,39,66,65") >= 0) {
new instance.web.Model('gamification.badge').call('check_progress', []);
$(document).off('keydown.klistener');
}
}
});
}
},
start: function() {
var self = this;
this._super.apply(this, arguments);
self.get_goal_todo_info();
self.get_challenge_suggestions();
},
get_goal_todo_info: function() {
var self = this;
var challenges = new instance.web.Model('res.users').call('get_serialised_gamification_summary', []).then(function(result) {
if (result.length === 0) {
self.$el.find(".oe_gamification_challenge_list").hide();
} else {
self.$el.find(".oe_gamification_challenge_list").empty();
_.each(result, function(item){
var $item = $(QWeb.render("gamification.ChallengeSummary", {challenge: item}));
self.render_money_fields($item);
self.render_user_avatars($item);
self.$el.find('.oe_gamification_challenge_list').append($item);
});
}
});
},
get_challenge_suggestions: function() {
var self = this;
var challenge_suggestions = new instance.web.Model('res.users').call('get_challenge_suggestions', []).then(function(result) {
if (result.length === 0) {
self.$el.find(".oe_gamification_suggestion").hide();
} else {
var $item = $(QWeb.render("gamification.ChallengeSuggestion", {challenges: result}));
self.$el.find('.oe_gamification_suggestion').append($item);
}
});
},
render_money_fields: function(item) {
var self = this;
self.dfm = new instance.web.form.DefaultFieldManager(self);
// Generate a FieldMonetary for each .oe_goal_field_monetary
item.find(".oe_goal_field_monetary").each(function() {
var currency_id = parseInt( $(this).attr('data-id'), 10);
money_field = new instance.web.form.FieldMonetary(self.dfm, {
attrs: {
modifiers: '{"readonly": true}'
}
});
money_field.set('currency', currency_id);
money_field.get_currency_info();
money_field.set('value', parseInt($(this).text(), 10));
money_field.replace($(this));
});
},
render_user_avatars: function(item) {
var self = this;
item.find(".oe_user_avatar").each(function() {
var user_id = parseInt( $(this).attr('data-id'), 10);
var url = instance.session.url('/web/binary/image', {model: 'res.users', field: 'image_small', id: user_id});
$(this).attr("src", url);
});
}
});
instance.mail.Widget.include({
start: function() {
this._super();
var sidebar = new instance.gamification.Sidebar(this);
sidebar.appendTo($('.oe_mail_wall_aside'));
},
});
instance.web_kanban.KanbanRecord.include({
// open related goals when clicking on challenge kanban view
on_card_clicked: function() {
if (this.view.dataset.model === 'gamification.challenge') {
this.$('.oe_kanban_project_list a').first().click();
} else {
this._super.apply(this, arguments);
}
},
});
};

View File

@ -0,0 +1,114 @@
<templates>
<t t-name="gamification.UserWallSidebar" class="oe_gamification_user_wall_sidebar">
<div>
<div class="oe_gamification_suggestion"></div>
<div class="oe_gamification_challenge_list"></div>
</div>
</t>
<t t-name="gamification.ChallengeSummary">
<div class="oe_goal">
<div>
<a class="oe_update_challenge oe_e" rol="button" t-attf-id="{challenge.id}">e</a>
<h4><t t-esc="challenge.name" /></h4>
</div>
<t t-if="challenge.visibility_mode == 'personal'">
<div class="oe_table oe_goals_list">
<div t-foreach="challenge.lines" t-as="line" t-attf-class="oe_row oe_goal_outer_box #{line.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}">
<t t-if="line.display_mode == 'progress'">
<div class="oe_goal_progress_background"></div>
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+line.completeness+'%;' : 'width:0;'}"></div>
<div class="oe_cell oe_goal_current"><t t-esc="line.current" /></div>
</t>
<div class="oe_cell">
<t t-if="line.computation_mode != 'manually' and !line.action">
<span t-att-title="line.description"><t t-esc="line.name" /></span>
</t>
<t t-if="line.action or line.computation_mode == 'manually'">
<span t-att-title="line.description"><a class="oe_goal_action" t-att-id="line.id"><t t-esc="line.name" /></a></span>
</t>
<t t-if="line.display_mode == 'progress'"><br/>
<div class="oe_grey">
<t t-if="line.definition_condition == 'higher'">
Target:
</t>
<t t-if="line.definition_condition == 'lower'">
Target: &lt;=
</t>
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="line.target"/></span>
<t t-if="line.suffix"><t t-esc="line.suffix"/></t>
</div>
</t>
</div>
</div>
</div>
</t>
<t t-if="challenge.visibility_mode == 'ranking'">
<div t-foreach="challenge.lines" t-as="line" class="oe_goals_list oe_table">
<div class="oe_row">
<div class="oe_cell oe_thead" colspan="3" t-attf-title="#{line.description ? line.description : ''}">
<strong><t t-esc="line.name"/></strong>
<br/>
<div class="oe_grey">
<t t-if="line.definition_condition == 'higher'">
Target:
</t>
<t t-if="line.definition_condition == 'lower'">
Target: &lt;=
</t>
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="line.target" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</div>
</div>
</div>
<div t-foreach="line.goals" t-as="goal" t-attf-class="#{goal.id == line.own_goal_id ? 'oe_bold' : ''}">
<div t-attf-class="oe_row oe_goal_outer_box #{goal.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}">
<t t-if="line.display_mode == 'progress'">
<div class="oe_goal_progress_background"></div>
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+goal.completeness+'%;' : 'width:0;'}"></div>
</t>
<div class="oe_cell col0"><t t-esc="goal.rank" /></div>
<div class="oe_cell col1"><img class="oe_user_avatar" t-attf-alt="#{goal.name}" t-attf-data-id="#{goal.user_id}"/></div>
<div class="oe_cell col2">
<t t-if="line.display_mode == 'progress'">
<!-- progress, action on current value -->
<t t-esc="goal.name"/><br/>
<t t-if="goal.id != line.own_goal_id or (line.computation_mode != 'manually' and !line.action)">
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="goal.current" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</t>
<t t-if="goal.id == line.own_goal_id and (line.action or line.computation_mode == 'manually')">
<a class="oe_goal_action" t-att-id="line.own_goal_id">
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="goal.current" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</a>
</t>
</t>
<t t-if="line.display_mode != 'progress'">
<!-- not progress, action on user name -->
<t t-if="goal.id != line.own_goal_id or (line.computation_mode != 'manually' and !line.action)">
<t t-esc="goal.name"/>
</t>
<t t-if="goal.id == line.own_goal_id and (line.action or line.computation_mode == 'manually')">
<a class="oe_goal_action" t-att-id="line.own_goal_id"><t t-esc="goal.name"/></a>
</t>
</t>
</div>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-name="gamification.ChallengeSuggestion">
<div class="oe_goal">
<h4>Invited Challenges</h4>
<ul t-foreach="challenges" t-as="challenge" class="oe_goals_list">
<li>
<strong><a class="oe_challenge_reply" t-attf-title="#{challenge.description.value ? challenge.description.value : ''}" t-attf-id="{challenge.id}" title="more details..."><t t-esc="challenge.name"/></a></strong>
</li>
</ul>
</div>
</t>
</templates>

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013 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
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_challenge
checks = [
test_challenge,
]

View File

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013 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
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests import common
class test_challenge(common.TransactionCase):
def setUp(self):
super(test_challenge, self).setUp()
cr, uid = self.cr, self.uid
self.data_obj = self.registry('ir.model.data')
self.user_obj = self.registry('res.users')
self.challenge_obj = self.registry('gamification.challenge')
self.line_obj = self.registry('gamification.challenge.line')
self.goal_obj = self.registry('gamification.goal')
self.badge_obj = self.registry('gamification.badge')
self.badge_user_obj = self.registry('gamification.badge.user')
self.demo_user_id = self.data_obj.get_object_reference(cr, uid, 'base', 'user_demo')[1]
self.group_user_id = self.data_obj.get_object_reference(cr, uid, 'base', 'group_user')[1]
self.challenge_base_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'challenge_base_discover')[1]
self.definition_timezone_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'definition_base_timezone')[1]
self.badge_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'badge_good_job')[1]
def test_00_join_challenge(self):
cr, uid, context = self.cr, self.uid, {}
user_ids = self.user_obj.search(cr, uid, [('groups_id', '=', self.group_user_id)])
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
self.assertGreaterEqual(len(challenge.user_ids), len(user_ids), "Not enough users in base challenge")
self.user_obj.create(cr, uid, {
'name': 'R2D2',
'login': 'r2d2@openerp.com',
'email': 'r2d2@openerp.com',
'groups_id': [(6, 0, [self.group_user_id])]
}, {'no_reset_password': True})
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
self.assertGreaterEqual(len(challenge.user_ids), len(user_ids)+1, "These are not droids you are looking for")
def test_10_reach_challenge(self):
cr, uid, context = self.cr, self.uid, {}
self.challenge_obj.write(cr, uid, [self.challenge_base_id], {'state': 'inprogress'}, context=context)
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
challenge_user_ids = [user.id for user in challenge.user_ids]
self.assertEqual(challenge.state, 'inprogress', "Challenge failed the change of state")
line_ids = self.line_obj.search(cr, uid, [('challenge_id', '=', self.challenge_base_id)], context=context)
goal_ids = self.goal_obj.search(cr, uid, [('challenge_id', '=', self.challenge_base_id), ('state', '!=', 'draft')], context=context)
self.assertEqual(len(goal_ids), len(line_ids)*len(challenge_user_ids), "Incorrect number of goals generated, should be 1 goal per user, per challenge line")
# demo user will set a timezone
self.user_obj.write(cr, uid, self.demo_user_id, {'tz': "Europe/Brussels"}, context=context)
goal_ids = self.goal_obj.search(cr, uid, [('user_id', '=', self.demo_user_id), ('definition_id', '=', self.definition_timezone_id)], context=context)
self.goal_obj.update(cr, uid, goal_ids, context=context)
reached_goal_ids = self.goal_obj.search(cr, uid, [('id', 'in', goal_ids), ('state', '=', 'reached')], context=context)
self.assertEqual(set(goal_ids), set(reached_goal_ids), "Not every goal was reached after changing timezone")
# reward for two firsts as admin may have timezone
self.challenge_obj.write(cr, uid, self.challenge_base_id, {'reward_first_id': self.badge_id, 'reward_second_id': self.badge_id}, context=context)
self.challenge_obj.write(cr, uid, self.challenge_base_id, {'state': 'done'}, context=context)
badge_ids = self.badge_user_obj.search(cr, uid, [('badge_id', '=', self.badge_id), ('user_id', '=', self.demo_user_id)])
self.assertGreater(len(badge_ids), 0, "Demo user has not received the badge")

View File

@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- Badge views -->
<record id="badge_list_action" model="ir.actions.act_window">
<field name="name">Badges</field>
<field name="res_model">gamification.badge</field>
<field name="view_mode">kanban,tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a badge.
</p>
<p>
A badge is a symbolic token granted to a user as a sign of reward.
It can be deserved automatically when some conditions are met or manually by users.
Some badges are harder than others to get with specific conditions.
</p>
</field>
</record>
<record id="badge_list_view" model="ir.ui.view">
<field name="name">Badge List</field>
<field name="model">gamification.badge</field>
<field name="arch" type="xml">
<tree string="Badge List">
<field name="name"/>
<field name="stat_count"/>
<field name="stat_this_month"/>
<field name="stat_my"/>
<field name="rule_auth"/>
</tree>
</field>
</record>
<record id="badge_form_view" model="ir.ui.view">
<field name="name">Badge Form</field>
<field name="model">gamification.badge</field>
<field name="arch" type="xml">
<form string="Badge" version="7.0">
<header>
<button string="Grant this Badge" type="action" name="%(action_grant_wizard)d" class="oe_highlight" attrs="{'invisible': [('remaining_sending','=',0)]}" />
<button string="Check Badge" type="object" name="check_automatic" groups="base.group_no_one" />
</header>
<sheet>
<div class="oe_right oe_button_box">
</div>
<field name="image" widget='image' class="oe_left oe_avatar"/>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
</div>
<group>
<field name="description" nolabel="1" placeholder="Badge Description" />
</group>
<group string="Granting">
<div class="oe_grey" colspan="4">
Security rules to define who is allowed to manually grant badges. Not enforced for administrator.
</div>
<field name="rule_auth" widget="radio" />
<field name="rule_auth_user_ids" attrs="{'invisible': [('rule_auth','!=','users')]}" widget="many2many_tags" />
<field name="rule_auth_badge_ids" attrs="{'invisible': [('rule_auth','!=','having')]}" widget="many2many_tags" />
<field name="rule_max" attrs="{'invisible': [('rule_auth','=','nobody')]}" />
<field name="rule_max_number" attrs="{'invisible': ['|',('rule_max','=',False),('rule_auth','=','nobody')]}"/>
<label for="stat_my_monthly_sending"/>
<div>
<field name="stat_my_monthly_sending" attrs="{'invisible': [('rule_auth','=','nobody')]}" />
<div attrs="{'invisible': [('remaining_sending','=',-1)]}" class="oe_grey">
You can still grant <field name="remaining_sending" class="oe_inline"/> badges this month
</div>
<div attrs="{'invisible': [('remaining_sending','!=',-1)]}" class="oe_grey">
No monthly sending limit
</div>
</div>
</group>
<group string="Rewards for challenges">
<field name="challenge_ids" widget="many2many_kanban" nolabel="1" />
</group>
<group string="Statistics">
<group>
<field name="stat_count"/>
<field name="stat_this_month"/>
<field name="stat_count_distinct"/>
</group>
<group>
<field name="stat_my"/>
<field name="stat_my_this_month"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="badge_kanban_view" model="ir.ui.view" >
<field name="name">Badge Kanban View</field>
<field name="model">gamification.badge</field>
<field name="arch" type="xml">
<kanban version="7.0" class="oe_background_grey">
<field name="name"/>
<field name="description"/>
<field name="image"/>
<field name="stat_my"/>
<field name="stat_count"/>
<field name="stat_this_month"/>
<field name="unique_owner_ids"/>
<field name="stat_my_monthly_sending"/>
<field name="remaining_sending" />
<field name="rule_max_number" />
<templates>
<t t-name="kanban-box">
<div t-attf-class="#{record.stat_my.raw_value ? 'oe_kanban_color_5' : 'oe_kanban_color_white'} oe_kanban_card oe_kanban_global_click oe_kanban_badge">
<div class="oe_kanban_content">
<div class="oe_kanban_left">
<a type="open"><img t-att-src="kanban_image('gamification.badge', 'image', record.image.raw_value)" t-att-title="record.name.value" width="90" height="90" /></a>
</div>
<div class="oe_no_overflow">
<h4><field name="name"/></h4>
<t t-if="record.remaining_sending.value != 0">
<button type="action" name="%(action_grant_wizard)d" class="oe_highlight">Grant</button>
<span class="oe_grey">
<t t-if="record.remaining_sending.value != -1">
<t t-esc="record.stat_my_monthly_sending.value"/>/<t t-esc="record.rule_max_number.value"/>
</t>
<t t-if="record.remaining_sending.value == -1">
<t t-esc="record.stat_my_monthly_sending.value"/>/∞
</t>
</span>
</t>
<t t-if="record.remaining_sending.value == 0">
<div class="oe_grey">Can not grant</div>
</t>
<p>
<strong><t t-esc="record.stat_count.raw_value"/></strong> granted,<br/>
<strong><t t-esc="record.stat_this_month.raw_value"/></strong> this month
</p>
</div>
<div class="oe_kanban_badge_avatars">
<t t-if="record.description.value">
<p><em><field name="description"/></em></p>
</t>
<a type="object" name="get_granted_employees">
<t t-foreach="record.unique_owner_ids.raw_value.slice(0,11)" t-as="owner">
<img t-att-src="kanban_image('res.users', 'image_small', owner)" t-att-data-member_id="owner"/>
</t>
</a>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Badge user viewss -->
<record id="badge_user_kanban_view" model="ir.ui.view" >
<field name="name">Badge User Kanban View</field>
<field name="model">gamification.badge.user</field>
<field name="arch" type="xml">
<kanban version="7.0" class="oe_background_grey">
<field name="badge_name"/>
<field name="badge_id"/>
<field name="user_id"/>
<field name="comment"/>
<field name="create_date"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_card oe_kanban_global_click oe_kanban_badge oe_kanban_color_white">
<div class="oe_kanban_content">
<div class="oe_kanban_left">
<a type="open"><img t-att-src="kanban_image('gamification.badge', 'image', record.badge_id.raw_value)" t-att-title="record.badge_name.value" width="24" height="24" /></a>
</div>
<h4>
<a type="open"><t t-esc="record.badge_name.raw_value" /></a>
</h4>
<t t-if="record.comment.raw_value">
<p><em><field name="comment"/></em></p>
</t>
<p>Granted by <a type="open"><field name="create_uid" /></a> the <t t-esc="record.create_date.raw_value.toString(Date.CultureInfo.formatPatterns.shortDate)" /></p>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,289 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="challenge_list_view" model="ir.ui.view">
<field name="name">Challenges List</field>
<field name="model">gamification.challenge</field>
<field name="arch" type="xml">
<tree string="Goal definitions" colors="blue:state == 'draft';grey:state == 'done'">
<field name="name"/>
<field name="period"/>
<field name="manager_id"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="goals_from_challenge_act" model="ir.actions.act_window">
<field name="res_model">gamification.goal</field>
<field name="name">Related Goals</field>
<field name="view_mode">kanban,tree</field>
<field name="context">{'search_default_group_by_definition': True, 'search_default_inprogress': True, 'search_default_challenge_id': active_id, 'default_challenge_id': active_id}</field>
<field name="help" type="html">
<p>
There is no goals associated to this challenge matching your search.
Make sure that your challenge is active and assigned to at least one user.
</p>
</field>
</record>
<record id="challenge_form_view" model="ir.ui.view">
<field name="name">Challenge Form</field>
<field name="model">gamification.challenge</field>
<field name="arch" type="xml">
<form string="Goal definitions" version="7.0">
<header>
<button string="Refresh Challenge" type="object" name="action_check" states="inprogress"/>
<button string="Send Report" type="object" name="action_report_progress" states="inprogress,done" groups="base.group_no_one"/>
<field name="state" widget="statusbar" clickable="True"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" placeholder="e.g. Monthly Sales Objectives"/>
</h1>
<label for="user_ids" class="oe_edit_only" string="Assign Challenge To"/>
<div>
<field name="user_ids" widget="many2many_tags" />
</div>
</div>
<!-- action buttons -->
<div class="oe_right oe_button_box">
<button type="action" name="%(goals_from_challenge_act)d" string="Related Goals" attrs="{'invisible': [('state','=','draft')]}" />
</div>
<group>
<group>
<field name="period" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="visibility_mode" widget="radio" colspan="1" />
</group>
<group>
<field name="manager_id"/>
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="end_date" attrs="{'readonly':[('state','!=','draft')]}"/>
</group>
</group>
<notebook>
<page string="Goals">
<field name="line_ids" nolabel="1" colspan="4">
<tree string="Line List" version="7.0" editable="bottom" >
<field name="sequence" widget="handle"/>
<field name="definition_id" on_change="on_change_definition_id(definition_id)" />
<field name="condition"/>
<field name="target_goal"/>
<field name="definition_full_suffix"/>
</tree>
</field>
<field name="description" placeholder="Describe the challenge: what is does, who it targets, why it matters..."/>
</page>
<page string="Reward">
<group>
<field name="reward_id"/>
<field name="reward_first_id" />
<field name="reward_second_id" attrs="{'invisible': [('reward_first_id','=', False)]}" />
<field name="reward_third_id" attrs="{'invisible': ['|',('reward_first_id','=', False),('reward_second_id','=', False)]}" />
<field name="reward_failure" attrs="{'invisible': [('reward_first_id','=', False)]}" />
</group>
<div class="oe_grey">
<p>Badges are granted when a challenge is finished. This is either at the end of a running period (eg: end of the month for a monthly challenge), at the end date of a challenge (if no periodicity is set) or when the challenge is manually closed.</p>
</div>
</page>
<page string="Advanced Options">
<group string="Subscriptions">
<field name="autojoin_group_id" />
<field name="invited_user_ids" widget="many2many_tags" />
</group>
<group string="Notification Messages">
<div class="oe_grey" colspan="4">
<p>Depending on the Display mode, reports will be individual or shared.</p>
</div>
<field name="report_message_frequency" />
<field name="report_template_id" attrs="{'invisible': [('report_message_frequency','=','never')]}" />
<field name="report_message_group_id" attrs="{'invisible': [('report_message_frequency','=','never')]}" />
</group>
<group string="Reminders for Manual Goals">
<label for="remind_update_delay" />
<div>
<field name="remind_update_delay" class="oe_inline"/> days
</div>
</group>
<group string="Category" groups="base.group_no_one">
<field name="category" widget="radio" />
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_challenge_kanban">
<field name="name">Challenge Kanban</field>
<field name="model">gamification.challenge</field>
<field name="arch" type="xml">
<kanban string="Challenges" class="oe_background_grey" version="7.0">
<field name="line_ids"/>
<field name="user_ids"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_goal oe_kanban_global_click">
<div class="oe_dropdown_toggle oe_dropdown_kanban">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<li><a type="edit">Configure Challenge</a></li>
</ul>
</div>
<div class="oe_kanban_content">
<h4><field name="name"/></h4>
<div class="oe_kanban_project_list">
<a type="action" name="%(goals_from_challenge_act)d" style="margin-right: 10px">
<span t-if="record.line_ids.raw_value.length gt 1"><t t-esc="record.line_ids.raw_value.length"/> Goals</span>
<span t-if="record.line_ids.raw_value.length lt 2"><t t-esc="record.line_ids.raw_value.length"/> Goal</span>
</a>
</div>
<div class="oe_kanban_badge_avatars">
<t t-foreach="record.user_ids.raw_value.slice(0,11)" t-as="member">
<img t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
</t>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="challenge_list_action" model="ir.actions.act_window">
<field name="name">Challenges</field>
<field name="res_model">gamification.challenge</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{'search_default_inprogress':True, 'default_inprogress':True}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a challenge.
</p>
<p>
Assign a list of goals to chosen users to evaluate them.
The challenge can use a period (weekly, monthly...) for automatic creation of goals.
The goals are created for the specified users or member of the group.
</p>
</field>
</record>
<!-- Specify form view ID to avoid selecting view_challenge_wizard -->
<record id="challenge_list_action_view1" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">kanban</field>
<field name="act_window_id" ref="challenge_list_action"/>
<field name="view_id" ref="view_challenge_kanban"/>
</record>
<record id="challenge_list_action_view2" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">form</field>
<field name="act_window_id" ref="challenge_list_action"/>
<field name="view_id" ref="challenge_form_view"/>
</record>
<!-- Line -->
<record id="challenge_line_list_view" model="ir.ui.view">
<field name="name">Challenge line list</field>
<field name="model">gamification.challenge.line</field>
<field name="arch" type="xml">
<tree string="Challenge Lines" >
<field name="definition_id"/>
<field name="target_goal"/>
</tree>
</field>
</record>
<record id="challenge_search_view" model="ir.ui.view">
<field name="name">Challenge Search</field>
<field name="model">gamification.challenge</field>
<field name="arch" type="xml">
<search string="Search Challenges">
<filter name="inprogress" string="Running Challenges"
domain="[('state', '=', 'inprogress')]"/>
<filter name="hr_challenges" string="HR Challenges"
domain="[('category', '=', 'hr')]"/>
<field name="name"/>
<group expand="0" string="Group By...">
<filter string="State" domain="[]" context="{'group_by':'state'}"/>
<filter string="Period" domain="[]" context="{'group_by':'period'}"/>
</group>
</search>
</field>
</record>
<record id="view_challenge_wizard" model="ir.ui.view">
<field name="name">Challenge Wizard</field>
<field name="model">gamification.challenge</field>
<field name="arch" type="xml">
<form string="Challenge" version="7.0">
<field name="reward_failure" invisible="1"/>
<div class="oe_title">
<h1><field name="name" nolabel="1" readonly="1"/></h1>
</div>
<field name="description" nolabel="1" readonly="1" />
<group>
<field name="start_date" readonly="1" />
<field name="end_date" readonly="1" />
<field name="user_ids" string="Participating" readonly="1" widget="many2many_tags" />
<field name="invited_user_ids" string="Invited" readonly="1" widget="many2many_tags" />
</group>
<group string="Goals">
<field name="line_ids" nolabel="1" readonly="1" colspan="4">
<tree string="Challenge Lines" version="7.0" editable="bottom" >
<field name="sequence" widget="handle"/>
<field name="definition_id"/>
<field name="condition"/>
<field name="target_goal"/>
<field name="definition_full_suffix"/>
</tree>
</field>
</group>
<group string="Reward">
<div class="oe_grey" attrs="{'invisible': ['|',('reward_id','!=',False),('reward_first_id','!=',False)]}">
There is no reward upon completion of this challenge.
</div>
<group attrs="{'invisible': [('reward_id','=',False),('reward_first_id','=',False)]}">
<field name="reward_id" readonly="1" attrs="{'invisible': [('reward_first_id','=', False)]}" />
<field name="reward_first_id" readonly="1" attrs="{'invisible': [('reward_first_id','=', False)]}" />
<field name="reward_second_id" readonly="1" attrs="{'invisible': [('reward_second_id','=', False)]}" />
<field name="reward_third_id" readonly="1" attrs="{'invisible': [('reward_third_id','=', False)]}" />
</group>
<div class="oe_grey" attrs="{'invisible': [('reward_failure','=',False)]}">
Even if the challenge is failed, best challengers will be rewarded
</div>
</group>
<footer>
<center>
<button string="Accept" type="object" name="accept_challenge" class="oe_highlight" />
<button string="Reject" type="object" name="discard_challenge"/> or
<button string="reply later" special="cancel" class="oe_link"/>
</center>
</footer>
</form>
</field>
</record>
<record id="challenge_wizard" model="ir.actions.act_window">
<field name="name">Challenge Description</field>
<field name="res_model">gamification.challenge</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_challenge_wizard"/>
<field name="target">new</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,289 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- Goal views -->
<record id="goal_list_action" model="ir.actions.act_window">
<field name="name">Goals</field>
<field name="res_model">gamification.goal</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_definition': True}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a goal.
</p>
<p>
A goal is defined by a user and a goal definition.
Goals can be created automatically by using challenges.
</p>
</field>
</record>
<record id="goal_list_view" model="ir.ui.view">
<field name="name">Goal List</field>
<field name="model">gamification.goal</field>
<field name="arch" type="xml">
<tree string="Goal List" colors="red:state == 'failed';green:state == 'reached';grey:state == 'canceled'">
<field name="definition_id" invisible="1" />
<field name="user_id" invisible="1" />
<field name="start_date"/>
<field name="end_date"/>
<field name="current"/>
<field name="target_goal"/>
<field name="completeness" widget="progressbar"/>
<field name="state" invisible="1"/>
<field name="line_id" invisible="1"/>
</tree>
</field>
</record>
<record id="goal_form_view" model="ir.ui.view">
<field name="name">Goal Form</field>
<field name="model">gamification.goal</field>
<field name="arch" type="xml">
<form string="Goal" version="7.0">
<header>
<button string="Start goal" type="object" name="action_start" states="draft" class="oe_highlight"/>
<button string="Goal Reached" type="object" name="action_reach" states="inprogress,inprogress_update" />
<button string="Goal Failed" type="object" name="action_fail" states="inprogress,inprogress_update"/>
<button string="Reset Completion" type="object" name="action_cancel" states="failed,reached" groups="base.group_no_one" />
<field name="state" widget="statusbar" statusbar_visible="draft,inprogress,reached" />
</header>
<sheet>
<group>
<group string="Reference">
<field name="definition_id" on_change="on_change_definition_id(definition_id)" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="challenge_id" attrs="{'readonly':[('state','!=','draft')]}"/>
</group>
<group string="Schedule">
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="end_date" />
<field name="computation_mode" invisible="1"/>
<label for="remind_update_delay" attrs="{'invisible':[('computation_mode','!=', 'manually')]}"/>
<div attrs="{'invisible':[('computation_mode','!=', 'manually')]}">
<field name="remind_update_delay" class="oe_inline"/>
days
</div>
<field name="last_update" groups="base.group_no_one"/>
</group>
<group string="Data" colspan="4">
<label for="target_goal" />
<div>
<field name="target_goal" attrs="{'readonly':[('state','!=','draft')]}" class="oe_inline"/>
<field name="definition_suffix" class="oe_inline"/>
</div>
<label for="current" />
<div>
<field name="current" class="oe_inline"/>
<button string="refresh" type="object" name="update" class="oe_link" attrs="{'invisible':['|',('computation_mode', '=', 'manually'),('state', '=', 'draft')]}" />
<div class="oe_grey" attrs="{'invisible':[('definition_id', '=', False)]}">
Reached when current value is <strong><field name="definition_condition" class="oe_inline"/></strong> than the target.
</div>
</div>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="goal_search_view" model="ir.ui.view">
<field name="name">Goal Search</field>
<field name="model">gamification.goal</field>
<field name="arch" type="xml">
<search string="Search Goals">
<filter name="my" string="My Goals" domain="[('user_id', '=', uid)]"/>
<separator/>
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
<filter name="inprogress" string="Current"
domain="[
'|',
('state', 'in', ('inprogress', 'inprogress_update')),
('end_date', '>=', context_today().strftime('%%Y-%%m-%%d'))
]"/>
<filter name="closed" string="Passed" domain="[('state', 'in', ('reached', 'failed'))]"/>
<separator/>
<field name="user_id"/>
<field name="definition_id"/>
<field name="challenge_id"/>
<group expand="0" string="Group By...">
<filter name="group_by_user" string="User" domain="[]" context="{'group_by':'user_id'}"/>
<filter name="group_by_definition" string="Goal Definition" domain="[]" context="{'group_by':'definition_id'}"/>
<filter string="State" domain="[]" context="{'group_by':'state'}"/>
<filter string="End Date" domain="[]" context="{'group_by':'end_date'}"/>
</group>
</search>
</field>
</record>
<record id="goal_kanban_view" model="ir.ui.view" >
<field name="name">Goal Kanban View</field>
<field name="model">gamification.goal</field>
<field name="arch" type="xml">
<kanban version="7.0" class="oe_background_grey">
<field name="definition_id"/>
<field name="user_id"/>
<field name="current"/>
<field name="completeness"/>
<field name="state"/>
<field name="target_goal"/>
<field name="definition_condition"/>
<field name="definition_suffix"/>
<field name="definition_display"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="last_update"/>
<templates>
<t t-name="kanban-tooltip">
<field name="definition_description"/>
</t>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_gamification_goal oe_kanban_goal #{record.end_date.raw_value &lt; record.last_update.raw_value &amp; record.state.raw_value == 'failed' ? 'oe_kanban_color_2' : ''} #{record.end_date.raw_value &lt; record.last_update.raw_value &amp; record.state.raw_value == 'reached' ? 'oe_kanban_color_5' : ''}">
<div class="oe_kanban_content">
<p><h4 class="oe_goal_name" tooltip="kanban-tooltip"><field name="definition_id" /></h4></p>
<div class="oe_kanban_left">
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" />
</div>
<field name="user_id" />
<div class="oe_goal_state_block">
<t t-if="record.definition_display.raw_value == 'boolean'">
<div class="oe_goal_state oe_e">
<t t-if="record.state.raw_value=='reached'"><span class="oe_green" title="Goal Reached">W</span></t>
<t t-if="record.state.raw_value=='inprogress' || record.state.raw_value=='inprogress_update'"><span title="Goal in Progress">N</span></t>
<t t-if="record.state.raw_value=='failed'"><span class="oe_red" title="Goal Failed">X</span></t>
</div>
</t>
<t t-if="record.definition_display.raw_value == 'progress'">
<t t-if="record.definition_condition.raw_value =='higher'">
<field name="current" widget="gauge" style="width:160px; height: 120px;" options="{'max_field': 'target_goal', 'label_field': 'definition_suffix'}" />
</t>
<t t-if="record.definition_condition.raw_value != 'higher'">
<div t-attf-class="oe_goal_state #{record.current.raw_value == record.target_goal.raw_value+1 ? 'oe_orange' : record.current.raw_value &gt; record.target_goal.raw_value ? 'oe_red' : 'oe_green'}">
<t t-esc="record.current.raw_value" />
</div>
<em>Target: less than <t t-esc="record.target_goal.raw_value" /></em>
</t>
</t>
</div>
<p>
<t t-if="record.start_date.value">
From <t t-esc="record.start_date.value" />
</t>
<t t-if="record.end_date.value">
To <t t-esc="record.end_date.value" />
</t>
</p>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Goal definitions view -->
<record id="goal_definition_list_action" model="ir.actions.act_window">
<field name="name">Goal Definitions</field>
<field name="res_model">gamification.goal.definition</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a goal definition.
</p>
<p>
A goal definition is a technical model of goal defining a condition to reach.
The dates, values to reach or users are defined in goal instance.
</p>
</field>
</record>
<record id="goal_definition_list_view" model="ir.ui.view">
<field name="name">Goal Definitions List</field>
<field name="model">gamification.goal.definition</field>
<field name="arch" type="xml">
<tree string="Goal Definitions">
<field name="name"/>
<field name="computation_mode"/>
</tree>
</field>
</record>
<record id="goal_definition_form_view" model="ir.ui.view">
<field name="name">Goal Definitions Form</field>
<field name="model">gamification.goal.definition</field>
<field name="arch" type="xml">
<form string="Goal definitions" version="7.0">
<sheet>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" class="oe_inline"/>
</h1>
<label for="description" class="oe_edit_only"/>
<div>
<field name="description" class="oe_inline"/>
</div>
<group string="How to compute the goal?">
<field widget="radio" name="computation_mode"/>
<!-- Hide the fields below if manually -->
<field name="model_id" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
<field name="field_id" attrs="{'invisible':[('computation_mode','!=','sum')], 'required':[('computation_mode','=','sum')]}" domain="[('model_id','=',model_id)]" class="oe_inline"/>
<field name="field_date_id" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))]}" domain="[('ttype', 'in', ('date', 'datetime')), ('model_id','=',model_id)]" class="oe_inline"/>
<field name="domain" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
<field name="compute_code" attrs="{'invisible':[('computation_mode','!=','python')], 'required':[('computation_mode','=','python')]}" placeholder="e.g. result = pool.get('mail.followers').search(cr, uid, [('res_model', '=', 'mail.group'), ('partner_id', '=', object.user_id.partner_id.id)], count=True, context=context)"/>
<field name="condition" widget="radio"/>
</group>
<group string="Formating Options">
<field name="display_mode" widget="radio" />
<field name="suffix" placeholder="e.g. days"/>
<field name="monetary"/>
</group>
<group string="Clickable Goals" attrs="{'invisible': [('computation_mode', '=', 'manually')]}">
<field name="action_id" class="oe_inline"/>
<field name="res_id_field" attrs="{'invisible': [('action_id', '=', False)]}" class="oe_inline"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="goal_definition_search_view" model="ir.ui.view">
<field name="name">Goal Definition Search</field>
<field name="model">gamification.goal.definition</field>
<field name="arch" type="xml">
<search string="Search Goal Definitions">
<field name="name"/>
<field name="model_id"/>
<field name="field_id"/>
<group expand="0" string="Group By...">
<filter string="Model" domain="[]" context="{'group_by':'model_id'}"/>
<filter string="Computation Mode" domain="[]" context="{'group_by':'computation_mode'}"/>
</group>
</search>
</field>
</record>
<!-- menus in settings - technical feature required -->
<menuitem id="gamification_menu" name="Gamification Tools" parent="base.menu_administration" groups="base.group_no_one" />
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="0"/>
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="10"/>
<menuitem id="gamification_definition_menu" parent="gamification_menu" action="goal_definition_list_action" sequence="20"/>
<menuitem id="gamification_badge_menu" parent="gamification_menu" action="badge_list_action" sequence="30"/>
</data>
</openerp>

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import update_goal
import grant_badge

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp.osv import fields, osv
from openerp.tools.translate import _
class grant_badge_wizard(osv.TransientModel):
""" Wizard allowing to grant a badge to a user"""
_name = 'gamification.badge.user.wizard'
_columns = {
'user_id': fields.many2one("res.users", string='User', required=True),
'badge_id': fields.many2one("gamification.badge", string='Badge', required=True),
'comment': fields.text('Comment'),
}
def action_grant_badge(self, cr, uid, ids, context=None):
"""Wizard action for sending a badge to a chosen user"""
badge_obj = self.pool.get('gamification.badge')
badge_user_obj = self.pool.get('gamification.badge.user')
for wiz in self.browse(cr, uid, ids, context=context):
if uid == wiz.user_id.id:
raise osv.except_osv(_('Warning!'), _('You can not grant a badge to yourself'))
#create the badge
values = {
'user_id': wiz.user_id.id,
'sender_id': uid,
'badge_id': wiz.badge_id.id,
'comment': wiz.comment,
}
badge_user = badge_user_obj.create(cr, uid, values, context=context)
result = badge_obj._send_badge(cr, uid, badge_user, context=context)
return result

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="view_badge_wizard_grant" model="ir.ui.view">
<field name="name">Grant Badge User Form</field>
<field name="model">gamification.badge.user.wizard</field>
<field name="arch" type="xml">
<form string="Grant Badge To" version="7.0">
Who would you like to reward?
<group>
<field name="user_id" nolabel="1" />
<field name="badge_id" invisible="1"/>
<field name="comment" nolabel="1" placeholder="Describe what they did and why it matters (will be public)" class="oe_no_padding" />
</group>
<footer>
<button string="Grant Badge" type="object" name="action_grant_badge" class="oe_highlight" /> or
<button string="Cancel" special="cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
<act_window domain="[]" id="action_grant_wizard"
name="Grant Badge"
target="new"
res_model="gamification.badge.user.wizard"
context="{'default_badge_id': active_id, 'badge_id': active_id}"
view_type="form" view_mode="form"
view_id="gamification.view_badge_wizard_grant" />
</data>
</openerp>

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp.osv import fields, osv
class goal_manual_wizard(osv.TransientModel):
"""Wizard to update a manual goal"""
_name = 'gamification.goal.wizard'
_columns = {
'goal_id': fields.many2one("gamification.goal", string='Goal', required=True),
'current': fields.float('Current'),
}
def action_update_current(self, cr, uid, ids, context=None):
"""Wizard action for updating the current value"""
goal_obj = self.pool.get('gamification.goal')
for wiz in self.browse(cr, uid, ids, context=context):
towrite = {
'current': wiz.current,
'goal_id': wiz.goal_id.id,
}
goal_obj.write(cr, uid, [wiz.goal_id.id], towrite, context=context)
goal_obj.update(cr, uid, [wiz.goal_id.id], context=context)
return {}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="view_goal_wizard_update_current" model="ir.ui.view">
<field name="name">Update the current value of the Goal</field>
<field name="model">gamification.goal.wizard</field>
<field name="arch" type="xml">
<form string="Grant Badge To" version="7.0">
Set the current value you have reached for this goal
<group>
<field name="goal_id" invisible="1"/>
<field name="current" />
</group>
<footer>
<button string="Update" type="object" name="action_update_current" class="oe_highlight" /> or
<button string="Cancel" special="cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'CRM Gamification',
'version': '1.0',
'author': 'OpenERP SA',
'category': 'hidden',
'depends': ['gamification','sale_crm'],
'description': """Example of goal definitions and challenges that can be used related to the usage of the CRM Sale module.""",
'data': ['sale_crm_goals.xml'],
'demo': ['sale_crm_goals_demo.xml'],
'auto_install': True,
}

View File

@ -0,0 +1,173 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- goal definitions -->
<record model="gamification.goal.definition" id="definition_crm_tot_invoices">
<field name="name">Total Invoiced</field>
<field name="description"></field>
<field name="computation_mode">sum</field>
<field name="monetary">True</field>
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_id" eval="ref('account.field_account_invoice_report_price_total')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','!=','cancel'),('user_id','=',user.id),('type','=','out_invoice')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_nbr_new_leads">
<field name="name">New Leads</field>
<field name="description">Based on the creation date</field>
<field name="computation_mode">count</field>
<field name="suffix">leads</field>
<field name="model_id" eval="ref('crm.model_crm_lead')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_create_date')" />
<!-- lead AND opportunity as don't want to be penalised for lead converted to opportunity -->
<field name="domain">[('user_id','=',user.id), '|', ('type', '=', 'lead'), ('type', '=', 'opportunity')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_lead_delay_open">
<field name="name">Time to Qualify a Lead</field>
<field name="description">The average number of days to open the case (lower than)</field>
<field name="computation_mode">sum</field>
<field name="condition">lower</field>
<field name="suffix">days</field>
<field name="model_id" eval="ref('crm.model_crm_lead_report')" />
<field name="field_id" eval="ref('crm.field_crm_lead_report_delay_close')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_report_date_closed')" />
<field name="domain">[('user_id','=',user.id),('type', '=', 'lead')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_lead_delay_close">
<field name="name">Days to Close a Deal</field>
<field name="description">The average number of days to close the case (lower than)</field>
<field name="computation_mode">sum</field>
<field name="condition">lower</field>
<field name="suffix">days</field>
<field name="model_id" eval="ref('crm.model_crm_lead_report')" />
<field name="field_id" eval="ref('crm.field_crm_lead_report_delay_open')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_report_opening_date')" />
<field name="domain">[('user_id','=',user.id)]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_nbr_call">
<field name="name">Logged Calls</field>
<field name="description">Log a certain number of calls to reach this goal</field>
<field name="computation_mode">count</field>
<field name="suffix">calls</field>
<field name="model_id" eval="ref('crm.model_crm_phonecall')" />
<field name="field_date_id" eval="ref('crm.field_crm_phonecall_date_closed')" />
<field name="domain">[('user_id','=',user.id),('state','=','done')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_nbr_new_opportunities">
<field name="name">New Opportunities</field>
<field name="description">Based on the opening date</field>
<field name="computation_mode">count</field>
<field name="suffix">opportunities</field>
<field name="model_id" eval="ref('crm.model_crm_lead')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_date_open')" />
<field name="domain">[('user_id','=',user.id),('type','=','opportunity')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_nbr_sale_order_created">
<field name="name">New Sales Orders</field>
<field name="description">Based on the creation date</field>
<field name="computation_mode">count</field>
<field name="suffix">orders</field>
<field name="model_id" eval="ref('sale.model_sale_order')" />
<field name="field_date_id" eval="ref('sale.field_sale_order_date_order')" />
<field name="domain">[('user_id','=',user.id),('state','not in',('draft', 'sent', 'cancel'))]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_nbr_paid_sale_order">
<field name="name">Paid Sales Orders</field>
<field name="description">Based on the invoice date</field>
<field name="computation_mode">count</field>
<field name="suffix">orders</field>
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_tot_paid_sale_order">
<field name="name">Total Paid Sales Orders</field>
<field name="description">Based on the invoice date</field>
<field name="computation_mode">count</field>
<field name="monetary">True</field>
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_id" eval="ref('account.field_account_invoice_report_price_total')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_nbr_customer_refunds">
<field name="name">Customer Refunds</field>
<field name="description">Refund the least customers (lower than)</field>
<field name="computation_mode">count</field>
<field name="condition">lower</field>
<field name="suffix">invoices</field>
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')]</field>
</record>
<record model="gamification.goal.definition" id="definition_crm_tot_customer_refunds">
<field name="name">Total Customer Refunds</field>
<field name="description">The total refunded value is a negative value. Validated when higher (min refunded).</field>
<field name="computation_mode">sum</field>
<field name="condition">higher</field>
<field name="monetary">True</field>
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_id" eval="ref('account.field_account_invoice_report_price_total')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')]</field>
</record>
<!-- challenges -->
<record model="gamification.challenge" id="challenge_crm_sale">
<field name="name">Monthly Sales Targets</field>
<field name="period">monthly</field>
<field name="visibility_mode">ranking</field>
<field name="autojoin_group_id" eval="ref('base.group_sale_salesman')" />
<field name="report_message_frequency">weekly</field>
</record>
<record model="gamification.challenge" id="challenge_crm_marketing">
<field name="name">Lead Acquisition</field>
<field name="period">monthly</field>
<field name="visibility_mode">ranking</field>
<field name="autojoin_group_id" eval="ref('base.group_sale_salesman')" />
<field name="report_message_frequency">weekly</field>
</record>
<!-- lines -->
<record model="gamification.challenge.line" id="line_crm_sale1">
<field name="definition_id" eval="ref('definition_crm_tot_invoices')" />
<field name="target_goal">20000</field>
<field name="challenge_id" eval="ref('challenge_crm_sale')" />
</record>
<record model="gamification.challenge.line" id="line_crm_marketing1">
<field name="definition_id" eval="ref('definition_crm_nbr_new_leads')" />
<field name="target_goal">7</field>
<field name="challenge_id" eval="ref('challenge_crm_marketing')" />
<field name="sequence">1</field>
</record>
<record model="gamification.challenge.line" id="line_crm_marketing2">
<field name="definition_id" eval="ref('definition_crm_lead_delay_open')" />
<field name="target_goal">15</field>
<field name="challenge_id" eval="ref('challenge_crm_marketing')" />
<field name="sequence">2</field>
</record>
<record model="gamification.challenge.line" id="line_crm_marketing3">
<field name="definition_id" eval="ref('definition_crm_nbr_new_opportunities')" />
<field name="target_goal">5</field>
<field name="challenge_id" eval="ref('challenge_crm_marketing')" />
<field name="sequence">3</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- challenges -->
<record model="gamification.challenge" id="challenge_crm_sale">
<field name="user_ids" eval="[(4,ref('base.user_demo'))]" />
<field name="state">inprogress</field>
</record>
<!-- goals -->
<record model="gamification.goal" id="goal_crm_sale1">
<field name="definition_id" eval="ref('definition_crm_tot_invoices')" />
<field name="user_id" eval="ref('base.user_demo')" />
<field name="line_id" eval="ref('line_crm_sale1')" />
<field name="start_date" eval="time.strftime('%Y-%m-01')" />
<field name="end_date" eval="time.strftime('%Y-%m-31')" />
<field name="target_goal">2000</field>
<field name="state">inprogress</field>
</record>
</data>
</openerp>

View File

@ -7,24 +7,24 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2012-05-10 17:48+0000\n"
"Last-Translator: Fabien (Open ERP) <fp@tinyerp.com>\n"
"PO-Revision-Date: 2013-12-27 05:02+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 05:56+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-28 05:19+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: hr
#: model:process.node,name:hr.process_node_openerpuser0
msgid "Openerp user"
msgstr ""
msgstr "Openerp 使用者"
#. module: hr
#: field:hr.config.settings,module_hr_timesheet_sheet:0
msgid "Allow timesheets validation by managers"
msgstr ""
msgstr "允許主管批准工時表"
#. module: hr
#: field:hr.job,requirements:0
@ -62,7 +62,7 @@ msgstr ""
#. module: hr
#: view:hr.config.settings:0
msgid "Time Tracking"
msgstr ""
msgstr "追蹤時間"
#. module: hr
#: view:hr.employee:0
@ -73,17 +73,17 @@ msgstr "分組根據..."
#. module: hr
#: model:ir.actions.act_window,name:hr.view_department_form_installer
msgid "Create Your Departments"
msgstr ""
msgstr "建立您的部門"
#. module: hr
#: help:hr.job,no_of_employee:0
msgid "Number of employees currently occupying this job position."
msgstr ""
msgstr "從事此職位的現有員工數量。"
#. module: hr
#: field:hr.config.settings,module_hr_evaluation:0
msgid "Organize employees periodic evaluation"
msgstr ""
msgstr "安排員工定期考評"
#. module: hr
#: view:hr.department:0
@ -98,19 +98,19 @@ msgstr "部門"
#. module: hr
#: field:hr.employee,work_email:0
msgid "Work Email"
msgstr ""
msgstr "工作電子郵件"
#. module: hr
#: help:hr.employee,image:0
msgid ""
"This field holds the image used as photo for the employee, limited to "
"1024x1024px."
msgstr ""
msgstr "此欄位存放圖片作為員工照片,限制大小為 1024*1024 像素。"
#. module: hr
#: help:hr.config.settings,module_hr_holidays:0
msgid "This installs the module hr_holidays."
msgstr ""
msgstr "此功能將安裝 hr_holidays 模組。"
#. module: hr
#: view:hr.job:0
@ -125,7 +125,7 @@ msgstr "正招聘"
#. module: hr
#: field:hr.job,message_unread:0
msgid "Unread Messages"
msgstr ""
msgstr "未讀訊息"
#. module: hr
#: field:hr.department,company_id:0
@ -143,7 +143,7 @@ msgstr ""
#. module: hr
#: field:res.users,employee_ids:0
msgid "Related employees"
msgstr ""
msgstr "相關員工"
#. module: hr
#: constraint:hr.employee.category:0
@ -153,28 +153,28 @@ msgstr ""
#. module: hr
#: help:hr.config.settings,module_hr_recruitment:0
msgid "This installs the module hr_recruitment."
msgstr ""
msgstr "此功能將安裝 hr_recruitment 模組。"
#. module: hr
#: view:hr.employee:0
msgid "Birth"
msgstr ""
msgstr "生日"
#. module: hr
#: model:ir.actions.act_window,name:hr.open_view_categ_form
#: model:ir.ui.menu,name:hr.menu_view_employee_category_form
msgid "Employee Tags"
msgstr ""
msgstr "員工標籤"
#. module: hr
#: view:hr.job:0
msgid "Launch Recruitement"
msgstr ""
msgstr "啟動招募"
#. module: hr
#: model:process.transition,name:hr.process_transition_employeeuser0
msgid "Link a user to an employee"
msgstr ""
msgstr "將一個使用者帳號連結到一位員工"
#. module: hr
#: field:hr.department,parent_id:0
@ -184,7 +184,7 @@ msgstr "上級部門"
#. module: hr
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_config
msgid "Leaves"
msgstr ""
msgstr "休假"
#. module: hr
#: selection:hr.employee,marital:0
@ -194,37 +194,37 @@ msgstr "已婚"
#. module: hr
#: field:hr.job,message_ids:0
msgid "Messages"
msgstr ""
msgstr "訊息"
#. module: hr
#: view:hr.config.settings:0
msgid "Talent Management"
msgstr ""
msgstr "人才管理"
#. module: hr
#: help:hr.config.settings,module_hr_timesheet_sheet:0
msgid "This installs the module hr_timesheet_sheet."
msgstr ""
msgstr "此功能將安裝 hr_timesheet_sheet 模組。"
#. module: hr
#: view:hr.employee:0
msgid "Mobile:"
msgstr ""
msgstr "行動電話:"
#. module: hr
#: view:hr.employee:0
msgid "Position"
msgstr ""
msgstr "職位"
#. module: hr
#: help:hr.job,message_unread:0
msgid "If checked new messages require your attention."
msgstr ""
msgstr "當有新訊息時通知您。"
#. module: hr
#: field:hr.employee,color:0
msgid "Color Index"
msgstr ""
msgstr "顏色索引"
#. module: hr
#: model:process.transition,note:hr.process_transition_employeeuser0
@ -236,7 +236,7 @@ msgstr ""
#. module: hr
#: field:hr.employee,image_medium:0
msgid "Medium-sized photo"
msgstr ""
msgstr "中等尺寸照片"
#. module: hr
#: field:hr.employee,identification_id:0
@ -251,7 +251,7 @@ msgstr "女"
#. module: hr
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_new_config
msgid "Attendance"
msgstr ""
msgstr "出勤"
#. module: hr
#: field:hr.employee,work_phone:0
@ -277,7 +277,7 @@ msgstr "辦公室位置"
#. module: hr
#: field:hr.job,message_follower_ids:0
msgid "Followers"
msgstr ""
msgstr "關注者"
#. module: hr
#: view:hr.employee:0
@ -307,12 +307,12 @@ msgstr "出生日期"
#. module: hr
#: help:hr.job,no_of_recruitment:0
msgid "Number of new employees you expect to recruit."
msgstr ""
msgstr "您期望聘雇的新員工數量。"
#. module: hr
#: model:ir.actions.client,name:hr.action_client_hr_menu
msgid "Open HR Menu"
msgstr ""
msgstr "開啟人資選單"
#. module: hr
#: help:hr.job,message_summary:0
@ -326,12 +326,12 @@ msgstr ""
msgid ""
"This installs the module account_analytic_analysis, which will install sales "
"management too."
msgstr ""
msgstr "此功能將安裝 account_analytic_analysis 模組,同時也安裝業務銷售管理模組。"
#. module: hr
#: view:board.board:0
msgid "Human Resources Dashboard"
msgstr ""
msgstr "人力資源儀表板"
#. module: hr
#: view:hr.employee:0
@ -343,7 +343,7 @@ msgstr "工作"
#. module: hr
#: field:hr.job,no_of_employee:0
msgid "Current Number of Employees"
msgstr ""
msgstr "現有員工數目"
#. module: hr
#: field:hr.department,member_ids:0
@ -353,22 +353,22 @@ msgstr "成員"
#. module: hr
#: model:ir.ui.menu,name:hr.menu_hr_configuration
msgid "Configuration"
msgstr ""
msgstr "組態設定"
#. module: hr
#: model:process.node,note:hr.process_node_employee0
msgid "Employee form and structure"
msgstr ""
msgstr "員工表單與架構"
#. module: hr
#: field:hr.config.settings,module_hr_expense:0
msgid "Manage employees expenses"
msgstr ""
msgstr "管理員工費用報支"
#. module: hr
#: view:hr.employee:0
msgid "Tel:"
msgstr ""
msgstr "電話:"
#. module: hr
#: selection:hr.employee,marital:0
@ -430,7 +430,7 @@ msgstr ""
#. module: hr
#: help:hr.config.settings,module_hr_evaluation:0
msgid "This installs the module hr_evaluation."
msgstr ""
msgstr "此功能將安裝 hr_evaluation 模組。"
#. module: hr
#: constraint:hr.employee:0
@ -440,12 +440,12 @@ msgstr ""
#. module: hr
#: help:hr.config.settings,module_hr_attendance:0
msgid "This installs the module hr_attendance."
msgstr ""
msgstr "此功能將安裝 hr_attendance 模組。"
#. module: hr
#: field:hr.employee,image_small:0
msgid "Smal-sized photo"
msgstr ""
msgstr "小尺吋照片"
#. module: hr
#: view:hr.employee.category:0
@ -456,22 +456,22 @@ msgstr "員工分類"
#. module: hr
#: field:hr.employee,category_ids:0
msgid "Tags"
msgstr ""
msgstr "標籤"
#. module: hr
#: help:hr.config.settings,module_hr_contract:0
msgid "This installs the module hr_contract."
msgstr ""
msgstr "此功能將安裝 hr_contract 模組。"
#. module: hr
#: view:hr.employee:0
msgid "Related User"
msgstr ""
msgstr "相關使用者"
#. module: hr
#: view:hr.config.settings:0
msgid "or"
msgstr ""
msgstr ""
#. module: hr
#: field:hr.employee.category,name:0
@ -481,12 +481,12 @@ msgstr "分類"
#. module: hr
#: view:hr.job:0
msgid "Stop Recruitment"
msgstr ""
msgstr "停止招募"
#. module: hr
#: field:hr.config.settings,module_hr_attendance:0
msgid "Install attendances feature"
msgstr ""
msgstr "安裝出勤管理功能"
#. module: hr
#: help:hr.employee,bank_account_id:0
@ -501,7 +501,7 @@ msgstr "備註"
#. module: hr
#: model:ir.actions.act_window,name:hr.open_view_employee_tree
msgid "Employees Structure"
msgstr ""
msgstr "員工架構"
#. module: hr
#: view:hr.employee:0
@ -511,7 +511,7 @@ msgstr "聯絡資料"
#. module: hr
#: field:hr.config.settings,module_hr_holidays:0
msgid "Manage holidays, leaves and allocation requests"
msgstr ""
msgstr "管理假日、請假及排假。"
#. module: hr
#: field:hr.department,child_ids:0
@ -528,7 +528,7 @@ msgstr "狀況"
#. module: hr
#: field:hr.employee,otherid:0
msgid "Other Id"
msgstr ""
msgstr "其他ID"
#. module: hr
#: model:process.process,name:hr.process_process_employeecontractprocess0
@ -538,12 +538,12 @@ msgstr "僱傭合約"
#. module: hr
#: view:hr.config.settings:0
msgid "Contracts"
msgstr ""
msgstr "合約"
#. module: hr
#: help:hr.job,message_ids:0
msgid "Messages and communication history"
msgstr ""
msgstr "訊息及聯絡紀錄"
#. module: hr
#: field:hr.employee,ssnid:0
@ -553,12 +553,12 @@ msgstr "社會保障號碼(美國)"
#. module: hr
#: field:hr.job,message_is_follower:0
msgid "Is a Follower"
msgstr ""
msgstr "為關注者"
#. module: hr
#: field:hr.config.settings,module_hr_recruitment:0
msgid "Manage the recruitment process"
msgstr ""
msgstr "管理招募流程"
#. module: hr
#: view:hr.employee:0
@ -568,12 +568,12 @@ msgstr "活躍"
#. module: hr
#: view:hr.config.settings:0
msgid "Human Resources Management"
msgstr ""
msgstr "人力資源管理"
#. module: hr
#: view:hr.config.settings:0
msgid "Install your country's payroll"
msgstr ""
msgstr "安裝您國家的薪資模組"
#. module: hr
#: field:hr.employee,bank_account_id:0
@ -588,7 +588,7 @@ msgstr "公司"
#. module: hr
#: field:hr.job,message_summary:0
msgid "Summary"
msgstr ""
msgstr "摘要"
#. module: hr
#: model:process.transition,note:hr.process_transition_contactofemployee0
@ -615,12 +615,12 @@ msgstr ""
#. module: hr
#: view:hr.employee:0
msgid "HR Settings"
msgstr ""
msgstr "人資設定"
#. module: hr
#: view:hr.employee:0
msgid "Citizenship & Other Info"
msgstr ""
msgstr "公民與其他資訊"
#. module: hr
#: constraint:hr.department:0
@ -635,7 +635,7 @@ msgstr "辦公地址"
#. module: hr
#: view:hr.employee:0
msgid "Public Information"
msgstr ""
msgstr "公開資訊"
#. module: hr
#: field:hr.employee,marital:0

View File

@ -42,6 +42,8 @@ class hr_config_settings(osv.osv_memory):
help ="""This installs the module hr_contract."""),
'module_hr_evaluation': fields.boolean('Organize employees periodic evaluation',
help ="""This installs the module hr_evaluation."""),
'module_hr_gamification': fields.boolean('Drive engagement with challenges and badges',
help ="""This installs the module hr_gamification."""),
'module_account_analytic_analysis': fields.boolean('Allow invoicing based on timesheets (the sale application will be installed)',
help ="""This installs the module account_analytic_analysis, which will install sales management too."""),
'module_hr_payroll': fields.boolean('Manage payroll',

View File

@ -56,6 +56,10 @@
<field name="module_hr_expense" class="oe_inline"/>
<label for="module_hr_expense"/>
</div>
<div>
<field name="module_hr_gamification" class="oe_inline"/>
<label for="module_hr_gamification"/>
</div>
</div>
</group>
<group name="timesheet_grp">

View File

@ -57,9 +57,10 @@ class res_users(osv.Model):
various mailboxes, we do not have access to the current partner_id. """
if kwargs.get('type') == 'email':
return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs)
res = None
employee_ids = self._message_post_get_eid(cr, uid, thread_id, context=context)
if not employee_ids:
pass # dpo something
if not employee_ids: # no employee: fall back on previous behavior
return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs)
for employee_id in employee_ids:
res = self.pool.get('hr.employee').message_post(cr, uid, employee_id, context=context, **kwargs)
return res

View File

@ -72,4 +72,4 @@
.openerp .oe_employee_vignette .oe_followers {
width: auto;
float: none;
}
}

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-today OpenERP SA (<http://www.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
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
import models

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
{
'name': 'Applicant Resumes and Letters',
'version': '1.0',
'category': 'Human Resources',
'sequence': 25,
'summary': 'Search job applications by Index content.',
'description': """This module allows you to search job applications by content
of resumes and letters.""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': [
'hr_recruitment',
'document'
],
'data': [
'views/hr_applicant.xml'
],
'demo': [
'demo/hr_applicant.xml'
],
'installable': True,
'auto_install': True,
'application': True,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,257 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record id="applicant_attach1" model="ir.attachment">
<field name="datas">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
aGKgRB9MdCaipUQDpDWUSIGEgHLkNhFgQphCMAiAxxJkIrgDOxq34DH/d37Q/7YA/+ZPJPpvgO3r
dQplbmRzdHJlYW0KZW5kb2JqCgo2IDAgb2JqCjEwNDEzCmVuZG9iagoKNyAwIG9iago8PC9UeXBl
L0ZvbnREZXNjcmlwdG9yL0ZvbnROYW1lL0JBQUFBQStEZWphVnVTYW5zTW9ubwovRmxhZ3MgNQov
Rm9udEJCb3hbLTU1NyAtMzc0IDcxNiAxMDQxXS9JdGFsaWNBbmdsZSAwCi9Bc2NlbnQgOTI4Ci9E
ZXNjZW50IC0yMzUKL0NhcEhlaWdodCAxMDQxCi9TdGVtViA4MAovRm9udEZpbGUyIDUgMCBSCj4+
CmVuZG9iagoKOCAwIG9iago8PC9MZW5ndGggNDE2L0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVh
bQp4nF2TzW7iMBSF93kKLzuLKvEPCZVQJAaKxKIz1dA+QEgME2lwIhMWvH197klbaRagz/G9l8/m
JN/st/vQT/lrHNqDn9SpD1301+EWW6+O/tyHTBvV9e00r+S7vTRjlqfew/06+cs+nIbVKsv/pL3r
FO/qYd0NR/8jy3/Hzsc+nNXD++aQ1ofbOP7zFx8mVWR1rTp/SnNemvFXc/G5dD3uu7TdT/fH1PJd
8HYfvTKy1lRph85fx6b1sQlnn62Kolar3a7OfOj+23OWLcdT+7eJqVSn0qJYFHViI1wZsBUud2BH
LsEL8hO4JG/AFXkBXgq7Z/ATn2vwmrwF/xQ28rsbYbsGb9krc55ZL3N2dMMcXZBRo+nv0Ktnfwum
v5P62d+B6W/lOf0tzqtnf2H6V1JPf4Mzavo7mU//cgmmf4Wz6NkfZ9T0txWY/hYzDf0t6s3sj7s1
9C8x39B/AU9Dfwc3Q38nvfQ3uB9Df4v/xdDfykz6W5lJf4t7M/S3SwnJnAbEBXn+jKFqbzGmCEro
JXtIXR/813sxDiO65PMBZrbPkAplbmRzdHJlYW0KZW5kb2JqCgo5IDAgb2JqCjw8L1R5cGUvRm9u
dC9TdWJ0eXBlL1RydWVUeXBlL0Jhc2VGb250L0JBQUFBQStEZWphVnVTYW5zTW9ubwovRmlyc3RD
aGFyIDAKL0xhc3RDaGFyIDQzCi9XaWR0aHNbNjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYw
MiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyCjYwMiA2MDIgNjAyIDYwMiA2MDIgNjAy
IDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMgo2MDIgNjAyIDYwMiA2MDIg
NjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMiBdCi9Gb250RGVzY3JpcHRvciA3IDAgUgov
VG9Vbmljb2RlIDggMCBSCj4+CmVuZG9iagoKMTAgMCBvYmoKPDwvRjEgOSAwIFIKPj4KZW5kb2Jq
CgoxMSAwIG9iago8PC9Gb250IDEwIDAgUgovUHJvY1NldFsvUERGL1RleHRdCj4+CmVuZG9iagoK
MSAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDQgMCBSL1Jlc291cmNlcyAxMSAwIFIvTWVkaWFC
b3hbMCAwIDU5NSA4NDJdL0dyb3VwPDwvUy9UcmFuc3BhcmVuY3kvQ1MvRGV2aWNlUkdCL0kgdHJ1
ZT4+L0NvbnRlbnRzIDIgMCBSPj4KZW5kb2JqCgo0IDAgb2JqCjw8L1R5cGUvUGFnZXMKL1Jlc291
cmNlcyAxMSAwIFIKL01lZGlhQm94WyAwIDAgNTk1IDg0MiBdCi9LaWRzWyAxIDAgUiBdCi9Db3Vu
dCAxPj4KZW5kb2JqCgoxMiAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgNCAwIFIKL09wZW5B
Y3Rpb25bMSAwIFIgL1hZWiBudWxsIG51bGwgMF0KL0xhbmcoZW4tSU4pCj4+CmVuZG9iagoKMTMg
MCBvYmoKPDwvQ3JlYXRvcjxGRUZGMDA1NzAwNzIwMDY5MDA3NDAwNjUwMDcyPgovUHJvZHVjZXI8
RkVGRjAwNEMwMDY5MDA2MjAwNzIwMDY1MDA0RjAwNjYwMDY2MDA2OTAwNjMwMDY1MDAyMDAwMzMw
MDJFMDAzNj4KL0NyZWF0aW9uRGF0ZShEOjIwMTMwOTE3MTA1MjE5KzA1JzMwJyk+PgplbmRvYmoK
CnhyZWYKMCAxNAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMTIxNDEgMDAwMDAgbiAKMDAwMDAw
MDAxOSAwMDAwMCBuIAowMDAwMDAwNTAzIDAwMDAwIG4gCjAwMDAwMTIyODQgMDAwMDAgbiAKMDAw
MDAwMDUyMyAwMDAwMCBuIAowMDAwMDExMDIxIDAwMDAwIG4gCjAwMDAwMTEwNDMgMDAwMDAgbiAK
MDAwMDAxMTIzOCAwMDAwMCBuIAowMDAwMDExNzIzIDAwMDAwIG4gCjAwMDAwMTIwNTQgMDAwMDAg
biAKMDAwMDAxMjA4NiAwMDAwMCBuIAowMDAwMDEyMzgzIDAwMDAwIG4gCjAwMDAwMTI0ODAgMDAw
MDAgbiAKdHJhaWxlcgo8PC9TaXplIDE0L1Jvb3QgMTIgMCBSCi9JbmZvIDEzIDAgUgovSUQgWyA8
MUQ3RURGOENDMTExODZGOUQwNERCQTNCNTIxRUIwRUQ+CjwxRDdFREY4Q0MxMTE4NkY5RDA0REJB
M0I1MjFFQjBFRD4gXQovRG9jQ2hlY2tzdW0gL0FGQTcyQ0IwMzY4QzE1RjAyN0YxODgxNDAxQkM3
QkQ0Cj4+CnN0YXJ0eHJlZgoxMjY1NQolJUVPRgo=</field>
<field name="datas_fname">Jones_CV.pdf</field>
<field name="name">Jones_CV.pdf</field>
<field name="res_id" ref="hr_recruitment.hr_case_salesman0"/>
<field name="res_model">hr.applicant</field>
</record>
<record id="applicant_attach2" model="ir.attachment">
<field name="datas">UFJPRklMRSANCg0KTmFtZSAgICAgICAgICAgIDogU2hhbmUgV2lsbGlhbXMgIA0KQWRkcmVzcyAgICAgICAgIDogODEgQWNhZGVteSBBdmVudWUsIA0KICAgICAgICAgICAgICAgICAgICAgOkJpcm1pbmdoYW1CNDYgM0FHLCANCiAgICAgICAgICAgICAgICAgICAgIDpVbml0ZWQgS2luZ2RvbSwgDQpRdWFsaWZpY2F0aW9uICAgOiBNQ0EgDQpFbWFpbCAgICAgICAgICAgICA6IFNoYW5lV2lsbGlhbXNAaW5mby5jb20gDQpNb2JpbGUgICAgICAgICAgIDogOTk2MzIxNDU4NyA=</field>
<field name="datas_fname">Williams_CV.doc</field>
<field name="name">Williams_CV.doc</field>
<field name="res_id" ref="hr_recruitment.hr_case_programmer"/>
<field name="res_model">hr.applicant</field>
</record>
<record id="applicant_attach3" model="ir.attachment">
<field name="datas">UHJvZmlsZQ0KDQpOYW1lICAgICAgICAgIDpKb3NlDQpBZGRyZXNzICAgICAgIDo5MywgUHJlc3MgQXZlbnVlDQogICAgICAgICAgICAgICAgICAgOkxlIEJvdXJnZXQgZHUgTGFjLCA3MzM3NywNCiAgICAgICAgICAgICAgICAgICA6IEZyYW5jZSANClF1YWxpZmljYXRpb24gOk1DQQ0KRW1haWwgICAgICAgICAgIDpKb3NlQGdtYWlsLmNvbQ0KTW9iaWxlICAgICAgICAgIDo5OTY4NTEzNTg3</field>
<field name="datas_fname">Jose_CV.txt</field>
<field name="name">Jose_CV.txt</field>
<field name="res_id" ref="hr_recruitment.hr_case_fresher0"/>
<field name="res_model">hr.applicant</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import hr_applicant

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from openerp.osv import fields, osv
class hr_applicant(osv.Model):
_inherit = 'hr.applicant'
def _get_index_content(self, cr, uid, ids, fields, args, context=None):
res = dict.fromkeys(ids, '')
Attachment = self.pool.get('ir.attachment')
attachment_ids = Attachment.search(cr, uid, [('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)], context=context)
for attachment in Attachment.browse(cr, uid, attachment_ids, context=context):
res[attachment.res_id] += attachment.index_content or ''
return res
def _content_search(self, cr, user, obj, name, args, context=None):
record_ids = set()
Attachment = self.pool.get('ir.attachment')
args = ['&'] + args + [('res_model', '=', 'hr.applicant')]
att_ids = Attachment.search(cr, user, args, context=context)
record_ids = set(att.res_id for att in Attachment.browse(cr, user, att_ids, context=context))
return [('id', 'in', list(record_ids))]
_columns = {
'index_content': fields.function(
_get_index_content, fnct_search=_content_search,
string='Index Content', type="text"),
}

View File

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="view_crm_case_jobs_filter_inherit" model="ir.ui.view">
<field name="name">Jobs - Recruitment Search</field>
<field name="model">hr.applicant</field>
<field name="inherit_id" ref="hr_recruitment.view_crm_case_jobs_filter" />
<field name="arch" type="xml">
<field name="job_id" position="before">
<field name="index_content" string="Resume Content"/>
</field>
</field>
</record>
<record model="ir.actions.act_window" id="hr_applicant_resumes">
<field name="name">Resumes and Letters</field>
<field name="res_model">ir.attachment</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="document.view_document_file_tree"/>
<field name="domain">[('res_model','=','hr.applicant')]</field>
<field name="help" type="html">
<p>
Search through resumes and motivation letters.
</p>
</field>
</record>
<menuitem
name="Resumes and Letters"
parent="base.menu_crm_case_job_req_main"
id="menu_crm_case_categ0_act_job02" action="hr_applicant_resumes" sequence="3"/>
</data>
</openerp>

View File

@ -101,6 +101,8 @@ Thanks,
class hr_employee(osv.osv):
_name = "hr.employee"
_inherit="hr.employee"
_columns = {
'evaluation_plan_id': fields.many2one('hr_evaluation.plan', 'Appraisal Plan'),
'evaluation_date': fields.date('Next Appraisal Date', help="The date of the next appraisal is computed by the appraisal plan's dates (first appraisal + periodicity)."),
@ -123,6 +125,8 @@ class hr_employee(osv.osv):
obj_evaluation.button_plan_in_progress(cr, uid, [plan_id], context=context)
return True
class hr_evaluation(osv.osv):
_name = "hr_evaluation.evaluation"
_inherit = "mail.thread"

View File

@ -39,4 +39,4 @@ access_survey_response_hr_employee,survey.response.employee,survey.model_survey_
access_survey_question_column_heading_hr_employee,survey.question.column.heading.employee,survey.model_survey_question_column_heading,base.group_user,1,0,0,0
access_survey_response_line_hr_employee,survey.response.line.employee,survey.model_survey_response_line,base.group_user,1,1,1,0
access_survey_response_answer_hr_employee,survey.response.answer.hr.employee,survey.model_survey_response_answer,base.group_user,1,1,1,0
access_survey_tbl_column_heading_hr_employee,survey.tbl.column.heading,survey.model_survey_tbl_column_heading,base.group_user,1,1,1,0
access_survey_tbl_column_heading_hr_employee,survey.tbl.column.heading,survey.model_survey_tbl_column_heading,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
39 access_survey_question_column_heading_hr_employee survey.question.column.heading.employee survey.model_survey_question_column_heading base.group_user 1 0 0 0
40 access_survey_response_line_hr_employee survey.response.line.employee survey.model_survey_response_line base.group_user 1 1 1 0
41 access_survey_response_answer_hr_employee survey.response.answer.hr.employee survey.model_survey_response_answer base.group_user 1 1 1 0
42 access_survey_tbl_column_heading_hr_employee survey.tbl.column.heading survey.model_survey_tbl_column_heading base.group_user 1 1 1 0

View File

@ -7,57 +7,57 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2009-01-30 13:19+0000\n"
"Last-Translator: <>\n"
"PO-Revision-Date: 2013-12-27 04:48+0000\n"
"Last-Translator: Andy Cheng <andy@dobtor.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: 2013-09-12 05:53+0000\n"
"X-Generator: Launchpad (build 16761)\n"
"X-Launchpad-Export-Date: 2013-12-28 05:19+0000\n"
"X-Generator: Launchpad (build 16877)\n"
#. module: hr_expense
#: view:hr.expense.expense:0
#: model:process.node,name:hr_expense.process_node_confirmedexpenses0
msgid "Confirmed Expenses"
msgstr ""
msgstr "已確認費用報支"
#. module: hr_expense
#: model:ir.model,name:hr_expense.model_hr_expense_line
msgid "Expense Line"
msgstr ""
msgstr "費用報支項目"
#. module: hr_expense
#: model:process.node,note:hr_expense.process_node_reimbursement0
msgid "The accoutant reimburse the expenses"
msgstr ""
msgstr "會計師報支費用"
#. module: hr_expense
#: model:mail.message.subtype,description:hr_expense.mt_expense_approved
msgid "Expense approved"
msgstr ""
msgstr "費用報支已核准"
#. module: hr_expense
#: field:hr.expense.expense,date_confirm:0
#: field:hr.expense.report,date_confirm:0
msgid "Confirmation Date"
msgstr ""
msgstr "確認日期"
#. module: hr_expense
#: view:hr.expense.expense:0
#: view:hr.expense.report:0
msgid "Group By..."
msgstr ""
msgstr "分組..."
#. module: hr_expense
#: model:product.template,name:hr_expense.air_ticket_product_template
msgid "Air Ticket"
msgstr ""
msgstr "機票"
#. module: hr_expense
#: report:hr.expense:0
msgid "Validated By"
msgstr ""
msgstr "已完成審核"
#. module: hr_expense
#: view:hr.expense.expense:0
@ -65,45 +65,45 @@ msgstr ""
#: view:hr.expense.report:0
#: field:hr.expense.report,department_id:0
msgid "Department"
msgstr ""
msgstr "部門"
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "New Expense"
msgstr ""
msgstr "新增費用報支"
#. module: hr_expense
#: field:hr.expense.line,uom_id:0
#: view:product.product:0
msgid "Unit of Measure"
msgstr ""
msgstr "度量單位"
#. module: hr_expense
#: selection:hr.expense.report,month:0
msgid "March"
msgstr ""
msgstr "三月"
#. module: hr_expense
#: field:hr.expense.expense,message_unread:0
msgid "Unread Messages"
msgstr ""
msgstr "未讀訊息"
#. module: hr_expense
#: field:hr.expense.expense,company_id:0
#: view:hr.expense.report:0
#: field:hr.expense.report,company_id:0
msgid "Company"
msgstr ""
msgstr "公司"
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "Set to Draft"
msgstr ""
msgstr "設為草稿"
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "To Pay"
msgstr ""
msgstr "應付款"
#. module: hr_expense
#: code:addons/hr_expense/hr_expense.py:172
@ -111,12 +111,12 @@ msgstr ""
msgid ""
"No expense journal found. Please make sure you have a journal with type "
"'purchase' configured."
msgstr ""
msgstr "未找到費用日記帳。請確認您是否有設置型態為「採購」的日記帳。"
#. module: hr_expense
#: model:ir.model,name:hr_expense.model_hr_expense_report
msgid "Expenses Statistics"
msgstr ""
msgstr "費用報支統計"
#. module: hr_expense
#: view:hr.expense.expense:0
@ -127,7 +127,7 @@ msgstr ""
#: view:hr.expense.report:0
#: field:hr.expense.report,day:0
msgid "Day"
msgstr ""
msgstr ""
#. module: hr_expense
#: help:hr.expense.expense,date_valid:0
@ -139,12 +139,12 @@ msgstr ""
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "Notes"
msgstr ""
msgstr "備註"
#. module: hr_expense
#: field:hr.expense.expense,message_ids:0
msgid "Messages"
msgstr ""
msgstr "訊息"
#. module: hr_expense
#: code:addons/hr_expense/hr_expense.py:172
@ -154,28 +154,28 @@ msgstr ""
#: code:addons/hr_expense/hr_expense.py:353
#, python-format
msgid "Error!"
msgstr ""
msgstr "錯誤!"
#. module: hr_expense
#: model:mail.message.subtype,description:hr_expense.mt_expense_refused
msgid "Expense refused"
msgstr ""
msgstr "費用報支被拒"
#. module: hr_expense
#: model:ir.actions.act_window,name:hr_expense.hr_expense_product
#: view:product.product:0
msgid "Products"
msgstr ""
msgstr "產品"
#. module: hr_expense
#: view:hr.expense.report:0
msgid "Confirm Expenses"
msgstr ""
msgstr "確認費用報支"
#. module: hr_expense
#: selection:hr.expense.report,state:0
msgid "Cancelled"
msgstr ""
msgstr "已取消"
#. module: hr_expense
#: model:process.node,note:hr_expense.process_node_refused0
@ -190,17 +190,17 @@ msgstr ""
#. module: hr_expense
#: selection:hr.expense.report,state:0
msgid "Waiting confirmation"
msgstr ""
msgstr "等待確認中"
#. module: hr_expense
#: selection:hr.expense.report,state:0
msgid "Accepted"
msgstr ""
msgstr "已接受"
#. module: hr_expense
#: field:hr.expense.line,ref:0
msgid "Reference"
msgstr ""
msgstr "參考"
#. module: hr_expense
#: report:hr.expense:0
@ -228,7 +228,7 @@ msgstr ""
#: view:hr.expense.report:0
#: field:hr.expense.report,nbr:0
msgid "# of Lines"
msgstr ""
msgstr "項目數量"
#. module: hr_expense
#: help:hr.expense.expense,message_summary:0
@ -241,33 +241,33 @@ msgstr ""
#: code:addons/hr_expense/hr_expense.py:453
#, python-format
msgid "Warning"
msgstr ""
msgstr "警告"
#. module: hr_expense
#: report:hr.expense:0
msgid "(Date and signature)"
msgstr ""
msgstr "(日期與簽名)"
#. module: hr_expense
#: report:hr.expense:0
msgid "Total:"
msgstr ""
msgstr "合計:"
#. module: hr_expense
#: model:process.transition,name:hr_expense.process_transition_refuseexpense0
msgid "Refuse expense"
msgstr ""
msgstr "拒絕費用報支"
#. module: hr_expense
#: field:hr.expense.report,price_average:0
msgid "Average Price"
msgstr ""
msgstr "平均價格"
#. module: hr_expense
#: view:hr.expense.expense:0
#: model:process.transition.action,name:hr_expense.process_transition_action_confirm0
msgid "Confirm"
msgstr ""
msgstr "確認"
#. module: hr_expense
#: model:process.node,note:hr_expense.process_node_supplierinvoice0

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import models
import wizard

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'HR Gamification',
'version': '1.0',
'author': 'OpenERP SA',
'category': 'hidden',
'depends': ['gamification', 'hr'],
'description': """Use the HR ressources for the gamification process.
The HR officer can now manage challenges and badges.
This allow the user to send badges to employees instead of simple users.
Badge received are displayed on the user profile.
""",
'data': [
'security/ir.model.access.csv',
'security/gamification_security.xml',
'wizard/grant_badge.xml',
'views/gamification.xml',
],
'js': ['static/src/js/gamification.js'],
}

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import gamification

View File

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp.osv import fields, osv
class hr_gamification_badge_user(osv.Model):
"""User having received a badge"""
_name = 'gamification.badge.user'
_inherit = ['gamification.badge.user']
_columns = {
'employee_id': fields.many2one("hr.employee", string='Employee'),
}
def _check_employee_related_user(self, cr, uid, ids, context=None):
for badge_user in self.browse(cr, uid, ids, context=context):
if badge_user.user_id and badge_user.employee_id:
if badge_user.employee_id not in badge_user.user_id.employee_ids:
return False
return True
_constraints = [
(_check_employee_related_user, "The selected employee does not correspond to the selected user.", ['employee_id']),
]
class gamification_badge(osv.Model):
_name = 'gamification.badge'
_inherit = ['gamification.badge']
def get_granted_employees(self, cr, uid, badge_ids, context=None):
if context is None:
context = {}
employee_ids = []
badge_user_ids = self.pool.get('gamification.badge.user').search(cr, uid, [('badge_id', 'in', badge_ids), ('employee_id', '!=', False)], context=context)
for badge_user in self.pool.get('gamification.badge.user').browse(cr, uid, badge_user_ids, context):
employee_ids.append(badge_user.employee_id.id)
# remove duplicates
employee_ids = list(set(employee_ids))
return {
'type': 'ir.actions.act_window',
'name': 'Granted Employees',
'view_mode': 'kanban,tree,form',
'view_type': 'form',
'res_model': 'hr.employee',
'domain': [('id', 'in', employee_ids)]
}
class hr_employee(osv.osv):
_name = "hr.employee"
_inherit = "hr.employee"
def _get_employee_goals(self, cr, uid, ids, field_name, arg, context=None):
"""Return the list of goals assigned to the employee"""
res = {}
for employee in self.browse(cr, uid, ids, context=context):
res[employee.id] = self.pool.get('gamification.goal').search(cr,uid,[('user_id', '=', employee.user_id.id), ('challenge_id.category', '=', 'hr')], context=context)
return res
def _get_employee_badges(self, cr, uid, ids, field_name, arg, context=None):
"""Return the list of badge_users assigned to the employee"""
res = {}
for employee in self.browse(cr, uid, ids, context=context):
res[employee.id] = self.pool.get('gamification.badge.user').search(cr, uid, [
'|',
('employee_id', '=', employee.id),
'&',
('employee_id', '=', False),
('user_id', '=', employee.user_id.id)
], context=context)
return res
def _has_badges(self, cr, uid, ids, field_name, arg, context=None):
"""Return the list of badge_users assigned to the employee"""
res = {}
for employee in self.browse(cr, uid, ids, context=context):
employee_badge_ids = self.pool.get('gamification.badge.user').search(cr, uid, [
'|',
('employee_id', '=', employee.id),
'&',
('employee_id', '=', False),
('user_id', '=', employee.user_id.id)
], context=context)
res[employee.id] = len(employee_badge_ids) > 0
return res
_columns = {
'goal_ids': fields.function(_get_employee_goals, type="one2many", obj='gamification.goal', string="Employee HR Goals"),
'badge_ids': fields.function(_get_employee_badges, type="one2many", obj='gamification.badge.user', string="Employee Badges"),
'has_badges': fields.function(_has_badges, type="boolean", string="Has Badges"),
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<openerp>
<data noupdate="1">
<record id="goal_officer_visibility" model="ir.rule">
<field name="name">HR Officer can see any goal</field>
<field name="model_id" ref="gamification.model_gamification_goal"/>
<field name="groups" eval="[(4, ref('base.group_hr_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
challenge_officer,"Challenge Officer",gamification.model_gamification_challenge,base.group_hr_user,1,1,1,1
challenge_line_officer,"Challenge Line Officer",gamification.model_gamification_challenge_line,base.group_hr_user,1,1,1,1
badge_officer,"Badge Officer",gamification.model_gamification_badge,base.group_hr_user,1,1,1,1
badge_user_officer,"Badge-user Officer",gamification.model_gamification_badge_user,base.group_hr_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 challenge_officer Challenge Officer gamification.model_gamification_challenge base.group_hr_user 1 1 1 1
3 challenge_line_officer Challenge Line Officer gamification.model_gamification_challenge_line base.group_hr_user 1 1 1 1
4 badge_officer Badge Officer gamification.model_gamification_badge base.group_hr_user 1 1 1 1
5 badge_user_officer Badge-user Officer gamification.model_gamification_badge_user base.group_hr_user 1 1 1 1

View File

@ -0,0 +1,19 @@
openerp.hr_gamification = function(instance) {
instance.web_kanban.KanbanRecord.include({
on_card_clicked: function() {
if (this.view.dataset.model === 'gamification.badge.user') {
var action = {
type: 'ir.actions.act_window',
res_model: 'gamification.badge',
view_mode: 'form',
view_type: 'form,kanban,tree',
views: [[false, 'form']],
res_id: this.record.badge_id.raw_value[0]
};
this.do_action(action);
} else {
this._super.apply(this, arguments);
}
}
});
};

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="hr_badge_form_view" model="ir.ui.view">
<field name="name">Badge Form</field>
<field name="model">gamification.badge</field>
<field name="inherit_id" ref="gamification.badge_form_view"/>
<field name="arch" type="xml">
<xpath expr="//div[@class='oe_right oe_button_box']" position="inside">
<button string="Granted Employees" type="object" name="get_granted_employees" attrs="{'invisible': [('stat_count','=',0)]}" />
</xpath>
</field>
</record>
<!-- HR Employee -->
<record id="hr_hr_employee_view_form" model="ir.ui.view">
<field name="name">hr.hr.employee.view.form</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@string='Public Information']" position="before">
<page string="Received Badges" attrs="{'invisible': [('user_id', '=', False)]}">
<field name="has_badges" invisible="1"/>
<button string="Grant a Badge" type="action" name="%(action_reward_wizard)d"/> to reward this employee for a good action
<div class="oe_view_nocontent" attrs="{'invisible': [('has_badges', '=', True)]}">
<p class="oe_view_nocontent_create">
Click to grant this employee his first badge
</p><p class="oe_grey">
Badges are rewards of good work. Give them to people you believe deserve it.
</p>
</div>
<field name="badge_ids" widget="many2many_kanban" />
</page>
</xpath>
<xpath expr="//page[@string='Public Information']" position="after">
<page string="Goals">
<field name="goal_ids" widget="many2many_kanban" />
</page>
</xpath>
</field>
</record>
<record id="goals_menu_groupby_action2" model="ir.actions.act_window">
<field name="res_model">gamification.goal</field>
<field name="view_type">form</field>
<field name="name">Goals History</field>
<field name="view_mode">tree,kanban</field>
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_type': True}</field>
<field name="domain">[('challenge_id.category', '=', 'hr')]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a goal.
</p>
<p>
A goal is defined by a user and a goal type.
Goals can be created automatically by using challenges.
</p>
</field>
</record>
<record id="challenge_list_action2" model="ir.actions.act_window">
<field name="name">Challenges</field>
<field name="res_model">gamification.challenge</field>
<field name="view_mode">kanban,tree,form</field>
<field name="domain">[('category', '=', 'hr')]</field>
<field name="context">{'search_default_inprogress':True, 'default_inprogress':True}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a challenge.
</p>
<p>
Assign a list of goals to chosen users to evaluate them.
The challenge can use a period (weekly, monthly...) for automatic creation of goals.
The goals are created for the specified users or member of the group.
</p>
</field>
</record>
<record id="challenge_list_action2_view1" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">kanban</field>
<field name="act_window_id" ref="challenge_list_action2"/>
<field name="view_id" ref="gamification.view_challenge_kanban"/>
</record>
<record id="challenge_list_action2_view2" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">form</field>
<field name="act_window_id" ref="challenge_list_action2"/>
<field name="view_id" ref="gamification.challenge_form_view"/>
</record>
<menuitem id="menu_hr_gamification" parent="hr.menu_hr_root" name="Engagement" sequence="40"/>
<menuitem id="gamification_badge_menu_hr" parent="menu_hr_gamification" action="gamification.badge_list_action" />
<menuitem id="gamification_challenge_menu_hr" parent="menu_hr_gamification" action="challenge_list_action2" groups="base.group_hr_user"/>
<menuitem id="gamification_goal_menu_hr" parent="menu_hr_gamification" action="goals_menu_groupby_action2" groups="base.group_hr_user"/>
</data>
</openerp>

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import grant_badge

View File

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp.osv import fields, osv
from openerp.tools.translate import _
class hr_grant_badge_wizard(osv.TransientModel):
_name = 'gamification.badge.user.wizard'
_inherit = ['gamification.badge.user.wizard']
_columns = {
'employee_id': fields.many2one("hr.employee", string='Employee', required=True),
'user_id': fields.related("employee_id", "user_id",
type="many2one", relation="res.users",
store=True, string='User')
}
def action_grant_badge(self, cr, uid, ids, context=None):
"""Wizard action for sending a badge to a chosen employee"""
if context is None:
context = {}
badge_user_obj = self.pool.get('gamification.badge.user')
for wiz in self.browse(cr, uid, ids, context=context):
if not wiz.user_id:
raise osv.except_osv(_('Warning!'), _('You can send badges only to employees linked to a user.'))
if uid == wiz.user_id.id:
raise osv.except_osv(_('Warning!'), _('You can not send a badge to yourself'))
values = {
'user_id': wiz.user_id.id,
'sender_id': uid,
'badge_id': wiz.badge_id.id,
'employee_id': wiz.employee_id.id,
'comment': wiz.comment,
}
badge_user = badge_user_obj.create(cr, uid, values, context=context)
result = badge_user_obj._send_badge(cr, uid, [badge_user], context=context)
return result

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="view_badge_wizard_grant_employee" model="ir.ui.view">
<field name="name">Grant Badge Employee Form</field>
<field name="model">gamification.badge.user.wizard</field>
<field name="inherit_id" ref="gamification.view_badge_wizard_grant" />
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='user_id']" position="replace">
<field name="employee_id" nolabel="1" domain="[('user_id', '!=', False),('user_id', '!=', uid)]" />
</xpath>
</data>
</field>
</record>
<record id="view_badge_wizard_reward" model="ir.ui.view">
<field name="name">Reward Employee Badge Form</field>
<field name="model">gamification.badge.user.wizard</field>
<field name="arch" type="xml">
<form string="Reward Employee with" version="7.0">
What are you thank for?
<group>
<field name="employee_id" invisible="1" />
<field name="badge_id" nolabel="1" colspan="4" />
<field name="comment" nolabel="1" placeholder="Describe what they did and why it matters (will be public)" />
</group>
<footer>
<button string="Reward Employee" type="object" name="action_grant_badge" class="oe_highlight" /> or
<button string="Cancel" special="cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
<act_window domain="[]" id="action_reward_wizard"
name="Reward Employee"
target="new"
res_model="gamification.badge.user.wizard"
context="{'default_employee_id': active_id, 'employee_id': active_id}"
view_type="form" view_mode="form"
view_id="view_badge_wizard_reward"/>
</data>
</openerp>

View File

@ -242,6 +242,10 @@ class hr_holidays(osv.osv):
result['value'] = {
'employee_id': ids_employee[0]
}
elif holiday_type != 'employee':
result['value'] = {
'employee_id': False
}
return result
def onchange_employee(self, cr, uid, ids, employee_id):

View File

@ -14,7 +14,7 @@
<separator/>
<filter icon="terp-go-year" name="year" string="Year" domain="[('holiday_status_id.active','=',True)]" help="Filters only on allocations and requests that belong to an holiday type that is 'active' (active field is True)"/>
<separator/>
<filter string="My Leaves" icon="terp-personal" name="my_leaves" domain="[('employee_id.user_id','=', uid)]" help="My Leaves"/>
<filter string="My Requests" icon="terp-personal" name="my_leaves" domain="[('employee_id.user_id','=', uid)]" help="My Leave Requests"/>
<separator/>
<filter string="My Department Leaves" icon="terp-personal+" help="My Department Leaves" domain="[('department_id.manager_id','=',uid)]"/>
<field name="employee_id"/>
@ -47,61 +47,14 @@
</field>
</record>
<record id="edit_holiday_new" model="ir.ui.view">
<!-- Holidays: Allocation Request -->
<record model="ir.ui.view" id="edit_holiday_new">
<field name="name">Leave Request</field>
<field name="model">hr.holidays</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<form string="Leave Request" version="7.0">
<field name="can_reset" invisible="1"/>
<header>
<button string="Confirm" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
<button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button string="Refuse" name="refuse" states="confirm,validate1,validate" type="workflow" groups="base.group_hr_user"/>
<button string="Reset to Draft" name="reset" type="workflow"
attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
</header>
<sheet string="Leave Request">
<group>
<group>
<field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id}"/>
<label for="number_of_days_temp" string="Duration" help="The default duration interval between the start date and the end date is 8 hours. Feel free to adapt it to your needs."/>
<div>
<group col="3">
<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"/>
</group>
<div>
<field name="number_of_days_temp" class="oe_inline"/> days
</div>
</div>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'invisible':[('holiday_type','=','employee')], 'readonly':[('state','!=','draft'), ('state','!=','confirm')]}"/>
</group>
<group>
<field name="holiday_type" on_change="onchange_type(holiday_type, employee_id)" attrs="{'readonly':[('state','!=','draft')]}" width="130" string="Mode" groups="base.group_hr_user"/>
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','=','category')]}" on_change="onchange_employee(employee_id)" groups="base.group_hr_user"/>
<field name="department_id" attrs="{'readonly':[('holiday_type','=','category')]}" groups="base.group_hr_user"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<!-- Holidays: Allocation Request -->
<record model="ir.ui.view" id="allocation_leave_new">
<field name="name">Allocation Request</field>
<field name="model">hr.holidays</field>
<field name="arch" type="xml">
<form string="Allocation Request" version="7.0">
<field name="can_reset" invisible="1"/>
<field name="type" invisible="1"/>
<header>
<button string="Confirm" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
<button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
@ -114,21 +67,27 @@
<sheet>
<group>
<group>
<field name="name" required="1" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')]}"/>
<field name="name" attrs="{'readonly':[('state','!=','draft'),('state','!=','confirm')], 'required': [('type', '=', 'add')]}"/>
<field name="holiday_status_id" context="{'employee_id':employee_id}"/>
<label for="number_of_days_temp"/>
<label for="number_of_days_temp" string="Duration"/>
<div>
<field name="number_of_days_temp" class="oe_inline"/> days
<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"/>
</group>
<div>
<field name="number_of_days_temp" class="oe_inline"/> days
</div>
</div>
</group>
<group>
<field name="holiday_type" on_change="onchange_type(holiday_type)"/>
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')], 'invisible':[('holiday_type','=','category')]}"/>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'invisible':[('holiday_type','=','employee')]}"/>
<field name="department_id" attrs="{'invisible':[('holiday_type','=','category')]}"/>
<field name="holiday_type" on_change="onchange_type(holiday_type)" attrs="{'readonly':[('type', '=', 'remove'),('state','!=','draft')]}" string="Mode" groups="base.group_hr_user"/>
<field name="employee_id" attrs="{'required':[('holiday_type','=','employee')],'invisible':[('holiday_type','=','category')]}" on_change="onchange_employee(employee_id)" groups="base.group_hr_user"/>
<field name="category_id" attrs="{'required':[('holiday_type','=','category')], 'readonly': [('type', '=', 'remove'),('state','!=','draft'), ('state','!=','confirm')], 'invisible':[('holiday_type','=','employee')]}"/>
<field name="department_id" attrs="{'readonly':['|', ('type','=','add'),('holiday_type','=','category')],'invisible':[('holiday_type','=','category')]}" groups="base.group_hr_user"/>
</group>
</group>
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/>
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..." attrs="{'invisible': [('type', '=', 'remove')]}"/>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
@ -231,7 +190,7 @@
<menuitem name="Leaves" parent="hr.menu_hr_root" id="menu_open_ask_holidays" sequence="20"/>
<record model="ir.actions.act_window" id="open_ask_holidays">
<field name="name">Leave Requests</field>
<field name="name">Leave Request</field>
<field name="res_model">hr.holidays</field>
<field name="view_type">form</field>
<field name="view_id" ref="edit_holiday_new"/>
@ -305,7 +264,7 @@
<field name="view_type">form</field>
<field name="context">{'default_type':'add', 'search_default_my_leaves':1}</field>
<field name="domain">[('type','=','add')]</field>
<field name="view_id" ref="allocation_leave_new"/>
<field name="view_id" ref="edit_holiday_new"/>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
@ -319,7 +278,7 @@
<record model="ir.actions.act_window.view" id="action_open_allocation_holidays_form">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="allocation_leave_new"/>
<field name="view_id" ref="edit_holiday_new"/>
<field name="act_window_id" ref="open_allocation_holidays"/>
</record>
@ -331,7 +290,7 @@
<field name="view_type">form</field>
<field name="context">{'default_type': 'add', 'search_default_approve':1}</field>
<field name="domain">[('type','=','add')]</field>
<field name="view_id" ref="allocation_leave_new"/>
<field name="view_id" ref="edit_holiday_new"/>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
@ -345,7 +304,7 @@
<record model="ir.actions.act_window.view" id="action_request_approve_allocation_form">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="allocation_leave_new"/>
<field name="view_id" ref="edit_holiday_new"/>
<field name="act_window_id" ref="request_approve_allocation"/>
</record>

View File

@ -96,7 +96,7 @@ class report_custom(report_rml):
res=cr.fetchone()[0]
date_xml=[]
date_today=time.strftime('%Y-%m-%d %H:%M:%S')
date_xml +=['<res name="%s" today="%s" />' % (res,date_today)]
date_xml +=['<res name="%s" today="%s" />' % (to_xml(res),date_today)]
cr.execute("SELECT id, name, color_name FROM hr_holidays_status ORDER BY id")
legend=cr.fetchall()
@ -128,7 +128,7 @@ class report_custom(report_rml):
# date_xml=[]
for l in range(0,len(legend)):
date_xml += ['<legend row="%d" id="%d" name="%s" color="%s" />' % (l+1,legend[l][0],_(legend[l][1]),legend[l][2])]
date_xml += ['<date month="%s" year="%d" />' % (som.strftime('%B'), som.year),'<days>']
date_xml += ['<date month="%s" year="%d" />' % (ustr(som.strftime('%B')), som.year),'<days>']
cell=1
if day_diff.days>=30:

View File

@ -3,6 +3,13 @@
Changelog
=========
`trunk (saas-3)`
----------------
- ``hr.recruitment.stage``: added template_id field. If an email template is linked
to the stage, it is used to render and post a message on the applicant. This
allows for example to have template for accepted or refused applicants.
`trunk (saas-2)`
----------------

View File

@ -53,6 +53,7 @@ class hr_recruitment_stage(osv.osv):
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of stages."),
'department_id':fields.many2one('hr.department', 'Specific to a Department', help="Stages of the recruitment process may be different per department. If this stage is common to all departments, keep this field empty."),
'requirements': fields.text('Requirements'),
'template_id': fields.many2one('email.template', 'Use template', help="If set, a message is posted on the applicant using the template when the applicant is set to the stage."),
'fold': fields.boolean('Folded in Kanban View',
help='This stage is folded in the kanban view when'
'there are no records in that stage to display.'),
@ -172,8 +173,14 @@ class hr_applicant(osv.Model):
res[issue.id][field] = abs(float(duration))
return res
def _get_attachment_number(self, cr, uid, ids, fields, args, context=None):
res = dict.fromkeys(ids, 0)
for app_id in ids:
res[app_id] = self.pool['ir.attachment'].search_count(cr, uid, [('res_model', '=', 'hr.applicant'), ('res_id', '=', app_id)], context=context)
return res
_columns = {
'name': fields.char('Subject', size=128, required=True),
'name': fields.char('Subject / Application Name', size=128, required=True),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."),
'description': fields.text('Description'),
'email_from': fields.char('Email', size=128, help="These people will receive email."),
@ -200,7 +207,7 @@ class hr_applicant(osv.Model):
'salary_expected_extra': fields.char('Expected Salary Extra', size=100, help="Salary Expected by Applicant, extra advantages"),
'salary_proposed': fields.float('Proposed Salary', help="Salary Proposed by the Organisation"),
'salary_expected': fields.float('Expected Salary', help="Salary Expected by Applicant"),
'availability': fields.integer('Availability'),
'availability': fields.integer('Availability', help="The number of days in which the applicant will be available to start working"),
'partner_name': fields.char("Applicant's Name", size=64),
'partner_phone': fields.char('Phone', size=32),
'partner_mobile': fields.char('Mobile', size=32),
@ -215,9 +222,9 @@ class hr_applicant(osv.Model):
'day_close': fields.function(_compute_day, string='Days to Close', \
multi='day_close', type="float", store=True),
'color': fields.integer('Color Index'),
'emp_id': fields.many2one('hr.employee', string='Employee',
help='Employee linked to the applicant.'),
'emp_id': fields.many2one('hr.employee', string='Employee', help='Employee linked to the applicant.'),
'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
'attachment_number': fields.function(_get_attachment_number, string='Number of Attachments', type="integer"),
}
_defaults = {
@ -235,11 +242,11 @@ class hr_applicant(osv.Model):
}
def onchange_job(self, cr, uid, ids, job_id=False, context=None):
department_id = False
if job_id:
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
if job_record and job_record.department_id:
return {'value': {'department_id': job_record.department_id.id}}
return {}
department_id = job_record and job_record.department_id and job_record.department_id.id or False
return {'value': {'department_id': department_id}}
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
if not stage_id:
@ -290,10 +297,15 @@ class hr_applicant(osv.Model):
@return: Dictionary value for created Meeting view
"""
applicant = self.browse(cr, uid, ids[0], context)
applicant_ids = []
if applicant.partner_id:
applicant_ids.append(applicant.partner_id.id)
if applicant.department_id and applicant.department_id.manager_id and applicant.department_id.manager_id.user_id and applicant.department_id.manager_id.user_id.partner_id:
applicant_ids.append(applicant.department_id.manager_id.user_id.partner_id.id)
category = self.pool.get('ir.model.data').get_object(cr, uid, 'hr_recruitment', 'categ_meet_interview', context)
res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context)
res['context'] = {
'default_partner_ids': applicant.partner_id and [applicant.partner_id.id] or False,
'default_partner_ids': applicant_ids,
'default_user_id': uid,
'default_name': applicant.name,
'default_categ_ids': category and [category.id] or False,
@ -319,6 +331,20 @@ 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 message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
for applicant in self.browse(cr, uid, ids, context=context):
@ -364,6 +390,8 @@ class hr_applicant(osv.Model):
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
res = True
# user_id change: update date_start
if vals.get('user_id'):
vals['date_start'] = fields.datetime.now()
@ -373,8 +401,34 @@ class hr_applicant(osv.Model):
for applicant in self.browse(cr, uid, ids, context=None):
vals['last_stage_id'] = applicant.stage_id.id
res = super(hr_applicant, self).write(cr, uid, [applicant.id], vals, context=context)
return res
return super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
else:
res = super(hr_applicant, self).write(cr, uid, ids, vals, 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)
if stage.template_id:
# TDENOTE: probably factorize me in a message_post_with_template generic method FIXME
compose_ctx = dict(context,
active_ids=ids)
compose_id = self.pool['mail.compose.message'].create(
cr, uid, {
'model': self._name,
'composition_mode': 'mass_mail',
'template_id': stage.template_id.id,
'same_thread': True,
'post': True,
'notify': True,
}, context=compose_ctx)
self.pool['mail.compose.message'].write(
cr, uid, [compose_id],
self.pool['mail.compose.message'].onchange_template_id(
cr, uid, [compose_id],
stage.template_id.id, 'mass_mail', self._name, False,
context=compose_ctx)['value'],
context=compose_ctx)
self.pool['mail.compose.message'].send_mail(cr, uid, [compose_id], context=compose_ctx)
return res
def create_employee_from_applicant(self, cr, uid, ids, context=None):
""" Create an hr.employee from the hr.applicants """
@ -394,7 +448,10 @@ class hr_applicant(osv.Model):
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,
'department_id': applicant.department_id.id
'department_id': applicant.department_id.id or False,
'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,
})
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
else:

View File

@ -17,6 +17,43 @@
<field name="name">Interview</field>
</record>
<!-- Templates for interest / refusing applicants -->
<record id="applicant_refuse" model="email.template">
<field name="name">Application refused</field>
<field name="subject">Application refused</field>
<field name="email_to">${object.email_from}</field>
<field name="partner_to">${object.partner_id and object.partner_id.id or ''}</field>
<field name="lang">${object.partner_id and object.partner_id.lang or ''}</field>
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
<field name="user_signature" eval="0"/>
<field name="body_html"><![CDATA[<p>Dear ${object.partner_name or 'applicant'},</p>
<p>We thank you for your interest in our company and for your application.
Unfortunately, your profile does not match with our needs or our recruitment
campaign has reached its term.</p>
<p>If you want more details, feel free to contact us by phone.</p>
<p>Kind regards,</p>
<br/>
${object.user_id and object.user_id.signature or ''}]]></field>
</record>
<record id="applicant_interest" model="email.template">
<field name="name">Application approved</field>
<field name="subject">Application approved</field>
<field name="email_to">${object.email_from}</field>
<field name="partner_to">${object.partner_id and object.partner_id.id or ''}</field>
<field name="lang">${object.partner_id and object.partner_id.lang or ''}</field>
<field name="model_id" ref="hr_recruitment.model_hr_applicant"/>
<field name="user_signature" eval="0"/>
<field name="body_html"><![CDATA[<p>Dear ${object.partner_name or 'applicant'},</p>
<p>Congrats! Your resume's got our interest!
I will call you as soon as possible to make a 10 minutes phone interview and plan a first meeting.</p>
<p>If we cant reach you or if you miss our call, feel free to reach me back on the number 001 312 349 3030
If I do not answer, please let me a message with some schedules to call you back.</p>
<p>Kind regards,</p>
<br/>
${object.user_id.signature}]]></field>
</record>
<!-- HR Recruitment Source -->
<record model="hr.recruitment.source" id="source_linkedin">
@ -58,6 +95,7 @@
</record>
<record model="hr.recruitment.stage" id="stage_job2">
<field name="name">First Interview</field>
<field name="template_id" ref="applicant_interest"/>
<field name="sequence">2</field>
</record>
<record model="hr.recruitment.stage" id="stage_job3">
@ -76,6 +114,7 @@
<record model="hr.recruitment.stage" id="stage_job6">
<field name="name">Refused</field>
<field name="sequence">6</field>
<field name="template_id" ref="applicant_refuse"/>
<field name="fold" eval="True"/>
</record>

View File

@ -2,10 +2,12 @@
<openerp>
<data noupdate="1">
<record id="hr_case_salesman0" model="hr.applicant">
<field name="name">Salesperson</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="name">Sales Manager</field>
<field name="job_id" ref="hr.job_marketing"/>
<field name="department_id" ref="hr.dep_sales"/>
<field name="type_id" ref="degree_graduate"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_sales')])]"/>
<field name="user_id" ref="base.user_demo"/>
<field name="priority">2</field>
<field name="partner_name">Enrique Jones</field>
<field name="partner_mobile">9963214587</field>
@ -15,8 +17,9 @@
</record>
<record id="hr_case_traineemca0" model="hr.applicant">
<field name="name">Trainee - MCA</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="type_id" ref="degree_bac5"/>
<field name="job_id" ref="hr.job_trainee"/>
<field name="department_id" ref="hr.dep_rd"/>
<field name="type_id" ref="degree_licenced"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_manager')])]"/>
<field name="user_id" ref="base.user_demo"/>
<field name="priority">3</field>
@ -29,10 +32,11 @@
</record>
<record id="hr_case_fresher0" model="hr.applicant">
<field name="name">Fresher</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="type_id" ref="degree_licenced"/>
<field name="job_id" ref="hr.job_trainee"/>
<field name="department_id" ref="hr.dep_administration"/>
<field name="type_id" ref="degree_bachelor"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
<field name="user_id" ref="base.user_root"/>
<field name="user_id" ref="base.user_demo"/>
<field name="priority">1</field>
<field name="partner_name">Jose</field>
<field name="stage_id" ref="stage_job3"/>
@ -42,8 +46,9 @@
</record>
<record id="hr_case_yrsexperienceinphp0" model="hr.applicant">
<field name="name">Marketing Job</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="type_id" ref="degree_bac5"/>
<field name="job_id" ref="hr.job_marketing"/>
<field name="department_id" ref="hr.dep_sales"/>
<field name="type_id" ref="degree_graduate"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_manager')])]"/>
<field name="user_id" ref="base.user_root"/>
<field name="partner_name">John Bruno</field>
@ -54,6 +59,7 @@
<record id="hr_case_marketingjob0" model="hr.applicant">
<field name="name">More than 5 yrs Experience in PHP</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="department_id" ref="hr.dep_rd"/>
<field name="type_id" ref="degree_licenced"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_reserve')])]"/>
<field name="user_id" ref="base.user_demo"/>
@ -63,8 +69,9 @@
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_financejob0" model="hr.applicant">
<field name="name">Finance Job</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="name">Finance Manager</field>
<field name="job_id" ref="hr.job_hrm"/>
<field name="department_id" ref="hr.dep_administration"/>
<field name="type_id" ref="degree_licenced"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_reserve')])]"/>
<field name="user_id" ref="base.user_root"/>
@ -77,8 +84,9 @@
</record>
<record id="hr_case_traineemca1" model="hr.applicant">
<field name="name">Trainee - MCA</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="type_id" ref="degree_bac5"/>
<field name="job_id" ref="hr.job_trainee"/>
<field name="department_id" ref="hr.dep_rd"/>
<field name="type_id" ref="degree_licenced"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_sales')])]"/>
<field name="partner_name">Tina Augustie</field>
<field name="partner_mobile">9898745745</field>
@ -90,7 +98,8 @@
<record id="hr_case_programmer" model="hr.applicant">
<field name="name">Programmer</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="type_id" ref="degree_bac5"/>
<field name="department_id" ref="hr.dep_rd"/>
<field name="type_id" ref="degree_licenced"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
<field name="partner_name">Shane Williams</field>
<field name="partner_mobile">9812398524</field>
@ -102,7 +111,8 @@
</record>
<record id="hr_case_advertisement" model="hr.applicant">
<field name="name">Advertisement</field>
<field name="job_id" ref="hr.job_developer"/>
<field name="job_id" ref="hr.job_consultant"/>
<field name="department_id" ref="hr.dep_ps"/>
<field name="type_id" ref="degree_licenced"/>
<field name="categ_ids" eval="[(6,0,[ref('tag_applicant_it')])]"/>
<field name="partner_name">David Armstrong</field>
@ -114,7 +124,80 @@
</record>
<record id="hr.job_developer" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">4</field>
<field name="survey_id" ref="survey_job_0"/>
</record>
<record id="hr.job_ceo" model="hr.job">
<field name="survey_id" ref="survey_job_0"/>
</record>
<record id="hr.job_cto" model="hr.job">
<field name="survey_id" ref="survey_job_0"/>
</record>
<record id="hr.job_consultant" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">1</field>
<field name="survey_id" ref="survey_job_0"/>
</record>
<record id="hr.job_hrm" model="hr.job">
<field name="no_of_recruitment">1</field>
<field name="state">recruit</field>
<field name="survey_id" ref="survey_job_0"/>
</record>
<record id="hr.job_marketing" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">3</field>
<field name="survey_id" ref="survey_job_0"/>
</record>
<record id="hr.job_trainee" model="hr.job">
<field name="state">recruit</field>
<field name="no_of_recruitment">6</field>
<field name="survey_id" ref="survey_job_0"/>
</record>
<record id="message_application_demo" model="mail.message">
<field name="model">hr.applicant</field>
<field name="res_id" ref="hr_case_advertisement"/>
<field name="body">Please do refer to this application for sure.</field>
<field name="type">comment</field>
<field name="author_id" ref="base.res_partner_2"/>
</record>
<record id="msg_case18_aplicant" model="mail.message">
<field name="subject">Regarding reference</field>
<field name="model">hr.applicant</field>
<field name="res_id" ref="hr_case_advertisement"/>
<field name="body"><![CDATA[<p>Hello!<br />
I will surely refer to this application as it is by your reference and <br />
will try to conduct an interview within a very short time<br />
Thanks,</p>]]></field>
<field name="type">comment</field>
<field name="subtype_id" ref="mail.mt_comment"/>
<field name="author_id" ref="base.partner_demo"/>
</record>
<function model="mail.message" name="set_message_starred"
eval="[ ref('msg_case18_aplicant')], True, {}"
/>
<record id="msg_case_salesman0_aplicant" model="mail.message">
<field name="subject">Refuse Application</field>
<field name="model">hr.applicant</field>
<field name="res_id" ref="hr_case_salesman0"/>
<field name="body"><![CDATA[<p>Hello,</p>
<p>I have checked this application but It's not match with our requirement. so no need to process further and we should refuse this application.</p>
<p>Kind regards,</p>]]></field>
<field name="type">comment</field>
<field name="subtype_id" ref="mail.mt_comment"/>
<field name="author_id" ref="base.partner_demo"/>
</record>
<record id="msg_case_fresher0_aplicant" model="mail.message">
<field name="model">hr.applicant</field>
<field name="res_id" ref="hr_case_fresher0"/>
<field name="body"><![CDATA[<p>Hello,</p>
<p>We should move further for this application as early as possible..</p>
<p>Kind regards,</p>]]></field>
<field name="type">comment</field>
<field name="subtype_id" ref="mail.mt_comment"/>
<field name="author_id" ref="base.partner_demo"/>
</record>
</data>
</openerp>

View File

@ -10,13 +10,15 @@
<field name="view_id" eval="False"/>
<field name="search_view_id" ref="view_crm_case_jobs_filter"/>
<field name="help" type="html">
<p>
OpenERP helps you track applicants in the recruitment
process and follow up all operations: meetings, interviews, etc.
<p class="oe_view_nocontent_create">
Click to add a new job applicant.
</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.
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.
</p>
</field>
</record>
@ -61,6 +63,7 @@
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"/>

View File

@ -44,7 +44,7 @@
<field name="last_stage_id" invisible="1"/>
<field name="create_date"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="name" string="Subject"/>
<field name="name"/>
<field name="partner_name"/>
<field name="email_from"/>
<field name="partner_phone"/>
@ -83,6 +83,7 @@
<button name="action_print_survey" type="object"
string="Print Interview" help="Print interview report"
attrs="{'invisible':[('survey','=',False)]}"/>
<button name="action_get_attachment_tree_view" string="Documents" type="object"/>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
@ -121,6 +122,7 @@
<field name="response" invisible="1"/>
<field name="job_id" on_change="onchange_job(job_id)"/>
<field name="department_id" on_change="onchange_department_id(department_id, stage_id)"/>
<field name="company_id" />
<label for="availability"/>
<div>
<field name="availability" class="oe_inline"/> <label string="Day(s)" class="oe_inline"/>
@ -181,6 +183,7 @@
<separator/>
<filter string="Next Actions" context="{'invisible_next_action':False, 'invisible_next_date':False}"
domain="[('date_action','&lt;&gt;',False)]" help="Filter and view on next actions and date"/>
<field name="job_id"/>
<field name="department_id"/>
<field name="user_id"/>
@ -239,6 +242,7 @@
<field name="department_id"/>
<field name="categ_ids"/>
<field name="message_summary"/>
<field name="attachment_number"/>
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@ -256,6 +260,7 @@
<li><a name="action_makeMeeting" type="object">Schedule Interview</a></li>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
</div>
<div class="oe_kanban_content" tooltip="kanban-tooltip">
<div>
@ -290,7 +295,9 @@
</div>
<div class="oe_kanban_footer_left" style="margin-top:5px;">
<t t-raw="record.message_summary.raw_value"/>
<a t-if="record.attachment_number" name="action_get_attachment_tree_view" type="object" style="margin-right: 10px"> <field name="attachment_number"/> Documents</a>
</div>
</div>
<div class="oe_clear"></div>
</div>
@ -362,6 +369,7 @@
<group>
<field name="sequence"/>
<field name="fold"/>
<field name="template_id" domain= "[('model_id.model', '=', 'hr.applicant')]"/>
</group>
</group>
<separator string="Requirements"/>

View File

@ -26,7 +26,7 @@ class hr_applicant_settings(osv.osv_memory):
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
_columns = {
'module_document_ftp': fields.boolean('Allow the automatic indexation of resumes',
'module_document': fields.boolean('Allow the automatic indexation of resumes',
help='Manage your CV\'s and motivation letter related to all applicants.\n'
'-This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)'),
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',

View File

@ -15,14 +15,8 @@
<field name="arch" type="xml">
<div name="recruitment" position="inside">
<div>
<field name="fetchmail_applicants" class="oe_inline"/>
<label for="fetchmail_applicants"/>
<button name="configure_fetchmail_applicants" type="object" string="Configure" icon="gtk-go-forward"
attrs="{'invisible': [('fetchmail_applicants','=',False)]}" class="oe_link"/>
</div>
<div>
<field name="module_document_ftp" class="oe_inline"/>
<label for="module_document_ftp"/>
<field name="module_document" class="oe_inline"/>
<label for="module_document"/>
</div>
</div>
</field>

View File

@ -106,7 +106,7 @@ class report_custom(report_rml):
<date>%s</date>
<company>%s</company>
</header>
''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),registry['res.users'].browse(cr,uid,uid).company_id.name)
''' % (str(rml_obj.formatLang(time.strftime("%Y-%m-%d"),date=True))+' ' + str(time.strftime("%H:%M")),toxml(registry['res.users'].browse(cr,uid,uid).company_id.name))
xml='''<?xml version="1.0" encoding="UTF-8" ?>
<report>

View File

@ -745,6 +745,8 @@
<field name="property_account_receivable" ref="pcg_3488"/>
<field name="property_account_payable" ref="pcg_4488"/>
<field name="currency_id" ref="base.MAD"/>
<field name="property_account_income_categ" ref="pcg_7111"/>
<field name="property_account_expense_categ" ref="pcg_1486"/>
</record>
<record model="account.tax.template" id="tva_exo">

View File

@ -119,3 +119,5 @@
"51100_general_product",51100,"Freight and Shipping Costs","other","l10n_us.user_type_cogs","cost_of_goods_sold","False","l10n_us.account_chart_template_general_product"
"52500_general_product",52500,"Purchase Discounts","other","l10n_us.user_type_cogs","cost_of_goods_sold","False","l10n_us.account_chart_template_general_product"
"52900_general_product",52900,"Purchases - Resale Items","other","l10n_us.user_type_cogs","cost_of_goods_sold","False","l10n_us.account_chart_template_general_product"
"base_miscincome","49000","Miscellaneous Income","other","l10n_us.user_type_income","income","False","l10n_us.account_chart_template_basic"
"base_miscexpense","69000","Miscellaneous Expense","other","l10n_us.user_type_expense","expense","False","l10n_us.account_chart_template_basic"
1 id code name type user_type:id parent_id:id reconcile chart_template_id:id
119 51100_general_product 51100 Freight and Shipping Costs other l10n_us.user_type_cogs cost_of_goods_sold False l10n_us.account_chart_template_general_product
120 52500_general_product 52500 Purchase Discounts other l10n_us.user_type_cogs cost_of_goods_sold False l10n_us.account_chart_template_general_product
121 52900_general_product 52900 Purchases - Resale Items other l10n_us.user_type_cogs cost_of_goods_sold False l10n_us.account_chart_template_general_product
122 base_miscincome 49000 Miscellaneous Income other l10n_us.user_type_income income False l10n_us.account_chart_template_basic
123 base_miscexpense 69000 Miscellaneous Expense other l10n_us.user_type_expense expense False l10n_us.account_chart_template_basic

Some files were not shown because too many files have changed in this diff Show More