[MERGE] merge with main addons
bzr revid: cha@tinyerp.com-20120524054256-lqep5fdpu631fbrp
This commit is contained in:
commit
edd6043b76
|
@ -234,65 +234,63 @@
|
|||
</ul>
|
||||
</t>
|
||||
<t t-name="kanban-box">
|
||||
<t t-if="record.date_deadline.raw_value and record.date_deadline.raw_value lt (new Date())" t-set="border">oe_kanban_color_red</t>
|
||||
<div t-attf-class="#{kanban_color(record.color.raw_value)} #{border || ''}">
|
||||
<div class="oe_kanban_box oe_kanban_color_border">
|
||||
<table class="oe_kanban_table oe_kanban_box_header oe_kanban_color_bgdark oe_kanban_color_border oe_kanban_draghandle">
|
||||
<tr>
|
||||
<td align="left" valign="middle" width="16">
|
||||
<a t-if="record.priority.raw_value == 1" icon="star-on" type="object" name="set_normal_priority"/>
|
||||
<a t-if="record.priority.raw_value != 1" icon="star-off" type="object" name="set_high_priority" style="opacity:0.6; filter:alpha(opacity=60);"/>
|
||||
</td>
|
||||
<td align="left" valign="middle" class="oe_kanban_title" tooltip="lead_details">
|
||||
<field name="partner_id"/>
|
||||
<t t-if="record.planned_revenue.raw_value">
|
||||
- <t t-esc="Math.round(record.planned_revenue.value)"/>
|
||||
<field name="company_currency"/>
|
||||
</t>
|
||||
</td>
|
||||
<td valign="top" width="22">
|
||||
<img t-att-src="kanban_image('res.users', 'avatar', record.user_id.raw_value[0])" t-att-title="record.user_id.value"
|
||||
width="22" height="22" class="oe_kanban_gravatar"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger">
|
||||
<div>
|
||||
<b>
|
||||
<a t-if="record.partner_address_email.raw_value" t-attf-href="mailto:#{record.partner_address_email.raw_value}">
|
||||
<field name="partner_address_name"/>
|
||||
</a>
|
||||
<field t-if="!record.partner_address_email.raw_value" name="partner_address_name"/>
|
||||
</b>
|
||||
</div>
|
||||
<div>
|
||||
<field name="name"/>
|
||||
</div>
|
||||
<div style="padding-left: 0.5em">
|
||||
<i><field name="date_action"/><t t-if="record.date_action.raw_value"> : </t><field name="title_action"/></i>
|
||||
</div>
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card">
|
||||
<a class="oe_kanban_menuaction oe_i">B</a>
|
||||
<ul class="oe_kanban_menu">
|
||||
<li><a type="edit" >Edit...</a></li>
|
||||
<li><a type="delete">Delete</a></li>
|
||||
<li><a name="%(mail.action_email_compose_message_wizard)d" type="action">Send New Email</a></li>
|
||||
<li><a name="%(opportunity2phonecall_act)d" type="action">Log Call</a></li>
|
||||
<li><a name="action_makeMeeting" type="object">Schedule Meeting</a></li>
|
||||
<li><a name="%(crm.action_crm_add_note)d" context="{'model': 'crm.lead' }" type="action">Add Internal Note</a></li>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
<div class="oe_kanban_content">
|
||||
<a type="edit"><h3>
|
||||
<field name="partner_id"/>
|
||||
<t t-if="record.planned_revenue.raw_value">
|
||||
- <t t-esc="Math.round(record.planned_revenue.value)"/>
|
||||
<field name="company_currency"/>
|
||||
</t>
|
||||
</h3></a>
|
||||
<div>
|
||||
<b> <field name="partner_address_name"/> </b>
|
||||
</div>
|
||||
|
||||
<div class="oe_kanban_buttons_set oe_kanban_color_border oe_kanban_color_bglight oe_kanban_box_show_onclick">
|
||||
<div class="oe_kanban_left">
|
||||
<a string="Edit" icon="gtk-edit" type="edit"/>
|
||||
<a string="Change Color" icon="color-picker" type="color" name="color"/>
|
||||
<a string="Send New Email" name="%(mail.action_email_compose_message_wizard)d" icon="terp-mail-message-new" type="action"/>
|
||||
<a string="Log Call" name="%(opportunity2phonecall_act)d" icon="terp-call-start" type="action"/>
|
||||
<a string="Schedule Meeting" name="action_makeMeeting" type="object" icon="stock_calendar"/>
|
||||
<a string="Add Internal Note" name="%(crm.action_crm_add_note)d" context="{'model': 'crm.lead' }" icon="terp-document-new" type="action"/>
|
||||
</div>
|
||||
<div class="oe_kanban_right">
|
||||
<a name="case_mark_lost" string="Mark Lost" states="open,pending" type="object" icon="kanban-stop" />
|
||||
<a name="case_pending" string="Pending" states="draft,open" type="object" icon="kanban-pause" />
|
||||
<a name="case_open" string="Open" states="pending" type="object" icon="gtk-media-play" />
|
||||
<a name="case_mark_won" string="Mark Won" states="open,pending" type="object" icon="kanban-apply" />
|
||||
</div>
|
||||
<br class="oe_kanban_clear"/>
|
||||
<div>
|
||||
<field name="name"/>
|
||||
</div>
|
||||
<div style="padding-left: 0.5em">
|
||||
<i>
|
||||
<t t-if="record.date_deadline.raw_value and record.date_deadline.raw_value lt (new Date())" t-set="red">oe_kanban_text_red</t>
|
||||
<span t-attf-class="#{red || ''}">
|
||||
<field name="date_action"/>
|
||||
</span>
|
||||
<t t-if="record.date_action.raw_value"> : </t>
|
||||
<field name="title_action"/>
|
||||
</i>
|
||||
</div>
|
||||
<div class="oe_right">
|
||||
<a t-if="record.priority.raw_value == 1" icon="star-on" type="object" name="set_normal_priority"/>
|
||||
<a t-if="record.priority.raw_value != 1" icon="star-off" type="object" name="set_high_priority" style="opacity:0.7; filter:alpha(opacity=70);"/>
|
||||
<!--
|
||||
<t t-if="record.date_deadline.raw_value and record.date_deadline.raw_value lt (new Date())" t-set="red">oe_kaban_status_red</t>
|
||||
<span t-attf-class="oe_kanban_status #{red}"> </span>
|
||||
-->
|
||||
<img t-att-src="kanban_image('res.users', 'avatar', record.user_id.raw_value[0])" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar"/>
|
||||
</div>
|
||||
<div class="oe_kanban_footer_left">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="oe_kanban_right">
|
||||
<a name="case_mark_lost" string="Mark Lost" states="open,pending" type="object" icon="kanban-stop" />
|
||||
<a name="case_pending" string="Pending" states="draft,open" type="object" icon="kanban-pause" />
|
||||
<a name="case_open" string="Open" states="pending" type="object" icon="gtk-media-play" />
|
||||
<a name="case_mark_won" string="Mark Won" states="open,pending" type="object" icon="kanban-apply" />
|
||||
</div>
|
||||
-->
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_users_gogole_form" model="ir.ui.view">
|
||||
<field name="name">res.users.google.form1</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook[last()]" position="inside">
|
||||
<page string=" Synchronization ">
|
||||
<separator string="Google Account" colspan="4" />
|
||||
<field name="gmail_user"/>
|
||||
<field name="gmail_password" password="True"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_users_gogole_form" model="ir.ui.view">
|
||||
<field name="name">res.users.google.form1</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook[last()]" position="inside">
|
||||
<page string=" Synchronization ">
|
||||
<separator string="Google Account" colspan="4"/>
|
||||
<field name="gmail_user"/>
|
||||
<field name="gmail_password" password="True"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -37,16 +37,18 @@ class google_login(osv.osv_memory):
|
|||
}
|
||||
|
||||
def google_login(self, user, password, type='', context=None):
|
||||
if type == 'group':
|
||||
if type == 'group':
|
||||
gd_client = gdata.contacts.client.ContactsClient(source='OpenERP')
|
||||
if type == 'contact' :
|
||||
if type == 'contact':
|
||||
gd_client = gdata.contacts.service.ContactsService()
|
||||
if type == 'calendar':
|
||||
gd_client = gdata.calendar.service.CalendarService()
|
||||
if type =='docs_client':
|
||||
gd_client = gdata.docs.client.DocsClient()
|
||||
else:
|
||||
gd_client = gdata.contacts.service.ContactsService()
|
||||
try:
|
||||
gd_client.ClientLogin(user, password,gd_client.source)
|
||||
gd_client = gdata.contacts.service.ContactsService()
|
||||
try:
|
||||
gd_client.ClientLogin(user, password, gd_client.source)
|
||||
except Exception:
|
||||
return False
|
||||
return gd_client
|
||||
|
@ -60,7 +62,7 @@ class google_login(osv.osv_memory):
|
|||
if 'password' in fields:
|
||||
res.update({'password': user_obj.gmail_password})
|
||||
return res
|
||||
|
||||
|
||||
def login(self, cr, uid, ids, context=None):
|
||||
data = self.read(cr, uid, ids)[0]
|
||||
user = data['user']
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
import google_docs
|
|
@ -0,0 +1,37 @@
|
|||
# -*- 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 Docs integration',
|
||||
'version': '0.2',
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://openerp.com',
|
||||
'category': 'Tools',
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'web': True,
|
||||
'js': ['static/src/js/gdocs.js'],
|
||||
'update_xml': [
|
||||
'res_config_user_view.xml'
|
||||
],
|
||||
'depends': ['google_base_account'],
|
||||
'description': 'Module to attach a google document to any model.'
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
##############################################################################
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
try:
|
||||
import gdata.docs.data
|
||||
import gdata.docs.client
|
||||
from gdata.client import RequestError
|
||||
from gdata.docs.service import DOCUMENT_LABEL
|
||||
import gdata.auth
|
||||
except ImportError:
|
||||
raise osv.except_osv(_('Google Docs Error!'), _('Please install gdata-python-client from http://code.google.com/p/gdata-python-client/downloads/list'))
|
||||
|
||||
class google_docs_ir_attachment(osv.osv):
|
||||
_inherit = 'ir.attachment'
|
||||
|
||||
def _auth(self, cr, uid, context=None):
|
||||
'''
|
||||
Connexion with google base account
|
||||
@return client object for connexion
|
||||
'''
|
||||
#pool the google.login in google_base_account
|
||||
google_pool = self.pool.get('google.login')
|
||||
#get gmail password and login. We use default_get() instead of a create() followed by a read() on the
|
||||
# google.login object, because it is easier. The keys 'user' and 'password' ahve to be passed in the dict
|
||||
# but the values will be replaced by the user gmail password and login.
|
||||
user_config = google_pool.default_get( cr, uid, {'user' : '' , 'password' : ''}, context=context)
|
||||
#login gmail account
|
||||
client = google_pool.google_login( user_config['user'], user_config['password'], type='docs_client', context=context)
|
||||
if not client:
|
||||
raise osv.except_osv( _('Google Docs Error!'), _("Check your google configuration in users/synchronization"))
|
||||
return client
|
||||
|
||||
def create_empty_google_doc(self, cr, uid, res_model, res_id, context=None):
|
||||
'''Create a new google document, empty and with a default type (txt)
|
||||
:param res_model: the object for which the google doc is created
|
||||
:param res_id: the Id of the object for which the google doc is created
|
||||
:return: the ID of the google document object created
|
||||
'''
|
||||
#login with the base account google module
|
||||
client = self._auth(cr, uid, context=context)
|
||||
# create the document in google docs
|
||||
local_resource = gdata.docs.data.Resource(gdata.docs.data.DOCUMENT_LABEL)
|
||||
#create a new doc in Google Docs
|
||||
gdocs_resource = client.post(entry=local_resource, uri='https://docs.google.com/feeds/default/private/full/')
|
||||
# create an ir.attachment into the db
|
||||
self.create(cr, uid, {
|
||||
'res_model': res_model,
|
||||
'res_id': res_id,
|
||||
'type': 'url',
|
||||
'name': _('Google Doc'),
|
||||
'url': gdocs_resource.get_alternate_link().href,
|
||||
}, context=context)
|
||||
return gdocs_resource.resource_id.text
|
||||
|
||||
def copy_gdoc(self, cr, uid, res_model, res_id, name_gdocs, gdoc_template_id, context=None):
|
||||
'''
|
||||
copy an existing document in google docs
|
||||
:param res_model: the object for which the google doc is created
|
||||
:param res_id: the Id of the object for which the google doc is created
|
||||
:param name_gdocs: the name of the future ir.attachment that will be created. Based on the google doc template foun.
|
||||
:param gdoc_template_id: the id of the google doc document to copy
|
||||
:return: the ID of the google document object created
|
||||
'''
|
||||
#login with the base account google module
|
||||
client = self._auth(cr, uid)
|
||||
# fetch and copy the original document
|
||||
try:
|
||||
original_resource = client.get_resource_by_id(gdoc_template_id)
|
||||
#copy the document you choose in the configuration
|
||||
copy_resource = client.copy_resource(original_resource, 'copy_%s' % original_resource.title.text)
|
||||
except:
|
||||
raise osv.except_osv(_('Google Docs Error!'), _("Your resource id is not correct. You can find the id in the google docs URL"))
|
||||
# create an ir.attachment
|
||||
self.create(cr, uid, {
|
||||
'res_model': res_model,
|
||||
'res_id': res_id,
|
||||
'type': 'url',
|
||||
'name': name_gdocs,
|
||||
'url': copy_resource.get_alternate_link().href
|
||||
}, context=context)
|
||||
return copy_resource.resource_id.text
|
||||
|
||||
def google_doc_get(self, cr, uid, res_model, ids, context=None):
|
||||
'''
|
||||
Function called by the js, when no google doc are yet associated with a record, with the aim to create one. It
|
||||
will first seek for a google.docs.config associated with the model `res_model` to find out what's the template
|
||||
of google doc to copy (this is usefull if you want to start with a non-empty document, a type or a name
|
||||
different than the default values). If no config is associated with the `res_model`, then a blank text document
|
||||
with a default name is created.
|
||||
:param res_model: the object for which the google doc is created
|
||||
:param ids: the list of ids of the objects for which the google doc is created. This list is supposed to have
|
||||
a length of 1 element only (batch processing is not supported in the code, though nothing really prevent it)
|
||||
:return: the google document object created
|
||||
'''
|
||||
assert len(ids) == 1, 'Creating google docs may only be done by one at a time'
|
||||
res_id = ids[0]
|
||||
pool_ir_attachment = self.pool.get('ir.attachment')
|
||||
pool_gdoc_config = self.pool.get('google.docs.config')
|
||||
name_gdocs = ''
|
||||
model_fields_dic = self.pool.get(res_model).read(cr, uid, res_id, [], context=context)
|
||||
|
||||
# check if a model is configured with a template
|
||||
google_docs_config = pool_gdoc_config.search(cr, uid, [('model_id', '=', res_model)], context=context)
|
||||
if google_docs_config:
|
||||
name_gdocs = pool_gdoc_config.browse(cr, uid, google_docs_config, context=context)[0].name_template
|
||||
name_gdocs = name_gdocs % model_fields_dic
|
||||
google_template_id = pool_gdoc_config.browse(cr, uid, google_docs_config[0], context=context).gdocs_resource_id
|
||||
google_document = pool_ir_attachment.copy_gdoc(cr, uid, res_model, res_id, name_gdocs, google_template_id, context=context)
|
||||
else:
|
||||
google_document = pool_ir_attachment.create_empty_google_doc(cr, uid, res_model, res_id, context=context)
|
||||
return google_document
|
||||
|
||||
class config(osv.osv):
|
||||
_name = 'google.docs.config'
|
||||
_description = "Google Docs templates config"
|
||||
|
||||
_columns = {
|
||||
'model_id': fields.many2one('ir.model', 'Model'),
|
||||
'gdocs_resource_id': fields.char('Google resource ID', size=64,help='''
|
||||
This is the id of the template document, on google side. You can find it thanks to its URL:
|
||||
*for a text document with url like `https://docs.google.com/a/openerp.com/document/d/123456789/edit`, the ID is `document:123456789`
|
||||
*for a spreadsheet document with url like `https://docs.google.com/a/openerp.com/spreadsheet/ccc?key=123456789#gid=0`, the ID is `spreadsheet:123456789`
|
||||
*for a presentation (slide show) document with url like `https://docs.google.com/a/openerp.com/presentation/d/123456789/edit#slide=id.p`, the ID is `presentation:123456789`
|
||||
*for a drawing document with url like `https://docs.google.com/a/openerp.com/drawings/d/123456789/edit`, the ID is `drawings:123456789`
|
||||
...
|
||||
'''),
|
||||
'name_template': fields.char('GDoc name template ', size=64, help='This is the name which appears on google side'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'name_template': 'gdoc_%(name)s',
|
||||
}
|
||||
config()
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- add google docs config field in user form -->
|
||||
|
||||
<record model="ir.ui.view" id="view_google_docs_config_tree">
|
||||
<field name="name">google_docs.config.tree</field>
|
||||
<field name="model">google.docs.config</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<group name="default_filters" position="inside">
|
||||
<field name="model_id"/>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_google_docs_config_form">
|
||||
<field name="name">google_docs.config.form</field>
|
||||
<field name="model">google.docs.config</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<group colspan='4'>
|
||||
<field name="model_id"/>
|
||||
<field name='gdocs_resource_id'/>
|
||||
<field name='name_template'/>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model='ir.actions.act_window' id='action_google_docs_users_config'>
|
||||
<field name='name'>Models configuration</field>
|
||||
<field name='res_model'>google.docs.config</field>
|
||||
<field name='type'>ir.actions.act_window</field>
|
||||
<field name='view_type'>form</field>
|
||||
<field name='view_id' ref='view_google_docs_config_tree'/>
|
||||
</record>
|
||||
<menuitem name='Google Docs configuration' id='menu_gdocs_config' parent='base.menu_administration'/>
|
||||
<menuitem name='Models configuration' id='menu_gdocs_model_config' parent='menu_gdocs_config' action='action_google_docs_users_config'/>
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,41 @@
|
|||
openerp.google_docs = function(instance, m) {
|
||||
var _t = instance.web._t;
|
||||
|
||||
instance.web.Sidebar = instance.web.Sidebar.extend({
|
||||
on_attachments_loaded: function(attachments) {
|
||||
self = this
|
||||
self._super(attachments);
|
||||
// if attachment contains a google doc url do nothing
|
||||
// else display a button to create a google doc
|
||||
var flag = false;
|
||||
_.each(attachments, function(i) {
|
||||
if (i.url && i.url.match('/docs.google.com/')) { flag = true; }
|
||||
});
|
||||
if (! flag) {
|
||||
this.add_items('files', [
|
||||
{ label: _t('Google Doc'), callback: self.on_google_doc },
|
||||
]);
|
||||
}
|
||||
},
|
||||
on_google_doc: function() {
|
||||
var self = this;
|
||||
var form = self.getParent();
|
||||
form.sidebar_context().then(function (context) {
|
||||
var ds = new instance.web.DataSet(this, 'ir.attachment', context);
|
||||
ds.call('google_doc_get', [form.dataset.model, [form.datarecord.id], context], function(r) {
|
||||
if (r == 'False') {
|
||||
var params = {
|
||||
error: response,
|
||||
message: _t("The user google credentials are not set yet. Contact your administrator for help.")
|
||||
}
|
||||
$(openerp.web.qweb.render("DialogWarning", params)).dialog({
|
||||
title: _t("User Google credentials are not yet set."),
|
||||
modal: true,
|
||||
});
|
||||
}
|
||||
form.reload();
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -10,7 +10,7 @@
|
|||
</table>
|
||||
<div class="oe_mail_wall_left">
|
||||
<div class="oe_mail_wall_act">
|
||||
<textarea class="oe_mail oe_mail_wall_action_textarea" placeholder="Add a personnal message here..."/>
|
||||
<textarea class="oe_mail oe_mail_wall_action_textarea" placeholder="What are you working on?"/>
|
||||
<button class="oe_right oe_mail_wall_button_comment" type="button">Post comment</button>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
|
|
|
@ -70,7 +70,7 @@ Dashboard for project members that includes:
|
|||
'auto_install': False,
|
||||
'application': True,
|
||||
'css': ['static/src/css/project.css'],
|
||||
'js': ['static/src/js/dropdown.js','static/src/js/project.js'],
|
||||
'js': ['static/src/js/project.js'],
|
||||
'certificate': '0075116868317',
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -188,61 +188,39 @@
|
|||
<field name="task_count"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="project_vignettes">
|
||||
<li t-attf-class="#{kanban_color(record.color.raw_value)} oe_project_kanban_vignette" id="oe_project_kanban_vignette">
|
||||
<a href="#" class="oe_project_kanban_action dropdown-toggle"><span class="oe_i">B</span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ><a type="edit" >Edit...</a></li>
|
||||
<li ><a type="delete">Delete</a></li>
|
||||
<li>
|
||||
<ul class="color-chooser">
|
||||
<li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_0 square"></span></a></li>
|
||||
<li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_1 square"></span></a></li>
|
||||
<li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_2 square"></span></a></li>
|
||||
<li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_3 square"></span></a></li>
|
||||
<li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_4 square"></span></a></li>
|
||||
<li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_5 square"></span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h4><t t-esc="record.name.value.substr(0,33)"/><t t-if="record.name.value.length > 33">...</t></h4>
|
||||
<div id="list">
|
||||
<a t-if="record.use_tasks.raw_value" class="oe_project_buttons"
|
||||
id="1" name="%(act_project_project_2_project_task_all)d" type="action">
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_project oe_kanban_auto_height">
|
||||
<a class="oe_kanban_menuaction oe_i">B</a>
|
||||
<ul class="oe_kanban_menu">
|
||||
<li><a type="edit">Edit...</a></li>
|
||||
<li><a type="delete">Delete</a></li>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
<div class="oe_kanban_content">
|
||||
<a type="edit"><h3 class="oe_kanban_ellipsis"><field name="name"/></h3></a>
|
||||
|
||||
<div class="oe_kanban_project_list">
|
||||
<a t-if="record.use_tasks.raw_value"
|
||||
name="%(act_project_project_2_project_task_all)d" type="action">
|
||||
Tasks(<field name="task_count"/>)</a>
|
||||
</div>
|
||||
<br/>
|
||||
<button class="click_button" name="dummy" type="object">
|
||||
<table class="project_fields">
|
||||
<tr id="deadline" t-if="record.date.raw_value">
|
||||
<th align="left">Deadline</th>
|
||||
<td align="left"><field name="date"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left" width="70px">Progress</th>
|
||||
<td align="left">
|
||||
<t t-esc="Math.round(record.effective_hours.raw_value)"/> / <t t-esc="Math.round(record.planned_hours.raw_value)"/> <field name="company_uom_id"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
<div class="oe_kanban_project_fields oe_kanban_project_deadline" t-if="record.date.raw_value">
|
||||
<div>Deadline</div>
|
||||
<div><field name="date"/></div>
|
||||
</div>
|
||||
|
||||
<div class="oe_kanban_project_fields oe_kanban_project_progress">
|
||||
<div>Progress</div>
|
||||
<div><t t-esc="Math.round(record.effective_hours.raw_value)"/> / <t t-esc="Math.round(record.planned_hours.raw_value)"/> <field name="company_uom_id"/></div>
|
||||
</div>
|
||||
|
||||
<div class="oe_kanban_project_avatars">
|
||||
<t t-foreach="record.members.raw_value" t-as="member">
|
||||
<img t-att-src="kanban_image('res.users', 'avatar', member)" t-att-id="member" class="project_avatar"/>
|
||||
<img t-att-src="kanban_image('res.users', 'avatar', member)" t-att-data-member_id="member"/>
|
||||
</t>
|
||||
</button>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
//open dropdwon when click on the icon.
|
||||
$('.dropdown-toggle').dropdown();
|
||||
|
||||
//show and hide the dropdown icon when mouseover and mouseour.
|
||||
$('.oe_project_kanban_vignette').mouseover(function() {
|
||||
return $(this).find('.oe_project_kanban_action').show();
|
||||
}).mouseout(function() {
|
||||
return $(this).find('.oe_project_kanban_action').hide();
|
||||
});
|
||||
</script>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
|
@ -268,12 +246,16 @@
|
|||
<field name="view_id" ref="view_project_kanban"/>
|
||||
<field name="search_view_id" ref="view_project_project_filter"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="xml"><p>You have no projects.</p>
|
||||
<field name="help" type="xml">
|
||||
<p>Click <i>'Create'</i> to <b>start a new project</b>.</p>
|
||||
<p>Projects are used to organize your activities; plan tasks,
|
||||
track issues, invoice timesheets. You can define internal
|
||||
projects (R&D, Improve Sales Process), private projects (My
|
||||
Todos) or customer ones.</p>
|
||||
<p>Click <i>'create'</i> to start a new project.</p>
|
||||
<p>
|
||||
You will be able collaborate with internal users on
|
||||
projects or invite customers to share your activities.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -1,246 +1,41 @@
|
|||
.project_fields {
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
font-size: 11px;
|
||||
padding-left: 0px;
|
||||
.oe_kanban_project {
|
||||
width: 300px;
|
||||
}
|
||||
.project_fields td {
|
||||
border: none;
|
||||
padding: 2px 0 2px 8px;
|
||||
.oe_kanban_project_list {
|
||||
margin: 8px 0 8px 0;
|
||||
}
|
||||
.project_fields th {
|
||||
padding: 0;
|
||||
/*.oe_kanban_project_list a:not(:first-child):before {
|
||||
content: ' - ';
|
||||
}*/
|
||||
.oe_kanban_project_fields div {
|
||||
display: inline-block;
|
||||
width: 49%;
|
||||
box-sizing: border-box;
|
||||
padding: 2px 0 2px 0;
|
||||
}
|
||||
.oe_kanban_project_fields div:nth-child(odd) {
|
||||
border-right: 1px solid #dddddd;
|
||||
vertical-align: top;
|
||||
margin-right: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.project_vignettes{
|
||||
padding: 4px !important;
|
||||
.oe_kanban_project_fields div:nth-child(even) {
|
||||
padding-left: 8px;
|
||||
color: #888888;
|
||||
}
|
||||
.project_vignettes li {
|
||||
float: left;
|
||||
|
||||
.oe_kanban_project_avatars {
|
||||
padding-top: 1em;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.project_vignettes .project_avatar {
|
||||
.oe_kanban_project_avatars img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
.project_vignettes .project_fields {
|
||||
width: 100%;
|
||||
}
|
||||
.project_vignettes .project_fields th {
|
||||
width: 120px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.project_vignettes .project_fields td {
|
||||
color: #888888;
|
||||
}
|
||||
.project_vignettes h4 a {
|
||||
color: #4c4c4c;
|
||||
}
|
||||
.project_vignettes > li h4 {
|
||||
margin-bottom: 2px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
.oe_project_buttons {
|
||||
padding: 2px 2px !important;
|
||||
background: none !important;
|
||||
background-color: transparent !important;
|
||||
border: hidden !important;
|
||||
color: #8A89BA !important;
|
||||
}
|
||||
.oe_project_buttons:hover {
|
||||
cursor: pointer;
|
||||
color: #0000FF;
|
||||
}
|
||||
.project_avatar {
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.click_button {
|
||||
background: none !important;
|
||||
background-color: transparent !important;
|
||||
border: hidden !important;
|
||||
min-height: 155px;
|
||||
min-width: 265px;
|
||||
border: hidden;
|
||||
/*margin-left: 6px !important;*/
|
||||
margin-top: 3px !important;
|
||||
text-align: left;
|
||||
vertical-align: super;
|
||||
font-size: 11px;
|
||||
-webkit-box-align: baseline;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.click_button:hover {
|
||||
cursor: default !important;
|
||||
}
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
.dropdown-toggle:after {
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: inline-block;
|
||||
content: "&darr";
|
||||
text-indent: -99999px;
|
||||
vertical-align: top;
|
||||
margin-top: 8px;
|
||||
margin-left: 4px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid white;
|
||||
filter: alpha(opacity=50);
|
||||
-khtml-opacity: 0.5;
|
||||
-moz-opacity: 0.5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu .color-chooser {
|
||||
padding: 0 3px;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu .color-chooser li {
|
||||
float: left;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu .color-chooser li a {
|
||||
padding: 2px;
|
||||
}
|
||||
a.oe_project_kanban_action {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: none;
|
||||
}
|
||||
a.oe_project_kanban_action:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
a.oe_project_kanban_action .oe_i {
|
||||
color: #4c4c4c;
|
||||
}
|
||||
|
||||
.square {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border:1px solid grey;
|
||||
}
|
||||
.oe_kanban_color_0 {
|
||||
background: white;
|
||||
}
|
||||
.oe_kanban_color_1 {
|
||||
background: #B1DCFE;
|
||||
}
|
||||
.oe_kanban_color_2 {
|
||||
background: #FF8E8E;
|
||||
}
|
||||
.oe_kanban_color_3 {
|
||||
background: khaki;
|
||||
}
|
||||
.oe_kanban_color_4 {
|
||||
background: thistle;
|
||||
}
|
||||
.oe_kanban_color_5 {
|
||||
background: orange;
|
||||
}
|
||||
|
||||
.open {
|
||||
display: block;
|
||||
}
|
||||
.open .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a.oe_project_kanban_action {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
a.oe_project_kanban_action:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
a.oe_project_kanban_action .eo_i {
|
||||
color: #4c4c4c;
|
||||
}
|
||||
|
||||
.oe_project_kanban_vignette {
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
/*background: white;*/
|
||||
border: 1px solid #d8d8d8;
|
||||
border-bottom-color: #b9b9b9;
|
||||
padding: 6px;
|
||||
margin: 6px 0;
|
||||
display: inline-block;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.oe_project_kanban_vignette:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.oe_project_kanban_vignette:hover {
|
||||
-moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6);
|
||||
-webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6);
|
||||
-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.oe_project_kanban_vignette h4 {
|
||||
margin: 0 0 2px;
|
||||
}
|
||||
|
||||
.oe_project_kanban_vignette .dropdown-menu {
|
||||
top: 30px;
|
||||
right: -140px;
|
||||
padding: 4px;
|
||||
border: 1px solid #afafb6;
|
||||
width: 160px;
|
||||
overflow-x: hidden;
|
||||
z-index: 900;
|
||||
background: white;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu p {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu li {
|
||||
float: none;
|
||||
display: block;
|
||||
background-color: none;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu li a {
|
||||
display: block;
|
||||
padding: 3px 6px;
|
||||
clear: both;
|
||||
font-weight: normal;
|
||||
line-height: 14px;
|
||||
color: #4c4c4c;
|
||||
text-decoration: none;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu li a:hover {
|
||||
background: #f0f0fa;
|
||||
background: -moz-linear-gradient(#f0f0fa, #eeeef6);
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6));
|
||||
background: -webkit-linear-gradient(#f0f0fa, #eeeef6);
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
-box-shadow: none;
|
||||
}
|
||||
|
||||
.oe_project_kanban_vignette .dropdown-menu .color-chooser {
|
||||
padding: 0 3px;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu .color-chooser li {
|
||||
float: left;
|
||||
}
|
||||
.oe_project_kanban_vignette .dropdown-menu .color-chooser li a {
|
||||
padding: 2px;
|
||||
}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/* ============================================================
|
||||
* bootstrap-dropdown.js v2.0.2
|
||||
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
|
||||
* ============================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ============================================================ */
|
||||
|
||||
|
||||
!function( $ ){
|
||||
|
||||
"use strict"
|
||||
|
||||
/* DROPDOWN CLASS DEFINITION
|
||||
* ========================= */
|
||||
|
||||
var toggle = '[data-toggle="dropdown"]'
|
||||
, Dropdown = function ( element ) {
|
||||
var $el = $(element).on('click.dropdown.data-api', this.toggle)
|
||||
$('html').on('click.dropdown.data-api', function () {
|
||||
$el.parent().removeClass('open')
|
||||
})
|
||||
}
|
||||
|
||||
Dropdown.prototype = {
|
||||
|
||||
constructor: Dropdown
|
||||
|
||||
, toggle: function ( e ) {
|
||||
var $this = $(this)
|
||||
, selector = $this.attr('data-target')
|
||||
, $parent
|
||||
, isActive
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
}
|
||||
|
||||
$parent = $(selector)
|
||||
$parent.length || ($parent = $this.parent())
|
||||
|
||||
isActive = $parent.hasClass('open')
|
||||
|
||||
clearMenus()
|
||||
!isActive && $parent.toggleClass('open')
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function clearMenus() {
|
||||
$(toggle).parent().removeClass('open')
|
||||
}
|
||||
|
||||
|
||||
/* DROPDOWN PLUGIN DEFINITION
|
||||
* ========================== */
|
||||
|
||||
$.fn.dropdown = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('dropdown')
|
||||
if (!data) $this.data('dropdown', (data = new Dropdown(this)))
|
||||
if (typeof option == 'string') data[option].call($this)
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.dropdown.Constructor = Dropdown
|
||||
|
||||
|
||||
/* APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
* =================================== */
|
||||
|
||||
$(function () {
|
||||
$('html').on('click.dropdown.data-api', clearMenus)
|
||||
$('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
||||
})
|
||||
|
||||
}( window.jQuery );
|
|
@ -1,46 +1,25 @@
|
|||
openerp.project = function(openerp) {
|
||||
openerp.web_kanban.ProjectKanban = openerp.web_kanban.KanbanRecord.include({
|
||||
bind_events: function() {
|
||||
openerp.web_kanban.KanbanView.include({
|
||||
on_groups_started: function() {
|
||||
var self = this;
|
||||
self._super();
|
||||
if (this.view.dataset.model == 'project.project') {
|
||||
/*set avatar title for members.
|
||||
In many2many fields, returns only list of ids.
|
||||
we can implement return value of m2m fields like [(1,"Adminstration"),...].
|
||||
self._super.apply(this, arguments);
|
||||
if (this.dataset.model === 'project.project') {
|
||||
/* Set avatar title for members.
|
||||
In many2many fields, returns only list of ids.
|
||||
we can implement return value of m2m fields like [(1,"Adminstration"),...].
|
||||
*/
|
||||
_.each($(this.$element).find('.project_avatar'), function(avatar) {
|
||||
var dataset = new openerp.web.DataSetSearch(this, 'res.users', self.session.context, [['id','=',avatar.id]]);
|
||||
dataset.read_slice(['name']).then(function(result) {
|
||||
avatar.setAttribute("title",result[0].name);
|
||||
var members_ids = [];
|
||||
this.$element.find('.oe_kanban_project_avatars img').each(function() {
|
||||
members_ids.push($(this).data('member_id'));
|
||||
});
|
||||
var dataset = new openerp.web.DataSetSearch(this, 'res.users', self.session.context, [['id', 'in', _.uniq(members_ids)]]);
|
||||
dataset.read_slice(['id', 'name']).then(function(result) {
|
||||
_.each(result, function(v, k) {
|
||||
self.$element.find('.oe_kanban_project_avatars img[data-member_id=' + v.id + ']').attr('title', v.name).tipsy({
|
||||
offset: 10
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// set sequence like Tasks,Issues,Timesheets and Phases
|
||||
var my_list = $("#list a");
|
||||
my_list.sort(function (a, b) {
|
||||
var aValue = parseInt(a.id, 10);
|
||||
var bValue = parseInt(b.id, 10);
|
||||
return aValue == bValue ? 0 : aValue < bValue ? -1 : 1;
|
||||
});
|
||||
$('#list').replaceWith(my_list);
|
||||
|
||||
// when vignette is clicked, it opens the first action in sequence
|
||||
if (my_list.length !== 0) {
|
||||
var click_button = $(this.$element).find('.click_button');
|
||||
click_button.attr('data-name', my_list[0].getAttribute('data-name'));
|
||||
click_button.attr('data-type', "action");
|
||||
}
|
||||
|
||||
/* set background color.
|
||||
we can do other way to implement new widget.
|
||||
because we need to rpc call for that.
|
||||
*/
|
||||
this.$element.find('.bgcolor').click(function() {
|
||||
var color = parseInt($(this).find('span').attr('class').split(' ')[0].substring(16), 10);
|
||||
var color_class = $(this).find('span').attr('class').split(' ')[0];
|
||||
$(this).closest('#oe_project_kanban_vignette').removeClass().addClass(color_class + ' oe_project_kanban_vignette');
|
||||
self.view.dataset.write(parseInt(this.id, 10), {color:color});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -372,9 +372,9 @@
|
|||
<field name="use_issues"/>
|
||||
<field name="issue_count" invisible="1"/>
|
||||
</field>
|
||||
<xpath expr="//div[@id='list']" position="inside">
|
||||
<a t-if="record.use_issues.raw_value" class="oe_project_buttons"
|
||||
id="2" name="%(act_project_project_2_project_issue_all)d" type="action">
|
||||
<xpath expr="//div[contains(@class, 'oe_kanban_project_list')]" position="inside">
|
||||
<a t-if="record.use_issues.raw_value"
|
||||
name="%(act_project_project_2_project_issue_all)d" type="action">
|
||||
Issues(<field name="issue_count"/>)</a>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
@ -129,9 +129,9 @@
|
|||
<field name="use_phases"/>
|
||||
<field name="phase_count"/>
|
||||
</field>
|
||||
<xpath expr="//div[@id='list']" position="inside">
|
||||
<a t-if="record.use_phases.raw_value" class="oe_project_buttons"
|
||||
id="4" name="%(act_project_phases)d" type="action">Phases(<field name="phase_count"/>)</a>
|
||||
<xpath expr="//div[contains(@class, 'oe_kanban_project_list')]" position="inside">
|
||||
<a t-if="record.use_phases.raw_value"
|
||||
name="%(act_project_phases)d" type="action">Phases(<field name="phase_count"/>)</a>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -36,25 +36,17 @@
|
|||
<field name="currency_id"/>
|
||||
<field name="partner_id"/>
|
||||
</field>
|
||||
<xpath expr="//div[@id='list']" position="inside">
|
||||
<a t-if="record.use_timesheets.raw_value" class="oe_project_buttons"
|
||||
id="3" name="open_timesheets" type="object">Timesheets(<field name="timesheet_count"/>)</a>
|
||||
<xpath expr="//div[contains(@class, 'oe_kanban_project_list')]" position="inside">
|
||||
<a t-if="record.use_timesheets.raw_value"
|
||||
name="open_timesheets" type="object">Timesheets(<field name="timesheet_count"/>)</a>
|
||||
</xpath>
|
||||
<xpath expr="//tr[@id='deadline']" position="before">
|
||||
<t t-if="record.partner_id.raw_value">
|
||||
<tr>
|
||||
<th align="left">Amount to invoice</th>
|
||||
<td align="left">
|
||||
<field name="amount_to_invoice"/> <t t-esc="record.currency_id.raw_value[1].split(' ')[1][1]"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">Time to Invoice</th>
|
||||
<td align="left">
|
||||
<field name="time_to_invoice"/> <field name="company_uom_id"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
<xpath expr="//div[contains(@class, 'oe_kanban_project_deadline')]" position="before">
|
||||
<div class="oe_kanban_project_fields oe_kanban_project_invoice" t-if="record.partner_id.raw_value">
|
||||
<div>Amount to invoice</div>
|
||||
<div><field name="amount_to_invoice"/> <t t-esc="record.currency_id.raw_value[1].split(' ')[1][1]"/></div>
|
||||
<div>Time to Invoice</div>
|
||||
<div><field name="time_to_invoice"/> <field name="company_uom_id"/></div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -1015,7 +1015,7 @@ class sale_order_line(osv.osv):
|
|||
help="If 'on order', it triggers a procurement when the sale order is confirmed to create a task, purchase order or manufacturing order linked to this sale order line."),
|
||||
'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'address_allotment_id': fields.many2one('res.partner', 'Allotment Partner'),
|
||||
'product_uom_qty': fields.float('Quantity (Unit of Measure)', digits_compute= dp.get_precision('Product UoS'), required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uom_qty': fields.float('Quantity', digits_compute= dp.get_precision('Product UoS'), required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uos_qty': fields.float('Quantity (UoS)' ,digits_compute= dp.get_precision('Product UoS'), readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uos': fields.many2one('product.uom', 'Product UoS'),
|
||||
|
|
|
@ -268,6 +268,7 @@
|
|||
<field name="supply_method">produce</field>
|
||||
<field name="uom_id" ref="product.uom_day"/>
|
||||
<field name="uom_po_id" ref="product.uom_day"/>
|
||||
<field name="company_id" eval="[]"/>
|
||||
</record>
|
||||
|
||||
<record id="base.user_demo" model="res.users">
|
||||
|
|
|
@ -199,9 +199,9 @@
|
|||
</form>
|
||||
<tree string="Sales Order Lines">
|
||||
<field colspan="4" name="name"/>
|
||||
<field name="product_uom_qty" string="Qty(Unit of Measure)"/>
|
||||
<field name="product_uom_qty" string="Quantity"/>
|
||||
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field groups="product.group_uos" name="product_uos_qty" string="Qty(UoS)"/>
|
||||
<field groups="product.group_uos" name="product_uos_qty" string="Quantity (UoS)"/>
|
||||
<field groups="product.group_uos" name="product_uos" string="UoS"/>
|
||||
<field name="discount" groups="sale.group_discount_per_so_line"/>
|
||||
<field name="price_unit"/>
|
||||
|
|
|
@ -20,20 +20,42 @@
|
|||
|
||||
from osv import fields, osv
|
||||
from tools.translate import _
|
||||
import decimal_precision as dp
|
||||
|
||||
class sale_advance_payment_inv(osv.osv_memory):
|
||||
_name = "sale.advance.payment.inv"
|
||||
_description = "Sales Advance Payment Invoice"
|
||||
|
||||
def _default_product_id(self, cr, uid, context=None):
|
||||
try:
|
||||
product_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'sale', 'advance_product_0')
|
||||
except ValueError:
|
||||
#a ValueError is returned if the xml id given is not found in the table ir_model_data
|
||||
return False
|
||||
return product_id[1]
|
||||
|
||||
_columns = {
|
||||
'product_id': fields.many2one('product.product', 'Advance Product', required=True,
|
||||
help="Select a product of type service which is called 'Advance Product'. You may have to create it and set it as a default value on this field."),
|
||||
'amount': fields.float('Advance Amount', digits=(16, 2), required=True, help="The amount to be invoiced in advance."),
|
||||
'product_id': fields.many2one('product.product', 'Advance Product', help="Select a product of type service which is called 'Advance Product'. You may have to create it and set it as a default value on this field."),
|
||||
'amount': fields.float('Advance Amount', digits_compute= dp.get_precision('Sale Price'), required=True, help="The amount to be invoiced in advance."),
|
||||
'qtty': fields.float('Quantity', digits=(16, 2), required=True),
|
||||
'advance_payment_method':fields.selection([('percentage','Percentage'), ('fixed','Fixed Price')], 'Type', required=True, help="Use Fixed Price if you want to give specific amound in Advance, Use Percentage if you want to give percentage of Total Invoice Amount."),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'qtty': 1.0
|
||||
'qtty': 1.0,
|
||||
'advance_payment_method': 'fixed',
|
||||
'product_id': _default_product_id,
|
||||
}
|
||||
|
||||
def onchange_advance_payment_method(self, cr, uid, ids, advance_payment_method, product_id, context=None):
|
||||
if advance_payment_method == 'percentage':
|
||||
return {'value': {'amount':0, 'product_id':False }}
|
||||
if not product_id:
|
||||
return {'value': {'amount': 0}}
|
||||
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
|
||||
return {'value': {'amount': product.list_price}}
|
||||
|
||||
|
||||
def create_invoices(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
To create invoices.
|
||||
|
@ -66,21 +88,55 @@ class sale_advance_payment_inv(osv.osv_memory):
|
|||
val = obj_lines.product_id_change(cr, uid, [], sale_adv_obj.product_id.id,
|
||||
uom = False, partner_id = sale.partner_id.id, fposition_id = sale.fiscal_position.id)
|
||||
res = val['value']
|
||||
|
||||
if not sale_adv_obj.product_id.id :
|
||||
prop = self.pool.get('ir.property').get(cr, uid,
|
||||
'property_account_income_categ', 'product.category',
|
||||
context=context)
|
||||
account_id = prop and prop.id or False
|
||||
account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, sale.fiscal_position.id or False, account_id)
|
||||
if not account_id:
|
||||
raise osv.except_osv(_('Configuration Error !'),
|
||||
_('There is no income account defined as global property.'))
|
||||
res['account_id'] = account_id
|
||||
|
||||
if not res.get('account_id'):
|
||||
raise osv.except_osv(_('Configuration Error !'),
|
||||
_('There is no income account defined ' \
|
||||
'for this product: "%s" (id:%d)') % \
|
||||
(sale_adv_obj.product_id.name, sale_adv_obj.product_id.id,))
|
||||
|
||||
|
||||
final_amount = 0
|
||||
if sale_adv_obj.amount <= 0.00:
|
||||
raise osv.except_osv(_('Data Insufficient !'),
|
||||
_('Please check the Advance Amount, it should not be 0 or less!'))
|
||||
if sale_adv_obj.advance_payment_method == 'percentage':
|
||||
final_amount = sale.amount_total * sale_adv_obj.amount / 100
|
||||
if not res.get('name'):
|
||||
res['name'] = _("Advance of %s %%") % (sale_adv_obj.amount)
|
||||
else:
|
||||
final_amount = sale_adv_obj.amount
|
||||
if not res.get('name'):
|
||||
#TODO: should find a way to call formatLang() from rml_parse
|
||||
if sale.pricelist_id.currency_id.position == 'after':
|
||||
res['name'] = _("Advance of %s %s") % (final_amount, sale.pricelist_id.currency_id.symbol)
|
||||
else:
|
||||
res['name'] = _("Advance of %s %s") % (sale.pricelist_id.currency_id.symbol, final_amount)
|
||||
|
||||
if res.get('invoice_line_tax_id'):
|
||||
res['invoice_line_tax_id'] = [(6, 0, res.get('invoice_line_tax_id'))]
|
||||
else:
|
||||
res['invoice_line_tax_id'] = False
|
||||
|
||||
line_id = obj_lines.create(cr, uid, {
|
||||
'name': res.get('name'),
|
||||
'account_id': res['account_id'],
|
||||
'price_unit': sale_adv_obj.amount,
|
||||
'quantity': sale_adv_obj.qtty,
|
||||
'price_unit': final_amount,
|
||||
'quantity': sale_adv_obj.qtty or 1.0,
|
||||
'discount': False,
|
||||
'uos_id': res.get('uos_id'),
|
||||
'uos_id': res.get('uos_id', False),
|
||||
'product_id': sale_adv_obj.product_id.id,
|
||||
'invoice_line_tax_id': [(6, 0, res.get('invoice_line_tax_id'))],
|
||||
'invoice_line_tax_id': res.get('invoice_line_tax_id'),
|
||||
'account_analytic_id': sale.project_id.id or False,
|
||||
#'note':'',
|
||||
})
|
||||
|
@ -112,49 +168,27 @@ class sale_advance_payment_inv(osv.osv_memory):
|
|||
# If not, the advance will be deduced when generating the final invoice
|
||||
#
|
||||
if sale.order_policy == 'picking':
|
||||
self.pool.get('sale.order.line').create(cr, uid, {
|
||||
vals = {
|
||||
'order_id': sale.id,
|
||||
'name': res.get('name'),
|
||||
'price_unit': -sale_adv_obj.amount,
|
||||
'product_uom_qty': sale_adv_obj.qtty,
|
||||
'product_uos_qty': sale_adv_obj.qtty,
|
||||
'product_uos': res.get('uos_id'),
|
||||
'product_uom': res.get('uos_id'),
|
||||
'product_id': sale_adv_obj.product_id.id,
|
||||
'price_unit': -final_amount,
|
||||
'product_uom_qty': sale_adv_obj.qtty or 1.0,
|
||||
'product_uos_qty': sale_adv_obj.qtty or 1.0,
|
||||
'product_uos': res.get('uos_id', False),
|
||||
'product_uom': res.get('uom_id', False),
|
||||
'product_id': sale_adv_obj.product_id.id or False,
|
||||
'discount': False,
|
||||
'tax_id': [(6, 0, res.get('invoice_line_tax_id'))],
|
||||
}, context)
|
||||
'tax_id': res.get('invoice_line_tax_id'),
|
||||
}
|
||||
self.pool.get('sale.order.line').create(cr, uid, vals, context=context)
|
||||
|
||||
context.update({'invoice_id':list_inv})
|
||||
|
||||
return {
|
||||
'name': 'Open Invoice',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sale.open.invoice',
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
'context': context
|
||||
}
|
||||
if context.get('open_invoices'):
|
||||
return self.open_invoices( cr, uid, ids, context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
sale_advance_payment_inv()
|
||||
|
||||
class sale_open_invoice(osv.osv_memory):
|
||||
_name = "sale.open.invoice"
|
||||
_description = "Sales Open Invoice"
|
||||
|
||||
def open_invoice(self, cr, uid, ids, context=None):
|
||||
|
||||
"""
|
||||
To open invoice.
|
||||
@param self: The object pointer.
|
||||
@param cr: A database cursor
|
||||
@param uid: ID of the user currently logged in
|
||||
@param ids: the ID or list of IDs if we want more than one
|
||||
@param context: A standard dictionary
|
||||
@return:
|
||||
|
||||
"""
|
||||
def open_invoices(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
|
@ -176,6 +210,6 @@ class sale_open_invoice(osv.osv_memory):
|
|||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
sale_open_invoice()
|
||||
sale_advance_payment_inv()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -7,15 +7,19 @@
|
|||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Advance Invoice">
|
||||
<field name="product_id"/>
|
||||
<field name="advance_payment_method" on_change="onchange_advance_payment_method(advance_payment_method,product_id)"/>
|
||||
<newline />
|
||||
<field name="qtty" invisible="1"/>
|
||||
<field name="amount"/>
|
||||
<field name="product_id" on_change="onchange_advance_payment_method(advance_payment_method,product_id)" context="{'search_default_services':1}" attrs="{'invisible': [('advance_payment_method','=','percentage')]}" colspan="2"/>
|
||||
<field name="amount" colspan="2"/>
|
||||
<newline />
|
||||
<separator string="" colspan="4"/>
|
||||
<label string="" colspan="2" />
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="create_invoices" string="Create Invoice" type="object" icon="gtk-go-forward"/>
|
||||
<label string="" colspan="6" />
|
||||
<group colspan="4">
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="create_invoices" string="Create Invoice" type="object" icon="gtk-go-forward" context="{'open_invoices': False}"/>
|
||||
<button name="create_invoices" string="Create and view Invoice" type="object" icon="terp-camera_test" context="{'open_invoices': True}" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -28,31 +32,5 @@
|
|||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record id="view_sale_open_invoice" model="ir.ui.view">
|
||||
<field name="name">Open Invoice</field>
|
||||
<field name="model">sale.open.invoice</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Invoices">
|
||||
<label string="You invoice has been successfully created!" />
|
||||
<newline />
|
||||
<separator string="" colspan="4"/>
|
||||
<group colspan="4">
|
||||
<button special="cancel" string="Close" icon="gtk-cancel"/>
|
||||
<button name="open_invoice" string="Open Invoice" type="object" icon="gtk-go-forward"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_sale_open_invoice" model="ir.actions.act_window">
|
||||
<field name="name">Open Invoice</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sale.open.invoice</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
Loading…
Reference in New Issue