[Merge]with trunk

bzr revid: mba@tinyerp.com-20140228131855-obhatiecnzv1bgqu
This commit is contained in:
Mahendra Barad (OpenERP) 2014-02-28 18:48:55 +05:30
commit da5f8b26df
247 changed files with 15874 additions and 5135 deletions

View File

@ -123,7 +123,9 @@ for a particular financial year and for preparation of vouchers there is a modul
'edi/invoice_action_data.xml',
'account_bank_view.xml',
'res_config_view.xml',
'account_pre_install.yml'
'account_pre_install.yml',
'views/report_vat.xml',
],
'js': [
'static/src/js/account_move_reconciliation.js',

View File

@ -114,7 +114,7 @@ class account_invoice(osv.osv):
#we check if the invoice is partially reconciled and if there are other invoices
#involved in this partial reconciliation (and we sum these invoices)
for line in aml.reconcile_partial_id.line_partial_ids:
if line.invoice:
if line.invoice and invoice.type == line.invoice.type:
nb_inv_in_partial_rec += 1
#store the max invoice id as for this invoice we will make a balance instead of a simple division
max_invoice_id = max(max_invoice_id, line.invoice.id)

View File

@ -27,13 +27,13 @@
<report auto="False" id="account_intracom" menu="False" model="account.move.line" name="account.intracom" string="IntraCom"/>
<report
auto="False"
id="account_vat_declaration"
menu="False"
model="account.tax.code"
name="account.vat.declaration"
rml="account/report/account_tax_report.rml"
string="Taxes Report"/>
id="action_report_vat"
model="account.tax.code"
report_type="qweb-pdf"
string="Account tax"
name="account.report_vat"
file="account.report_vat"
/>
<menuitem
id="menu_tax_report"

View File

@ -1451,7 +1451,7 @@
<act_window
id="act_account_move_to_account_move_line_open"
name="Journal Items"
context="{'search_default_journal_id': active_id, 'default_journal_id': active_id}"
context="{'search_default_move_id': active_id, 'default_move_id': active_id}"
res_model="account.move.line"
src_model="account.move"/>

10849
addons/account/i18n/fr_CA.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@ import account_print_invoice
import account_print_overdue
import account_aged_partner_balance
#import tax_report
import account_tax_report
import report_vat
import account_invoice_report
import account_report
import account_entries_report

View File

@ -1,160 +0,0 @@
<?xml version="1.0"?>
<document filename="Taxes.pdf">
<template title="Taxes" author="OpenERP S.A.(sales@openerp.com)" allowSplitting="20">
<pageTemplate id="first">
<frame id="first" x1="42.0" y1="62.0" width="511" height="728"/>
</pageTemplate>
</template>
<stylesheet>
<blockTableStyle id="Standard_Outline">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
</blockTableStyle>
<blockTableStyle id="Table1">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
<blockBackground colorName="#e6e6e6" start="0,0" stop="0,0"/>
<blockBackground colorName="#e6e6e6" start="1,0" stop="1,0"/>
<blockBackground colorName="#e6e6e6" start="2,0" stop="2,0"/>
<blockBackground colorName="#e6e6e6" start="0,1" stop="0,1"/>
<blockBackground colorName="#e6e6e6" start="1,1" stop="1,1"/>
<blockBackground colorName="#e6e6e6" start="2,1" stop="2,1"/>
</blockTableStyle>
<blockTableStyle id="Table2">
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,0" stop="0,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,0" stop="1,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,0" stop="2,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,0" stop="3,0"/>
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,1" stop="0,-1"/>
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,1" stop="1,-1"/>
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,1" stop="2,-1"/>
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,1" stop="3,-1"/>
<blockValign value="TOP"/>
</blockTableStyle>
<blockTableStyle id="Table4">
<blockAlignment value="LEFT"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
</blockTableStyle>
<blockTableStyle id="Table3">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,0" stop="-1,0"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="2,0" stop="2,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="4,0" stop="4,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="4,0" stop="4,0"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="4,-1" stop="4,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="5,0" stop="5,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="5,0" stop="5,0"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="5,-1" stop="5,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="6,0" stop="6,-1"/>
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="6,0" stop="6,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="6,0" stop="6,0"/>
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="6,-1" stop="6,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="7,0" stop="7,-1"/>
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="7,0" stop="7,-1"/>
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="7,0" stop="7,0"/>
</blockTableStyle>
<blockTableStyle id="Tableau1">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
</blockTableStyle>
<blockTableStyle id="Tableau2">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
</blockTableStyle>
<initialize>
<paraStyle name="all" alignment="justify"/>
</initialize>
<paraStyle name="P1" fontName="Helvetica-Bold" alignment="CENTER" fontSize="14.5" leftIndent="-5.0"/>
<paraStyle name="P2" fontName="Helvetica-bold" fontSize="15.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P3" fontName="Helvetica" fontSize="8.0" leading="10" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P4" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P5" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P6" fontName="Helvetica" fontSize="8.0" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P7" fontName="Helvetica" fontSize="8.0" leading="14" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P8" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P9" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P9a" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P9c" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P9b" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P10" fontName="Helvetica" alignment="CENTER"/>
<paraStyle name="P11" fontName="Helvetica" fontSize="8.0" leading="14"/>
<paraStyle name="P12" fontName="Helvetica-bold" fontSize="9.0" leading="10" alignment="LEFT" spaceBefore="0.6" spaceAfter="6.0"/>
<paraStyle name="P12a" fontName="Helvetica-bold" fontSize="9.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
<paraStyle name="P13" fontName="Helvetica" fontSize="8.0" leading="10" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P14" fontName="Helvetica-Bold" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P15" rightIndent="17.0" leftIndent="-0.0" fontName="Helvetica" fontSize="8.0" leading="10" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P16" fontName="Helvetica-Bold" fontSize="8.0" leading="14" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="Standard" fontName="Helvetica"/>
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="Table Contents" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="Caption" fontName="Helvetica" fontSize="1.0" leading="1" spaceBefore="0" spaceAfter="0"/>
<paraStyle name="Index" fontName="Helvetica"/>
<paraStyle name="terp_tblheader_General_Centre" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
<paraStyle name="terp_default_Centre_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
</stylesheet>
<story>
<para style="P2">Tax Statement</para>
<para style="P2"><font color="white"> </font></para>
<blockTable colWidths="140.0,120.0,160.0,120.0" style="Table3">
<tr>
<td><para style="terp_tblheader_General_Centre">Chart of Tax</para></td>
<td><para style="terp_tblheader_General_Centre">Fiscal Year</para></td>
<td><para style="terp_tblheader_General_Centre">Periods</para></td>
<td><para style="terp_tblheader_General_Centre">Based On</para></td>
</tr>
<tr>
<td><para style="terp_default_Centre_8">[[ get_account(data) or removeParentNode('para') ]]</para></td>
<td><para style="terp_default_Centre_8">[[ get_fiscalyear(data) or '' ]]</para></td>
<td>
<blockTable colWidths="80.0,80.0" style="Table4">
<tr>
<td><para style="terp_tblheader_General_Centre">Start Period</para></td>
<td><para style="terp_tblheader_General_Centre">End Period</para></td>
</tr>
<tr>
<td><para style="terp_default_Centre_8">[[ get_start_period(data) or '' ]]</para></td>
<td><para style="terp_default_Centre_8">[[ get_end_period(data) or '' ]]</para></td>
</tr>
</blockTable>
</td>
<td><para style="terp_default_Centre_8">[[ get_basedon(data) or '' ]]</para></td>
</tr>
</blockTable>
<para style="P2"><font color="white"> </font></para>
<blockTable colWidths="340.0,55.0,55.0,90.0" style="Table2" repeatRows="1">
<tr>
<td><para style="P12">Tax Name</para></td>
<td><para style="P12a">Debit</para></td>
<td><para style="P12a">Credit</para></td>
<td><para style="P12a">Tax Amount</para></td>
</tr>
<tr>
<td><para style="P5"><font>[[ repeatIn(get_lines(data['form']['based_on'], data['form']['company_id']), 'o') ]]</font><font color="white">[[ (o['level']) ]]</font> <font>[[ (len(o['level'])&lt;5 and setTag('para','para',{'fontName':'Helvetica-Bold'})) or removeParentNode('font') ]]</font><font>[[ o['code'] ]] [[ o['name'] ]] </font></para></td>
<td><para style="P6"><font>[[ len(o['level'])&lt;5 and setTag('para','para',{'fontName':"Helvetica-Bold"}) or removeParentNode('font')]]</font><font>[[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['debit']) ]]</font><font>[[ o['type']&lt;&gt;'view' and removeParentNode('font') ]][[ formatLang(o['debit']) ]]</font></para></td>
<td><para style="P6"><font>[[ len(o['level'])&lt;5 and setTag('para','para',{'fontName':"Helvetica-Bold"}) or removeParentNode('font')]]</font><font>[[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['credit']) ]]</font><font>[[ o['type']&lt;&gt;'view' and removeParentNode('font') ]][[ formatLang(o['credit'])]]</font></para></td>
<td><para style="P6"><font>[[ len(o['level'])&lt;5 and setTag('para','para',{'fontName':"Helvetica-Bold"}) or removeParentNode('font')]]</font><font>[[ o['type']=='view' and removeParentNode('font') ]][[ formatLang(o['tax_amount'], currency_obj=company.currency_id) ]]</font><font>[[ o['type']&lt;&gt;'view' and removeParentNode('font') ]][[ formatLang(o['tax_amount'], currency_obj=company.currency_id) ]]</font> </para></td>
</tr>
</blockTable>
</story>
</document>

View File

@ -19,16 +19,25 @@
#
##############################################################################
import time
from openerp.addons.web import http
from openerp.addons.web.http import request
from common_report_header import common_report_header
from openerp.report import report_sxw
try:
import cStringIO as StringIO
except ImportError:
import StringIO
import xlwt
class tax_report(report_sxw.rml_parse, common_report_header):
_name = 'report.account.vat.declaration'
def set_context(self, objects, data, ids, report_type=None):
new_ids = ids
class tax_report(http.Controller, common_report_header):
@http.route(['/report/account.report_vat'], type='http', auth='user', website=True, multilang=True)
def report_account_tax(self, **data):
report_obj = request.registry['report']
self.cr, self.uid, self.pool = request.cr, request.uid, request.registry
data = report_obj.eval_params(data)
res = {}
self.period_ids = []
period_obj = self.pool.get('account.period')
@ -38,28 +47,16 @@ class tax_report(report_sxw.rml_parse, common_report_header):
if data['form'].get('period_from', False) and data['form'].get('period_to', False):
self.period_ids = period_obj.build_ctx_periods(self.cr, self.uid, data['form']['period_from'], data['form']['period_to'])
periods_l = period_obj.read(self.cr, self.uid, self.period_ids, ['name'])
for period in periods_l:
if res['periods'] == '':
res['periods'] = period['name']
else:
res['periods'] += ", "+ period['name']
return super(tax_report, self).set_context(objects, data, new_ids, report_type=report_type)
def __init__(self, cr, uid, name, context=None):
super(tax_report, self).__init__(cr, uid, name, context=context)
self.localcontext.update({
'time': time,
'get_codes': self._get_codes,
'get_general': self._get_general,
'get_currency': self._get_currency,
'get_lines': self._get_lines,
'get_fiscalyear': self._get_fiscalyear,
'get_account': self._get_account,
'get_start_period': self.get_start_period,
'get_end_period': self.get_end_period,
'get_basedon': self._get_basedon,
})
docargs = {
'fiscalyear': self._get_fiscalyear(data),
'account': self._get_account(data),
'based_on': self._get_basedon(data),
'period_from': self.get_start_period(data),
'period_to': self.get_end_period(data),
'taxlines': self._get_lines(self._get_basedon(data), company_id=data['form']['company_id']),
}
return request.registry['report'].render(self.cr, self.uid, [], 'account.report_vat', docargs)
def _get_basedon(self, form):
return form['form']['based_on']
@ -194,7 +191,6 @@ class tax_report(report_sxw.rml_parse, common_report_header):
return self.pool.get('res.company').browse(self.cr, self.uid, form['company_id'], context=context).currency_id.name
def sort_result(self, accounts, context=None):
# On boucle sur notre rapport
result_accounts = []
ind=0
old_level=0
@ -233,7 +229,43 @@ class tax_report(report_sxw.rml_parse, common_report_header):
return result_accounts
report_sxw.report_sxw('report.account.vat.declaration', 'account.tax.code',
'addons/account/report/account_tax_report.rml', parser=tax_report, header="internal")
@http.route(['/report/account.report_vat_xls'], type='http', auth='user', website=True, multilang=True)
def report_account_tax_xls(self, **data):
report_obj = request.registry['report']
self.cr, self.uid, self.pool = request.cr, request.uid, request.registry
data = report_obj.eval_params(data)
res = {}
self.period_ids = []
period_obj = self.pool.get('account.period')
self.display_detail = data['form']['display_detail']
res['periods'] = ''
res['fiscalyear'] = data['form'].get('fiscalyear_id', False)
if data['form'].get('period_from', False) and data['form'].get('period_to', False):
self.period_ids = period_obj.build_ctx_periods(self.cr, self.uid, data['form']['period_from'], data['form']['period_to'])
content = ''
lines = self._get_lines(self._get_basedon(data), company_id=data['form']['company_id'])
if lines:
xls = StringIO.StringIO()
xls_workbook = xlwt.Workbook()
vat_sheet = xls_workbook.add_sheet('report_vat')
for x in range(0, len(lines)):
for y in range(0, len(lines[0])):
vat_sheet.write(x, y, lines[x].values()[y])
xls_workbook.save(xls)
xls.seek(0)
content = xls.read()
response = request.make_response(content, headers=[
('Content-Type', 'application/vnd.ms-excel'),
('Content-Disposition', 'attachment; filename=report_vat.xls;')
])
return response
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -22,6 +22,7 @@ openerp.account.quickadd = function (instance) {
start:function(){
var tmp = this._super.apply(this, arguments);
var self = this;
var defs = [];
this.$el.parent().prepend(QWeb.render("AccountMoveLineQuickAdd", {widget: this}));
this.$el.parent().find('.oe_account_select_journal').change(function() {
@ -41,11 +42,17 @@ openerp.account.quickadd = function (instance) {
self.$el.parent().find('.oe_account_select_period').removeAttr('disabled');
});
var mod = new instance.web.Model("account.move.line", self.dataset.context, self.dataset.domain);
mod.call("default_get", [['journal_id','period_id'],self.dataset.context]).then(function(result) {
defs.push(mod.call("default_get", [['journal_id','period_id'],self.dataset.context]).then(function(result) {
self.current_period = result['period_id'];
self.current_journal = result['journal_id'];
});
return tmp;
}));
defs.push(mod.call("list_journals", []).then(function(result) {
self.journals = result;
}));
defs.push(mod.call("list_periods", []).then(function(result) {
self.periods = result;
}));
return $.when(tmp, defs);
},
do_search: function(domain, context, group_by) {
var self = this;
@ -53,34 +60,27 @@ openerp.account.quickadd = function (instance) {
this.last_context = context;
this.last_group_by = group_by;
this.old_search = _.bind(this._super, this);
var mod = new instance.web.Model("account.move.line", context, domain);
return $.when(mod.call("list_journals", []).then(function(result) {
self.journals = result;
}),mod.call("list_periods", []).then(function(result) {
self.periods = result;
})).then(function () {
var o;
self.$el.parent().find('.oe_account_select_journal').children().remove().end();
self.$el.parent().find('.oe_account_select_journal').append(new Option('', ''));
for (var i = 0;i < self.journals.length;i++){
o = new Option(self.journals[i][1], self.journals[i][0]);
if (self.journals[i][0] === self.current_journal){
self.current_journal_type = self.journals[i][2];
self.current_journal_currency = self.journals[i][3];
self.current_journal_analytic = self.journals[i][4];
$(o).attr('selected',true);
}
self.$el.parent().find('.oe_account_select_journal').append(o);
var o;
self.$el.parent().find('.oe_account_select_journal').children().remove().end();
self.$el.parent().find('.oe_account_select_journal').append(new Option('', ''));
for (var i = 0;i < self.journals.length;i++){
o = new Option(self.journals[i][1], self.journals[i][0]);
if (self.journals[i][0] === self.current_journal){
self.current_journal_type = self.journals[i][2];
self.current_journal_currency = self.journals[i][3];
self.current_journal_analytic = self.journals[i][4];
$(o).attr('selected',true);
}
self.$el.parent().find('.oe_account_select_period').children().remove().end();
self.$el.parent().find('.oe_account_select_period').append(new Option('', ''));
for (var i = 0;i < self.periods.length;i++){
o = new Option(self.periods[i][1], self.periods[i][0]);
self.$el.parent().find('.oe_account_select_period').append(o);
}
self.$el.parent().find('.oe_account_select_period').val(self.current_period).attr('selected',true);
return self.search_by_journal_period();
});
self.$el.parent().find('.oe_account_select_journal').append(o);
}
self.$el.parent().find('.oe_account_select_period').children().remove().end();
self.$el.parent().find('.oe_account_select_period').append(new Option('', ''));
for (var i = 0;i < self.periods.length;i++){
o = new Option(self.periods[i][1], self.periods[i][0]);
self.$el.parent().find('.oe_account_select_period').append(o);
}
self.$el.parent().find('.oe_account_select_period').val(self.current_period).attr('selected',true);
return self.search_by_journal_period();
},
search_by_journal_period: function() {
var self = this;
@ -93,7 +93,9 @@ openerp.account.quickadd = function (instance) {
self.last_context["journal_type"] = self.current_journal_type;
self.last_context["currency"] = self.current_journal_currency;
self.last_context["analytic_journal_id"] = self.current_journal_analytic;
return self.old_search(new instance.web.CompoundDomain(self.last_domain, domain), self.last_context, self.last_group_by);
var compound_domain = new instance.web.CompoundDomain(self.last_domain, domain);
self.dataset.domain = compound_domain.eval();
return self.old_search(compound_domain, self.last_context, self.last_group_by);
},
});
};

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="report_vat">
<t t-call="report.html_container">
<t t-call="report.internal_layout">
<div class="page">
<h2>Tax Statement</h2>
<div class="row mt32 mb32">
<div class="col-xs-3">
<strong>Chart of Tax:</strong>
<p t-esc="account"/>
</div>
<div class="col-xs-3" t-if="fiscalyear">
<strong>Fiscal Year:</strong>
<p t-esc="fiscalyear"/>
</div>
<div class="col-xs-3" t-if="period_from and period_to">
<strong>Periods:</strong>
<p>
Start Period: <span t-esc="period_from"/><br/>
End Period: <span t-esc="period_to"/>
</p>
</div>
<div class="col-xs-3">
<strong>Based On:</strong>
<p t-esc="based_on"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Tax Name</th>
<th class="text-right">Debit</th>
<th class="text-right">Credit</th>
<th class="text-right">Tax Amount</th>
</tr>
</thead>
<tbody>
<tr t-foreach="taxlines" t-as="taxline">
<t t-if="len(taxline['level']) > 5">
<t t-set="style" t-value="'font-weight:normal;'"/>
</t>
<t t-if="not len(taxline['level']) > 5">
<t t-set="style" t-value="'font-weight:bold;'"/>
</t>
<td>
<span t-att-style="style" t-esc="taxline['level']" style="color:white;"/>
<span t-att-style="style" t-if="taxline['code']" t-esc="taxline['code']"/>
<span t-att-style="style" t-esc="taxline['name']"/>
</td>
<td class="text-right"><span t-att-style="style" t-esc="taxline['debit']"/></td>
<td class="text-right"><span t-att-style="style" t-esc="taxline['credit']"/></td>
<td class="text-right">
<span t-att-style="style" t-esc="formatLang(taxline['tax_amount'], currency_obj=res_company.currency_id)"/>
</td>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</template>
</data>
</openerp>

View File

@ -46,18 +46,20 @@ class account_vat_declaration(osv.osv_memory):
def create_vat(self, cr, uid, ids, context=None):
if context is None:
context = {}
datas = {'ids': context.get('active_ids', [])}
datas['model'] = 'account.tax.code'
datas['form'] = self.read(cr, uid, ids, context=context)[0]
for field in datas['form'].keys():
if isinstance(datas['form'][field], tuple):
datas['form'][field] = datas['form'][field][0]
datas['form']['company_id'] = self.pool.get('account.tax.code').browse(cr, uid, [datas['form']['chart_tax_id']], context=context)[0].company_id.id
return {
'type': 'ir.actions.report.xml',
'report_name': 'account.vat.declaration',
'datas': datas,
}
taxcode_obj = self.pool.get('account.tax.code')
taxcode_id = datas['form']['chart_tax_id']
taxcode = taxcode_obj.browse(cr, uid, [taxcode_id], context=context)[0]
datas['form']['company_id'] = taxcode.company_id.id
return self.pool['report'].get_action(cr, uid, ids, 'account.report_vat', datas=datas, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -664,13 +664,20 @@ class account_analytic_account(osv.osv):
partner_payment_term = contract.partner_id.property_payment_term and contract.partner_id.property_payment_term.id or False
currency_id = False
if contract.pricelist_id:
currency_id = contract.pricelist_id.currency_id.id
elif contract.partner_id.property_product_pricelist:
currency_id = contract.partner_id.property_product_pricelist.currency_id.id
elif contract.company_id:
currency_id = contract.company_id.currency_id.id
inv_data = {
'reference': contract.code or False,
'account_id': contract.partner_id.property_account_receivable.id,
'type': 'out_invoice',
'partner_id': contract.partner_id.id,
'currency_id': contract.partner_id.property_product_pricelist.id or False,
'currency_id': currency_id,
'journal_id': len(journal_ids) and journal_ids[0] or False,
'date_invoice': contract.recurring_next_date,
'origin': contract.name,

View File

@ -911,9 +911,10 @@ class account_voucher(osv.osv):
if context.get('payment_expected_currency') and currency_id != context.get('payment_expected_currency'):
vals['value']['amount'] = 0
amount = 0
res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
for key in res.keys():
vals[key].update(res[key])
if partner_id:
res = self.onchange_partner_id(cr, uid, ids, partner_id, journal_id, amount, currency_id, ttype, date, context)
for key in res.keys():
vals[key].update(res[key])
return vals
def button_proforma_voucher(self, cr, uid, ids, context=None):
@ -965,7 +966,7 @@ class account_voucher(osv.osv):
res = {}
if not partner_id:
return res
res = {'account_id':False}
res = {}
partner_pool = self.pool.get('res.partner')
journal_pool = self.pool.get('account.journal')
if pay_now == 'pay_later':
@ -977,7 +978,8 @@ class account_voucher(osv.osv):
account_id = partner.property_account_payable.id
else:
account_id = journal.default_credit_account_id.id or journal.default_debit_account_id.id
res['account_id'] = account_id
if account_id:
res['account_id'] = account_id
return {'value':res}
def _sel_context(self, cr, uid, voucher_id, context=None):
@ -1366,6 +1368,7 @@ class account_voucher(osv.osv):
move_pool = self.pool.get('account.move')
move_line_pool = self.pool.get('account.move.line')
for voucher in self.browse(cr, uid, ids, context=context):
local_context = dict(context, force_company=voucher.journal_id.company_id.id)
if voucher.move_id:
continue
company_currency = self._get_company_currency(cr, uid, voucher.id, context)
@ -1380,7 +1383,7 @@ class account_voucher(osv.osv):
# Get the name of the account_move just created
name = move_pool.browse(cr, uid, move_id, context=context).name
# Create the first line of the voucher
move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, context), context)
move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, local_context), local_context)
move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context)
line_total = move_line_brw.debit - move_line_brw.credit
rec_list_ids = []
@ -1392,9 +1395,9 @@ class account_voucher(osv.osv):
line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context)
# Create the writeoff line if needed
ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context)
ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, local_context)
if ml_writeoff:
move_line_pool.create(cr, uid, ml_writeoff, context)
move_line_pool.create(cr, uid, ml_writeoff, local_context)
# We post the voucher.
self.write(cr, uid, [voucher.id], {
'move_id': move_id,
@ -1605,7 +1608,11 @@ class account_bank_statement(osv.osv):
bank_st_line_obj = self.pool.get('account.bank.statement.line')
st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
if st_line.voucher_id:
voucher_obj.write(cr, uid, [st_line.voucher_id.id], {'number': next_number}, context=context)
voucher_obj.write(cr, uid, [st_line.voucher_id.id],
{'number': next_number,
'date': st_line.date,
'period_id': st_line.statement_id.period_id.id},
context=context)
if st_line.voucher_id.state == 'cancel':
voucher_obj.action_cancel_draft(cr, uid, [st_line.voucher_id.id], context=context)
voucher_obj.signal_proforma_voucher(cr, uid, [st_line.voucher_id.id])

View File

@ -73,8 +73,14 @@
<group>
<group>
<field name="type" invisible="True"/>
<field name="partner_id" domain="[('customer','=',True)]" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" string="Customer" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": True}'/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
<field name="partner_id" domain="[('customer','=',True)]" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" string="Customer" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": True}'/>
<field name="account_id"
domain="[('type','in', ['liquidity', 'receivable'])]"/>
<field name="pay_now" on_change="onchange_payment(pay_now, journal_id, partner_id)" required="1"/>
<field name="date_due" attrs="{'invisible':[('pay_now','=','pay_now')]}"/>
<field name="reference"
attrs="{'invisible':[('pay_now','!=','pay_now')]}"/>
</group>
<group>
<field name="journal_id" domain="[('type','in',['sale','sale_refund'])]" widget="selection" on_change="onchange_journal(journal_id, line_cr_ids, tax_id, partner_id, date, amount, type, company_id, context)" groups="account.group_account_user"/>
@ -112,15 +118,6 @@
</div>
<field name="amount" class="oe_subtotal_footer_separator" nolabel="1"/>
</group>
<group>
<field name="pay_now" on_change="onchange_payment(pay_now, journal_id, partner_id)" required="1"/>
<field name="date_due" attrs="{'invisible':[('pay_now','=','pay_now')]}"/>
<field name="account_id"
attrs="{'invisible':[('pay_now','!=','pay_now')]}"
domain="[('type','=','liquidity')]"/>
<field name="reference"
attrs="{'invisible':[('pay_now','!=','pay_now')]}"/>
</group>
</group>
</page>
<page string="Journal Items" attrs="{'invisible': [('state','!=','posted')]}">
@ -224,11 +221,11 @@
<h1><label for="number" string="Purchase Receipt"/> <field name="number" class="oe_inline" readonly="1"/></h1>
<field name="pay_now" invisible="1"/>
<field name="account_id" domain="[('type','=','other')]" invisible="True"/>
<field name="type" invisible="True"/>
<group>
<group>
<field name="partner_id" domain="[('supplier','=',True)]" string="Supplier" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}" />
<field name="account_id" domain="[('type', 'in', ['liquidity', 'payable'])]"/>
<field name="name" colspan="2"/>
<field name="reference"/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/>

View File

@ -94,7 +94,7 @@ class account_statement_from_invoice_lines(osv.osv_memory):
'account_id': result['value'].get('account_id', statement.journal_id.default_credit_account_id.id),
'company_id': statement.company_id.id,
'currency_id': statement.currency.id,
'date': line.date,
'date': statement.date,
'amount': sign*amount,
'payment_rate': result['value']['payment_rate'],
'payment_rate_currency_id': result['value']['payment_rate_currency_id'],
@ -119,7 +119,7 @@ class account_statement_from_invoice_lines(osv.osv_memory):
'statement_id': statement_id,
'ref': line.ref,
'voucher_id': voucher_id,
'date': time.strftime('%Y-%m-%d'),
'date': statement.date,
}, context=context)
return {'type': 'ir.actions.act_window_close'}

View File

@ -0,0 +1,72 @@
# Japanese translation for openobject-addons
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2014-02-21 02:29+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Japanese <ja@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-02-22 06:39+0000\n"
"X-Generator: Launchpad (build 16926)\n"
#. module: analytic_contract_hr_expense
#: view:account.analytic.account:0
msgid "or view"
msgstr "または参照"
#. module: analytic_contract_hr_expense
#: view:account.analytic.account:0
msgid "Nothing to invoice, create"
msgstr ""
#. module: analytic_contract_hr_expense
#: view:account.analytic.account:0
msgid "expenses"
msgstr "経費"
#. module: analytic_contract_hr_expense
#: model:ir.model,name:analytic_contract_hr_expense.model_account_analytic_account
msgid "Analytic Account"
msgstr "分析勘定"
#. module: analytic_contract_hr_expense
#: code:addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py:144
#, python-format
msgid "Expenses to Invoice of %s"
msgstr ""
#. module: analytic_contract_hr_expense
#: code:addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py:136
#, python-format
msgid "Expenses of %s"
msgstr "%sの経費"
#. module: analytic_contract_hr_expense
#: field:account.analytic.account,expense_invoiced:0
#: field:account.analytic.account,expense_to_invoice:0
#: field:account.analytic.account,remaining_expense:0
msgid "unknown"
msgstr "不明"
#. module: analytic_contract_hr_expense
#: field:account.analytic.account,est_expenses:0
msgid "Estimation of Expenses to Invoice"
msgstr "請求対象経費見込"
#. module: analytic_contract_hr_expense
#: field:account.analytic.account,charge_expenses:0
msgid "Charge Expenses"
msgstr "経費請求"
#. module: analytic_contract_hr_expense
#: view:account.analytic.account:0
msgid "⇒ Invoice"
msgstr "⇒ 請求"

View File

@ -268,10 +268,14 @@ def log_fct(cr, uid_orig, model, method, fct_src, *args, **kw):
new_values = get_data(cr, uid_orig, pool, res_ids, model, method)
elif method == 'read':
res = fct_src(cr, uid_orig, model.model, method, *args, **kw)
if isinstance(res, dict):
records = [res]
else:
records = res
# build the res_ids and the old_values dict. Here we don't use get_data() to
# avoid performing an additional read()
res_ids = []
for record in res:
for record in records:
res_ids.append(record['id'])
old_values[(model.id, record['id'])] = {'value': record, 'text': record}
# log only the fields read
@ -279,7 +283,9 @@ def log_fct(cr, uid_orig, model, method, fct_src, *args, **kw):
elif method == 'unlink':
res_ids = args[0]
old_values = get_data(cr, uid_orig, pool, res_ids, model, method)
res = fct_src(cr, uid_orig, model.model, method, *args, **kw)
# process_data first as fct_src will unlink the record
self.process_data(cr, uid_orig, pool, res_ids, model, method, old_values, new_values, field_list)
return fct_src(cr, uid_orig, model.model, method, *args, **kw)
else: # method is write, action or workflow action
res_ids = []
if args:
@ -322,7 +328,7 @@ def get_data(cr, uid, pool, res_ids, model, method):
data = {}
resource_pool = pool[model.model]
# read all the fields of the given resources in super admin mode
for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids):
for resource in resource_pool.read(cr, SUPERUSER_ID, res_ids, resource_pool._all_columns):
values = {}
values_text = {}
resource_id = resource['id']
@ -456,7 +462,9 @@ def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_val
# if at least one modification has been found
for model_id, resource_id in lines:
name = pool[model.model].name_get(cr, uid, [resource_id])[0][1]
line_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, model_id).model
name = pool.get(line_model).name_get(cr, uid, [resource_id])[0][1]
vals = {
'method': method,
'object_id': model_id,

View File

@ -31,7 +31,7 @@ Allow users to login through OAuth2 Provider.
'author': 'OpenERP s.a.',
'maintainer': 'OpenERP s.a.',
'website': 'http://www.openerp.com',
'depends': ['base', 'web', 'base_setup'],
'depends': ['base', 'web', 'base_setup', 'auth_signup'],
'data': [
'res_users.xml',
'auth_oauth_data.xml',

View File

@ -8,8 +8,8 @@ from werkzeug.exceptions import BadRequest
import openerp
from openerp import SUPERUSER_ID
from openerp import http
from openerp.http import request, LazyResponse
from openerp.addons.web.controllers.main import db_monodb, set_cookie_and_redirect, login_and_redirect
from openerp.http import request
from openerp.addons.web.controllers.main import db_monodb, ensure_db, set_cookie_and_redirect, login_and_redirect
from openerp.modules.registry import RegistryManager
from openerp.tools.translate import _
@ -25,7 +25,7 @@ def fragment_to_query_string(func):
return """<html><head><script>
var l = window.location;
var q = l.hash.substring(1);
var r = '/' + l.search;
var r = l.pathname + l.search;
if(q.length !== 0) {
var s = l.search ? (l.search === '?' ? '' : '&') : '?';
r = l.pathname + l.search + s + q;
@ -61,17 +61,22 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
return providers
def get_state(self, provider):
return dict(
state = dict(
d=request.session.db,
p=provider['id']
)
token = request.params.get('token')
if token:
state['t'] = token
return state
@http.route()
def web_login(self, *args, **kw):
ensure_db()
providers = self.list_providers()
response = super(OAuthLogin, self).web_login(*args, **kw)
if isinstance(response, LazyResponse):
if response.is_qweb:
error = request.params.get('oauth_error')
if error == '1':
error = _("Sign up is not allowed on this database.")
@ -82,12 +87,30 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
else:
error = None
response.params['values']['providers'] = providers
response.qcontext['providers'] = providers
if error:
response.params['values']['error'] = error
response.qcontext['error'] = error
return response
@http.route()
def web_auth_signup(self, *args, **kw):
providers = self.list_providers()
if len(providers) == 1:
werkzeug.exceptions.abort(werkzeug.utils.redirect(providers[0]['auth_link'], 303))
response = super(OAuthLogin, self).web_auth_signup(*args, **kw)
response.qcontext.update(providers=providers)
return response
@http.route()
def web_auth_reset_password(self, *args, **kw):
providers = self.list_providers()
if len(providers) == 1:
werkzeug.exceptions.abort(werkzeug.utils.redirect(providers[0]['auth_link'], 303))
response = super(OAuthLogin, self).web_auth_reset_password(*args, **kw)
response.qcontext.update(providers=providers)
return response
class OAuthController(http.Controller):
@http.route('/auth_oauth/signin', type='http', auth='none')

View File

@ -3,15 +3,35 @@
-->
<openerp>
<data>
<template id="auth_oauth.providers" name="OAuth Providers">
<div t-foreach="providers" t-as="p">
<a t-att-href="p['auth_link']" class="btn btn-link">
<i t-att-class="p['css_class']"/>
<t t-esc="p['body']"/>
</a>
</div>
</template>
<template id="auth_oauth.login" inherit_id="web.login" name="OAuth Login buttons">
<xpath expr="//button[@type='submit']" position="before">
<div class="pull-right">
<div t-foreach="providers" t-as="p">
<a t-att-href="p['auth_link']" class="btn btn-link">
<i t-att-class="p['css_class']"/>
<t t-esc="p['body']"/>
</a>
</div>
<t t-call="auth_oauth.providers"/>
</div>
</xpath>
</template>
<template id="auth_oauth.signup" inherit_id="auth_signup.signup" name="OAuth Signup buttons">
<xpath expr="//button[@type='submit']" position="before">
<div class="pull-right">
<t t-call="auth_oauth.providers"/>
</div>
</xpath>
</template>
<template id="auth_oauth.reset_password" inherit_id="auth_signup.reset_password" name="OAuth Reset Password buttons">
<xpath expr="//button[@type='submit']" position="before">
<div class="pull-right">
<t t-call="auth_oauth.providers"/>
</div>
</xpath>
</template>

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
import res_users
import controllers

View File

@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-2014 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# 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': 'Signup with OAuth2 Authentication',
'version': '1.0',
'category': 'Hidden',
'description': """
Allow users to sign up through OAuth2 Provider.
===============================================
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['auth_oauth', 'auth_signup'],
'data': [
'views/auth_oauth_signup.xml',
],
'js': [],
'css': [],
'qweb': [],
'installable': True,
'auto_install': True,
}

View File

@ -1,3 +0,0 @@
import main
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,20 +0,0 @@
import openerp
import werkzeug
from openerp.http import request
class OAuthSignupLogin(openerp.addons.web.controllers.main.Home):
def list_providers(self):
providers = super(OAuthSignupLogin, self).list_providers()
if len(providers) == 1 and request.params.get('mode') == 'signup':
werkzeug.exceptions.abort(werkzeug.utils.redirect(providers[0]['auth_link'], 303))
return providers
def get_state(self, provider):
state = super(OAuthSignupLogin, self).get_state(provider)
token = request.params.get('token')
if token:
state['t'] = token
return state
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,23 +0,0 @@
# Arabic translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-11-26 18:16+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Arabic <ar@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "المستخدمين"

View File

@ -1,22 +0,0 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * auth_oauth_signup
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0alpha\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-21 17:05+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: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr ""

View File

@ -1,23 +0,0 @@
# Czech translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2014-02-03 16:54+0000\n"
"Last-Translator: Jakub Drozd <Unknown>\n"
"Language-Team: Czech <cs@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-02-04 05:51+0000\n"
"X-Generator: Launchpad (build 16916)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Uživatelé"

View File

@ -1,23 +0,0 @@
# Danish translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-09-15 20:08+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Danish <da@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Bruger"

View File

@ -1,23 +0,0 @@
# German translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-27 22:22+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: German <de@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Benutzer"

View File

@ -1,23 +0,0 @@
# English (United Kingdom) translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-02-06 14:33+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: English (United Kingdom) <en_GB@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Users"

View File

@ -1,23 +0,0 @@
# Spanish translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-27 11:38+0000\n"
"Last-Translator: Pedro Manuel Baeza <pedro.baeza@gmail.com>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Usuarios"

View File

@ -1,23 +0,0 @@
# Estonian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-09 14:34+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Estonian <et@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Kasutajad"

View File

@ -1,23 +0,0 @@
# French translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-29 16:08+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: French <fr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Utilisateurs"

View File

@ -1,23 +0,0 @@
# Galician translation for openobject-addons
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2014-02-05 16:37+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Galician <gl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-02-06 05:33+0000\n"
"X-Generator: Launchpad (build 16916)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Usuarios"

View File

@ -1,23 +0,0 @@
# Croatian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-01-24 12:30+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Croatian <hr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Korisnici"

View File

@ -1,23 +0,0 @@
# Hungarian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-03-19 18:13+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Hungarian <hu@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Felhasználók"

View File

@ -1,23 +0,0 @@
# Italian translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-27 09:12+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Italian <it@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Utenti"

View File

@ -1,23 +0,0 @@
# Lithuanian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-04-24 18:21+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Lithuanian <lt@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Naudotojai"

View File

@ -1,23 +0,0 @@
# Macedonian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-02-28 14:54+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Macedonian <mk@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Корисници"

View File

@ -1,23 +0,0 @@
# Mongolian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-02-06 07:44+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Mongolian <mn@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Хэрэглэгчид"

View File

@ -1,23 +0,0 @@
# Dutch translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-27 09:12+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Dutch <nl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Gebruikers"

View File

@ -1,23 +0,0 @@
# Dutch (Belgium) translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-04-15 16:01+0000\n"
"Last-Translator: Els Van Vossel (Foxy) <Unknown>\n"
"Language-Team: Dutch (Belgium) <nl_BE@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Gebruikers"

View File

@ -1,23 +0,0 @@
# Polish translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-11-14 12:00+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Polish <pl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Użytkownicy"

View File

@ -1,23 +0,0 @@
# Portuguese translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-01-08 17:56+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Portuguese <pt@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Utilizadores"

View File

@ -1,23 +0,0 @@
# Brazilian Portuguese translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-01-02 11:56+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Usuários"

View File

@ -1,23 +0,0 @@
# Romanian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-01-14 19:07+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Romanian <ro@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Utilizatori"

View File

@ -1,23 +0,0 @@
# Russian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-02-13 09:46+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Russian <ru@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Пользователи"

View File

@ -1,23 +0,0 @@
# Slovenian translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-30 09:36+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Slovenian <sl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Uporabniki"

View File

@ -1,23 +0,0 @@
# Swedish translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-01-17 23:47+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Swedish <sv@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Användare"

View File

@ -1,23 +0,0 @@
# Turkish translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-02-03 12:07+0000\n"
"Last-Translator: Ahmet Altınışık <Unknown>\n"
"Language-Team: Turkish <tr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Kullanıcılar"

View File

@ -1,23 +0,0 @@
# Vietnamese translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-06-27 06:49+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Vietnamese <vi@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Người dùng"

View File

@ -1,23 +0,0 @@
# Chinese (Simplified) translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-01-02 10:59+0000\n"
"Last-Translator: Oliver Yuan <Unknown>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "用户"

View File

@ -1,23 +0,0 @@
# Chinese (Traditional) translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-01-30 13:18+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (Traditional) <zh_TW@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:41+0000\n"
"X-Generator: Launchpad (build 16914)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "使用者"

View File

@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-2012 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
import simplejson
import openerp
from openerp.addons.auth_signup.res_users import SignupError
from openerp.osv import osv, fields
_logger = logging.getLogger(__name__)
class res_users(osv.Model):
_inherit = 'res.users'
def _auth_oauth_signin(self, cr, uid, provider, validation, params, context=None):
# overridden to use signup method if regular oauth signin fails
try:
login = super(res_users, self)._auth_oauth_signin(cr, uid, provider, validation, params, context=context)
except openerp.exceptions.AccessDenied, access_denied_exception:
if context and context.get('no_user_creation'):
return None
state = simplejson.loads(params['state'])
token = state.get('t')
oauth_uid = validation['user_id']
email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid))
name = validation.get('name', email)
values = {
'name': name,
'login': email,
'email': email,
'oauth_provider_id': provider,
'oauth_uid': oauth_uid,
'oauth_access_token': params['access_token'],
'active': True,
}
try:
_, login, _ = self.signup(cr, uid, values, token, context=context)
except SignupError:
raise access_denied_exception
return login

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:et:si:ts=2:sts=2:sw=2 -->
<openerp>
<data>
<template id="auth_oauth_signup.signup" inherit_id="auth_signup.signup" name="OAuth Signup buttons">
<xpath expr="//button[@type='submit']" position="before">
<div class="pull-right">
<div t-foreach="providers or []" t-as="p">
<a t-att-href="p['auth_link']" class="btn btn-link">
<i t-att-class="p['css_class']"/>
<t t-esc="p['body']"/>
</a>
</div>
</div>
</xpath>
</template>
</data>
</openerp>

View File

@ -42,6 +42,6 @@ Allow users to sign up and reset their password
'res_users_view.xml',
'views/auth_signup_login.xml',
],
'js': ['static/src/js/auth_signup.js'],
'js': [],
'bootstrap': True,
}

View File

@ -19,81 +19,106 @@
#
##############################################################################
import logging
import werkzeug
import openerp
import openerp.addons.web.controllers.main as webmain
from openerp.addons.auth_signup.res_users import SignupError
from openerp.addons.web.controllers.main import ensure_db
from openerp import http
from openerp.http import request, LazyResponse
from openerp.http import request
from openerp.tools.translate import _
from openerp.tools import exception_to_unicode
_logger = logging.getLogger(__name__)
class AuthSignup(openerp.addons.web.controllers.main.Home):
class AuthSignupHome(openerp.addons.web.controllers.main.Home):
@http.route()
def web_login(self, *args, **kw):
mode = request.params.get('mode')
ensure_db()
response = super(AuthSignupHome, self).web_login(*args, **kw)
response.qcontext.update(self.get_auth_signup_config())
return response
@http.route('/web/signup', type='http', auth='public', website=True, multilang=True)
def web_auth_signup(self, *args, **kw):
qcontext = self.get_auth_signup_qcontext()
if not qcontext.get('token') and not qcontext.get('signup_enabled'):
raise werkzeug.exceptions.NotFound()
if 'error' not in qcontext and request.httprequest.method == 'POST':
try:
self.do_signup(qcontext)
return super(AuthSignupHome, self).web_login(*args, **kw)
except (SignupError, AssertionError), e:
qcontext['error'] = _(e.message)
return request.render('auth_signup.signup', qcontext)
@http.route('/web/reset_password', type='http', auth='public', website=True, multilang=True)
def web_auth_reset_password(self, *args, **kw):
qcontext = self.get_auth_signup_qcontext()
if not qcontext.get('token') and not qcontext.get('reset_password_enabled'):
raise werkzeug.exceptions.NotFound()
if 'error' not in qcontext and request.httprequest.method == 'POST':
try:
if qcontext.get('token'):
self.do_signup(qcontext)
return super(AuthSignupHome, self).web_login(*args, **kw)
else:
login = qcontext.get('login')
assert login, "No login provided."
res_users = request.registry.get('res.users')
res_users.reset_password(request.cr, openerp.SUPERUSER_ID, login)
qcontext['message'] = _("An email has been sent with credentials to reset your password")
except SignupError:
qcontext['error'] = _("Could not reset your password")
_logger.exception('error when resetting password')
except Exception, e:
qcontext['error'] = _(e.message)
return request.render('auth_signup.reset_password', qcontext)
def get_auth_signup_config(self):
"""retrieve the module config (which features are enabled) for the login page"""
icp = request.registry.get('ir.config_parameter')
return {
'signup_enabled': icp.get_param(request.cr, openerp.SUPERUSER_ID, 'auth_signup.allow_uninvited') == 'True',
'reset_password_enabled': icp.get_param(request.cr, openerp.SUPERUSER_ID, 'auth_signup.reset_password') == 'True',
}
def get_auth_signup_qcontext(self):
""" Shared helper returning the rendering context for signup and reset password """
qcontext = request.params.copy()
super_response = super(AuthSignup, self).web_login(*args, **kw)
response = webmain.render_bootstrap_template(request.session.db, 'auth_signup.signup', qcontext, lazy=True)
if isinstance(super_response, LazyResponse):
response.params['values'].update(super_response.params['values'])
token = qcontext.get('token', None)
token_infos = None
if token:
qcontext.update(self.get_auth_signup_config())
if qcontext.get('token'):
try:
# retrieve the user info (name, login or email) corresponding to a signup token
res_partner = request.registry.get('res.partner')
token_infos = res_partner.signup_retrieve_info(request.cr, openerp.SUPERUSER_ID, token)
token_infos = res_partner.signup_retrieve_info(request.cr, openerp.SUPERUSER_ID, qcontext.get('token'))
for k, v in token_infos.items():
qcontext.setdefault(k, v)
except:
qcontext['error'] = _("Invalid signup token")
response.params['template'] = 'web.login'
return response
return qcontext
# retrieve the module config (which features are enabled) for the login page
icp = request.registry.get('ir.config_parameter')
config = {
'signup': icp.get_param(request.cr, openerp.SUPERUSER_ID, 'auth_signup.allow_uninvited') == 'True',
'reset': icp.get_param(request.cr, openerp.SUPERUSER_ID, 'auth_signup.reset_password') == 'True',
}
qcontext.update(config)
if 'error' in qcontext or mode not in ('reset', 'signup') or (not token and not config[mode]):
if isinstance(super_response, LazyResponse):
super_response.params['values'].update(config)
return super_response
if request.httprequest.method == 'GET':
if token_infos:
qcontext.update(token_infos)
else:
res_users = request.registry.get('res.users')
login = request.params.get('login')
if mode == 'reset' and not token:
try:
res_users.reset_password(request.cr, openerp.SUPERUSER_ID, login)
qcontext['message'] = _("An email has been sent with credentials to reset your password")
response.params['template'] = 'web.login'
except Exception:
qcontext['error'] = _("Could not reset your password")
_logger.exception('error when resetting password')
else:
values = dict((key, qcontext.get(key)) for key in ('login', 'name', 'password'))
try:
self._signup_with_values(token, values)
request.cr.commit()
except SignupError, e:
qcontext['error'] = exception_to_unicode(e)
return super(AuthSignup, self).web_login(*args, **kw)
return response
def do_signup(self, qcontext):
""" Shared helper that creates a res.partner out of a token """
values = dict((key, qcontext.get(key)) for key in ('login', 'name', 'password'))
assert any([k for k in values.values()]), "The form was not properly filled in."
assert values.get('password') == qcontext.get('confirm_password'), "Passwords do not match; please retype them."
self._signup_with_values(qcontext.get('token'), values)
request.cr.commit()
def _signup_with_values(self, token, values):
request.registry['res.users'].signup(request.cr, openerp.SUPERUSER_ID, values, token)
db, login, password = request.registry['res.users'].signup(request.cr, openerp.SUPERUSER_ID, values, token)
request.cr.commit() # as authenticate will use its own cursor we need to commit the current transaction
uid = request.session.authenticate(db, login, password)
if not uid:
raise SignupError(_('Authentification Failed.'))
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -25,7 +25,7 @@ from urlparse import urljoin
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.osv import osv, fields
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, ustr
from ast import literal_eval
from openerp.tools.translate import _
@ -66,11 +66,12 @@ class res_partner(osv.Model):
self.signup_prepare(cr, uid, [partner.id], context=context)
partner.refresh()
route = 'login'
# the parameters to encode for the query
query = dict(db=cr.dbname)
signup_type = context.get('signup_force_type_in_url', partner.signup_type or '')
if signup_type:
query['mode'] = signup_type
route = 'reset_password' if signup_type == 'reset' else signup_type
if partner.signup_token and signup_type:
query['token'] = partner.signup_token
@ -89,7 +90,7 @@ class res_partner(osv.Model):
if res_id:
fragment['id'] = res_id
res[partner.id] = urljoin(base_url, "/web/login?%s#%s" % (urlencode(query), urlencode(fragment)))
res[partner.id] = urljoin(base_url, "/web/%s?%s#%s" % (route, urlencode(query), urlencode(fragment)))
return res
@ -236,7 +237,12 @@ class res_users(osv.Model):
# create a copy of the template user (attached to a specific partner_id if given)
values['active'] = True
context = dict(context or {}, no_reset_password=True)
return self.copy(cr, uid, template_user_id, values, context=context)
try:
with cr.savepoint():
return self.copy(cr, uid, template_user_id, values, context=context)
except Exception, e:
# copy may failed if asked login is not available.
raise SignupError(ustr(e))
def reset_password(self, cr, uid, login, context=None):
""" retrieve the user corresponding to login (login or email),

View File

@ -1,19 +0,0 @@
openerp.auth_signup = function(instance) {
openerp.web.LoginForm.include({
start: function () {
var self = this;
this.$el.on('submit', function () {
var password = self.get_password_field('password');
var confirm_password = self.get_password_field('confirm_password');
if (password && confirm_password && (password.value != confirm_password.value)) {
alert("Passwords do not match; please retype them.");
return false;
}
});
},
get_password_field: function (field) {
var selector = 'input[name="' + field + '"][type="password"]:visible';
return this.$(selector)[0];
},
});
};

View File

@ -5,85 +5,95 @@
<data>
<template id="auth_signup.login" inherit_id="web.login" name="Sign up - Reset Password">
<xpath expr="//button[@type='submit']" position="before">
<a t-if="signup" t-attf-href="?{{ keep_query('*', mode='signup') }}" class="btn btn-link pull-right">Sign up</a>
<a t-if="reset" t-attf-href="?{{ keep_query('*', mode='reset') }}" class="btn btn-link pull-right">Reset Password</a>
<a t-if="signup_enabled" t-attf-href="/web/signup?redirect=/web%3f{{ quote_plus(keep_query()) }}" class="btn btn-link pull-right">Sign up</a>
<a t-if="reset_password_enabled" t-attf-href="/web/reset_password?redirect=/web/login%3f{{ quote_plus(keep_query()) }}" class="btn btn-link pull-right">Reset Password</a>
</xpath>
</template>
<template id="auth_signup.signup" name="Sign up">
<template id="auth_signup.fields" name="Auth Signup/ResetPassword form fields">
<t t-call="web.database_select"/>
<div class="form-group field-name">
<label for="name" class="control-label">Your Name</label>
<input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="e.g. John Doe"
required="required" t-att-autofocus="'autofocus' if not only_passwords else None" t-att-readonly="'readonly' if only_passwords else None"/>
</div>
<div class="form-group field-login">
<label for="login" class="control-label">Your Email</label>
<input type="text" name="login" t-att-value="login" id="login" class="form-control"
required="required" t-att-readonly="'readonly' if only_passwords else None"/>
</div>
<div class="form-group field-password">
<label for="password" class="control-label">Password</label>
<input type="password" name="password" autofocus="autofocus" id="password" class="form-control"
required="required" t-att-autofocus="'autofocus' if only_passwords else None"/>
</div>
<div class="form-group field-confirm_password">
<label for="confirm_password" class="control-label">Confirm Password</label>
<input type="password" name="confirm_password" id="confirm_password" class="form-control" required="required"/>
</div>
</template>
<template id="auth_signup.signup" name="Sign up login">
<t t-call="web.login_layout">
<t t-set="head">
<t t-foreach="css" t-as="css_file">
<link rel="stylesheet" t-att-href="css_file"/>
</t>
<t t-foreach="js" t-as="js_file">
<script type="text/javascript" t-att-src="js_file"></script>
</t>
</t>
<script type="text/javascript">
$(function() {
var s = new openerp.init(<t t-raw="modules"/>);
var login_form = new openerp.web.LoginForm($('.oe_signup_form'));
});
</script>
<form class="oe_signup_form" role="form" t-attf-action="/web/signup{{ '?debug' if debug else '' }}" method="post" t-if="not message">
<t t-set="reset_without_token" t-value="mode == 'reset' and not token"/>
<form class="oe_signup_form" role="form" method="post">
<t t-call="web.database_select"/>
<div class="form-group field-name" t-if="not reset_without_token">
<label for="name" class="control-label">Your Name</label>
<input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="e.g. John Doe"
required="required" autofocus="autofocus" t-att-disabled="'disabled' if mode == 'reset' and token else None"/>
</div>
<div class="form-group field-login">
<label for="login" class="control-label">Your Email</label>
<input type="text" name="login" t-att-value="login" id="login" class="form-control"
t-att-autofocus="'autofocus' if reset_without_token else None"
required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"/>
<input type="hidden" name="login" t-att-value="login" t-if="mode == 'reset' and token"/>
</div>
<div class="form-group field-password" t-if="not reset_without_token">
<label for="password" class="control-label">Password</label>
<input type="password" name="password" id="password" class="form-control"
required="required" t-att-autofocus="'autofocus' if mode == 'reset' and token else None"/>
</div>
<div class="form-group field-confirm_password" t-if="not reset_without_token">
<label for="confirm_password" class="control-label">Confirm Password</label>
<input type="password" name="confirm_password" id="confirm_password" class="form-control" required="required"/>
</div>
<t t-call="auth_signup.fields"/>
<p class="alert alert-danger" t-if="error">
<t t-esc="error"/>
</p>
<p class="alert alert-success" t-if="message">
<t t-esc="message"/>
</p>
<input type="hidden" name="redirect" t-att-value="redirect"/>
<input type="hidden" name="mode" t-att-value="mode"/>
<input type="hidden" name="token" t-att-value="token"/>
<div class="clearfix oe_login_buttons">
<a t-attf-href="?{{ keep_query('*', mode='login') }}" class="btn btn-link pull-right">Back to Login</a>
<button type="submit" class="btn btn-primary pull-left">
<t t-if="mode == 'signup'">Sign up</t>
<t t-if="mode == 'reset'">Reset password</t>
</button>
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
<button type="submit" class="btn btn-primary pull-left">Sign up</button>
</div>
</form>
<div class="oe_single_form_footer" t-if="not disable_footer">
<t t-if="not disable_database_manager">
<a class="oe_login_manage_db" t-attf-href="/web/database/manager{{ '?debug' if debug else '' }}">Manage Databases</a>
<span class="oe_footer_seperator"> | </span>
</t>
<a href="http://www.openerp.com" target="_blank">Powered by <span>OpenERP</span></a>
</t>
</template>
<template id="auth_signup.reset_password" name="Reset password">
<t t-call="web.login_layout">
<div t-if="message">
<p class="alert alert-success" t-if="message">
<t t-esc="message"/>
</p>
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
</div>
<form class="oe_reset_password_form" role="form" t-attf-action="/web/reset_password{{ '?debug' if debug else '' }}" method="post" t-if="not message">
<t t-if="token">
<t t-call="auth_signup.fields">
<t t-set="only_passwords" t-value="1"/>
</t>
</t>
<t t-if="not token">
<div class="form-group field-login">
<label for="login" class="control-label">Your Email</label>
<input type="text" name="login" t-att-value="login" id="login" class="form-control"
autofocus="autofocus" required="required"/>
</div>
</t>
<p class="alert alert-danger" t-if="error">
<t t-esc="error"/>
</p>
<input type="hidden" name="redirect" t-att-value="redirect"/>
<input type="hidden" name="token" t-att-value="token"/>
<div class="clearfix oe_login_buttons">
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
<button type="submit" class="btn btn-primary pull-left">Reset password</button>
</div>
</form>
</t>
</template>

View File

@ -275,6 +275,13 @@ class base_action_rule(osv.osv):
if action.filter_id:
domain = eval(action.filter_id.domain)
ctx.update(eval(action.filter_id.context))
if 'lang' not in ctx:
# Filters might be language-sensitive, attempt to reuse creator lang
# as we are usually running this as super-user in background
[filter_meta] = action.filter_id.perm_read()
user_id = filter_meta['write_uid'] and filter_meta['write_uid'][0] or \
filter_meta['create_uid'][0]
ctx['lang'] = self.pool['res.users'].browse(cr, uid, user_id).lang
record_ids = model.search(cr, uid, domain, context=ctx)
# determine when action should occur for the records

View File

@ -41,7 +41,9 @@ class base_config_settings(osv.osv_memory):
help="""This installs the module google_docs."""),
'module_google_calendar': fields.boolean('Allow the users to synchronize their calendar with Google Calendar',
help="""This installs the module google_calendar."""),
'font': fields.many2one('res.font', string="Report Font", help="Set the font into the report header, it will be used as default font in the RML reports of the user company"),
'font': fields.many2one('res.font', string="Report Font", domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))],
help="Set the font into the report header, it will be used as default font in the RML reports of the user company"),
}
_defaults= {

View File

@ -99,7 +99,7 @@
<label for="font" />
<div>
<div>
<field name="font" class="oe_inline" domain="[('mode', 'in', ('normal', 'regular', 'all', 'book'))]" />
<field name="font" class="oe_inline" />
<button string="(reload fonts)" name="act_discover_fonts" type="object" class="oe_link"/>
</div>
</div>

View File

@ -54,7 +54,7 @@ _ref_vat = {
'gr': 'GR12345670',
'hu': 'HU12345676',
'hr': 'HR01234567896', # Croatia, contributed by Milan Tribuson
'ie': 'IE1234567T',
'ie': 'IE1234567FA',
'it': 'IT12345670017',
'lt': 'LT123456715',
'lu': 'LU12345613',
@ -190,6 +190,34 @@ class res_partner(osv.osv):
return check == int(num[8])
return False
def _ie_check_char(self, vat):
vat = vat.zfill(8)
extra = 0
if vat[7] not in ' W':
if vat[7].isalpha():
extra = 9 * (ord(vat[7]) - 64)
else:
# invalid
return -1
checksum = extra + sum((8-i) * int(x) for i, x in enumerate(vat[:7]))
return 'WABCDEFGHIJKLMNOPQRSTUV'[checksum % 23]
def check_vat_ie(self, vat):
""" Temporary Ireland VAT validation to support the new format
introduced in January 2013 in Ireland, until upstream is fixed.
TODO: remove when fixed upstream"""
if len(vat) not in (8, 9) or not vat[2:7].isdigit():
return False
if len(vat) == 8:
# Normalize pre-2013 numbers: final space or 'W' not significant
vat += ' '
if vat[:7].isdigit():
return vat[7] == self._ie_check_char(vat[:7] + vat[8])
elif vat[1] in (string.ascii_uppercase + '+*'):
# Deprecated format
# See http://www.revenue.ie/en/online/third-party-reporting/reporting-payment-details/faqs.html#section3
return vat[7] == self._ie_check_char(vat[2:7] + vat[0] + vat[8])
return False
# Mexican VAT verification, contributed by <moylop260@hotmail.com>
# and Panos Christeas <p_christ@hol.gr>

View File

@ -19,13 +19,13 @@
#
##############################################################################
import hashlib
import pytz
import re
import time
import openerp
import openerp.service.report
import uuid
from werkzeug.exceptions import BadRequest
from datetime import datetime, timedelta
from dateutil import parser
from dateutil import rrule
@ -34,10 +34,11 @@ from openerp import tools, SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
from openerp import http
from openerp.http import request
from operator import itemgetter
from werkzeug.exceptions import BadRequest
import logging
_logger = logging.getLogger(__name__)
@ -776,7 +777,7 @@ class calendar_event(osv.Model):
else:
result[event] = ""
return result
def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None):
if not isinstance(ids, list):
ids = [ids]
@ -789,7 +790,7 @@ class calendar_event(osv.Model):
data.update(update_data)
self.write(cr, uid, ids, data, context=context)
return True
def _tz_get(self, cr, uid, context=None):
return [(x.lower(), x) for x in pytz.all_timezones]
@ -802,7 +803,7 @@ class calendar_event(osv.Model):
},
}
_columns = {
'id': fields.integer('ID', readonly=True),
'id': fields.integer('ID', readonly=True),
'state': fields.selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', readonly=True, track_visibility='onchange'),
'name': fields.char('Meeting Subject', required=True, states={'done': [('readonly', True)]}),
'is_attendee': fields.function(_compute, string='Attendee', type="boolean", multi='attendee'),
@ -844,6 +845,7 @@ class calendar_event(osv.Model):
'attendee_ids': fields.one2many('calendar.attendee', 'event_id', 'Attendees', ondelete='cascade'),
'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}),
'alarm_ids': fields.many2many('calendar.alarm', string='Reminders', ondelete="restrict"),
}
_defaults = {
'end_type': 'count',
@ -870,11 +872,9 @@ class calendar_event(osv.Model):
]
def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None):
"""Returns duration and/or end date based on values passed
@param ids: List of calendar event's IDs.
@param start_date: Starting date
@param duration: Duration between start date and end date
@param end_date: Ending Datee
"""
if context is None:
context = {}
@ -888,14 +888,14 @@ class calendar_event(osv.Model):
value['duration'] = duration
if allday: # For all day event
start = datetime.strptime(start_date.split(' ')[0].split('T')[0], "%Y-%m-%d")
duration = 24.0
value['duration'] = duration
start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
user = self.pool['res.users'].browse(cr, uid, uid)
tz = pytz.timezone(user.tz) if user.tz else pytz.utc
start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone
start = start.astimezone(pytz.utc) # convert start back to utc
value['date'] = start.strftime("%Y-%m-%d") + ' 00:00:00'
value['duration'] = 24.0
value['date'] = datetime.strftime(start, "%Y-%m-%d %H:%M:%S")
else:
start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
@ -1353,15 +1353,55 @@ class calendar_event(osv.Model):
res = super(calendar_event, self).copy(cr, uid, calendar_id2real_id(id), default, context)
return res
def _detach_one_event(self, cr, uid, id, values=dict(), context=None):
real_event_id = calendar_id2real_id(id)
data = self.read(cr, uid, id, ['date', 'date_deadline', 'rrule', 'duration'])
if data.get('rrule'):
data.update(
values,
recurrent_id=real_event_id,
recurrent_id_date=data.get('date'),
rrule_type=False,
rrule='',
recurrency=False,
end_date = datetime.strptime(values.get('date', False) or data.get('date'),"%Y-%m-%d %H:%M:%S")
+ timedelta(hours=values.get('duration', False) or data.get('duration'))
)
#do not copy the id
if data.get('id'):
del(data['id'])
new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
return new_id
def open_after_detach_event(self, cr, uid, ids, context=None):
if context is None:
context = {}
new_id = self._detach_one_event(cr, uid, ids[0], context=context)
return {
'type': 'ir.actions.act_window',
'res_model': 'calendar.event',
'view_mode': 'form',
'res_id': new_id,
'target': 'current',
'flags': {'form': {'action_buttons': True, 'options' : { 'mode' : 'edit' } } }
}
def write(self, cr, uid, ids, values, context=None):
def _only_changes_to_apply_on_real_ids(field_names):
''' return True if changes are only to be made on the real ids'''
''' return True if changes are only to be made on the real ids'''
for field in field_names:
if field not in ['name', 'message_follower_ids','oe_update_date']:
return False
return True
if field in ['date','active']:
return True
return False
context = context or {}
if isinstance(ids, (str,int, long)):
if len(str(ids).split('-')) == 1:
@ -1383,31 +1423,14 @@ class calendar_event(osv.Model):
# if we are setting the recurrency flag to False or if we are only changing fields that
# should be only updated on the real ID and not on the virtual (like message_follower_ids):
# then set real ids to be updated.
if not values.get('recurrency', True) or _only_changes_to_apply_on_real_ids(values.keys()):
if not values.get('recurrency', True) or not _only_changes_to_apply_on_real_ids(values.keys()):
ids.append(real_event_id)
continue
#if edit one instance of a reccurrent id
data = self.read(cr, uid, event_id, ['date', 'date_deadline', 'rrule', 'duration'])
if data.get('rrule'):
data.update(
values,
recurrent_id=real_event_id,
recurrent_id_date=data.get('date'),
rrule_type=False,
rrule='',
recurrency=False,
end_date = datetime.strptime(values.get('date', False) or data.get('date'),"%Y-%m-%d %H:%M:%S")
+ timedelta(hours=values.get('duration', False) or data.get('duration'))
)
#do not copy the id
if data.get('id'):
del(data['id'])
new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
context.update({'active_id': new_id, 'active_ids': [new_id]})
continue
else:
data = self.read(cr, uid, event_id, ['date', 'date_deadline', 'rrule', 'duration'])
if data.get('rrule'):
new_id = self._detach_one_event(cr, uid, event_id, values, context=None)
res = super(calendar_event, self).write(cr, uid, ids, values, context=context)
# set end_date for calendar searching
@ -1473,7 +1496,6 @@ class calendar_event(osv.Model):
if context is None:
context = {}
fields2 = fields and fields[:] or None
EXTRAFIELDS = ('class', 'user_id', 'duration', 'date', 'rrule', 'vtimezone')
for f in EXTRAFIELDS:
if fields and (f not in fields):
@ -1518,6 +1540,7 @@ class calendar_event(osv.Model):
for k in EXTRAFIELDS:
if (k in r) and (fields and (k not in fields)):
del r[k]
if isinstance(ids, (str, int, long)):
return result and result[0] or False
return result
@ -1531,7 +1554,7 @@ class calendar_event(osv.Model):
ids_to_unlink = []
# One time moved to google_Calendar, we can specify, if not in google, and not rec or get_inst = 0, we delete it
for event_id in ids:
for event_id in ids:
if unlink_level == 1 and len(str(event_id).split('-')) == 1: # if ID REAL
if self.browse(cr, uid, event_id).recurrent_id:
ids_to_exclure.append(event_id)

View File

@ -6,20 +6,7 @@
-->
<!--For Meetings -->
<record id="res_partner_another" model="res.partner">
<field name="name">Arshaw</field>
<field name="company_id" ref="base.main_company"/>
<field name="customer" eval="False"/>
<field name="email">fullcalendar@example.com</field>
</record>
<record id="res_user_another" model="res.users">
<field name="name" >Second Demo User</field>
<field name="login" >Second Demo User</field>
<field name="partner_id" ref="res_partner_another"/>
<field name="company_id" ref="base.main_company" />
</record>
<record id="cal_contact_1" model="calendar.contacts">
<record id="cal_contact_1" model="calendar.contacts">
<field eval="1" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_1"/>
@ -105,8 +92,8 @@
<record id="calendar_event_7" model="calendar.event">
<field eval="1" name="active"/>
<field name="user_id" ref="res_user_another"/>
<field name="partner_ids" eval="[(6,0,[ref('res_partner_another'),ref('base.res_partner_8')])]"/>
<field name="user_id" ref="base.user_demo"/>
<field name="partner_ids" eval="[(6,0,[ref('base.res_partner_7')])]"/>
<field name="name">Presentation of the new Calendar</field>
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
<field eval="time.strftime('%Y-%m-16 6:00:00')" name="date"/>
@ -114,18 +101,7 @@
<field eval="8.5" name="duration"/>
<field name="state">draft</field>
</record>
<record id="calendar_event_8" model="calendar.event">
<field eval="1" name="active"/>
<field name="user_id" ref="res_user_another"/>
<field name="partner_ids" eval="[(6,0,[ref('res_partner_another'),ref('base.partner_root')])]"/>
<field name="name">Discuss about the module : Calendar </field>
<field name="categ_ids" eval="[(6,0,[ref('categ_meet1'), ref('categ_meet2')])]"/>
<field eval="time.strftime('%Y-%m-16 6:00:00')" name="date"/>
<field eval="time.strftime('%Y-%m-16 18:30:00')" name="date_deadline"/>
<field eval="8.5" name="duration"/>
<field name="state">draft</field>
</record>
</data>
</openerp>

View File

@ -51,21 +51,26 @@
</div>
<notebook>
<page string="Meeting Details">
<group attrs="{'invisible': [('recurrency','==',False)]}" class="oe_edit_only ">
<p class='alert alert-warning'> This event is linked to a recurrence...<br/>
<button type="object" name="open_after_detach_event" string="Update only this instance" help="Click here to update only this instance and not all recurrences. " class="oe_link"/>
</p>
</group>
<group>
<group>
<field name="date" string="Starting at" on_change="onchange_dates(date, duration, False, allday)"/>
<label for="duration"/>
<div>
<field name="duration" widget="float_time"
on_change="onchange_dates(date,duration,False,allday)"
on_change="onchange_dates(date, duration, False, allday)"
class="oe_inline" attrs="{'invisible': [('allday','=',True)]}"/>
<label string="hours" attrs="{'invisible': [('allday','=',True)]}"/>
(<field name="allday" on_change="onchange_dates(date,False,False,allday)" class="oe_inline"/>
(<field name="allday" class="oe_inline"/>
<label for="allday" string="All Day?"/>)
</div>
<field name="date_deadline" groups="base.group_no_one"
attrs="{'invisible': ['|', ('allday','=',True), ('duration','&lt;', 24)]}"
on_change="onchange_dates(date,False,date_deadline)"/>
/>
</group>
<group>
<field name="categ_ids" widget="many2many_tags"/>
@ -121,10 +126,10 @@
</div>
</group>
</group>
<group>
<group>
<field name="class"/>
<field name="show_as"/>
<field name="rrule" invisible="1" readonly="0" />
<field name="rrule" invisible="1" readonly="0" />
<field name="recurrent_id" invisible="1" />
</group>
</group>
@ -136,7 +141,7 @@
<tree string="Invitation details" editable="top" create="false" delete="false">
<field name="partner_id" />
<field name="state" />
<field name="email" widget="email"/>
<field name="email" widget="email"/>
<button name="do_tentative" states="needsAction,declined,accepted" string="Uncertain" type="object" icon="terp-crm" />
<button name="do_accept" string="Accept" states="needsAction,tentative,declined" type="object" icon="gtk-apply"/>
@ -176,7 +181,7 @@
<field name="date" string="Start" />
<field name="duration" string="Duration" widget="float_time" attrs="{'invisible': [('allday','=',True)]}"/>
<field name="allday" class="oe_inline" attrs="{'invisible': [('allday','=',False)]}"/>
<field name="partner_ids" widget="many2manyattendee" string="Attendees"/>
<field name="partner_ids" widget="many2manyattendee" string="Attendees"/>
</group>
<group>
<field name="categ_ids" widget="many2many_tags"/>
@ -213,7 +218,7 @@
<field name="name">Meetings Calendar</field>
<field name="model">calendar.event</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<field name="arch" type="xml">
<calendar string="Meetings" date_start="date" date_stop="date_deadline" date_delay="duration" all_day="allday"
display="[name]" color="color_partner_id" attendee="partner_ids" avatar_model="res.partner"
use_contacts="True" event_open_popup="%(calendar.view_calendar_event_form_popup)s">
@ -248,14 +253,14 @@
<field name="user_id"/>
<field name="show_as"/>
<field name="class" string="Privacy"/>
<filter icon="terp-go-today" string="My Events" domain="[('user_id','=',uid)]" help="My Events"/>
<filter icon="terp-go-today" string="My Events" domain="[('user_id','=',uid)]" help="My Events"/>
<filter string="My Meetings" help="My Meetings" name="mymeetings" context='{"mymeetings": 1}'/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<separator/>
<group expand="0" string="Group By...">
<filter string="Responsible" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Availability" icon="terp-camera_test" domain="[]" context="{'group_by':'show_as'}"/>
<filter string="Privacy" icon="terp-locked" domain="[]" context="{'group_by':'class'}"/>
<filter string="Privacy" icon="terp-locked" domain="[]" context="{'group_by':'class'}"/>
<filter string="Event Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}" help="Start Date of Event by Month"/>
</group>
</search>
@ -267,8 +272,7 @@
<field name="res_model">calendar.event</field>
<field name="view_mode">calendar,tree,form,gantt</field>
<field name="view_id" ref="view_calendar_event_calendar"/>
<field name="search_view_id" ref="view_calendar_event_search"/>
<field name="context">{"search_default_mymeetings": 1}</field>
<field name="search_view_id" ref="view_calendar_event_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to schedule a new meeting.
@ -313,7 +317,7 @@
<field name="name">Meetings</field>
<field name="res_model">calendar.event</field>
<field name="view_mode">form,calendar,tree,gantt</field>
<field name="view_id" ref="view_calendar_event_form"/>
<field name="view_id" ref="view_calendar_event_form"/>
</record>

View File

@ -26,7 +26,7 @@ class calendar_contacts(osv.osv):
_columns = {
'user_id': fields.many2one('res.users','Me'),
'partner_id': fields.many2one('res.partner','Employee',required=True, domain=[('customer','=',True)]),
'partner_id': fields.many2one('res.partner','Employee',required=True, domain=[]),
'active':fields.boolean('active'),
}
_defaults = {

View File

@ -16,6 +16,7 @@
<field name="res_model">calendar.contacts</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="domain">[('user_id','=',uid)]</field>
<field name="view_id" ref="view_calendar_contacts" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">

View File

@ -1,7 +1,7 @@
openerp.calendar = function(instance) {
var _t = instance.web._t;
var QWeb = instance.web.qweb;
instance.calendar = {}
instance.calendar = {};
instance.web.WebClient = instance.web.WebClient.extend({
@ -11,8 +11,8 @@ openerp.calendar = function(instance) {
get_next_notif: function() {
var self= this;
this.rpc("/calendar/notify")
.then(
function(result) {
.then(
function(result) {
_.each(result, function(res) {
setTimeout(function() {
//If notification not already displayed, we add button and action on it
@ -21,25 +21,25 @@ openerp.calendar = function(instance) {
res.message += QWeb.render("notify_footer");
a = self.do_notify(res.title,res.message,true);
$(".link2event").on('click', function() {
$(".link2event").on('click', function() {
self.rpc("/web/action/load", {
action_id: "calendar.action_calendar_event_notify",
}).then( function(r) {
}).then( function(r) {
r.res_id = res.event_id;
return self.action_manager.do_action(r);
});
return self.action_manager.do_action(r);
});
});
a.element.find(".link2recall").on('click',function() {
a.element.find(".link2recall").on('click',function() {
self.get_notif_box(this).find('.ui-notify-close').trigger("click");
});
a.element.find(".link2showed").on('click',function() {
});
a.element.find(".link2showed").on('click',function() {
self.get_notif_box(this).find('.ui-notify-close').trigger("click");
self.rpc("/calendar/notify_ack");
});
}
//If notification already displayed in the past, we remove the css attribute which hide this notification
else if (self.get_notif_box($.find(".eid_"+res.event_id)).attr("style") !== ""){
self.get_notif_box($.find(".eid_"+res.event_id)).attr("style","");
self.get_notif_box($.find(".eid_"+res.event_id)).attr("style","");
}
},res.timer * 1000);
});
@ -48,7 +48,7 @@ openerp.calendar = function(instance) {
},
check_notifications: function() {
var self= this;
self.get_next_notif();
self.get_next_notif();
setInterval(function(){
self.get_next_notif();
}, 5 * 60 * 1000 );
@ -58,7 +58,7 @@ openerp.calendar = function(instance) {
show_application: function() {
this._super();
this.check_notifications();
}
},
});
@ -77,7 +77,6 @@ openerp.calendar = function(instance) {
if(instance.session.session_is_valid(self.db) && instance.session.username != "anonymous") {
self.redirect_meeting_view(self.db,self.action,self.id,self.view);
} else {
alert('in anonymous or null ');
self.open_invitation_form(self.attendee_data);
}
},
@ -92,9 +91,8 @@ openerp.calendar = function(instance) {
var reload_page = function(){
return location.replace(action_url);
}
};
reload_page();
},
});
@ -108,7 +106,7 @@ openerp.calendar = function(instance) {
});
},
map_tag: function(value){
return _.map(value, function(el) {return {name: el[1], id:el[0], state: el[2]};})
return _.map(value, function(el) {return {name: el[1], id:el[0], state: el[2]};});
},
get_render_data: function(ids){
var self = this;
@ -121,7 +119,7 @@ openerp.calendar = function(instance) {
if (! self.get("effective_readonly")) {
var tag_element = self.tags.tagElements();
_.each(data,function(value, key){
$(tag_element[key]).find(".custom-edit").addClass(data[key][2])
$(tag_element[key]).find(".custom-edit").addClass(data[key][2]);
});
}
}
@ -134,7 +132,7 @@ openerp.calendar = function(instance) {
instance.session.session_bind(instance.session.origin).done(function () {
new instance.calendar.invitation(null,db,action,id,view,attendee_data).appendTo($("body").addClass('openerp'));
});
}
};

View File

@ -33,12 +33,14 @@
!python {model: calendar.event}: |
ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': True} )
before = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False})
self.write(cr, uid,[ids[1]], {'name':'New Name','recurrency' : True}, context={'virtual_id': True})
# We start by detach the event
newid = self._detach_one_event(cr, uid,ids[1])
self.write(cr, uid,[newid], {'name':'New Name','recurrency' : True}, context={'virtual_id': True})
after = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False})
assert len(after) == len(before)+1, 'Wrong number of events found, after to have moved a virtual event'
new_id = list(set(after)-set(before))[0]
new_id = list(set(after)-set(before))[0]
new_event = self.browse(cr,uid,new_id,context=context)
assert new_event.recurrent_id == before[0], 'Recurrent_id not correctly passed to the new event'
assert new_event.recurrent_id == before[0], 'Recurrent_id not correctly passed to the new event'
-
Now I will make All day event and test it
-
@ -58,7 +60,6 @@
duration: 1
interval: days
type: notification
-
Now I will assign this reminder to all day event
-

View File

@ -84,9 +84,12 @@
idval = '%d-%s' % (ref('calendar_event_sprintreview0'), '20110425124700')
self.write(cr, uid, [idval], {'description': 'Review code of the module: sync_google_calendar.'})
-
I check whether the record is edited perfectly or not.
I check whether that all the records of this recurrence has been edited.
-
!python {model: calendar.event}: |
meeting_ids = self.search(cr, uid, [('recurrent_id', '=', ref('calendar_event_sprintreview0')), ('recurrent_id_date','=','2011-04-25 12:47:00')], context)
assert meeting_ids, 'Meeting is not edited !'
meeting_ids = self.search(cr, uid, [('recurrent_id', '=', ref('calendar_event_sprintreview0'))], context)
meetings = self.browse(cr, uid, meeting_ids, context)
for meeting in meetings:
assert meeting.description == 'Review code of the module: sync_google_calendar.', 'Description not changed for id: %s' %meeting.id

View File

@ -120,14 +120,14 @@ class crm_case_section(osv.osv):
month_begin = date.today().replace(day=1)
section_result = [{
'value': 0,
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B %Y'),
} for i in range(self._period_number - 1, -1, -1)]
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
for group in group_obj:
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern)
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field, 0)}
return section_result
def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None):
@ -140,13 +140,18 @@ class crm_case_section(osv.osv):
month_begin = date.today().replace(day=1)
date_begin = month_begin - relativedelta.relativedelta(months=self._period_number - 1)
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1])
date_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)), ('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
lead_pre_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('type', '=', 'lead')]
opp_pre_domain = [('date_deadline', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('date_deadline', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('type', '=', 'opportunity')]
for id in ids:
res[id] = dict()
lead_domain = date_domain + [('type', '=', 'lead'), ('section_id', '=', id)]
lead_domain = lead_pre_domain + [('section_id', '=', id)]
opp_domain = opp_pre_domain + [('section_id', '=', id)]
res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context)
opp_domain = date_domain + [('type', '=', 'opportunity'), ('section_id', '=', id)]
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date'], 'planned_revenue', 'create_date', context=context)
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context)
return res
_columns = {

View File

@ -63,9 +63,9 @@
<record id="action_report_crm_lead_salesteam" model="ir.actions.act_window">
<field name="name">Leads Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="context">{"search_default_month":1}</field>
<field name="view_mode">tree,graph</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm.view_report_crm_lead_graph_two"/>
<field name="domain">[('type','=', 'lead'),('section_id', '=', active_id)]</field>
<field name="help">Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.</field>
</record>
@ -73,9 +73,8 @@
<record id="action_report_crm_opportunity_salesteam" model="ir.actions.act_window">
<field name="name">Opportunities Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="context">{"search_default_month":1}</field>
<field name="view_mode">tree,graph</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm.view_report_crm_opportunity_graph"/>
<field name="domain">[('type','=', 'opportunity'), ('section_id', '=', active_id)]</field>
<field name="help">Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline.</field>
</record>
@ -117,14 +116,14 @@
<a name="%(crm_case_form_view_salesteams_lead)d" type="action">Leads</a>
<a name="%(action_report_crm_lead_salesteam)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_open_leads" widget="sparkline_bar"
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': 'Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': ' Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
</a>
</div>
<div class="oe_salesteams_opportunities">
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
<a name="%(action_report_crm_opportunity_salesteam)d" type="action">
<field name="monthly_planned_revenue" widget="sparkline_bar"
options="{'height': '20px', 'barWidth': '4', 'barSpacing': '1', 'delayIn': '3000', 'tooltip_suffix': 'Opportunities'}">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field>
options="{'height': '20px', 'barWidth': '4', 'barSpacing': '1', 'delayIn': '3000', 'tooltip_suffix': ' (Planned Revenue)'}">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field>
</a>
</div>
</div>

View File

@ -330,6 +330,7 @@ class crm_lead(format_address, osv.osv):
'phone': partner.phone,
'mobile': partner.mobile,
'fax': partner.fax,
'zip': partner.zip,
}
return {'value': values}

View File

@ -146,6 +146,7 @@
<!-- CRM-related subtypes for messaging / Chatter -->
<record id="mt_lead_create" model="mail.message.subtype">
<field name="name">Lead Created</field>
<field name="hidden" eval="True"/>
<field name="res_model">crm.lead</field>
<field name="default" eval="False"/>
<field name="description">Lead created</field>
@ -171,6 +172,7 @@
<!-- Salesteam-related subtypes for messaging / Chatter -->
<record id="mt_salesteam_lead" model="mail.message.subtype">
<field name="name">Lead Created</field>
<field name="sequence">10</field>
<field name="res_model">crm.case.section</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_lead_create')"/>
@ -178,18 +180,21 @@
</record>
<record id="mt_salesteam_lead_stage" model="mail.message.subtype">
<field name="name">Opportunity Stage Changed</field>
<field name="sequence">11</field>
<field name="res_model">crm.case.section</field>
<field name="parent_id" eval="ref('mt_lead_stage')"/>
<field name="relation_field">section_id</field>
</record>
<record id="mt_salesteam_lead_won" model="mail.message.subtype">
<field name="name">Opportunity Won</field>
<field name="sequence">12</field>
<field name="res_model">crm.case.section</field>
<field name="parent_id" eval="ref('mt_lead_won')"/>
<field name="relation_field">section_id</field>
</record>
<record id="mt_salesteam_lead_lost" model="mail.message.subtype">
<field name="name">Opportunity Lost</field>
<field name="sequence">13</field>
<field name="res_model">crm.case.section</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_lead_lost')"/>

View File

@ -350,6 +350,7 @@
help="Leads that are assigned to any sales teams I am member of"/>
<filter string="Dead" name="dead"
domain="[('probability', '=', '0'), ('stage_id.fold', '=', True)]"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator />
<filter string="Available for mass mailing"
name='not_opt_out' domain="[('opt_out', '=', False)]"
@ -569,6 +570,7 @@
<filter string="My Team(s)"
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Opportunities that are assigned to any sales teams I am member of"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<group expand="0" string="Group By..." colspan="16">
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>

View File

@ -171,6 +171,7 @@
<filter icon="terp-gtk-go-back-rtl" string="To Do" name="current" domain="[('state','=','open')]"/>
<separator/>
<filter string="Unassigned Phonecalls" icon="terp-personal-" domain="[('user_id','=',False)]" help="Unassigned Phonecalls"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="Phone Calls Assigned to Me or My Team(s)" icon="terp-personal+" domain="['|', ('section_id.user_id','=',uid), ('user_id', '=', uid)]"
help="Phone Calls Assigned to the current user or with a team having the current user as team leader"/>

View File

@ -89,10 +89,6 @@ class crm_lead_report(osv.osv):
id,
c.date_deadline,
to_char(c.create_date, 'YYYY') as creation_year,
to_char(c.create_date, 'MM') as creation_month,
to_char(c.create_date, 'YYYY-MM-DD') as creation_day,
to_char(c.date_open, 'YYYY-MM-DD') as opening_date,
to_char(c.date_closed, 'YYYY-mm-dd') as date_closed,

View File

@ -15,6 +15,17 @@
</field>
</record>
<record id="view_report_crm_lead_graph_two" model="ir.ui.view">
<field name="name">crm.lead.report.graph.two</field>
<field name="model">crm.lead.report</field>
<field name="arch" type="xml">
<graph string="Leads Analysis" type="pivot" stacked="True">
<field name="create_date" type="row"/>
<field name="user_id" type="col"/>
</graph>
</field>
</record>
<record id="view_report_crm_opportunity_graph" model="ir.ui.view">
<field name="name">crm.opportunity.report.graph</field>
<field name="model">crm.lead.report</field>
@ -23,7 +34,7 @@
<field name="date_deadline" type="row"/>
<field name="user_id" type="col"/>
<field name="stage_id" type="col"/>
<field name="probable_revenue" type="measure"/>
<field name="planned_revenue" type="measure"/>
</graph>
</field>
</record>
@ -84,7 +95,7 @@
<filter string="Creation date (week)" icon="terp-go-year"
domain="[]" context="{'group_by':'create_date:week'}"/>
<filter string="Creation date (month)" icon="terp-go-year"
domain="[]" context="{'group_by':'create_date:month'}"/>
domain="[]" context="{'group_by':'create_date:month'}" name="month"/>
<filter string="Creation date (year)" icon="terp-go-year"
domain="[]" context="{'group_by':'create_date:year'}"/>
<separator orientation="vertical" />

View File

@ -140,8 +140,8 @@
</group>
<group col="2">
<separator string="Categorization" colspan="2"/>
<field name="type_id" widget="selection" readonly="1"/>
<field name="channel_id" widget="selection" readonly="1"/>
<field name="type_id" readonly="1"/>
<field name="channel_id" readonly="1"/>
</group>
</group>
<separator string="Details" />

View File

@ -381,12 +381,18 @@ class email_template(osv.osv):
for res_id in template_res_ids:
attachments = []
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
report = report_xml_pool.browse(cr, uid, template.report_template.id, context)
report_service = report.report_name
# Ensure report is rendered using template's language
ctx = context.copy()
if template.lang:
ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, [res_id], context)[res_id] # take 0 ?
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
if report.report_type in ['qweb-html', 'qweb-pdf']:
result, format = self.pool['report'].get_pdf(report, res_id, context=ctx), 'pdf'
else:
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
result = base64.b64encode(result)
if not report_name:
report_name = 'report.' + report_service

View File

@ -60,7 +60,7 @@
<h3>Body</h3>
<field name="body_html" width="250" height="450"
placeholder="Rich-text/HTML content of the message (placeholders may be used here)"/>
<field name="attachment_ids" nolabel="1" widget="many2many_binary"/>
<field name="attachment_ids" widget="many2many_binary"/>
</page>
<page string="Advanced Settings">
<group>

View File

@ -219,6 +219,7 @@ class event_event(osv.osv):
]
def onchange_event_type(self, cr, uid, ids, type_event, context=None):
values = {}
if type_event:
type_info = self.pool.get('event.type').browse(cr,uid,type_event,context)
dic ={
@ -228,7 +229,8 @@ class event_event(osv.osv):
'seats_min': type_info.default_registration_min,
'seats_max': type_info.default_registration_max,
}
return {'value': dic}
values.update(dic)
return values
def onchange_start_date(self, cr, uid, ids, date_begin=False, date_end=False, context=None):
res = {'value':{}}

View File

@ -187,7 +187,7 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
for server in self.browse(cr, uid, ids, context=context):
_logger.info('start checking for new emails on %s server %s', server.type, server.name)
context.update({'fetchmail_server_id': server.id, 'server_type': server.type})
count = 0
count, failed = 0, 0
imap_server = False
pop_server = False
if server.type == 'imap':
@ -196,20 +196,26 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
imap_server.select()
result, data = imap_server.search(None, '(UNSEEN)')
for num in data[0].split():
res_id = None
result, data = imap_server.fetch(num, '(RFC822)')
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
data[0][1],
save_original=server.original,
strip_attachments=(not server.attach),
context=context)
imap_server.store(num, '-FLAGS', '\\Seen')
try:
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
data[0][1],
save_original=server.original,
strip_attachments=(not server.attach),
context=context)
except Exception:
_logger.exception('Failed to process mail from %s server %s.', server.type, server.name)
failed += 1
if res_id and server.action_id:
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", server.object_id.model)})
imap_server.store(num, '+FLAGS', '\\Seen')
cr.commit()
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids': [res_id], 'active_model': context.get("thread_model", server.object_id.model)})
imap_server.store(num, '+FLAGS', '\\Seen')
cr.commit()
count += 1
_logger.info("fetched/processed %s email(s) on %s server %s", count, server.type, server.name)
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", count, server.type, server.name, (count - failed), failed)
except Exception:
_logger.exception("Failed to fetch mail from %s server %s.", server.type, server.name)
_logger.exception("General failure when trying to fetch mail from %s server %s.", server.type, server.name)
finally:
if imap_server:
imap_server.close()
@ -222,18 +228,23 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
for num in range(1, numMsgs + 1):
(header, msges, octets) = pop_server.retr(num)
msg = '\n'.join(msges)
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
msg,
save_original=server.original,
strip_attachments=(not server.attach),
context=context)
res_id = None
try:
res_id = mail_thread.message_process(cr, uid, server.object_id.model,
msg,
save_original=server.original,
strip_attachments=(not server.attach),
context=context)
except Exception:
_logger.exception('Failed to process mail from %s server %s.', server.type, server.name)
failed += 1
if res_id and server.action_id:
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", server.object_id.model)})
action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids': [res_id], 'active_model': context.get("thread_model", server.object_id.model)})
pop_server.dele(num)
cr.commit()
_logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name)
_logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", numMsgs, server.type, server.name, (numMsgs - failed), failed)
except Exception:
_logger.exception("Failed to fetch mail from %s server %s.", server.type, server.name)
_logger.exception("General failure when trying to fetch mail from %s server %s.", server.type, server.name)
finally:
if pop_server:
pop_server.quit()

View File

@ -1,86 +0,0 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Drive Engagement with Gamification</h2>
<h3 class="oe_slogan">Leverage natural desire for competition</h3>
<p class="oe_mt32">
Reinforce good habits and improve win rates with real-time recognition and rewards inspired by <a href="http://en.wikipedia.org/wiki/Gamification">game mechanics</a>. Align teams around clear business objectives with challenges, personal objectives and team leader boards.
</p>
<div class="oe_span4 oe_centered">
<h3>Leaderboards</h3>
<div class="oe_row_img oe_centered">
<img class="oe_picture" src="crm_game_01.png">
</div>
<p>
Promote leaders and competition amongst sales team with performance ratios.
</p>
</div>
<div class="oe_span4 oe_centered">
<h3>Personnal Objectives</h3>
<div class="oe_row_img">
<img class="oe_picture" src="crm_game_02.png">
</div>
<p>
Assign clear goals to users to align them with the company objectives.
</p>
</div>
<div class="oe_span4 oe_centered">
<h3>Visual Information</h3>
<div class="oe_row_img oe_centered">
<img class="oe_picture" src="crm_game_03.png">
</div>
<p>
See in an glance the progress of each user.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Create custom Challenges</h2>
<div class="oe_span6">
<p class="oe_mt32">
Use predefined goals to generate easily your own challenges. Assign it to a team or individual users. Receive feedback as often as needed: daily, weekly... Repeat it automatically to compare progresses through time.
</p>
</div>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img class="oe_picture oe_screenshot" src="crm_sc_05.png">
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Motivate with Badges</h2>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img class="oe_picture" src="crm_linkedin.png">
</div>
</div>
<div class="oe_span6">
<p class="oe_mt32">
Inspire achievement with recognition of coworker's good work by rewarding badges. These can be deserved manually or upon completion of challenges. Add fun to the competition with rare badges.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Adapt to any module</h2>
<div class="oe_span6">
<p class="oe_mt32">
Create goals linked to any module. The evaluation system is very flexible and can be used for many different tasks : sales evaluation, creation of events, project completion or even helping new users to complete their profile.
</p>
</div>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img class="oe_picture oe_screenshot" src="crm_sc_02.png">
</div>
</div>
</div>
</section>

View File

@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
"Project-Id-Version: OpenERP Server 7.saas~3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-02-13 15:08+0000\n"
"PO-Revision-Date: 2014-02-13 15:08+0000\n"
"POT-Creation-Date: 2014-02-13 15:03+0000\n"
"PO-Revision-Date: 2014-02-13 15:03+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"

View File

@ -433,7 +433,7 @@ class gamification_challenge(osv.Model):
##### JS utilities #####
def _get_serialized_challenge_lines(self, cr, uid, challenge, user_id=False, restrict_goal_ids=False, restrict_top=False, context=None):
"""Return a serialised version of the goals information
"""Return a serialised version of the goals information if the user has not completed every goal
:challenge: browse record of challenge to compute
:user_id: res.users id of the user retrieving progress (False if no distinction, only for ranking challenges)
@ -487,6 +487,7 @@ class gamification_challenge(osv.Model):
(start_date, end_date) = start_end_date_for_period(challenge.period)
res_lines = []
all_reached = True
for line in challenge.line_ids:
line_data = {
'name': line.definition_id.name,
@ -537,6 +538,8 @@ class gamification_challenge(osv.Model):
'completeness': goal.completeness,
'state': goal.state,
})
if goal.state != 'reached':
all_reached = False
else:
ranking += 1
if user_id and goal.user_id.id == user_id:
@ -554,8 +557,12 @@ class gamification_challenge(osv.Model):
'completeness': goal.completeness,
'state': goal.state,
})
if goal.state != 'reached':
all_reached = False
if goal_ids:
res_lines.append(line_data)
if all_reached:
return []
return res_lines
##### Reporting #####
@ -620,22 +627,28 @@ class gamification_challenge(osv.Model):
return self.write(cr, uid, challenge.id, {'last_report_date': fields.date.today()}, context=context)
##### Challenges #####
# TODO in trunk, remove unused parameter user_id
def accept_challenge(self, cr, uid, challenge_ids, context=None, user_id=None):
"""The user accept the suggested challenge"""
user_id = user_id or uid
return self._accept_challenge(cr, uid, uid, challenge_ids, context=context)
def _accept_challenge(self, cr, uid, user_id, challenge_ids, context=None):
user = self.pool.get('res.users').browse(cr, uid, user_id, context=context)
message = "%s has joined the challenge" % user.name
self.message_post(cr, uid, challenge_ids, body=message, context=context)
self.message_post(cr, SUPERUSER_ID, challenge_ids, body=message, context=context)
self.write(cr, SUPERUSER_ID, challenge_ids, {'invited_user_ids': [(3, user_id)], 'user_ids': [(4, user_id)]}, context=context)
return self.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
return self.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
# TODO in trunk, remove unused parameter user_id
def discard_challenge(self, cr, uid, challenge_ids, context=None, user_id=None):
"""The user discard the suggested challenge"""
user_id = user_id or uid
return self._discard_challenge(cr, uid, uid, challenge_ids, context=context)
def _discard_challenge(self, cr, uid, user_id, challenge_ids, context=None):
user = self.pool.get('res.users').browse(cr, uid, user_id, context=context)
message = "%s has refused the challenge" % user.name
self.message_post(cr, SUPERUSER_ID, challenge_ids, body=message, context=context)
return self.write(cr, uid, challenge_ids, {'invited_user_ids': (3, user_id)}, context=context)
return self.write(cr, SUPERUSER_ID, challenge_ids, {'invited_user_ids': (3, user_id)}, context=context)
def reply_challenge_wizard(self, cr, uid, challenge_id, context=None):
result = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'challenge_wizard')

View File

@ -19,6 +19,7 @@
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import osv
from challenge import MAX_VISIBILITY_RANKING
@ -39,10 +40,10 @@ class res_users_gamification_group(osv.Model):
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
challenge_obj = self.pool.get('gamification.challenge')
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', user_group_ids)], context=context)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
return write_res
def create(self, cr, uid, vals, context=None):
@ -54,10 +55,10 @@ class res_users_gamification_group(osv.Model):
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
challenge_obj = self.pool.get('gamification.challenge')
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', user_group_ids)], context=context)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
return write_res
# def get_goals_todo_info(self, cr, uid, context=None):
@ -130,8 +131,8 @@ class res_groups_gamification_group(osv.Model):
user_ids += [id for command in vals['users'] if command[0] == 6 for id in command[2]]
challenge_obj = self.pool.get('gamification.challenge')
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', ids)], context=context)
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', ids)], context=context)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
return write_res

View File

@ -7,6 +7,7 @@
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
@ -36,21 +37,185 @@ from dateutil import parser
import pytz
from openerp.osv import fields, osv
from openerp.osv import osv
from collections import namedtuple
import logging
_logger = logging.getLogger(__name__)
class Meta(type):
""" This Meta class allow to define class as a structure, and so instancied variable
in __init__ to avoid to have side effect alike 'static' variable """
def __new__(typ, name, parents, attrs):
methods = dict((k, v) for k, v in attrs.iteritems()
if callable(v))
attrs = dict((k, v) for k, v in attrs.iteritems()
if not callable(v))
def init(self, **kw):
for k, v in attrs.iteritems():
setattr(self, k, v)
for k, v in kw.iteritems():
assert k in attrs
setattr(self, k, v)
methods['__init__'] = init
methods['__getitem__'] = getattr
return type.__new__(typ, name, parents, methods)
class Struct(object):
__metaclass__ = Meta
class OpenerpEvent(Struct):
event = False
found = False
event_id = False
isRecurrence = False
isInstance = False
update = False
status = False
attendee_id = False
synchro = False
class GmailEvent(Struct):
event = False
found = False
isRecurrence = False
isInstance = False
update = False
status = False
class SyncEvent(object):
def __init__(self):
self.OE = OpenerpEvent()
self.GG = GmailEvent()
self.OP = None
def __getitem__(self, key):
return getattr(self,key)
def compute_OP(self):
#If event are already in Gmail and in OpenERP
if self.OE.found and self.GG.found:
#If the event has been deleted from one side, we delete on other side !
if self.OE.status != self.GG.status:
self.OP = Delete((self.OE.status and "OE") or (self.GG.status and "GG"),
'The event has been deleted from one side, we delete on other side !' )
#If event is not deleted !
elif self.OE.status and self.GG.status:
if self.OE.update.split('.')[0] != self.GG.update.split('.')[0]:
if self.OE.update < self.GG.update:
tmpSrc = 'GG'
elif self.OE.update > self.GG.update:
tmpSrc = 'OE'
assert tmpSrc in ['GG','OE']
#if self.OP.action == None:
if self[tmpSrc].isRecurrence:
if self[tmpSrc].status:
self.OP = Update(tmpSrc, 'Only need to update, because i\'m active')
else:
self.OP = Exclude(tmpSrc, 'Need to Exclude (Me = First event from recurrence) from recurrence')
elif self[tmpSrc].isInstance:
self.OP= Update(tmpSrc, 'Only need to update, because already an exclu');
else:
self.OP = Update(tmpSrc, 'Simply Update... I\'m a single event');
#end-if self.OP.action == None:
else:
if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]:
self.OP = Update('OE','Event already updated by another user, but not synchro with my google calendar')
#import ipdb; ipdb.set_trace();
else:
self.OP = NothingToDo("",'Not update needed')
else:
self.OP = NothingToDo("", "Both are already deleted");
# New in openERP... Create on create_events of synchronize function
elif self.OE.found and not self.GG.found:
#Has been deleted from gmail
if self.OE.status:
self.OP = Delete('OE', 'Removed from GOOGLE')
else:
self.OP = NothingToDo("","Already Deleted in gmail and unlinked in OpenERP")
elif self.GG.found and not self.OE.found:
tmpSrc = 'GG'
if not self.GG.status and not self.GG.isInstance:
# don't need to make something... because event has been created and deleted before the synchronization
self.OP = NothingToDo("", 'Nothing to do... Create and Delete directly')
else:
if self.GG.isInstance:
if self[tmpSrc].status:
self.OP = Exclude(tmpSrc, 'Need to create the new exclu')
else:
self.OP = Exclude(tmpSrc, 'Need to copy and Exclude')
else:
self.OP = Create(tmpSrc, 'New EVENT CREATE from GMAIL')
def __str__(self):
return self.__repr__()
def __repr__(self):
myPrint = "---- A SYNC EVENT ---"
myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id)
myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False))
myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name)
myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False))
myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found)
myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence)
myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance)
myPrint += "\n Synchro OE: %10s " % (self.OE.synchro)
myPrint += "\n Update OE: %10s " % (self.OE.update)
myPrint += "\n Update GG: %10s " % (self.GG.update)
myPrint += "\n Status OE:%5s vs GG: %5s" % (self.OE.status, self.GG.status)
if (self.OP is None):
myPrint += "\n Action %s" % "---!!!---NONE---!!!---"
else:
myPrint += "\n Action %s" % type(self.OP).__name__
myPrint += "\n Source %s" % (self.OP.src)
myPrint += "\n comment %s" % (self.OP.info)
return myPrint
class SyncOperation(object):
def __init__(self, src,info, **kw):
self.src = src
self.info = info
for k,v in kw.items():
setattr(self,k,v)
def __str__(self):
return 'in__STR__'
class Create(SyncOperation):
pass
class Update(SyncOperation):
pass
class Delete(SyncOperation):
pass
class NothingToDo(SyncOperation):
pass
class Exclude(SyncOperation):
pass
class google_calendar(osv.AbstractModel):
STR_SERVICE = 'calendar'
_name = 'google.%s' % STR_SERVICE
def generate_data(self, cr, uid, event, context=None):
def generate_data(self, cr, uid, event, context=None):
if event.allday:
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) , context=context).isoformat('T').split('T')[0]
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration), context=context).isoformat('T').split('T')[0]
type = 'date'
vstype = 'dateTime'
else:
start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T')
type = 'dateTime'
vstype = 'date'
attendee_list = []
for attendee in event.attendee_ids:
@ -64,10 +229,12 @@ class google_calendar(osv.AbstractModel):
"description": event.description or '',
"start":{
type:start_date,
vstype:None,
'timeZone':'UTC'
},
"end":{
type:end_date,
vstype:None,
'timeZone':'UTC'
},
"attendees":attendee_list,
@ -80,10 +247,13 @@ class google_calendar(osv.AbstractModel):
if not event.active:
data["state"] = "cancelled"
if not self.get_need_synchro_attendee(cr,uid,context=context):
data.pop("attendees")
return data
def create_an_event(self, cr, uid,event, context=None):
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
data = self.generate_data(cr, uid,event, context=context)
@ -94,7 +264,7 @@ class google_calendar(osv.AbstractModel):
return gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context)
def delete_an_event(self, cr, uid,event_id, context=None):
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
params = {
'access_token' : self.get_token(cr,uid,context)
@ -108,12 +278,14 @@ class google_calendar(osv.AbstractModel):
if not token:
token = self.get_token(cr,uid,context)
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
params = {
'fields': 'items,nextPageToken',
'access_token' : token,
'maxResults':1000
'maxResults':1000,
'timeMin': self.get_start_time_to_synchro(cr,uid,context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"),
}
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
@ -134,7 +306,7 @@ class google_calendar(osv.AbstractModel):
def update_to_google(self, cr, uid, oe_event, google_event, context):
calendar_event = self.pool['calendar.event']
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'],'id,updated', self.get_token(cr,uid,context))
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
@ -148,10 +320,10 @@ class google_calendar(osv.AbstractModel):
calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date':update_date})
if context['curr_attendee']:
self.pool.get('calendar.attendee').write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date},context)
self.pool['calendar.attendee'].write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date},context)
def update_an_event(self, cr, uid,event, context=None):
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
data = self.generate_data(cr, uid,event, context=context)
@ -164,7 +336,7 @@ class google_calendar(osv.AbstractModel):
return response
def update_recurrent_event_exclu(self, cr, uid,instance_id,event_ori_google_id,event_new, context=None):
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
data = self.generate_data(cr, uid,event_new, context=context)
@ -186,7 +358,7 @@ class google_calendar(osv.AbstractModel):
calendar_event = self.pool['calendar.event']
res_partner_obj = self.pool['res.partner']
calendar_attendee_obj = self.pool['calendar.attendee']
user_obj = self.pool.get('res.users')
user_obj = self.pool['res.users']
myPartnerID = user_obj.browse(cr,uid,uid,context).partner_id.id
attendee_record = []
partner_record = [(4,myPartnerID)]
@ -203,14 +375,16 @@ class google_calendar(osv.AbstractModel):
if google_attendee.get('found',False):
continue
attendee_id = res_partner_obj.search(cr, uid,[('email', '=', google_attendee['email'])], context=context)
if not attendee_id:
attendee_id = [res_partner_obj.create(cr, uid,{'email': google_attendee['email'],'Customer': False, 'name': google_attendee.get("displayName",False) or google_attendee['email'] }, context=context)]
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
partner_record.append((4, attendee.get('id')))
attendee['partner_id'] = attendee.pop('id')
attendee['state'] = google_attendee['responseStatus']
attendee_record.append((0, 0, attendee))
if self.get_need_synchro_attendee(cr,uid,context=context):
attendee_id = res_partner_obj.search(cr, uid,[('email', '=', google_attendee['email'])], context=context)
if not attendee_id:
attendee_id = [res_partner_obj.create(cr, uid,{'email': google_attendee['email'],'customer': False, 'name': google_attendee.get("displayName",False) or google_attendee['email'] }, context=context)]
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
partner_record.append((4, attendee.get('id')))
attendee['partner_id'] = attendee.pop('id')
attendee['state'] = google_attendee['responseStatus']
attendee_record.append((0, 0, attendee))
UTC = pytz.timezone('UTC')
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
if single_event_dict['start'].get('dateTime',False) and single_event_dict['end'].get('dateTime',False):
@ -244,7 +418,7 @@ class google_calendar(osv.AbstractModel):
'location':single_event_dict.get('location',False),
'class':single_event_dict.get('visibility','public'),
'oe_update_date':update_date,
# 'google_internal_event_id': single_event_dict.get('id',False),
# 'google_internal_event_id': single_event_dict.get('id',False),
})
if single_event_dict.get("recurrence",False):
@ -261,16 +435,17 @@ class google_calendar(osv.AbstractModel):
res = calendar_event.create(cr, uid, result, context=context)
if context['curr_attendee']:
self.pool.get('calendar.attendee').write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date,'google_internal_event_id': single_event_dict.get('id',False)},context)
self.pool['calendar.attendee'].write(cr,uid,[context['curr_attendee']], {'oe_synchro_date':update_date,'google_internal_event_id': single_event_dict.get('id',False)},context)
return res
def synchronize_events(self, cr, uid, ids, context=None):
gc_obj = self.pool.get('google.calendar')
gc_obj = self.pool['google.calendar']
# Create all new events from OpenERP into Gmail, if that is not recurrent event
self.create_new_events(cr, uid, context=context)
self.bind_recurring_events_to_google(cr, uid, context)
cr.commit()
res = self.update_events(cr, uid, context)
@ -279,10 +454,9 @@ class google_calendar(osv.AbstractModel):
"url" : ''
}
def create_new_events(self, cr, uid, context):
gc_pool = self.pool.get('google.calendar')
calendar_event = self.pool['calendar.event']
def create_new_events(self, cr, uid, context=None):
gc_pool = self.pool['google.calendar']
ev_obj = self.pool['calendar.event']
att_obj = self.pool['calendar.attendee']
user_obj = self.pool['res.users']
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
@ -290,75 +464,73 @@ class google_calendar(osv.AbstractModel):
context_norecurrent = context.copy()
context_norecurrent['virtual_id'] = False
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '=', False)], context=context_norecurrent)
my_att_ids = att_obj.search(cr, uid,[ ('partner_id', '=', myPartnerID),
('google_internal_event_id', '=', False),
'|',
('event_id.date_deadline','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
('event_id.end_date','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
], context=context_norecurrent)
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0:
response = self.create_an_event(cr,uid,att.event_id,context=context)
update_date = datetime.strptime(response['updated'],"%Y-%m-%dT%H:%M:%S.%fz")
calendar_event.write(cr, uid, att.event_id.id, {'oe_update_date':update_date})
ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date':update_date})
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date':update_date})
cr.commit()
return True
cr.commit()
def get_empty_synchro_summarize(self) :
return {
#OPENERP
'OE_event' : False,
'OE_found' : False,
'OE_event_id' : False,
'OE_isRecurrence':False,
'OE_isInstance':False,
'OE_update':False,
'OE_status':False,
'OE_attendee_id': False,
'OE_synchro':False,
def bind_recurring_events_to_google(self, cr, uid, context):
ev_obj = self.pool['calendar.event']
att_obj = self.pool['calendar.attendee']
user_obj = self.pool['res.users']
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
#GOOGLE
'GG_event' : False,
'GG_found' : False,
'GG_isRecurrence':False,
'GG_isInstance':False,
'GG_update':False,
'GG_status':False,
context_norecurrent = context.copy()
context_norecurrent['virtual_id'] = False
context_norecurrent['active_test'] = False
#TO_DO_IN_GOOGLE
'td_action':'', # create, update, delete, None
#If 'td_action' in (create , update),
# If td_source == OE
# We create in google the event based on OpenERP
# If td_source == GG
# We create in OpenERP the event based on Gmail
#
#If 'td_action' in (delete),
# If td_source == OE
# We delete in OpenERP the event
# If td_source == GG
# We delete in Gmail the event
# If td_source == ALL
# We delete in openERP AND in Gmail the event
'td_source': '', # OE, GG, ALL
'td_comment':''
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '=', False)], context=context_norecurrent)
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
if att.event_id.recurrent_id and att.event_id.recurrent_id > 0:
new_google_internal_event_id = False
source_event_record = ev_obj.browse(cr, uid, att.event_id.recurrent_id, context)
source_attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',source_event_record.id)], context=context)
source_attendee_record = att_obj.browse(cr, uid, source_attendee_record_id, context)[0]
if att.event_id.recurrent_id_date and source_event_record.allday and source_attendee_record.google_internal_event_id:
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.split(' ')[0].replace('-','')
elif att.event_id.recurrent_id_date and source_attendee_record.google_internal_event_id:
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.replace('-','').replace(' ','T').replace(':','') + 'Z'
}
if new_google_internal_event_id:
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
res = self.update_recurrent_event_exclu(cr, uid,new_google_internal_event_id, source_attendee_record.google_internal_event_id,att.event_id, context=context)
att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context)
cr.commit()
def update_events(self, cr, uid, context):
def update_events(self, cr, uid, context=None):
if context is None:
context = {}
calendar_event = self.pool['calendar.event']
user_obj = self.pool['res.users']
att_obj = self.pool['calendar.attendee']
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id
context_novirtual = context.copy()
context_novirtual['virtual_id'] = False
context_novirtual['active_test'] = False
all_event_from_google = self.get_event_dict(cr,uid,context=context)
all_new_event_from_google = all_event_from_google.copy()
all_event_from_google = self.get_event_dict(cr, uid, context=context)
# Select all events from OpenERP which have been already synchronized in gmail
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '!=', False)], context=context_novirtual)
my_att_ids = att_obj.search(cr, uid,[ ('partner_id', '=', myPartnerID),
('google_internal_event_id', '!=', False),
'|',
('event_id.date_deadline','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
('event_id.end_date','>',self.get_start_time_to_synchro(cr,uid,context).strftime("%Y-%m-%d %H:%M:%S")),
], context=context_novirtual)
event_to_synchronize = {}
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
event = att.event_id
@ -369,18 +541,19 @@ class google_calendar(osv.AbstractModel):
event_to_synchronize[base_event_id] = {}
if att.google_internal_event_id not in event_to_synchronize[base_event_id]:
event_to_synchronize[base_event_id][att.google_internal_event_id] = self.get_empty_synchro_summarize()
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_attendee_id'] = att.id
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_event'] = event
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_found'] = True
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_event_id'] = event.id
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_isRecurrence'] = event.recurrency
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_isInstance'] = bool(event.recurrent_id and event.recurrent_id > 0)
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_update'] = event.oe_update_date
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_status'] = event.active
event_to_synchronize[base_event_id][att.google_internal_event_id]['OE_synchro'] = att.oe_synchro_date
event_to_synchronize[base_event_id][att.google_internal_event_id] = SyncEvent()
ev_to_sync = event_to_synchronize[base_event_id][att.google_internal_event_id]
ev_to_sync.OE.attendee_id = att.id
ev_to_sync.OE.event = event
ev_to_sync.OE.found = True
ev_to_sync.OE.event_id = event.id
ev_to_sync.OE.isRecurrence = event.recurrency
ev_to_sync.OE.isInstance = bool(event.recurrent_id and event.recurrent_id > 0)
ev_to_sync.OE.update = event.oe_update_date
ev_to_sync.OE.status = event.active
ev_to_sync.OE.synchro = att.oe_synchro_date
for event in all_event_from_google.values():
event_id = event.get('id')
@ -390,98 +563,28 @@ class google_calendar(osv.AbstractModel):
event_to_synchronize[base_event_id] = {}
if event_id not in event_to_synchronize[base_event_id]:
event_to_synchronize[base_event_id][event_id] = self.get_empty_synchro_summarize()
event_to_synchronize[base_event_id][event_id]['GG_event'] = event
event_to_synchronize[base_event_id][event_id]['GG_found'] = True
event_to_synchronize[base_event_id][event_id]['GG_isRecurrence'] = bool(event.get('recurrence',''))
event_to_synchronize[base_event_id][event_id]['GG_isInstance'] = bool(event.get('recurringEventId',0))
event_to_synchronize[base_event_id][event_id]['GG_update'] = event.get('updated',None) # if deleted, no date without browse event
if event_to_synchronize[base_event_id][event_id]['GG_update']:
event_to_synchronize[base_event_id][event_id]['GG_update'] =event_to_synchronize[base_event_id][event_id]['GG_update'].replace('T',' ').replace('Z','')
event_to_synchronize[base_event_id][event_id]['GG_status'] = (event.get('status') != 'cancelled')
event_to_synchronize[base_event_id][event_id] = SyncEvent()
ev_to_sync = event_to_synchronize[base_event_id][event_id]
ev_to_sync.GG.event = event
ev_to_sync.GG.found = True
ev_to_sync.GG.isRecurrence = bool(event.get('recurrence',''))
ev_to_sync.GG.isInstance = bool(event.get('recurringEventId',0))
ev_to_sync.GG.update = event.get('updated',None) # if deleted, no date without browse event
if ev_to_sync.GG.update:
ev_to_sync.GG.update = ev_to_sync.GG.update.replace('T',' ').replace('Z','')
ev_to_sync.GG.status = (event.get('status') != 'cancelled')
######################
# PRE-PROCESSING #
######################
for base_event in event_to_synchronize:
for current_event in event_to_synchronize[base_event]:
event = event_to_synchronize[base_event][current_event]
#If event are already in Gmail and in OpenERP
if event['OE_found'] and event['GG_found']:
#If the event has been deleted from one side, we delete on other side !
if event['OE_status'] != event['GG_status']:
event['td_action'] = "DELETE"
event['td_source'] = (event['OE_status'] and "OE") or (event['GG_status'] and "GG")
#If event is not deleted !
elif event['OE_status'] and event['GG_status']:
if event['OE_update'].split('.')[0] != event['GG_update'].split('.')[0]:
if event['OE_update'] < event['GG_update']:
event['td_source'] = 'GG'
elif event['OE_update'] > event['GG_update']:
event['td_source'] = 'OE'
if event['td_action'] != "None":
if event['%s_isRecurrence' % event['td_source']]:
if event['%s_status' % event['td_source']]:
event['td_action'] = "UPDATE"
event['td_comment'] = 'Only need to update, because i\'m active'
else:
event['td_action'] = "EXCLUDE"
event['td_comment'] = 'Need to Exclude (Me = First event from recurrence) from recurrence'
elif event['%s_isInstance' % event['td_source']]:
event['td_action'] = "UPDATE"
event['td_comment'] = 'Only need to update, because already an exclu'
else:
event['td_action'] = "UPDATE"
event['td_comment'] = 'Simply Update... I\'m a single event'
else:
if not event['OE_synchro'] or event['OE_synchro'].split('.')[0] < event['OE_update'].split('.')[0]:
event['td_source'] = 'OE'
event['td_action'] = "UPDATE"
event['td_comment'] = 'Event already updated by another user, but not synchro with my google calendar'
else:
event['td_action'] = "None"
event['td_comment'] = 'Not update needed'
else:
event['td_action'] = "None"
event['td_comment'] = "Both are already deleted"
# New in openERP... Create on create_events of synchronize function
elif event['OE_found'] and not event['GG_found']:
#Has been deleted from gmail
if event['OE_status']:
event['td_source'] = 'OE'
event['td_action'] = 'DELETE'
event['td_comment'] = 'Removed from GOOGLE ?'
else:
event['td_action'] = "None"
event['td_comment'] = "Already Deleted in gmail and unlinked in OpenERP"
elif event['GG_found'] and not event['OE_found']:
event['td_source'] = 'GG'
if not event['GG_status'] and not event['GG_isInstance']:
# don't need to make something... because event has been created and deleted before the synchronization
event['td_action'] = 'None'
event['td_comment'] = 'Nothing to do... Create and Delete directly'
else:
if event['GG_isInstance']:
if event['%s_status' % event['td_source']]:
event['td_action'] = "EXCLUDE"
event['td_comment'] = 'Need to create the new exclu'
else:
event['td_action'] = "EXCLUDE"
event['td_comment'] = 'Need to copy and Exclude'
else:
event['td_action'] = "CREATE"
event['td_comment'] = 'New EVENT CREATE from GMAIL'
event_to_synchronize[base_event][current_event].compute_OP()
#print event_to_synchronize[base_event]
#print "========================================================"
######################
# DO ACTION #
######################
@ -489,105 +592,62 @@ class google_calendar(osv.AbstractModel):
event_to_synchronize[base_event] = sorted(event_to_synchronize[base_event].iteritems(),key=operator.itemgetter(0))
for current_event in event_to_synchronize[base_event]:
cr.commit()
event = current_event[1]
#############
### DEBUG ###
#############
# if event['td_action'] and event['td_action'] != 'None':
# print " Real Event %s (%s)" % (current_event[0],event['OE_event_id'])
# print " Found OE:%5s vs GG: %5s" % (event['OE_found'],event['GG_found'])
# print " Recurrence OE:%5s vs GG: %5s" % (event['OE_isRecurrence'],event['GG_isRecurrence'])
# print " Instance OE:%5s vs GG: %5s" % (event['OE_isInstance'],event['GG_isInstance'])
# print " Synchro OE: %10s " % (event['OE_synchro'])
# print " Update OE: %10s " % (event['OE_update'])
# print " Update GG: %10s " % (event['GG_update'])
# print " Status OE:%5s vs GG: %5s" % (event['OE_status'],event['GG_status'])
# print " Action %s" % (event['td_action'])
# print " Source %s" % (event['td_source'])
# print " comment %s" % (event['td_comment'])
event = current_event[1] # event is an Sync Event !
actToDo = event.OP
actSrc = event.OP.src
context['curr_attendee'] = event.get('OE_attendee_id',False)
# if not isinstance(actToDo, NothingToDo):
# print event
context['curr_attendee'] = event.OE.attendee_id
actToDo = event['td_action']
actSrc = event['td_source']
if not actToDo:
raise ("#!? WHAT I NEED TO DO ????")
else:
if actToDo == 'None':
continue
elif actToDo == 'CREATE':
context_tmp = context.copy()
context_tmp['NewMeeting'] = True
if actSrc == 'GG':
res = self.update_from_google(cr, uid, False, event['GG_event'], "create", context=context_tmp)
event['OE_event_id'] = res
meeting = calendar_event.browse(cr,uid,res,context=context)
attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',res)], context=context)
self.pool.get('calendar.attendee').write(cr,uid,attendee_record_id, {'oe_synchro_date':meeting.oe_update_date,'google_internal_event_id': event['GG_event']['id']},context=context_tmp)
elif actSrc == 'OE':
raise "Should be never here, creation for OE is done before update !"
#TODO Add to batch
elif actToDo == 'UPDATE':
if actSrc == 'GG':
self.update_from_google(cr, uid, event['OE_event'], event['GG_event'], 'write', context)
elif actSrc == 'OE':
self.update_to_google(cr, uid, event['OE_event'], event['GG_event'], context)
elif actToDo == 'EXCLUDE' :
if actSrc == 'OE':
self.delete_an_event(cr,uid,current_event[0],context=context)
elif actSrc == 'GG':
new_google_event_id = event['GG_event']['id'].split('_')[1]
if 'T' in new_google_event_id:
new_google_event_id = new_google_event_id.replace('T','')[:-1]
if isinstance(actToDo, NothingToDo):
continue
elif isinstance(actToDo, Create):
context_tmp = context.copy()
context_tmp['NewMeeting'] = True
if actSrc == 'GG':
res = self.update_from_google(cr, uid, False, event.GG.event, "create", context=context_tmp)
event.OE.event_id = res
meeting = calendar_event.browse(cr,uid,res,context=context)
attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',res)], context=context)
self.pool['calendar.attendee'].write(cr, uid, attendee_record_id, {'oe_synchro_date':meeting.oe_update_date, 'google_internal_event_id':event.GG.event['id']}, context=context_tmp)
elif actSrc == 'OE':
raise "Should be never here, creation for OE is done before update !"
#TODO Add to batch
elif isinstance(actToDo, Update):
if actSrc == 'GG':
self.update_from_google(cr, uid, event.OE.event, event.GG.event, 'write', context)
elif actSrc == 'OE':
self.update_to_google(cr, uid, event.OE.event, event.GG.event, context)
elif isinstance(actToDo, Exclude):
if actSrc == 'OE':
self.delete_an_event(cr,uid,current_event[0],context=context)
elif actSrc == 'GG':
new_google_event_id = event.GG.event['id'].split('_')[1]
if 'T' in new_google_event_id:
new_google_event_id = new_google_event_id.replace('T','')[:-1]
else:
new_google_event_id = new_google_event_id + "000000"
if event.GG.status:
parent_event = {}
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id , new_google_event_id)
res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)
else:
new_google_event_id = new_google_event_id + "000000"
if event['GG_status']:
parent_event = {}
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].get('OE_event_id') , new_google_event_id)
res = self.update_from_google(cr, uid, parent_event, event['GG_event'], "copy", context)
else:
if event_to_synchronize[base_event][0][1].get('OE_event_id'):
parent_oe_id = event_to_synchronize[base_event][0][1].get('OE_event_id')
calendar_event.unlink(cr,uid,"%s-%s" % (parent_oe_id,new_google_event_id),unlink_level=1,context=context)
if event_to_synchronize[base_event][0][1].OE.event_id:
parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id
calendar_event.unlink(cr,uid,"%s-%s" % (parent_oe_id,new_google_event_id),unlink_level=1,context=context)
elif actToDo == 'DELETE':
if actSrc == 'GG':
self.delete_an_event(cr,uid,current_event[0],context=context)
elif actSrc == 'OE':
calendar_event.unlink(cr,uid,event['OE_event_id'],unlink_level=0,context=context)
elif isinstance(actToDo, Delete):
if actSrc == 'GG':
self.delete_an_event(cr,uid,current_event[0],context=context)
elif actSrc == 'OE':
calendar_event.unlink(cr,uid,event.OE.event_id,unlink_level=0,context=context)
return True
def bind_recurring_events_to_google(self, cr, uid, context):
calendar_event = self.pool['calendar.event']
att_obj = self.pool.get('calendar.attendee')
user_obj = self.pool['res.users']
myPartnerID = user_obj.browse(cr,uid,uid,context=context).partner_id.id
context_norecurrent = context.copy()
context_norecurrent['virtual_id'] = False
context_norecurrent['active_test'] = False
my_att_ids = att_obj.search(cr, uid,[('partner_id', '=', myPartnerID),('google_internal_event_id', '=', False)], context=context_norecurrent)
for att in att_obj.browse(cr,uid,my_att_ids,context=context):
if att.event_id.recurrent_id and att.event_id.recurrent_id > 0:
new_google_internal_event_id = False
source_event_record = calendar_event.browse(cr, uid, att.event_id.recurrent_id, context)
source_attendee_record_id = att_obj.search(cr, uid, [('partner_id','=', myPartnerID), ('event_id','=',source_event_record.id)], context=context)
source_attendee_record = att_obj.browse(cr, uid, source_attendee_record_id, context)
if source_attendee_record:
source_attendee_record = source_attendee_record[0]
if att.event_id.recurrent_id_date and source_event_record.allday and source_attendee_record.google_internal_event_id:
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.split(' ')[0].replace('-','')
elif event.recurrent_id_date and source_attendee_record.google_internal_event_id:
new_google_internal_event_id = source_attendee_record.google_internal_event_id +'_'+ att.event_id.recurrent_id_date.replace('-','').replace(' ','T').replace(':','') + 'Z'
if new_google_internal_event_id:
#TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE !
res = self.update_recurrent_event_exclu(cr,uid,new_google_internal_event_id,source_attendee_record.google_internal_event_id,att.event_id,context=context)
att_obj.write(cr, uid, [att.event_id.id], {'google_internal_event_id': new_google_internal_event_id})
def check_and_sync(self, cr, uid, oe_event, google_event, context):
if datetime.strptime(oe_event.oe_update_date,"%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'],"%Y-%m-%dT%H:%M:%S.%fz"):
self.update_to_google(cr, uid, oe_event, google_event, context)
@ -595,7 +655,7 @@ class google_calendar(osv.AbstractModel):
self.update_from_google(cr, uid, oe_event, google_event, 'write', context)
def get_sequence(self,cr,uid,instance_id,context=None):
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
params = {
'fields': 'sequence',
@ -613,7 +673,7 @@ class google_calendar(osv.AbstractModel):
#################################
def get_token(self,cr,uid,context=None):
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < (datetime.now() + timedelta(minutes=1)):
self.do_refresh_token(cr,uid,context=context)
@ -622,20 +682,20 @@ class google_calendar(osv.AbstractModel):
return current_user.google_calendar_token
def do_refresh_token(self,cr,uid,context=None):
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
gs_pool = self.pool.get('google.service')
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
gs_pool = self.pool['google.service']
refresh = current_user.google_calendar_rtoken
all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken,self.STR_SERVICE,context=context)
all_token = gs_pool._refresh_google_token_json(cr, uid, current_user.google_calendar_rtoken, self.STR_SERVICE, context=context)
vals = {}
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
self.pool.get('res.users').write(cr,SUPERUSER_ID,uid,vals,context=context)
self.pool['res.users'].write(cr,SUPERUSER_ID,uid,vals,context=context)
def need_authorize(self,cr,uid,context=None):
current_user = self.pool.get('res.users').browse(cr,uid,uid,context=context)
current_user = self.pool['res.users'].browse(cr,uid,uid,context=context)
return current_user.google_calendar_rtoken == False
def get_calendar_scope(self,RO=False):
@ -643,22 +703,30 @@ class google_calendar(osv.AbstractModel):
return 'https://www.googleapis.com/auth/calendar%s' % (readonly)
def authorize_google_uri(self,cr,uid,from_url='http://www.openerp.com',context=None):
url = self.pool.get('google.service')._get_authorize_uri(cr,uid,from_url,self.STR_SERVICE,scope=self.get_calendar_scope(),context=context)
url = self.pool['google.service']._get_authorize_uri(cr,uid,from_url,self.STR_SERVICE,scope=self.get_calendar_scope(),context=context)
return url
def can_authorize_google(self,cr,uid,context=None):
return self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager')
def set_all_tokens(self,cr,uid,authorization_code,context=None):
gs_pool = self.pool.get('google.service')
gs_pool = self.pool['google.service']
all_token = gs_pool._get_google_token_json(cr, uid, authorization_code,self.STR_SERVICE,context=context)
vals = {}
vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token')
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
self.pool.get('res.users').write(cr,SUPERUSER_ID,uid,vals,context=context)
self.pool['res.users'].write(cr,SUPERUSER_ID,uid,vals,context=context)
def get_start_time_to_synchro(self, cr, uid, context=None) :
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
number_of_week = 13
return datetime.now()-timedelta(weeks=number_of_week)
def get_need_synchro_attendee(self, cr, uid, context=None):
# WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4
return True
class res_users(osv.Model):
_inherit = 'res.users'
@ -718,7 +786,6 @@ class calendar_attendee(osv.Model):
# If attendees are updated, we need to specify that next synchro need an action
# Except if it come from an update_from_google
if not context.get('curr_attendee', False) and not context.get('NewMeeting', False):
self.pool.get('calendar.event').write(cr, uid, ref, {'oe_update_date':datetime.now()},context)
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)
self.pool['calendar.event'].write(cr, uid, ref, {'oe_update_date':datetime.now()},context)
return super(calendar_attendee, self).write(cr, uid, ids, vals, context=context)

View File

@ -74,8 +74,8 @@
<label for="number_of_days_temp" string="Duration"/>
<div>
<group col="3" attrs="{'invisible': [('type', '=', 'add')]}">
<field name="date_from" nolabel="1" on_change="onchange_date_from(date_to, date_from)" required="1" class="oe_inline"/><label string="-" class="oe_inline"/>
<field name="date_to" nolabel="1" on_change="onchange_date_to(date_to, date_from)" required="1" class="oe_inline"/>
<field name="date_from" nolabel="1" on_change="onchange_date_from(date_to, date_from)" attrs="{'required':[('type', '=', 'remove')]}" class="oe_inline"/><label string="-" class="oe_inline"/>
<field name="date_to" nolabel="1" on_change="onchange_date_to(date_to, date_from)" attrs="{'required':[('type', '=', 'remove')]}" class="oe_inline"/>
</group>
<div>
<field name="number_of_days_temp" class="oe_inline"/> days

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: 2012-02-11 15:26+0000\n"
"Last-Translator: 开阖软件 Jeff Wang <jeff@osbzr.com>\n"
"PO-Revision-Date: 2014-02-20 15:53+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:29+0000\n"
"X-Generator: Launchpad (build 16914)\n"
"X-Launchpad-Export-Date: 2014-02-21 05:48+0000\n"
"X-Generator: Launchpad (build 16916)\n"
#. module: hr_recruitment
#: help:hr.applicant,active:0
@ -33,24 +33,24 @@ msgstr "必备条件"
#. module: hr_recruitment
#: view:hr.applicant:0
msgid "Application Summary"
msgstr ""
msgstr "申请摘要"
#. module: hr_recruitment
#: view:hr.applicant:0
msgid "Start Interview"
msgstr ""
msgstr "开始面试"
#. module: hr_recruitment
#: view:hr.applicant:0
msgid "Mobile:"
msgstr ""
msgstr "手机:"
#. module: hr_recruitment
#: help:hr.recruitment.stage,fold:0
msgid ""
"This stage is not visible, for example in status bar or kanban view, when "
"there are no records in that stage to display."
msgstr ""
msgstr "此阶段是不可见的。例如:状态栏或看板视图中,在该阶段中 不存在可显示的记录。"
#. module: hr_recruitment
#: model:hr.recruitment.degree,name:hr_recruitment.degree_graduate
@ -99,7 +99,7 @@ msgstr "暂停的职位"
#: view:hr.applicant:0
#: field:hr.applicant,message_unread:0
msgid "Unread Messages"
msgstr ""
msgstr "未读消息"
#. module: hr_recruitment
#: field:hr.applicant,company_id:0
@ -157,7 +157,7 @@ msgstr ""
#: model:ir.actions.act_window,name:hr_recruitment.crm_case_categ0_act_job
#: model:ir.ui.menu,name:hr_recruitment.menu_crm_case_categ0_act_job
msgid "Applications"
msgstr ""
msgstr "求职申请"
#. module: hr_recruitment
#: field:hr.applicant,day_open:0
@ -167,12 +167,12 @@ msgstr "开启天数"
#. module: hr_recruitment
#: field:hr.applicant,emp_id:0
msgid "employee"
msgstr ""
msgstr "雇员"
#. module: hr_recruitment
#: field:hr.config.settings,fetchmail_applicants:0
msgid "Create applicants from an incoming email account"
msgstr ""
msgstr "从电子邮件帐号接收的邮件创建求职申请"
#. module: hr_recruitment
#: view:hr.recruitment.report:0
@ -184,7 +184,7 @@ msgstr "天数"
#: view:hr.recruitment.partner.create:0
#: model:ir.actions.act_window,name:hr_recruitment.action_hr_recruitment_partner_create
msgid "Create Contact"
msgstr ""
msgstr "新建联系人"
#. module: hr_recruitment
#: view:hr.applicant:0
@ -808,7 +808,7 @@ msgstr "生效"
#: view:hr.recruitment.report:0
#: field:hr.recruitment.report,nbr:0
msgid "# of Applications"
msgstr ""
msgstr "个申请"
#. module: hr_recruitment
#: model:ir.actions.act_window,help:hr_recruitment.hr_recruitment_stage_act
@ -1251,7 +1251,7 @@ msgstr "开始"
#. module: hr_recruitment
#: view:board.board:0
msgid "Applications to be Processed"
msgstr ""
msgstr "待处理的申请"
#. module: hr_recruitment
#: view:hr.applicant:0

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: 2012-11-01 08:13+0000\n"
"Last-Translator: 盈通 ccdos <ccdos@intoerp.com>\n"
"PO-Revision-Date: 2014-02-20 15:57+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 06:13+0000\n"
"X-Generator: Launchpad (build 16914)\n"
"X-Launchpad-Export-Date: 2014-02-21 05:48+0000\n"
"X-Generator: Launchpad (build 16916)\n"
#. module: hr_timesheet_sheet
#: field:hr.analytic.timesheet,sheet_id:0
@ -33,7 +33,7 @@ msgstr "服务"
#: field:hr.timesheet.report,quantity:0
#: field:timesheet.report,quantity:0
msgid "Time"
msgstr ""
msgstr "时间"
#. module: hr_timesheet_sheet
#: help:hr.config.settings,timesheet_max_difference:0
@ -92,7 +92,7 @@ msgstr "# 成本"
#. module: hr_timesheet_sheet
#: field:hr_timesheet_sheet.sheet,message_unread:0
msgid "Unread Messages"
msgstr ""
msgstr "未读消息"
#. module: hr_timesheet_sheet
#: view:hr.timesheet.report:0
@ -133,7 +133,7 @@ msgstr "日期到"
#. module: hr_timesheet_sheet
#: view:hr_timesheet_sheet.sheet:0
msgid "to"
msgstr ""
msgstr ""
#. module: hr_timesheet_sheet
#: model:process.node,note:hr_timesheet_sheet.process_node_invoiceonwork0
@ -145,7 +145,7 @@ msgstr "根据计工单"
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:398
#, python-format
msgid "You cannot modify an entry in a confirmed timesheet."
msgstr ""
msgstr "你不能修改已确认的时间表条目"
#. module: hr_timesheet_sheet
#: view:hr.timesheet.report:0
@ -188,13 +188,13 @@ msgstr "拒绝"
#: view:hr_timesheet_sheet.sheet:0
#: model:ir.actions.act_window,name:hr_timesheet_sheet.act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet
msgid "Timesheet Activities"
msgstr ""
msgstr "时间表活动"
#. module: hr_timesheet_sheet
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:38
#, python-format
msgid "Please create an employee and associate it with this user."
msgstr ""
msgstr "请创建员工信息并关联到此用户"
#. module: hr_timesheet_sheet
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:402
@ -202,7 +202,7 @@ msgstr ""
#, python-format
msgid ""
"You cannot enter an attendance date outside the current timesheet dates."
msgstr ""
msgstr "无法录入当前时间表以外的出勤日期"
#. module: hr_timesheet_sheet
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:205
@ -226,7 +226,7 @@ msgstr ""
#. module: hr_timesheet_sheet
#: field:hr_timesheet_sheet.sheet,message_ids:0
msgid "Messages"
msgstr ""
msgstr "消息"
#. module: hr_timesheet_sheet
#: help:hr_timesheet_sheet.sheet,state:0
@ -252,7 +252,7 @@ msgstr ""
#: code:addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py:38
#, python-format
msgid "Error!"
msgstr ""
msgstr "错误!"
#. module: hr_timesheet_sheet
#: field:hr.config.settings,timesheet_max_difference:0
@ -307,7 +307,7 @@ msgstr "员工计工单"
#: code:addons/hr_timesheet_sheet/hr_timesheet_sheet.py:215
#, python-format
msgid "Invalid Action!"
msgstr ""
msgstr "无效的动作!"
#. module: hr_timesheet_sheet
#: view:hr.timesheet.report:0

View File

@ -95,6 +95,9 @@ class EscposDriver(Thread):
_logger.warning('ESC/POS Device Disconnected: '+message)
def run(self):
if not escpos:
_logger.error('ESC/POS cannot initialize, please verify system dependencies.')
return
while True:
try:
timestamp, task, data = self.queue.get(True)

View File

@ -5,6 +5,10 @@ PosBox Documentation
Posbox Setup Guide
==================
.. image:: _images/posbox_setup.png
:width: 100%
:align: center
Prerequisites
-------------
@ -20,64 +24,64 @@ You will need :
- A local network set up with DHCP ( this is the default setting )
- A RJ45 Ethrnet Cable
- An Epson USB TM-T20 Printer or another compatible printer.
- A Honewell Voyager USB Barcode Scanner or another compatible scanner.
- A Honeywell Eclipse USB Barcode Scanner or another compatible scanner.
- An Epson compatible cash drawer.
Setup
-----
Step By Step Setup Guide
------------------------
.. image:: _images/posbox_doc_schema.png
:width: 100%
:align: center
1) Power the PosBox.
~~~~~~~~~~~~~~~~~~~~
Power the PosBox.
~~~~~~~~~~~~~~~~
Plug the PosBox to the 2A Power Adapter, a bright red status led should
light up.
2) Connect it to the Local Network
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect it to the Local Network
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect the PosBox to the Local Network with the RJ45 Cable. Make sure
You connect the PosBox to the same Network as your POS device. Once
connected a bright yellow status led should light up, and a green status
led should flash.
3) Connect the USB Printer
~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect the USB Printer
~~~~~~~~~~~~~~~~~~~~~~~
Put a paper roll in the printer, power it up, then connect it to one of
the PosBox's USB port
4) Connect the cash drawer
~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect the cash drawer
~~~~~~~~~~~~~~~~~~~~~~~
The cash drawer should be connected to the printer with the RJ25 cable
5) Connect the USB Barcode Scanner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect the USB Barcode Scanner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect the usb barcode scanner to one of the PosBox's USB port. The
Barcode Scanner should immediately light up and emit a loud beep.
6) Configure the USB Barcode Scanner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Configure the USB Barcode Scanner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The barcode scanner should be configured in QWERTY and emit a new line /
return symbol after each scan. This is most likely the default
configuration of your barcode scanner.
7) Make sure the PosBox is ready
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Make sure the PosBox is ready
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Once powered, The PosBox needs less than a minute to boot. Once the
PosBox is ready, it should print a status receipt with its IP Adress.
Also, the last status led, just after the red power status led, should
be permanently lit green.
8) Set up the Point of Sale
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Setup the Point of Sale
~~~~~~~~~~~~~~~~~~~~~~~~
Make sure to activate the 'Print via Proxy', 'Remote Scanning' or
'Cashdrawer' options in the Point of Sale configuration. If you are
@ -87,8 +91,12 @@ was printed on the status receipt.
If you are running multiple Point of Sales on the same PosBox, make sure
that only one of them has Remote Scanning activated.
9) Launch the Point of Sale.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you manually specify the PosBox's IP address, you must configure your
network to make sure the PosBox's IP address doesn't change. Please
refer to your Router documentation.
Launch the Point of Sale.
~~~~~~~~~~~~~~~~~~~~~~~~~
If you didn't spcecify the PosBox's IP address in the config, the POS
will need some time to perform a network scan to find the PosBox. This
@ -97,9 +105,21 @@ is only done once.
The Point of Sale is now connected to the PosBox and your Hardware
should be ready to use.
Multi-Pos Configuration
-----------------------
The advised way to setup a multi Point-of-Sale shop is to have one PosBox
per Point-of-Sale. In this case it is mandatory to manually specify the
IP address of each PosBox in each Point of Sale. You must also configure
your network to make sure the PosBox's IP address doesn't change. Please
refer to your Router documentation.
PosBoxless Setup Guide
======================
.. image:: _images/posboxless_setup.png
:width: 100%
:align: center
If you are running your Point of Sale on a debian-based linux
distribution, you do not need the PosBox as you can run its software
locally. However the installation process is not foolproof. You'll need
@ -111,15 +131,15 @@ Prerequisites
-------------
- A debian based linux distribution (Debian, Ubuntu, Mint, etc.)
- A running trunk version of the OpenERP server
- A running OpenERP server (version trunk or saas-3 or 8.0 or later)
- You must uninstall any esc-pos printer driver as it will conflict
with OpenERP's built-in driver.
Setup
-----
Step By Step Setup Guide
------------------------
1) Extra dependencies
~~~~~~~~~~~~~~~~~~~~~
Extra dependencies
~~~~~~~~~~~~~~~~~~
The driver modules requires the installation of new python modules:
@ -128,14 +148,14 @@ The driver modules requires the installation of new python modules:
$ sudo pip install pyserial
$ sudo pip install --pre pyusb
2) Database Setup
~~~~~~~~~~~~~~~~~
Database Setup
~~~~~~~~~~~~~~
You must create a database called ``posbox`` with the modules
``hw_proxy``, ``hw_escpos``, and ``hw_scanner`` installed.
3) Access Rights
~~~~~~~~~~~~~~~~
Access Rights
~~~~~~~~~~~~~
The drivers need raw access to the printer and barcode scanner devices.
Doing so requires a bit system administration. First we are going to
@ -163,8 +183,8 @@ following content:
Then you need to reboot your machine.
4) Start the local OpenERP Installl
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Start the local OpenERP Installl
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We must launch the OpenERP server on the port ``8069`` with the correct
database settings:
@ -174,8 +194,8 @@ database settings:
$ ./server/openerp-server --addons-path=addons,web/addons --db-filter='^posbox$' \
--xmlrpc-port=8069 -d posbox
5) Check that everything works
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Check that everything works
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Plug all your hardware to your machine's USB ports, and go to
``http://localhost/hw_proxy/status`` refresh the page a few times and
@ -184,20 +204,19 @@ errors are: The paths on the distribution differ from the paths expected
by the drivers, another process has grabbed exclusive access to the
devices, the udev rules do not apply or a superceeded by others.
5) Automatically Start OpenERP
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Automatically Start OpenERP
~~~~~~~~~~~~~~~~~~~~~~~~~~~
You must now make sure that this OpenERP install is automatically
started after boot. There are various ways to do so, and how to do it
depends on your particular setup. We use *systemd* on the PosBox, but
*upstart* or *sysvinit* are other options.
6) Set up the Point of Sale
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Setup the Point of Sale
~~~~~~~~~~~~~~~~~~~~~~~~
Make sure that one of the posbox Make sure the IP Adress field in the
POS Config is either ``127.0.0.1`` or ``localhost`` or simply leave it
empty.
The IP Adress field in the POS Config must be either ``127.0.0.1``
or ``localhost``. You can also leave it empty.
PosBox Technical Documentation
==============================
@ -231,7 +250,7 @@ Compatible Peripherals
- Barcode Scannners:
- Metapace S61
- Honeywell Voyager 95x0 Series.
- Honeywell Eclipse or Voyager 95x0 Series.
- Most other barcode scanners should work out of the box. Some
barcode scanners need more power than the PosBox can provide and
must be plugged in a self-powered USB HUB. This is the case for
@ -265,6 +284,9 @@ found in instead ``/var/logs/syslog``
We removed all graphical software from the default install to reduce to
image size but nothing prevents you from reinstalling them.
Accessing the PosBox
--------------------
Local Access
~~~~~~~~~~~~
@ -284,8 +306,8 @@ Remote Access
If you have the PosBox's IP address and a SSH client you can access the
PosBox's system remotely. The login / password are ``pi``/``admin``
Unsupported Printers
~~~~~~~~~~~~~~~~~~~~
Getting Unsupported Printers to Work
------------------------------------
The PosBox should be able to print to any ESC-POS printer, not just the
Epson TM-T20. If You have such a printer, you can activate it with the
@ -305,7 +327,7 @@ following steps:
supported devices.
Updating The PosBox Software
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
----------------------------
The best way to update the PosBox software is to download a new version
of the image and flash the SD-Card with it. This operation is described
@ -315,10 +337,10 @@ raspberry pi image to the latest one found at
``http://nightly.openerp.com/trunk/posbox/``
Troubleshoot
------------
============
The POS cannot connect to the PosBox.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------------------
- The easiest way to make sure the PosBox is properly set-up is to turn
it on with the printer plugged in as it will print a receipt
@ -346,7 +368,7 @@ The POS cannot connect to the PosBox.
the PosBox's ip address in the POS config.
The Barcode Scanner is not working
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
----------------------------------
- The PosBox needs a 2A power supply to work with some barcode
scanners. If you are not using the provided power supply, make sure
@ -357,15 +379,17 @@ The Barcode Scanner is not working
- Some poorly built barcode scanners do not advertise themselves as
barcode scanners but as a usb keyboard instead, and will not be
recognized by the PosBox.
- The barcode scanner must be configured in US QWERTY and emit a linefeed
after each codebar.
The Barcode Scanner is not working reliably
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------------------------
- Make sure that no more than one device with 'Scan via Proxy' enabled
are connected to the PosBox at the same time.
Printing the receipt takes too much time.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------------------------------------
- A small delay before the first print is expected, as the PosBox will
do some preprocessing to speed up the next printings. If you suffer
@ -373,14 +397,14 @@ Printing the receipt takes too much time.
between the POS and the PosBox.
Some characters are not correctly printed on the receipt.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------------------------------------------------
- The PosBox does not support all languages and characters. It
currently supports latin and cyrillic based scripts, with basic
japanese support.
The Printer is Offline
~~~~~~~~~~~~~~~~~~~~~~
----------------------
- The PosBox only supports EPSON TM-T20 printers. Make sure the printer
is connected, powered, has enough paper and has its lid closed, and
@ -388,8 +412,41 @@ The Printer is Offline
support.
The Cashdrawer does not open.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------------------------
- The cashdrawer should be connected to the printer and should be
activated in the POS Configuration
Credits
=======
The PosBox project was developped by Frédéric van der Essen with the
kind help of Gary Malherbe, Fabien Meghazi, Nicolas Wisniewsky,
Dimitri Del Marmol and Antony Lesuisse.
This development would not have been possible without the Indiegogo
campaign and those who contributed to it. Special thanks goes to the
partners who backed the campaign with founding partner bundles:
- Camptocamp
- BHC
- openBig
- Eeezee-IT
- Solarsis LDA
- ACSONE
- Vauxoo
- Ekomurz
- Datalp
- Dao Systems
- Eggs Solutions
- OpusVL
And also the partners who've backed the development with the Founding
PosBox Bundle:
- Willow IT
- E\. Akhalwaya & Sons
- Multibase
- Mindesa
- bpso.biz
- Shine IT.

Binary file not shown.

View File

@ -1,4 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_im_message,im.message,model_im_message,,1,0,1,0
access_im_user,im.user,model_im_user,,1,1,1,0
access_im_session,im.session,model_im_session,,1,0,0,0
access_im_session,im.session,model_im_session,,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_im_message im.message model_im_message 1 0 1 0
3 access_im_user im.user model_im_user 1 1 1 0
4 access_im_session im.session model_im_session 1 0 1 0 1 0

View File

@ -42,7 +42,7 @@ If you want to save your employees' time and avoid them to always have coins in
""",
'data': ['security/lunch_security.xml','lunch_view.xml','wizard/lunch_order_view.xml','wizard/lunch_validation_view.xml','wizard/lunch_cancel_view.xml','lunch_report.xml',
'report/report_lunch_order_view.xml',
'security/ir.model.access.csv',],
'security/ir.model.access.csv','views/report_lunchorder.xml'],
'css':['static/src/css/lunch.css'],
'images': ['images/new_order.jpeg','images/lunch_account.jpeg','images/order_by_supplier_analysis.jpeg','images/alert.jpeg'],
'demo': ['lunch_demo.xml',],

View File

@ -2,12 +2,12 @@
<openerp>
<data>
<report
id="report_lunch_order"
id="action_report_lunch_order"
string="Lunch Order"
model="lunch.order.line"
name="lunch.order.line"
rml="lunch/report/order.rml"
auto="False"
/>
report_type="qweb-pdf"
name="lunch.report_lunchorder"
file="lunch.report_lunchorder"
/>
</data>
</openerp>

View File

@ -20,11 +20,11 @@
##############################################################################
import time
from openerp.report import report_sxw
from openerp.osv import osv
from openerp.addons.web import http
from openerp.addons.web.http import request
class order(report_sxw.rml_parse):
class order(http.Controller):
def get_lines(self, user,objects):
lines=[]
@ -60,19 +60,24 @@ class order(report_sxw.rml_parse):
notes.append(obj.note)
return notes
def __init__(self, cr, uid, name, context):
super(order, self).__init__(cr, uid, name, context)
@http.route(['/report/lunch.report_lunchorder/<docids>'], type='http', auth='user', website=True, multilang=True)
def report_lunch(self, docids):
self.cr, self.uid, self.context = request.cr, request.uid, request.context
ids = [int(i) for i in docids.split(',')]
report_obj = request.registry['lunch.order.line']
docs = report_obj.browse(self.cr, self.uid, ids, context=self.context)
self.net_total=0.0
self.localcontext.update({
docargs = {
'docs': docs,
'time': time,
'get_lines': self.get_lines,
'get_users': self.get_users,
'get_total': self.get_total,
'get_nettotal': self.get_nettotal,
'get_note': self.get_note,
})
}
return request.registry['report'].render(self.cr, self.uid, [], 'lunch.report_lunchorder', docargs)
report_sxw.report_sxw('report.lunch.order.line', 'lunch.order.line',
'addons/lunch/report/order.rml',parser=order, header='external')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

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