[MERGE] Sync with trunk

bzr revid: tde@openerp.com-20130725135057-a8m4pxqjbwnxzvs3
bzr revid: tde@openerp.com-20130726102342-1luyh4sh6rbziev4
This commit is contained in:
Thibault Delavallée 2013-07-26 12:23:42 +02:00
commit d83c7aeccc
52 changed files with 1418 additions and 579 deletions

View File

@ -612,6 +612,13 @@
</field>
</page>
</notebook>
<group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total">
<div class="oe_subtotal_footer_separator oe_inline">
<label for="balance_end" />
</div>
<field name="balance_end" nolabel="1" class="oe_subtotal_footer_separator" widget='monetary' options="{'currency_field': 'currency_id'}"/>
</group>
<div class="oe_clear"/>
</sheet>
</form>
</field>

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:04+0000\n"
"PO-Revision-Date: 2013-05-15 10:09+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"PO-Revision-Date: 2013-07-24 08:53+0000\n"
"Last-Translator: Sumonchai ( เหลา ) <sumonchai@gmail.com>\n"
"Language-Team: Thai <th@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-05-16 05:12+0000\n"
"X-Generator: Launchpad (build 16626)\n"
"X-Launchpad-Export-Date: 2013-07-25 05:13+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: account_asset
#: view:account.asset.asset:0
@ -42,7 +42,7 @@ msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Group By..."
msgstr ""
msgstr "จัดกลุ่มตาม..."
#. module: account_asset
#: field:asset.asset.report,gross_value:0
@ -58,7 +58,7 @@ msgstr ""
#: field:asset.asset.report,asset_id:0
#: model:ir.model,name:account_asset.model_account_asset_asset
msgid "Asset"
msgstr ""
msgstr "สินทรัพย์"
#. module: account_asset
#: help:account.asset.asset,prorata:0
@ -72,7 +72,7 @@ msgstr ""
#: selection:account.asset.asset,method:0
#: selection:account.asset.category,method:0
msgid "Linear"
msgstr ""
msgstr "เชิงเส้น"
#. module: account_asset
#: field:account.asset.asset,company_id:0
@ -80,24 +80,24 @@ msgstr ""
#: view:asset.asset.report:0
#: field:asset.asset.report,company_id:0
msgid "Company"
msgstr ""
msgstr "บริษัท"
#. module: account_asset
#: view:asset.modify:0
msgid "Modify"
msgstr ""
msgstr "ปรับเปลี่ยน"
#. module: account_asset
#: selection:account.asset.asset,state:0
#: view:asset.asset.report:0
#: selection:asset.asset.report,state:0
msgid "Running"
msgstr ""
msgstr "กำลังทำงานอยู่"
#. module: account_asset
#: view:account.asset.asset:0
msgid "Set to Draft"
msgstr ""
msgstr "กำหนดให้เป็นแบบร่าง"
#. module: account_asset
#: view:asset.asset.report:0
@ -110,7 +110,7 @@ msgstr ""
#. module: account_asset
#: field:asset.modify,name:0
msgid "Reason"
msgstr ""
msgstr "เหตุผล"
#. module: account_asset
#: field:account.asset.asset,method_progress_factor:0
@ -122,7 +122,7 @@ msgstr ""
#: model:ir.actions.act_window,name:account_asset.action_account_asset_asset_list_normal
#: model:ir.ui.menu,name:account_asset.menu_action_account_asset_asset_list_normal
msgid "Asset Categories"
msgstr ""
msgstr "หมวดหมู่สินทรัพย์"
#. module: account_asset
#: view:account.asset.asset:0
@ -173,7 +173,7 @@ msgstr ""
#: model:ir.ui.menu,name:account_asset.menu_finance_assets
#: model:ir.ui.menu,name:account_asset.menu_finance_config_assets
msgid "Assets"
msgstr ""
msgstr "สินทรัพย์"
#. module: account_asset
#: field:account.asset.category,account_depreciation_id:0
@ -187,7 +187,7 @@ msgstr ""
#: view:asset.modify:0
#: field:asset.modify,note:0
msgid "Notes"
msgstr ""
msgstr "บันทึกย่อ"
#. module: account_asset
#: field:account.asset.depreciation.line,move_id:0
@ -221,7 +221,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,code:0
msgid "Reference"
msgstr ""
msgstr "อ้างถึง"
#. module: account_asset
#: view:account.asset.asset:0
@ -246,7 +246,7 @@ msgstr ""
#: view:asset.asset.report:0
#: selection:asset.asset.report,state:0
msgid "Draft"
msgstr ""
msgstr "ฉบับร่าง"
#. module: account_asset
#: view:asset.asset.report:0
@ -273,13 +273,13 @@ msgstr ""
#. module: account_asset
#: field:account.asset.category,account_analytic_id:0
msgid "Analytic account"
msgstr ""
msgstr "วิเคราะห์บัญชี"
#. module: account_asset
#: field:account.asset.asset,method:0
#: field:account.asset.category,method:0
msgid "Computation Method"
msgstr ""
msgstr "วิธีการคำนวณ"
#. module: account_asset
#: constraint:account.asset.asset:0
@ -308,7 +308,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,salvage_value:0
msgid "Salvage Value"
msgstr ""
msgstr "มูลค่าซาก"
#. module: account_asset
#: field:account.asset.asset,category_id:0
@ -364,20 +364,20 @@ msgstr ""
#: field:account.asset.category,method_time:0
#: field:account.asset.history,method_time:0
msgid "Time Method"
msgstr ""
msgstr "วิธีการจัดการเวลา"
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0
#: view:asset.modify:0
msgid "or"
msgstr ""
msgstr "หรือ"
#. module: account_asset
#: field:account.asset.asset,note:0
#: field:account.asset.category,note:0
#: field:account.asset.history,note:0
msgid "Note"
msgstr ""
msgstr "บันทึกย่อ"
#. module: account_asset
#: help:account.asset.history,method_time:0
@ -409,7 +409,7 @@ msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Closed"
msgstr ""
msgstr "ปิด"
#. module: account_asset
#: help:account.asset.asset,state:0
@ -425,13 +425,13 @@ msgstr ""
#: field:account.asset.asset,state:0
#: field:asset.asset.report,state:0
msgid "Status"
msgstr ""
msgstr "สถานะ"
#. module: account_asset
#: field:account.asset.asset,partner_id:0
#: field:asset.asset.report,partner_id:0
msgid "Partner"
msgstr ""
msgstr "พาร์ทเนอร์"
#. module: account_asset
#: view:asset.asset.report:0
@ -451,7 +451,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.history,user_id:0
msgid "User"
msgstr ""
msgstr "ผู้ใช้"
#. module: account_asset
#: field:account.asset.category,account_asset_id:0
@ -482,12 +482,12 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,active:0
msgid "Active"
msgstr ""
msgstr "เปิดใช้งาน"
#. module: account_asset
#: field:account.asset.depreciation.line,parent_state:0
msgid "State of Asset"
msgstr ""
msgstr "สถานะของสินทรัพย์"
#. module: account_asset
#: field:account.asset.depreciation.line,name:0
@ -498,12 +498,12 @@ msgstr ""
#: view:account.asset.asset:0
#: field:account.asset.asset,history_ids:0
msgid "History"
msgstr ""
msgstr "ประวัติ"
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0
msgid "Compute Asset"
msgstr ""
msgstr "คำนวณสินทรัพย์"
#. module: account_asset
#: field:asset.depreciation.confirmation.wizard,period_id:0
@ -513,7 +513,7 @@ msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "General"
msgstr ""
msgstr "ทั่วไป"
#. module: account_asset
#: field:account.asset.asset,prorata:0
@ -524,7 +524,7 @@ msgstr ""
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_invoice
msgid "Invoice"
msgstr ""
msgstr "ใบแจ้งหนี้"
#. module: account_asset
#: view:account.asset.asset:0
@ -535,13 +535,13 @@ msgstr ""
#: view:asset.depreciation.confirmation.wizard:0
#: view:asset.modify:0
msgid "Cancel"
msgstr ""
msgstr "ยกเลิก"
#. module: account_asset
#: selection:account.asset.asset,state:0
#: selection:asset.asset.report,state:0
msgid "Close"
msgstr ""
msgstr "ปิด"
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_move_line
@ -558,7 +558,7 @@ msgstr ""
#: view:asset.asset.report:0
#: field:asset.asset.report,purchase_date:0
msgid "Purchase Date"
msgstr ""
msgstr "วันที่ซื้อ"
#. module: account_asset
#: selection:account.asset.asset,method:0
@ -606,7 +606,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,currency_id:0
msgid "Currency"
msgstr ""
msgstr "สกุลเงิน"
#. module: account_asset
#: field:account.asset.category,journal_id:0
@ -637,7 +637,7 @@ msgstr ""
#: view:asset.asset.report:0
#: field:asset.asset.report,move_check:0
msgid "Posted"
msgstr ""
msgstr "ลงบัญชีแล้ว"
#. module: account_asset
#: model:ir.actions.act_window,help:account_asset.action_asset_asset_report
@ -660,7 +660,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.category,name:0
msgid "Name"
msgstr ""
msgstr "ชื่อ"
#. module: account_asset
#: help:account.asset.category,open_asset:0
@ -672,7 +672,7 @@ msgstr ""
#. module: account_asset
#: field:asset.asset.report,name:0
msgid "Year"
msgstr ""
msgstr "ปี"
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_asset_depreciation_line
@ -693,7 +693,7 @@ msgid "Amount of Depreciation Lines"
msgstr ""
#. module: account_asset
#: code:addons/account_asset/wizard/wizard_asset_compute.py:49
#: code:addons/account_asset/wizard/wizard_asset_compute.py:50
#, python-format
msgid "Created Asset Moves"
msgstr ""
@ -701,7 +701,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,sequence:0
msgid "Sequence"
msgstr ""
msgstr "ลำดับ"
#. module: account_asset
#: help:account.asset.category,method_period:0
@ -711,7 +711,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.history,date:0
msgid "Date"
msgstr ""
msgstr "วันที่"
#. module: account_asset
#: field:account.asset.asset,method_number:0

View File

@ -14,13 +14,7 @@
<field name="condition">True</field>
<field name="type">ir.actions.server</field>
<field name="state">email</field>
<field name="email">object.user_id.email</field>
<field name="subject">Reminder on Lead: [[object.id ]] [[object.partner_id and 'of ' +object.partner_id.name or '']]</field>
<field name="message">Warning unprocessed incoming lead is more than 5 day old.
Name: [[object.name ]]
ID: [[object.id ]]
Description: [[object.description]]
</field>
<field name="template_id" ref="email_template_opportunity_reminder_mail"/>
</record>
<record id="rule_set_reminder_lead" model="base.action.rule">
<field name="name">Set Auto Reminder on leads which are not open since 5 days.</field>

View File

@ -945,42 +945,6 @@ class crm_lead(format_address, osv.osv):
vals.update(onchange_stage_values)
return super(crm_lead, self).write(cr, uid, ids, vals, context=context)
def new_mail_send(self, cr, uid, ids, context=None):
'''
This function opens a window to compose an email, with the edi sale template message loaded by default
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
ir_model_data = self.pool.get('ir.model.data')
try:
template_id = ir_model_data.get_object_reference(cr, uid, 'crm', 'email_template_opportunity_mail')[1]
except ValueError:
template_id = False
try:
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
if context is None:
context = {}
ctx = context.copy()
ctx.update({
'default_model': 'crm.lead',
'default_res_id': ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
})
return {
'name': _('Compose Email'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
}
# ----------------------------------------
# Mail Gateway
# ----------------------------------------

View File

@ -220,6 +220,20 @@
<field name="email_to">${not object.partner_id and object.email_from}</field>
<field name="body_html"></field>
</record>
<record id="email_template_opportunity_reminder_mail" model="email.template">
<field name="name">Reminder to User</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="auto_delete" eval="True"/>
<field name="email_from">admin@example.com</field>
<field name="email_to">${object.user_id != False and object.user_id.email}</field>
<field name="subject">Reminder on Lead: ${object.id} from ${object.partner_id != False and object.partner_id.name or object.contact_name}</field>
<field name="body_html"><![CDATA[<p>This opportunity did not have any activity since at least 5 days. Here are some details:</p>
<ul>
<li>Name: ${object.name}</li>
<li>ID: ${object.id}</li>
<li>Description: ${object.description}</field></li>
</ul>]]></field>
</record>
</data>
</openerp>

View File

@ -408,7 +408,7 @@
<group>
<group>
<field name="partner_id"
on_change="onchange_partner_id(partner_id, email_from)"
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"/>

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-09 07:36+0000\n"
"PO-Revision-Date: 2013-07-25 13:52+0000\n"
"Last-Translator: krnkris <Unknown>\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-07-10 05:25+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-07-26 05:14+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead3
@ -241,7 +241,7 @@ msgstr "Kilép"
#: view:crm.lead:0
#: field:crm.lead,state_id:0
msgid "State"
msgstr "Állapot"
msgstr "Állam/Megye"
#. module: crm
#: field:res.partner,meeting_count:0

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-23 05:01+0000\n"
"X-Launchpad-Export-Date: 2013-07-24 05:31+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: crm

View File

@ -8,13 +8,13 @@ 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-07-23 03:45+0000\n"
"PO-Revision-Date: 2013-07-23 06:22+0000\n"
"Last-Translator: fanvil <fanvil@hotmail.com>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-23 05:01+0000\n"
"X-Launchpad-Export-Date: 2013-07-24 05:31+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: crm_partner_assign
@ -471,7 +471,7 @@ msgstr "子信息"
#: field:crm.partner.report.assign,date_review:0
#: field:res.partner,date_review:0
msgid "Latest Partner Review"
msgstr ""
msgstr "最新合作伙伴评论"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,subject:0
@ -481,17 +481,17 @@ msgstr "主题"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
msgid "or"
msgstr ""
msgstr ""
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,body:0
msgid "Contents"
msgstr ""
msgstr "内容"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,vote_user_ids:0
msgid "Votes"
msgstr ""
msgstr "投票"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@ -501,13 +501,13 @@ msgstr "#商机"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,starred:0
msgid "Current user has a starred notification linked to this message"
msgstr ""
msgstr "当前用户用 星号 提醒关联到这条消息"
#. module: crm_partner_assign
#: field:crm.partner.report.assign,date_partnership:0
#: field:res.partner,date_partnership:0
msgid "Partnership Date"
msgstr ""
msgstr "合作关系日期"
#. module: crm_partner_assign
#: view:crm.lead:0
@ -661,7 +661,7 @@ msgstr ""
#. module: crm_partner_assign
#: field:crm.partner.report.assign,period_id:0
msgid "Invoice Period"
msgstr ""
msgstr "发票期间"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_res_partner_grade
@ -682,7 +682,7 @@ msgstr "附件"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,record_name:0
msgid "Message Record Name"
msgstr ""
msgstr "消息记录名称"
#. module: crm_partner_assign
#: field:res.partner.activation,sequence:0
@ -696,7 +696,7 @@ msgstr "序列"
msgid ""
"Cannot contact geolocation servers. Please make sure that your internet "
"connection is up and running (%s)."
msgstr ""
msgstr "无法连接地理信息服务器。请确保你的互联网链接畅通(%s)。"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,month:0
@ -722,7 +722,7 @@ msgstr "开启"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,subtype_id:0
msgid "Subtype"
msgstr ""
msgstr "子类型"
#. module: crm_partner_assign
#: field:res.partner,date_localization:0
@ -737,12 +737,12 @@ msgstr "当前的"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_crm_lead
msgid "Lead/Opportunity"
msgstr ""
msgstr "线索/商机"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,notified_partner_ids:0
msgid "Notified partners"
msgstr ""
msgstr "已通知的合作伙伴"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
@ -773,7 +773,7 @@ msgstr "可能收入"
#: field:res.partner,activation:0
#: view:res.partner.activation:0
msgid "Activation"
msgstr ""
msgstr "激活"
#. module: crm_partner_assign
#: view:crm.lead:0
@ -784,12 +784,12 @@ msgstr "指定的业务伙伴"
#. module: crm_partner_assign
#: field:res.partner,grade_id:0
msgid "Partner Level"
msgstr ""
msgstr "合作伙伴级别"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,to_read:0
msgid "Current user has an unread notification linked to this message"
msgstr ""
msgstr "当前用户有关联到这条消息的未读的提醒"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,type:0
@ -815,7 +815,7 @@ msgstr "名称"
#: model:ir.actions.act_window,name:crm_partner_assign.res_partner_activation_act
#: model:ir.ui.menu,name:crm_partner_assign.res_partner_activation_config_mi
msgid "Partner Activations"
msgstr ""
msgstr "合作伙伴激活"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@ -872,7 +872,7 @@ msgstr "客户关系管理 线索报表"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,composition_mode:0
msgid "Composition mode"
msgstr ""
msgstr "写作模式"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,model:0

View File

@ -22,5 +22,6 @@
import email_template
import wizard
import res_partner
import ir_actions
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -59,6 +59,7 @@ campaigns on any OpenERP document.
'wizard/email_template_preview_view.xml',
'email_template_view.xml',
'res_partner_view.xml',
'ir_actions_view.xml',
'wizard/mail_compose_message_view.xml',
'security/ir.model.access.csv'
],

View File

@ -0,0 +1,11 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- Server action update
- added `email` server action type, now entirely based on email templates.

View File

@ -0,0 +1,13 @@
Email Template module documentation
===================================
Email Template documentation topics
'''''''''''''''''''''''''''''''''''
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013 OpenERP S.A. <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/>.
#
##############################################################################
from openerp.osv import fields, osv
class actions_server(osv.Model):
""" Add email option in server actions. """
_name = 'ir.actions.server'
_inherit = ['ir.actions.server']
def _get_states(self, cr, uid, context=None):
res = super(actions_server, self)._get_states(cr, uid, context=context)
res.insert(0, ('email', 'Send Email'))
return res
_columns = {
'email_from': fields.char('From',
help="Sender address; define the template to see its value. If not set, the default "
"value will be the author's email alias if configured, or email address."),
'email_to': fields.char('To (Emails)',
help="Comma-separated recipient addresses; define the template to see its value"),
'partner_to': fields.char('To (Partners)',
help="Comma-separated ids of recipient partners; define the template to see its value"),
'subject': fields.char('Subject',
help="Email subject; define the template to see its value"),
'body_html': fields.text('Body',
help="Rich-text/HTML version of the message; define the template to see its value"),
'template_id': fields.many2one('email.template', 'Email Template', ondelete='set null',
help="Define the email template to use for the email to send.")
}
def on_change_template_id(self, cr, uid, ids, template_id, context=None):
""" Render the raw template in the server action fields. """
if template_id:
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
if not values.get('email_from'):
return {'warning': {'title': 'Incomplete template', 'message': 'Your template should define email_from'}, 'value': values}
else:
values = self.default_get(cr, uid, ['subject', 'body_html', 'email_from', 'email_to', 'partner_to'], context=context)
return {'value': values}
def create(self, cr, uid, values, context=None):
if values.get('template_id'):
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context)
values.update(dict((field, template_values[field]) for field in fields if template_values.get(field)))
return super(actions_server, self).create(cr, uid, values, context=context)
def write(self, cr, uid, ids, values, context=None):
if values.get('template_id'):
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context)
values.update(dict((field, template_values[field]) for field in fields if template_values.get(field)))
return super(actions_server, self).write(cr, uid, ids, values, context=context)
def run_action_email(self, cr, uid, action, eval_context=None, context=None):
if not action.template_id or not context.get('active_id'):
return False
self.pool['email.template'].send_mail(cr, uid, action.template_id.id, context.get('active_id'),
force_send=False, raise_exception=False, context=context)
return False
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_server_action_form_template">
<field name="name">ir.actions.server.form</field>
<field name="model">ir.actions.server</field>
<field name="inherit_id" ref="base.view_server_action_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='code']" position="after">
<page string="Email" autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'email')]}">
<p attrs="{'invisible': [('model_id', '!=', False)]}">
Please set the Base Model before setting the action details.
</p>
<group attrs="{'invisible': [('model_id', '=', False)]}">
<field name="template_id"
on_change='on_change_template_id(template_id)'
domain="[('model_id', '=', model_id)]"
attrs="{'required': [('state', '=', 'email')]}"/>
<p colspan="2" attrs="{'invisible': [('template_id', '!=', False)]}">
Choose a template to display its values.
</p>
<p colspan="2" attrs="{'invisible': [('template_id', '=', False)]}">
The values displayed hereunder are informative. When sending the email, the values
will be taken from the email template.
</p>
</group>
<group attrs="{'invisible': ['|', ('model_id', '=', False), ('template_id', '=', False)]}">
<label for="email_from"/>
<div>
<field name="email_from" nolabel="1'" readonly="1"
attrs="{'required': [('state', '=', 'email')]}"/>
<p attrs="{'invisible': [('email_from', '!=', False)]}">
Your template does not defined any email_from. Please update your template.
</p>
</div>
<field name="email_to" readonly="1"/>
<field name="partner_to" readonly="1"/>
<field name="subject" readonly="1" attrs="{'required': [('state', '=', 'email')]}"/>
<field name="body_html" readonly="1" attrs="{'required': [('state', '=', 'email')]}"/>
</group>
</page>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -18,10 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_mail
from . import test_mail, test_ir_actions
checks = [
test_mail,
test_ir_actions,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <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/>.
#
##############################################################################
from openerp.addons.base.tests.test_ir_actions import TestServerActionsBase
class TestServerActionsEmail(TestServerActionsBase):
def test_00_state_email(self):
""" Test ir.actions.server email type """
cr, uid = self.cr, self.uid
# create email_template
template_id = self.registry('email.template').create(cr, uid, {
'name': 'TestTemplate',
'email_from': 'myself@example.com',
'email_to': 'brigitte@example.com',
'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>',
})
self.ir_actions_server.write(cr, uid, self.act_id, {
'state': 'email',
'template_id': template_id,
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: email server action correctly finished should return False')
# check an email is waiting for sending
mail_ids = self.registry('mail.mail').search(cr, uid, [('subject', '=', 'About TestingPartner')])
self.assertEqual(len(mail_ids), 1, 'ir_actions_server: TODO')
# check email content
mail = self.registry('mail.mail').browse(cr, uid, mail_ids[0])
self.assertEqual(mail.body, '<p>Dear TestingPartner, your parent is False</p>',
'ir_actions_server: TODO')

View File

@ -28,7 +28,7 @@ import urllib2
import simplejson
class google_service(osv.osv):
class google_service(osv.osv_memory):
_name = 'google.service'
def generate_refresh_token(self, cr, uid, service, authorization_code, context=None):
@ -51,10 +51,10 @@ class google_service(osv.osv):
content = simplejson.loads(content)
return content.get('refresh_token')
def _get_google_token_uri(self, cr, uid, service, context=None):
def _get_google_token_uri(self, cr, uid, service, scope, context=None):
ir_config = self.pool['ir.config_parameter']
params = {
'scope': 'https://www.googleapis.com/auth/drive',
'scope': scope,
'redirect_uri': ir_config.get_param(cr, SUPERUSER_ID, 'google_redirect_uri'),
'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service),
'response_type': 'code',

View File

@ -54,58 +54,69 @@ class config(osv.osv):
attachment = attach_pool.browse(cr, uid, attach_ids[0], context)
url = attachment.url
else:
url = self.copy_doc(cr, uid, res_id, template_id, name_gdocs, model.model, context)
url = self.copy_doc(cr, uid, res_id, template_id, name_gdocs, model.model, context).get('url')
return url
def copy_doc(self, cr, uid, res_id, template_id, name_gdocs, res_model, context=None):
def get_access_token(self, cr, uid, scope=None, context=None):
ir_config = self.pool['ir.config_parameter']
google_drive_refresh_token = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_refresh_token')
group_config = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_erp_manager')[1]
user = self.pool['res.users'].read(cr, uid, uid, "groups_id")
if not google_drive_refresh_token:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("You haven't configured 'Authorization Code' generated from google, Please generate and configure it in %(menu:base_setup.menu_general_configuration)s."), context=context)
if group_config in user['groups_id']:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("You haven't configured 'Authorization Code' generated from google, Please generate and configure it in %(menu:base_setup.menu_general_configuration)s."), context=context)
else:
raise osv.except_osv(_('Error!'), _("Google Drive is not yet configured. Please contact your administrator."))
google_drive_client_id = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_id')
google_drive_client_secret = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_secret')
google_web_base_url = ir_config.get_param(cr, SUPERUSER_ID, 'web.base.url')
#For Getting New Access Token With help of old Refresh Token
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"}
data = dict(client_id=google_drive_client_id,
refresh_token=google_drive_refresh_token,
client_secret=google_drive_client_secret,
grant_type="refresh_token")
data = urllib.urlencode(data)
data = urllib.urlencode(dict(client_id=google_drive_client_id,
refresh_token=google_drive_refresh_token,
client_secret=google_drive_client_secret,
grant_type="refresh_token",
scope=scope or 'https://www.googleapis.com/auth/drive'))
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"}
try:
req = urllib2.Request('https://accounts.google.com/o/oauth2/token', data, headers)
content = urllib2.urlopen(req).read()
except urllib2.HTTPError:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during the token generation. Please request again an authorization code in %(menu:base_setup.menu_general_configuration)s."), context=context)
if group_config in user['groups_id']:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during the token generation. Please request again an authorization code in %(menu:base_setup.menu_general_configuration)s."), context=context)
else:
raise osv.except_osv(_('Error!'), _("Google Drive is not yet configured. Please contact your administrator."))
content = json.loads(content)
return content.get('access_token')
def copy_doc(self, cr, uid, res_id, template_id, name_gdocs, res_model, context=None):
ir_config = self.pool['ir.config_parameter']
google_web_base_url = ir_config.get_param(cr, SUPERUSER_ID, 'web.base.url')
access_token = self.get_access_token(cr, uid, context=context)
# Copy template in to drive with help of new access token
if 'access_token' in content:
request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (template_id, content['access_token'])
try:
req = urllib2.Request(request_url, None, headers)
parents = urllib2.urlopen(req).read()
except urllib2.HTTPError:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("The Google Template cannot be found. Maybe it has been deleted."), context=context)
parents_dict = json.loads(parents)
request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (template_id, access_token)
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"}
try:
req = urllib2.Request(request_url, None, headers)
parents = urllib2.urlopen(req).read()
except urllib2.HTTPError:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("The Google Template cannot be found. Maybe it has been deleted."), context=context)
parents_dict = json.loads(parents)
record_url = "Click on link to open Record in OpenERP\n %s/?db=%s#id=%s&model=%s" % (google_web_base_url, cr.dbname, res_id, res_model)
data = {"title": name_gdocs, "description": record_url, "parents": parents_dict['parents']}
request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % (template_id, content['access_token'])
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
data_json = json.dumps(data)
# resp, content = Http().request(request_url, "POST", data_json, headers)
req = urllib2.Request(request_url, data_json, headers)
content = urllib2.urlopen(req).read()
content = json.loads(content)
res = False
if 'alternateLink' in content.keys():
attach_pool = self.pool.get("ir.attachment")
attach_vals = {'res_model': res_model, 'name': name_gdocs, 'res_id': res_id, 'type': 'url', 'url': content['alternateLink']}
attach_pool.create(cr, uid, attach_vals)
res = content['alternateLink']
record_url = "Click on link to open Record in OpenERP\n %s/?db=%s#id=%s&model=%s" % (google_web_base_url, cr.dbname, res_id, res_model)
data = {"title": name_gdocs, "description": record_url, "parents": parents_dict['parents']}
request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % (template_id, access_token)
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
data_json = json.dumps(data)
# resp, content = Http().request(request_url, "POST", data_json, headers)
req = urllib2.Request(request_url, data_json, headers)
content = urllib2.urlopen(req).read()
content = json.loads(content)
res = {}
if content.get('alternateLink'):
attach_pool = self.pool.get("ir.attachment")
attach_vals = {'res_model': res_model, 'name': name_gdocs, 'res_id': res_id, 'type': 'url', 'url': content['alternateLink']}
res['id'] = attach_pool.create(cr, uid, attach_vals)
res['url'] = content['alternateLink']
return res
def get_google_drive_config(self, cr, uid, res_model, res_id, context=None):
@ -166,6 +177,7 @@ class config(osv.osv):
'google_drive_resource_id': fields.function(_resource_get, type="char", string='Resource Id'),
'google_drive_client_id': fields.function(_client_id_get, type="char", string='Google Client '),
'name_template': fields.char('Google Drive Name Pattern', size=64, help='Choose how the new google drive will be named, on google side. Eg. gdoc_%(field_name)s', required=True),
'active': fields.boolean('Active'),
}
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
@ -179,6 +191,7 @@ class config(osv.osv):
_defaults = {
'name_template': 'Document %(name)s',
'active': True,
}
def _check_model_id(self, cr, uid, ids, context=None):
@ -191,6 +204,9 @@ class config(osv.osv):
(_check_model_id, 'Model of selected filter is not matching with model of current template.', ['model_id', 'filter_id']),
]
def get_google_scope(self):
return 'https://www.googleapis.com/auth/drive'
config()
@ -202,7 +218,7 @@ class base_config_settings(osv.osv):
'google_drive_uri': fields.char('URI', readonly=True, help="The URL to generate the authorization code from Google"),
}
_defaults = {
'google_drive_uri': lambda s, cr, uid, c: s.pool['google.service']._get_google_token_uri(cr, uid, 'drive', context=c),
'google_drive_uri': lambda s, cr, uid, c: s.pool['google.service']._get_google_token_uri(cr, uid, 'drive', scope=s.pool['google.drive.config'].get_google_scope(), context=c),
}
def set_google_authorization_code(self, cr, uid, ids, context=None):

View File

@ -4,12 +4,12 @@
<record id="config_google_drive_client_id" model="ir.config_parameter">
<field name="key">google_drive_client_id</field>
<field name="value">39623646228-eg3ggo3mk6o40m7rguobi3rkl9frh4tb.apps.googleusercontent.com</field>
<field name="value">598905559630.apps.googleusercontent.com</field>
</record>
<record id="config_google_drive_client_secret" model="ir.config_parameter">
<field name="key">google_drive_client_secret</field>
<field name="value">Ul-PtmnSWs3euWs20fdono0e</field>
<field name="value">vTmou73c-njP-1qCxm7qx7QE</field>
</record>
</data>

View File

@ -23,6 +23,7 @@
<field name="model" invisible="1" />
<group>
<field name="name" />
<field name="active" />
<field name="model_id" on_change="onchange_model_id(model_id)" />
<label for='filter_id' />
<div>
@ -54,7 +55,7 @@
</record>
<record model='ir.actions.act_window' id='action_google_drive_users_config'>
<field name='name'>Google Drive Templates</field>
<field name='name'>Templates</field>
<field name='res_model'>google.drive.config</field>
<field name='type'>ir.actions.act_window</field>
<field name='view_type'>form</field>
@ -87,7 +88,7 @@
</field>
</record>
<menuitem name='Google Drive configuration' id='menu_google_drive_config' parent='base.menu_administration' />
<menuitem name='Google Drive' id='menu_google_drive_config' parent='base.menu_administration' />
<menuitem id='menu_google_drive_model_config' parent='menu_google_drive_config' action='action_google_drive_users_config' />
</data>
</openerp>

View File

@ -29,19 +29,24 @@ openerp.google_drive = function (instance, m) {
ds.call('get_google_drive_config', [view.dataset.model, res_id, context]).done(function (r) {
if (!_.isEmpty(r)) {
_.each(r, function (res) {
var g_item = _.indexOf(_.pluck(self.items.other, 'label'), res.name);
if (g_item !== -1) {
self.items.other.splice(g_item, 1);
var already_there = false;
for (var i = 0;i < self.items.other.length;i++){
if (self.items.other[i].classname === "oe_share_gdoc" && self.items.other[i].label.indexOf(res.name) > -1){
already_there = true;
break;
}
}
if (!already_there){
self.add_items('other', [{
label: res.name+ '<img style="position:absolute;right:5px;height:20px;width:20px;" title="Google Drive" src="google_drive/static/src/img/drive_icon.png"/>',
config_id: res.id,
res_id: res_id,
res_model: view.dataset.model,
callback: self.on_google_doc,
classname: 'oe_share_gdoc'
},
]);
}
self.add_items('other', [{
label: res.name+ '<img style="position:absolute;right:5px;height:20px;width:20px;" title="Google Drive" src="google_drive/static/src/img/drive_icon.png"/>',
config_id: res.id,
res_id: res_id,
res_model: view.dataset.model,
callback: self.on_google_doc,
classname: 'oe_share_gdoc'
},
]);
})
}
});

View File

@ -0,0 +1 @@
import google_spreadsheet

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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': 'Google Spreadsheet',
'version': '1.0',
'category': 'Tools',
'description': """
The module adds the possibility to display data from OpenERP in Google Spreadsheets in real time.
========================================
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['board', 'google_drive'],
'js': [
'static/src/js/search.js',
],
'qweb': ['static/src/xml/*.xml'],
'data': ['google_spreadsheet_view.xml', 'google_spreadsheet_data.xml'],
'demo': [],
'installable': True,
'auto_install': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,109 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2012 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 simplejson
from lxml import etree
import re
import requests
from openerp.osv import osv
from openerp import SUPERUSER_ID
class config(osv.osv):
_inherit = 'google.drive.config'
def get_google_scope(self):
scope = super(config, self).get_google_scope()
return '%s https://spreadsheets.google.com/feeds' % scope
def write_config_formula(self, cr, uid, attachment_id, spreadsheet_key, model, domain, groupbys, view_id, context=None):
access_token = self.get_access_token(cr, uid, scope='https://spreadsheets.google.com/feeds', context=context)
fields = self.pool.get(model).fields_view_get(cr, uid, view_id=view_id, view_type='tree')
doc = etree.XML(fields.get('arch'))
display_fields = []
for node in doc.xpath("//field"):
if node.get('modifiers'):
modifiers = simplejson.loads(node.get('modifiers'))
if not modifiers.get('invisible') and not modifiers.get('tree_invisible'):
display_fields.append(node.get('name'))
fields = " ".join(display_fields)
domain = domain.replace("'", r"\'").replace('"', "'")
if groupbys:
fields = "%s %s" % (groupbys, fields)
formula = '=oe_read_group("%s";"%s";"%s";"%s")' % (model, fields, groupbys, domain)
else:
formula = '=oe_browse("%s";"%s";"%s")' % (model, fields, domain)
url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
dbname = cr.dbname
user = self.pool['res.users'].read(cr, uid, uid, ['login', 'password'], context=context)
username = user['login']
password = user['password']
if self.pool['ir.module.module'].search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_crypt'), ('state', '=', 'installed')]) == 1:
config_formula = '=oe_settings("%s";"%s")' % (url, dbname)
else:
config_formula = '=oe_settings("%s";"%s";"%s";"%s")' % (url, dbname, username, password)
request = '''<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:batch="http://schemas.google.com/gdata/batch"
xmlns:gs="http://schemas.google.com/spreadsheets/2006">
<id>https://spreadsheets.google.com/feeds/cells/%s/od6/private/full</id>
<entry>
<batch:id>A1</batch:id>
<batch:operation type="update"/>
<id>https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R1C1</id>
<link rel="edit" type="application/atom+xml"
href="https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R1C1"/>
<gs:cell row="1" col="1" inputValue="%s"/>
</entry>
<entry>
<batch:id>A2</batch:id>
<batch:operation type="update"/>
<id>https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R60C15</id>
<link rel="edit" type="application/atom+xml"
href="https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R60C15"/>
<gs:cell row="60" col="15" inputValue="%s"/>
</entry>
</feed>''' % (spreadsheet_key, spreadsheet_key, spreadsheet_key, formula.replace('"', '&quot;'), spreadsheet_key, spreadsheet_key, config_formula.replace('"', '&quot;'))
requests.post('https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/batch?v=3&access_token=%s' % (spreadsheet_key, access_token), data=request, headers={'content-type': 'application/atom+xml', 'If-Match': '*'})
description = '''
formula: %s
''' % formula
if attachment_id:
self.pool['ir.attachment'].write(cr, uid, attachment_id, {'description': description}, context=context)
return True
def set_spreadsheet(self, cr, uid, model, domain, groupbys, view_id, context=None):
try:
config_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'google_spreadsheet', 'google_spreadsheet_template')[1]
except ValueError:
raise
config = self.browse(cr, uid, config_id, context=context)
title = 'Spreadsheet %s' % model
res = self.copy_doc(cr, uid, False, config.google_drive_resource_id, title, model, context=context)
mo = re.search("(key=|/d/)([A-Za-z0-9-_]+)", res['url'])
if mo:
key = mo.group(2)
self.write_config_formula(cr, uid, res.get('id'), key, model, domain, groupbys, view_id, context=context)
return res

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="google_spreadsheet_template" model="google.drive.config">
<field name="name">Base Spreadsheet Template</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="google_drive_template_url">https://docs.google.com/spreadsheet/ccc?key=0ApGVjjwUC-ygdDZ0TG5EQnRlLVFQNlFGdFN5b1ZrY1E</field>
<field name="name_template">Reporting %(name)s</field>
<field name="active" eval="0" />
</record>
</data>
</openerp>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- add google drive config field in user form -->
<record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_tree">
<field name="name">ir.attachment.google.spreadsheet.tree</field>
<field name="model">ir.attachment</field>
<field name="arch" type="xml">
<tree string="Google Spreadsheets" version="7.0">
<field name="name" string="Name"/>
<field name="url" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_form">
<field name="name">ir.attachment.google.spreadsheet.form</field>
<field name="model">ir.attachment</field>
<field name="arch" type="xml">
<form string="Google Spreadsheets" version="7.0">
<sheet>
<group>
<group>
<field name="name" string="Name"/>
<field name="url" widget="url"/>
</group>
<group colspan="2">
<label for="description" colspan="2"/>
<field name="description" nolabel="1" colspan="2"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_ir_attachment_google_spreadsheet_tree" model="ir.actions.act_window">
<field name="name">Google Spreadsheets</field>
<field name="res_model">ir.attachment</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{}</field>
<field name="domain">[('url', 'like', '/spreadsheet/')]</field>
<field name="help">Google Spreadsheets</field>
</record>
<record id="action_ir_attachment_google_spreadsheet_tree_view" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_ir_attachment_google_spreadsheet_tree"/>
<field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
</record>
<record id="action_ir_attachment_google_spreadsheet_form_view" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_ir_attachment_google_spreadsheet_form"/>
<field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
</record>
<menuitem id="menu_reporting_dashboard_google_spreadsheets" parent="base.menu_reporting_dashboard" action="action_ir_attachment_google_spreadsheet_tree"/>
</data>
</openerp>

View File

@ -0,0 +1,42 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * google_spreadsheet
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-25 12:39+0000\n"
"PO-Revision-Date: 2013-07-25 12:39+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: google_spreadsheet
#: model:ir.actions.act_window,help:google_spreadsheet.action_ir_attachment_google_spreadsheet_tree
#: model:ir.actions.act_window,name:google_spreadsheet.action_ir_attachment_google_spreadsheet_tree
#: view:ir.attachment:0
#: model:ir.ui.menu,name:google_spreadsheet.menu_reporting_dashboard_google_spreadsheets
msgid "Google Spreadsheets"
msgstr ""
#. module: google_spreadsheet
#. openerp-web
#: code:addons/google_spreadsheet/static/src/xml/addtospreadsheet.xml:3
#, python-format
msgid "Add to Google Spreadsheet"
msgstr ""
#. module: google_spreadsheet
#: view:ir.attachment:0
msgid "Name"
msgstr ""
#. module: google_spreadsheet
#: model:ir.model,name:google_spreadsheet.model_google_drive_config
msgid "Google Drive templates config"
msgstr ""

View File

@ -0,0 +1,54 @@
openerp.google_spreadsheet = function(instance) {
var _t = instance.web._t;
instance.web.FormView.include({
on_processed_onchange: function(result, processed) {
var self = this;
var fields = self.fields;
_(result.selection).each(function (selection, fieldname) {
var field = fields[fieldname];
if (!field) { return; }
field.field.selection = selection;
field.values = selection;
field.renderElement();
});
return this._super(result, processed);
},
});
instance.board.AddToGoogleSpreadsheet = instance.web.search.Input.extend({
template: 'SearchView.addtogooglespreadsheet',
_in_drawer: true,
start: function () {
var self = this;
this.$el.on('click', 'h4', function(){
var view = self.view;
var data = view.build_search_data();
var model = view.model;
var list_view = self.view.getParent().views['list'];
var view_id = list_view ? list_view.view_id : false;
var context = new instance.web.CompoundContext(view.dataset.get_context() || []);
var domain = new instance.web.CompoundDomain(view.dataset.get_domain() || []);
_.each(data.contexts, context.add, context);
_.each(data.domains, domain.add, domain);
domain = JSON.stringify(domain.eval());
var groupbys = instance.web.pyeval.eval('groupbys', data.groupbys).join(" ");
var view_id = view_id;
var ds = new instance.web.DataSet(self, 'google.drive.config');
ds.call('set_spreadsheet', [model, domain, groupbys, view_id]).done(function (res) {
if (res['url']){
window.open(res['url'], '_blank');
}
});
});
},
});
instance.web.SearchView.include({
add_common_inputs: function() {
this._super();
var vm = this.getParent().getParent();
if (vm.inner_action && vm.inner_action.views) {
(new instance.board.AddToGoogleSpreadsheet(this));
}
}
});
};

View File

@ -0,0 +1,5 @@
<template>
<div t-name="SearchView.addtogooglespreadsheet" class="oe_searchview_dashboard">
<h4>Add to Google Spreadsheet</h4>
</div>
</template>

View File

@ -51,9 +51,9 @@ class hr_employee_category(osv.osv):
_name = "hr.employee.category"
_description = "Employee Category"
_columns = {
'name': fields.char("Category", size=64, required=True),
'name': fields.char("Employee Tag", size=64, required=True),
'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
'parent_id': fields.many2one('hr.employee.category', 'Parent Category', select=True),
'parent_id': fields.many2one('hr.employee.category', 'Parent Employee Tag', select=True),
'child_ids': fields.one2many('hr.employee.category', 'parent_id', 'Child Categories'),
'employee_ids': fields.many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', 'Employees'),
}
@ -129,7 +129,7 @@ class hr_job(osv.osv):
}
_sql_constraints = [
('name_company_uniq', 'unique(name, company_id)', 'The name of the job position must be unique per company!'),
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
]
@ -156,6 +156,8 @@ class hr_employee(osv.osv):
_inherits = {'resource.resource': "resource_id"}
_inherit = ['mail.thread']
_mail_post_access = 'read'
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
@ -324,22 +326,6 @@ class hr_employee(osv.osv):
(_check_recursion, 'Error! You cannot create recursive hierarchy of Employee(s).', ['parent_id']),
]
# ---------------------------------------------------
# Mail gateway
# ---------------------------------------------------
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
""" mail.message document permission rule: can post a new message if can read
because of portal document. """
if not model_obj:
model_obj = self
employee_ids = model_obj.search(cr, uid, [('user_id', '=', uid)], context=context)
if employee_ids and operation == 'create':
model_obj.check_access_rights(cr, uid, 'read')
model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
else:
return super(hr_employee, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
class hr_department(osv.osv):
_description = "Department"

View File

@ -79,7 +79,7 @@ class hr_holidays_status(osv.osv):
'categ_id': fields.many2one('crm.meeting.type', 'Meeting Type',
help='Once a leave is validated, OpenERP will create a corresponding meeting of this type in the calendar.'),
'color_name': fields.selection([('red', 'Red'),('blue','Blue'), ('lightgreen', 'Light Green'), ('lightblue','Light Blue'), ('lightyellow', 'Light Yellow'), ('magenta', 'Magenta'),('lightcyan', 'Light Cyan'),('black', 'Black'),('lightpink', 'Light Pink'),('brown', 'Brown'),('violet', 'Violet'),('lightcoral', 'Light Coral'),('lightsalmon', 'Light Salmon'),('lavender', 'Lavender'),('wheat', 'Wheat'),('ivory', 'Ivory')],'Color in Report', required=True, help='This color will be used in the leaves summary located in Reporting\Leaves by Department.'),
'limit': fields.boolean('Allow to Override Limit', help='If you select this check box, the system allows the employees to take more leaves than the available ones for this type and take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
'limit': fields.boolean('Allow to Override Limit', help='If you select this check box, the system allows the employees to take more leaves than the available ones for this type and will not take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the leave type without removing it."),
'max_leaves': fields.function(_user_left_days, string='Maximum Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
'leaves_taken': fields.function(_user_left_days, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),

View File

@ -35,7 +35,7 @@
new_id = self.create(cr, uid, {'account_id': ref('account.analytic_nebula'),'analytic_amount': 7.0,
'date': (datetime.now()+timedelta(1)).strftime('%Y-%m-%d %H:%M:%S') ,
'date_start': time.strftime('%Y-%m-%d %H:%M:%S'), 'info': 'Create Yaml for hr module',
'name': 'Quentin Paolino', 'server_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'state': 'action'})
'name': 'Quentin Paolino', 'server_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'state': 'present'})
self.sign_out_result(cr, uid, [new_id], context)
-
My work for this project "Sednacom" is over and I stop working by clicking on "Stop Work" button of this wizard.

View File

@ -32,8 +32,8 @@ class hr_so_project(osv.osv_memory):
'date_start': fields.datetime('Starting Date', readonly=True),
'date': fields.datetime('Closing Date'),
'analytic_amount': fields.float('Minimum Analytic Amount'),
'name': fields.char('Employees name', size=32, required=True, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='char', required=True, readonly=True),
'name': fields.char('Employee\'s Name', size=32, required=True, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='selection', selection=[('present', 'Present'), ('absent', 'Absent')], required=True, readonly=True),
'server_date': fields.datetime('Current Date', required=True, readonly=True),
'emp_id': fields.many2one('hr.employee', 'Employee ID')
}
@ -109,8 +109,8 @@ class hr_si_project(osv.osv_memory):
_name = 'hr.sign.in.project'
_description = 'Sign In By Project'
_columns = {
'name': fields.char('Employees name', size=32, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='char', required=True, readonly=True),
'name': fields.char('Employee\'s Name', size=32, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='selection', selection=[('present', 'Present'), ('absent', 'Absent')], required=True, readonly=True),
'date': fields.datetime('Starting Date'),
'server_date': fields.datetime('Current Date', readonly=True),
'emp_id': fields.many2one('hr.employee', 'Employee ID')

View File

@ -43,7 +43,7 @@
</record>
<record id="action_hr_timesheet_sign_in" model="ir.actions.act_window">
<field name="name">Sign in / Sign out by project</field>
<field name="name">Sign in / Sign out by Project</field>
<field name="res_model">hr.sign.in.project</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
@ -67,7 +67,7 @@
<field name="account_id" colspan="2"/>
<field name="info" colspan="2"/>
<field name="date"/>
<label string="(Keep empty for current_time)" colspan="2"/>
<label string="(Keep empty for current time)" colspan="2"/>
<field name="analytic_amount"/>
</group>
@ -82,7 +82,7 @@
</record>
<record id="action_hr_timesheet_sign_out" model="ir.actions.act_window">
<field name="name">Sign in / Sign out by project</field>
<field name="name">Sign in / Sign out by Project</field>
<field name="res_model">hr.sign.out.project</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>

View File

@ -0,0 +1,169 @@
# Chinese (Simplified) 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-02-08 01:06+0000\n"
"PO-Revision-Date: 2013-07-24 12:29+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-25 05:13+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_fiscal_position_template
msgid "Template for Fiscal Position"
msgstr "财务结构模板"
#. module: l10n_multilang
#: sql_constraint:account.account:0
msgid "The code of the account must be unique per company !"
msgstr "该科目的代码,每家公司必须是唯一的!"
#. module: l10n_multilang
#: constraint:account.account.template:0
msgid ""
"Configuration Error!\n"
"You can not define children to an account with internal type different of "
"\"View\"! "
msgstr ""
"配置错误! \n"
"上级科目必须是“视图”类型的科目! "
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_analytic_journal
msgid "Analytic Journal"
msgstr "辅助核算类型"
#. module: l10n_multilang
#: constraint:account.account.template:0
msgid "Error ! You can not create recursive account templates."
msgstr "错误!您不能创建递归的科目模板。"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_journal
msgid "Journal"
msgstr "分录序时薄"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_chart_template
msgid "Templates for Account Chart"
msgstr "科目一览表模板"
#. module: l10n_multilang
#: sql_constraint:account.tax:0
msgid "The description must be unique per company!"
msgstr "此说明必须是每个公司唯一的"
#. module: l10n_multilang
#: constraint:account.tax.code.template:0
msgid "Error ! You can not create recursive Tax Codes."
msgstr "错误!您不能创建递归的税编码。"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_tax_template
msgid "account.tax.template"
msgstr "account.tax.template"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_tax
msgid "account.tax"
msgstr "account.tax"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_account
msgid "Account"
msgstr "科目"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_wizard_multi_charts_accounts
msgid "wizard.multi.charts.accounts"
msgstr "wizard.multi.charts.accounts"
#. module: l10n_multilang
#: constraint:account.journal:0
msgid ""
"Configuration error! The currency chosen should be shared by the default "
"accounts too."
msgstr "设置错误!所选币种应与默认科目共享。"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_account_template
msgid "Templates for Accounts"
msgstr "科目模板"
#. module: l10n_multilang
#: help:account.chart.template,spoken_languages:0
msgid ""
"State here the languages for which the translations of templates could be "
"loaded at the time of installation of this localization module and copied in "
"the final object when generating them from templates. You must provide the "
"language codes separated by ';'"
msgstr ""
#. module: l10n_multilang
#: constraint:account.account:0
msgid "Error ! You can not create recursive accounts."
msgstr "错误!您不能创建递归的科目"
#. module: l10n_multilang
#: constraint:account.account:0
msgid ""
"Configuration Error! \n"
"You can not select an account type with a deferral method different of "
"\"Unreconciled\" for accounts with internal type \"Payable/Receivable\"! "
msgstr ""
"配置错误!\n"
"对于内部类型是‘应收/应付’的会计科目,你需要选择结转方式是‘未核销’的科目类型。 "
#. module: l10n_multilang
#: sql_constraint:account.journal:0
msgid "The name of the journal must be unique per company !"
msgstr "每个公司的日记账名称必须唯一!"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_analytic_account
msgid "Analytic Account"
msgstr "辅助核算项目"
#. module: l10n_multilang
#: sql_constraint:account.journal:0
msgid "The code of the journal must be unique per company !"
msgstr "每个公司的日记账代码必须唯一!"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_fiscal_position
msgid "Fiscal Position"
msgstr "财务结构"
#. module: l10n_multilang
#: constraint:account.account:0
msgid ""
"Configuration Error! \n"
"You can not define children to an account with internal type different of "
"\"View\"! "
msgstr ""
"配置错误! \n"
"您不能给 非“视图”类型的科目定义一个子科目。 "
#. module: l10n_multilang
#: constraint:account.analytic.account:0
msgid "Error! You can not create recursive analytic accounts."
msgstr "错误! 你不能创建递归的辅助核算项目"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_tax_code_template
msgid "Tax Code Template"
msgstr "税编码模板"
#. module: l10n_multilang
#: field:account.chart.template,spoken_languages:0
msgid "Spoken Languages"
msgstr "交流语言"

View File

@ -0,0 +1,11 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- added ``_mail_post_access`` attribute that specifies the access right that
should have the user in order to post a new message on a given model. Values
are ``read`` (portal documents), ``write`` (default value), ``unlink`` or ``create``.

View File

@ -12,3 +12,12 @@ Mail Module documentation topics
mail_partner
mail_state
mail_subtype
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

View File

@ -70,6 +70,7 @@ class mail_thread(osv.AbstractModel):
_name = 'mail.thread'
_description = 'Email Thread'
_mail_flat_thread = True
_mail_post_access = 'write'
# Automatic logging system if mail installed
# _track = {
@ -156,12 +157,26 @@ class mail_thread(osv.AbstractModel):
res[id]['message_summary'] = "<span class='oe_kanban_mail_new' title='%s'><span class='oe_e'>9</span> %d %s</span>" % (title, res[id].pop('message_unread_count'), _("New"))
return res
def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
def read_followers_data(self, cr, uid, follower_ids, context=None):
result = []
technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one')
for follower in self.pool.get('res.partner').browse(cr, uid, follower_ids, context=context):
is_editable = uid in map(lambda x: x.id, technical_group.users)
is_uid = uid in map(lambda x: x.id, follower.user_ids)
data = (follower.id,
follower.name,
{'is_editable': is_editable, 'is_uid': is_uid},
)
result.append(data)
return result
def _get_subscription_data(self, cr, uid, ids, name, args, user_pid=None, context=None):
""" Computes:
- message_subtype_data: data about document subtypes: which are
available, which are followed if any """
res = dict((id, dict(message_subtype_data='')) for id in ids)
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
if user_pid is None:
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
# find current model subtypes, add them to a dictionary
subtype_obj = self.pool.get('mail.message.subtype')
@ -495,12 +510,22 @@ class mail_thread(osv.AbstractModel):
access rule on the document, for portal document such as issues. """
if not model_obj:
model_obj = self
if operation in ['create', 'write', 'unlink']:
model_obj.check_access_rights(cr, uid, 'write')
model_obj.check_access_rule(cr, uid, mids, 'write', context=context)
if hasattr(self, '_mail_post_access'):
create_allow = self._mail_post_access
else:
model_obj.check_access_rights(cr, uid, operation)
model_obj.check_access_rule(cr, uid, mids, operation, context=context)
create_allow = 'write'
if operation in ['write', 'unlink']:
check_operation = 'write'
elif operation == 'create' and create_allow in ['create', 'read', 'write', 'unlink']:
check_operation = create_allow
elif operation == 'create':
check_operation = 'write'
else:
check_operation = operation
model_obj.check_access_rights(cr, uid, check_operation)
model_obj.check_access_rule(cr, uid, mids, check_operation, context=context)
def _get_formview_action(self, cr, uid, id, model=None, context=None):
""" Return an action to open the document. This method is meant to be
@ -1401,9 +1426,9 @@ class mail_thread(osv.AbstractModel):
# Followers API
#------------------------------------------------------
def message_get_subscription_data(self, cr, uid, ids, context=None):
def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None):
""" Wrapper to get subtypes data. """
return self._get_subscription_data(cr, uid, ids, None, None, context=context)
return self._get_subscription_data(cr, uid, ids, None, None, user_pid=user_pid, context=context)
def message_subscribe_users(self, cr, uid, ids, user_ids=None, subtype_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not

View File

@ -14,11 +14,22 @@
<record id="mail_followers_read_write_own" model="ir.rule">
<field name="name">mail.followers: read and write its own entries</field>
<field name="model_id" ref="model_mail_followers"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">[('partner_id', '=', user.partner_id.id)]</field>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- If technical rights then read and write others subscriptions -->
<record id="mail_followers_read_write_others" model="ir.rule">
<field name="name">mail.followers: read and write others entries</field>
<field name="model_id" ref="model_mail_followers"/>
<field name="groups" eval="[(4, ref('base.group_no_one'))]"/>
<field name="domain_force">[]</field>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="mail_notification_read_write_own" model="ir.rule">
<field name="name">mail.notification: read and write its own entries</field>
<field name="model_id" ref="model_mail_notification"/>

View File

@ -632,11 +632,10 @@
}
.openerp .oe_followers .oe_partner {
height: 32px;
margin-right: 8px;
margin-right: 24px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-right: 10px;
}
.openerp .oe_followers .oe_partner img{
width: 32px;
@ -649,7 +648,18 @@
right: 0px;
line-height: 20px;
}
.openerp .oe_followers .oe_edit_subtype{
cursor: pointer;
position: absolute;
right: 12px;
line-height: 20px;
}
.openerp .oe_followers .oe_partner .oe_hidden{
display: none;
}
.openerp.ui-dialog .ui-dialog-titlebar .ui-dialog-title{
padding-right: 20px;
}
.openerp .oe_followers .oe_show_more{
cursor: pointer;
}

View File

@ -29,7 +29,6 @@ openerp_mail_followers = function(session, mail) {
this.displayed_limit = this.node.attrs.displayed_nb || 10;
this.displayed_nb = this.displayed_limit;
this.ds_model = new session.web.DataSetSearch(this, this.view.model);
this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
this.ds_users = new session.web.DataSetSearch(this, 'res.users');
this.value = [];
@ -74,10 +73,33 @@ openerp_mail_followers = function(session, mail) {
this.$el.on('click', '.oe_subtype_list input', self.do_update_subscription);
// event: click on 'invite' button, that opens the invite wizard
this.$('.oe_invite').on('click', self.on_invite_follower);
// event: click on 'edit_subtype(pencil)' button to edit subscription
this.$el.on('click', '.oe_edit_subtype', self.on_edit_subtype);
this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
this.$el.on('click', '.oe_show_more', self.on_show_more_followers)
},
on_edit_subtype: function(event) {
var self = this;
var $currentTarget = $(event.currentTarget);
var user_pid = $currentTarget.data('id');
$('div.oe_edit_actions').remove();
self.$dialog = new session.web.dialog($('<div class="oe_edit_actions">'), {
modal: true,
width: 'auto',
height: 'auto',
title: _t('Edit Subscription of ') + $currentTarget.siblings('a').text(),
buttons: [
{ text: _t("Apply"), click: function() {
self.do_update_subscription(event, user_pid);
$(this).dialog("close");
}},
{ text: _t("Cancel"), click: function() { $(this).dialog("close"); }}
],
});
return self.fetch_subtypes(user_pid);
},
on_invite_follower: function (event) {
var self = this;
var action = {
@ -130,7 +152,7 @@ openerp_mail_followers = function(session, mail) {
fetch_followers: function (value_) {
this.value = value_ || {};
return this.ds_follow.call('read', [this.value, ['name', 'user_ids']])
return this.ds_model.call('read_followers_data', [this.value])
.then(this.proxy('display_followers'), this.proxy('fetch_generic'))
.then(this.proxy('display_buttons'))
.then(this.proxy('fetch_subtypes'));
@ -147,6 +169,7 @@ openerp_mail_followers = function(session, mail) {
self.message_is_follower = (_.indexOf(self.value, pid) != -1);
}).then(self.proxy('display_generic'));
},
_format_followers: function(count){
var str = '';
if(count <= 0){
@ -158,6 +181,7 @@ openerp_mail_followers = function(session, mail) {
}
return str;
},
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function () {
var self = this;
@ -168,16 +192,32 @@ openerp_mail_followers = function(session, mail) {
/** Display the followers */
display_followers: function (records) {
var self = this;
this.message_is_follower = false;
this.followers = records || this.followers;
this.message_is_follower = this.set_is_follower(this.followers);
// clean and display title
var node_user_list = this.$('.oe_follower_list').empty();
this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
// truncate number of displayed followers
var truncated = this.followers.slice(0, this.displayed_nb);
_(truncated).each(function (record) {
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record, 'widget': self})).appendTo(node_user_list);
partner = {
'id': record[0],
'name': record[1],
'is_uid': record[2]['is_uid'],
'is_editable': record[2]['is_editable'],
'avatar_url': mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record[0]),
}
if (partner.is_uid) {
self.message_is_follower = partner.is_uid;
}
$(session.web.qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
// On mouse-enter it will show the edit_subtype pencil.
if (partner.is_editable) {
self.$('.oe_follower_list').on('mouseenter mouseleave', function(e) {
self.$('.oe_edit_subtype').toggleClass('oe_hidden', e.type == 'mouseleave');
self.$('.oe_follower_list').find('.oe_partner').toggleClass('oe_partner_name', e.type == 'mouseenter');
});
}
});
// FVA note: be sure it is correctly translated
if (truncated.length < this.followers.length) {
@ -185,12 +225,6 @@ openerp_mail_followers = function(session, mail) {
}
},
/** Computes whether the current user is in the followers */
set_is_follower: function (records) {
var user_ids = _.pluck(_.pluck(records, 'user_ids'), 0);
return _.indexOf(user_ids, this.session.uid) != -1;
},
display_buttons: function () {
if (this.message_is_follower) {
this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
@ -206,25 +240,37 @@ openerp_mail_followers = function(session, mail) {
},
/** Fetch subtypes, only if current user is follower */
fetch_subtypes: function () {
fetch_subtypes: function (user_pid) {
var self = this;
var subtype_list_ul = this.$('.oe_subtype_list').empty();
if (! this.message_is_follower) return;
var dialog = false;
if (user_pid) {
dialog = true;
} else {
var subtype_list_ul = this.$('.oe_subtype_list').empty();
if (! this.message_is_follower) return;
}
var id = this.view.datarecord.id;
this.ds_model.call('message_get_subscription_data', [[id], new session.web.CompoundContext(this.build_context(), {})])
.then(function (data) {self.display_subtypes(data, id);});
this.ds_model.call('message_get_subscription_data', [[id], user_pid, new session.web.CompoundContext(this.build_context(), {})])
.then(function (data) {self.display_subtypes(data, id, dialog);});
},
/** Display subtypes: {'name': default, followed} */
display_subtypes:function (data, id) {
display_subtypes:function (data, id, dialog) {
var self = this;
var $list = this.$('.oe_subtype_list');
if (dialog) {
var $list = self.$dialog;
}
else {
var $list = this.$('.oe_subtype_list');
}
$list.empty().hide();
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
this.records_length = $.map(records, function(value, index) { return index; }).length;
if (this.records_length > 1) { self.display_followers(); }
_(records).each(function (record, record_name) {
record.name = record_name;
record.followed = record.followed || undefined;
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo($list);
});
if (_.size(records) > 1) {
$list.show();
@ -241,35 +287,49 @@ openerp_mail_followers = function(session, mail) {
});
},
do_unfollow: function () {
do_unfollow: function (user_pid) {
if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
_(this.$('.oe_msg_subtype_check')).each(function (record) {
$(record).attr('checked',false);
});
var action_unsubscribe = 'message_unsubscribe_users';
var follower_ids = [this.session.uid];
if (user_pid) {
action_unsubscribe = 'message_unsubscribe';
follower_ids = [user_pid];
}
var context = new session.web.CompoundContext(this.build_context(), {});
return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], [this.session.uid], context])
.then(this.proxy('read_value'));
return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], follower_ids, context])
.then(this.proxy('read_value'));
}
return false;
},
do_update_subscription: function (event) {
do_update_subscription: function (event, user_pid) {
var self = this;
var action_subscribe = 'message_subscribe_users';
var follower_ids = [this.session.uid];
var oe_action = this.$('.oe_actions input[type="checkbox"]');
if (user_pid) {
action_subscribe = 'message_subscribe';
follower_ids = [user_pid];
oe_action = $('.oe_edit_actions input[type="checkbox"]');
}
var checklist = new Array();
_(this.$('.oe_actions input[type="checkbox"]')).each(function (record) {
_(oe_action).each(function (record) {
if ($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')));
}
});
if (!checklist.length) {
if (!this.do_unfollow()) {
if (!this.do_unfollow(user_pid)) {
$(event.target).attr("checked", "checked");
}
} else {
var context = new session.web.CompoundContext(this.build_context(), {});
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], checklist, context])
return this.ds_model.call(action_subscribe, [[this.view.datarecord.id], follower_ids, checklist, context])
.then(this.proxy('read_value'));
}
},

View File

@ -31,6 +31,7 @@
<div t-name="mail.followers.partner" class='oe_partner'>
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
<a t-attf-href="#model=res.partner&amp;id=#{record.id}" t-att-title="record.name"><t t-esc="record.name"/></a>
<span t-if="record.is_editable and (widget.records_length &gt; 1)" class="oe_edit_subtype oe_e oe_hidden" title="Edit subscription" t-att-data-id="record.id">&amp;</span>
<span t-if="widget.view_is_editable" class="oe_remove_follower oe_e" title="Remove this follower" t-att-data-id="record.id">X</span>
</div>

View File

@ -1,10 +1,10 @@
<openerp>
<data>
<record id="action_dummy" model="ir.actions.server">
<field name="name">Dummy Action</field>
<field name="name">Dummy Python Code</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="state">dummy</field>
<field name="code"/>
<field name="state">code</field>
<field name="code">True</field>
<field eval="5" name="sequence"/>
<field eval="True" name="condition"/>
</record>

View File

@ -376,11 +376,26 @@ class product_pricelist_item(osv.osv):
result.append((-2, _('Supplier Prices on the product form')))
return result
# Added default function to fetch the Price type Based on Pricelist type.
def _get_default_base(self, cr, uid, fields, context=None):
product_price_type_obj = self.pool.get('product.price.type')
if fields.get('type') == 'purchase':
product_price_type_ids = product_price_type_obj.search(cr, uid, [('field', '=', 'standard_price')], context=context)
elif fields.get('type') == 'sale':
product_price_type_ids = product_price_type_obj.search(cr, uid, [('field','=','list_price')], context=context)
else:
return -1
if not product_price_type_ids:
return False
else:
pricetype = product_price_type_obj.browse(cr, uid, product_price_type_ids, context=context)[0]
return pricetype.id
_name = "product.pricelist.item"
_description = "Pricelist item"
_order = "sequence, min_quantity desc"
_defaults = {
'base': lambda *a: -1,
'base': _get_default_base,
'min_quantity': lambda *a: 0,
'sequence': lambda *a: 5,
'price_discount': lambda *a: 0,

View File

@ -152,7 +152,7 @@
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
<field name="version_id">
<field name="version_id" context="{'type':type}">
<form string="Pricelist Version" version="7.0">
<group col="4">
<field name="name"/>
@ -160,7 +160,7 @@
<field name="date_start"/>
<field name="date_end"/>
</group>
<field name="items_id"/>
<field name="items_id" context="{'type':type}"/>
</form>
<tree string="Pricelist Version">
<field name="name"/>

View File

@ -545,6 +545,7 @@ class task(osv.osv):
_date_name = "date_start"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_mail_post_access = 'read'
_track = {
'stage_id': {
'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
@ -1106,17 +1107,6 @@ class task(osv.osv):
return [task.project_id.message_get_reply_to()[0] if task.project_id else False
for task in self.browse(cr, uid, ids, context=context)]
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
""" mail.message document permission rule: can post a new message if can read
because of portal document. """
if not model_obj:
model_obj = self
if operation == 'create':
model_obj.check_access_rights(cr, uid, 'read')
model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
else:
return super(task, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
def message_new(self, cr, uid, msg, custom_values=None, context=None):
""" Override to updates the document according to the email. """
if custom_values is None: custom_values = {}

View File

@ -135,6 +135,7 @@
<div style="position: relative">
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_module_vignette">
<img t-att-src="kanban_image('res.users', 'image_small', record.id.value)" class="oe_avatar oe_kanban_avatar_smallbox"/>
<div class="oe_module_desc">
<field name="name"/>
</div>

View File

@ -45,6 +45,7 @@ class project_issue(osv.Model):
_order = "priority, create_date desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_mail_post_access = 'read'
_track = {
'stage_id': {
'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
@ -477,17 +478,6 @@ class project_issue(osv.Model):
return [issue.project_id.message_get_reply_to()[0] if issue.project_id else False
for issue in self.browse(cr, uid, ids, context=context)]
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
""" mail.message document permission rule: can post a new message if can read
because of portal document. """
if not model_obj:
model_obj = self
if operation == 'create':
model_obj.check_access_rights(cr, uid, 'read')
model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
else:
return super(project_issue, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(project_issue, self).message_get_suggested_recipients(cr, uid, ids, context=context)
try:

View File

@ -174,8 +174,8 @@
<button name="purchase_approve" states="confirmed" string="Approve Order" class="oe_highlight" groups="purchase.group_purchase_manager"/>
<button name="view_picking" string="Receive Products" type="object" attrs="{'invisible': ['|', ('shipped','=',True), ('state','!=', 'approved')]}" class="oe_highlight"/>
<button name="view_invoice" string="Receive Invoice" type="object" attrs="{'invisible': ['|', ('invoice_method','=','picking'), '|', ('state','!=', 'approved'), ('invoiced','=',True) ]}" class="oe_highlight"/>
<button name="action_cancel_draft" states="cancel,sent,confirmed" string="Set to Draft" type="object" />
<button name="purchase_cancel" states="draft,confirmed,sent" string="Cancel Order"/>
<button name="action_cancel_draft" states="cancel,confirmed" string="Set to Draft" type="object" />
<button name="purchase_cancel" states="draft,confirmed,sent" string="Cancel"/>
<field name="state" widget="statusbar" statusbar_visible="draft,sent,approved,done" statusbar_colors='{"except_picking":"red","except_invoice":"red","confirmed":"blue"}' readonly="1"/>
</header>
<sheet>
@ -292,7 +292,7 @@
<field name="name" string="Reference"/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter icon="terp-document-new" name="draft" string="Quotations" domain="[('state','=','draft')]" help="Purchase orders which are in draft state"/>
<filter icon="terp-document-new" name="draft" string="Quotations" domain="[('state','in',('draft','sent'))]" help="Purchase orders that haven't yet been confirmed"/>
<filter icon="terp-check" name="approved" string="Purchase Orders" domain="[('state','not in',('draft','cancel'))]" help="Approved purchase orders"/>
<filter icon="terp-emblem-important" name="exception" string="Exception" domain="[('state','in',('except_invoice','except_picking'))]" help="Purchase orders which are in exception state"/>
<separator/>

View File

@ -95,7 +95,7 @@
<para style="terp_default_8">
<font color="white"> </font>
</para>
<blockTable colWidths="371.0,98.0,61.0" repeatRows="1" style="Table_Product_Header_Title">
<blockTable colWidths="349.0,90.0,92.0" repeatRows="1" style="Table_Product_Header_Title">
<tr>
<td>
<para style="terp_tblheader_Details"><b>Description</b></para>
@ -110,7 +110,7 @@
</blockTable>
<section>
<para style="terp_default_9">[[ repeatIn(order.order_line,'order_line') ]]</para>
<blockTable colWidths="371.0,98.0,61.0" style="Table_Product_Line">
<blockTable colWidths="349.0,89.0,49.0,43.0" style="Table_Product_Line">
<tr>
<td>
<para style="terp_default_9">[[ order_line.name ]]</para>
@ -119,10 +119,10 @@
<para style="terp_default_Centre_9">[[ formatLang(order_line.date_planned, date = True) ]]</para>
</td>
<td>
<para style="terp_default_Right_9">
[[ formatLang(order_line.product_qty )]]
<i>[[ (order_line.product_uom and order_line.product_uom.name) or '' ]]</i>
</para>
<para style="terp_default_Right_9">[[ formatLang(order_line.product_qty )]]</para>
</td>
<td>
<para style="terp_default_Right_9">[[ (order_line.product_uom and order_line.product_uom.name) or '' ]]</para>
</td>
</tr>
</blockTable>