[MERGE] merged with lp:openobject-addons

bzr revid: shp@tinyerp.com-20121012101438-opjebg6rjxio90y9
This commit is contained in:
pankita shah (Open ERP) 2012-10-12 15:44:38 +05:30
commit da44c4d5b6
253 changed files with 6289 additions and 8749 deletions

View File

@ -155,6 +155,7 @@ class account_account_type(osv.osv):
return res
def _save_report_type(self, cr, uid, account_type_id, field_name, field_value, arg, context=None):
field_value = field_value or 'none'
obj_data = self.pool.get('ir.model.data')
obj_financial_report = self.pool.get('account.financial.report')
#unlink if it exists somewhere in the financial reports related to BS or PL
@ -299,7 +300,6 @@ class account_account(osv.osv):
if aml_query.strip():
wheres.append(aml_query.strip())
filters = " AND ".join(wheres)
_logger.debug('Filters: %s',(filters))
# IN might not work ideally in case there are too many
# children_and_consolidated, in that case join on a
# values() e.g.:
@ -315,7 +315,6 @@ class account_account(osv.osv):
" GROUP BY l.account_id")
params = (tuple(children_and_consolidated),) + query_params
cr.execute(request, params)
_logger.debug('Status: %s',(cr.statusmessage))
for row in cr.dictfetchall():
accounts[row['id']] = row
@ -596,10 +595,7 @@ class account_account(osv.osv):
return res
def copy(self, cr, uid, id, default=None, context=None, done_list=None, local=False):
if default is None:
default = {}
else:
default = default.copy()
default = {} if default is None else default.copy()
if done_list is None:
done_list = []
account = self.browse(cr, uid, id, context=context)
@ -781,10 +777,7 @@ class account_journal(osv.osv):
]
def copy(self, cr, uid, id, default=None, context=None, done_list=None, local=False):
if default is None:
default = {}
else:
default = default.copy()
default = {} if default is None else default.copy()
if done_list is None:
done_list = []
journal = self.browse(cr, uid, id, context=context)
@ -1185,6 +1178,7 @@ class account_fiscalyear(osv.osv):
}
def copy(self, cr, uid, id, default=None, context=None):
default = {} if default is None else default.copy()
default.update({
'period_ids': [],
'end_journal_period_id': False
@ -1444,14 +1438,8 @@ class account_move(osv.osv):
return result
def copy(self, cr, uid, id, default=None, context=None):
if context is None:
default = {}
else:
default = default.copy()
if context is None:
context = {}
else:
context = context.copy()
default = {} if default is None else default.copy()
context = {} if context is None else context.copy()
default.update({
'state':'draft',
'name':'/',

View File

@ -461,9 +461,10 @@ class account_bank_statement(osv.osv):
return {}
balance_start = self._compute_balance_end_real(cr, uid, journal_id, context=context)
journal_data = self.pool.get('account.journal').read(cr, uid, journal_id, ['company_id'], context=context)
journal_data = self.pool.get('account.journal').read(cr, uid, journal_id, ['company_id', 'currency'], context=context)
company_id = journal_data['company_id']
return {'value': {'balance_start': balance_start, 'company_id': company_id}}
currency_id = journal_data['currency'] or self.pool.get('res.company').browse(cr, uid, company_id[0], context=context).currency_id.id
return {'value': {'balance_start': balance_start, 'company_id': company_id, 'currency': currency_id}}
def unlink(self, cr, uid, ids, context=None):
stat = self.read(cr, uid, ids, ['state'], context=context)

View File

@ -211,17 +211,17 @@
</tree>
</field>
<group class="oe_subtotal_footer oe_right">
<field name="amount_untaxed"/>
<field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<div>
<label for="amount_tax"/>
<button name="button_reset_taxes" states="draft,proforma2"
string="(update)" class="oe_link oe_edit_only"
type="object" help="Recompute taxes and total"/>
</div>
<field name="amount_tax" nolabel="1"/>
<field name="amount_total" class="oe_subtotal_footer_separator"/>
<field name="amount_tax" nolabel="1" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="amount_total" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="residual"/>
<field name="residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="reconciled" invisible="1"/>
</group>
<div style="width: 50%%">
@ -322,7 +322,7 @@
<field string="Customer" name="partner_id"
on_change="onchange_partner_id(type,partner_id,date_invoice,payment_term, partner_bank_id,company_id)"
groups="base.group_user" context="{'search_default_customer':1, 'show_address': 1}"
options='{"always_reload": true}'/>
options='{"always_reload": True}'/>
<field name="fiscal_position" widget="selection" />
</group>
<group>
@ -368,16 +368,16 @@
</tree>
</field>
<group class="oe_subtotal_footer oe_right">
<field name="amount_untaxed"/>
<field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<div>
<label for="amount_tax"/>
<button name="button_reset_taxes" states="draft,proforma2"
string="(update)" class="oe_link oe_edit_only"
type="object" help="Recompute taxes and total"/>
</div>
<field name="amount_tax" nolabel="1"/>
<field name="amount_total" class="oe_subtotal_footer_separator"/>
<field name="residual" groups="account.group_account_user"/>
<field name="amount_tax" nolabel="1" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="amount_total" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="residual" groups="account.group_account_user" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="reconciled" invisible="1"/>
</group>
<group>
@ -458,7 +458,6 @@
<filter domain="[('user_id','=',uid)]" help="My Invoices" icon="terp-personal"/>
<field name="partner_id"/>
<field name="user_id" string="Salesperson"/>
<field name="journal_id"/>
<field name="period_id" string="Period"/>
<group expand="0" string="Group By...">
<filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>

View File

@ -633,13 +633,10 @@
<field name="period_id" class="oe_inline"/>
</div>
<field name='company_id' widget="selection" groups="base.group_multi_company" />
<field name="currency" invisible="1"/>
</group><group>
<label for="balance_start"/>
<div>
<field name="balance_start" class="oe_inline"/>
<field name="currency" class="oe_inline"/>
</div>
<field name="balance_end_real"/>
<field name="balance_start" widget="monetary" options='{"currency_field" : "currency"}'/>
<field name="balance_end_real" widget="monetary" options='{"currency_field" : "currency"}'/>
</group>
</group>
@ -1130,8 +1127,8 @@
<field name="account_tax_id" domain="[('parent_id','=',False)]"/>
</group>
<group attrs="{'readonly':[('state','=','valid')]}" string="Currency" groups="base.group_multi_currency">
<field name="currency_id"/>
<field name="amount_currency"/>
<field name="currency_id" invisible="1"/>
<field name="amount_currency" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
<group string="Reconciliation">
<field name="reconcile_id"/>
@ -1177,8 +1174,8 @@
<field name="credit"/>
<separator colspan="4" string="Optional Information"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="amount_currency" groups="base.group_multi_currency"/>
<field name="currency_id" invisible="1"/>
<field name="amount_currency" groups="base.group_multi_currency" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="quantity"/>
<field name="move_id" required="False"/>
<newline/>
@ -2567,7 +2564,7 @@ action = pool.get('res.config').next(cr, uid, [], context)
<field name="date" attrs="{'readonly':[('state','!=','draft')]}" on_change="onchange_date(date, company_id)"/>
<field name="closing_date" readonly="1"/>
<field name="period_id" class="oe_inline"/>
<field name="currency" invisible="1" groups="base.group_multi_currency"/>
<field name="currency" invisible="1"/>
</group>
</group>
<notebook>
@ -2629,12 +2626,12 @@ action = pool.get('res.config').next(cr, uid, [], context)
<group col="6" colspan="4">
<group col="2" colspan="2">
<separator string="Opening Balance" colspan="4"/>
<field name="balance_start" readonly="1" string="Opening Cash Control"/>
<field name="last_closing_balance" readonly="1" string="Last Closing Balance" />
<field name="total_entry_encoding" />
<field name="balance_start" readonly="1" string="Opening Cash Control" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="last_closing_balance" readonly="1" string="Last Closing Balance" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="total_entry_encoding" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
<group string="Closing Balance">
<field name="balance_end"/>
<field name="balance_end" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
</group>
</sheet>

View File

@ -14,8 +14,8 @@
<field name="taxes_id" colspan="2" attrs="{'readonly':[('sale_ok','=',0)]}" widget="many2many_tags"/>
</group>
<group>
<field name="property_account_expense" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]" attrs="{'readonly':[('purchase_ok','=',0)]}"/>
<field name="supplier_taxes_id" colspan="2" attrs="{'readonly':[('purchase_ok','=',0)]}" widget="many2many_tags"/>
<field name="property_account_expense" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]" />
<field name="supplier_taxes_id" colspan="2" widget="many2many_tags"/>
</group>
</group>
</page>

View File

@ -163,7 +163,7 @@
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_all(get_objects(data['form']['empty_acc']),data['form']['date1'],data['form']['date2'],'credit')) ]] </para>
</td>
<td>
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_balance(get_objects(data['form']['empty_acc']),data['form']['date1'],data['form']['date2'])) ]] [[ company.currency_id.symbol]]</para>
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_balance(get_objects(data['form']['empty_acc']),data['form']['date1'],data['form']['date2']), currency_obj = company.currency_id)]]</para>
</td>
<td>
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_all(get_objects(data['form']['empty_acc']),data['form']['date1'],data['form']['date2'],'quantity')) ]]</para>
@ -187,7 +187,7 @@
<para style="terp_default_Right_9_Bold">[[ formatLang(move_sum(o['id'],data['form']['date1'],data['form']['date2'],'credit')) ]] </para>
</td>
<td>
<para style="terp_default_Right_9_Bold">[[ formatLang(move_sum_balance(o['id'],data['form']['date1'],data['form']['date2'])) ]] [[ company.currency_id.symbol]]</para>
<para style="terp_default_Right_9_Bold">[[ formatLang(move_sum_balance(o['id'],data['form']['date1'],data['form']['date2']), currency_obj = company.currency_id)]]</para>
</td>
<td>
<para style="terp_default_Right_9_Bold">[[ formatLang(move_sum(o['id'],data['form']['date1'],data['form']['date2'],'quantity')) ]]</para>
@ -211,7 +211,7 @@
<para style="terp_default_Right_9">[[ formatLang(move_g['credit']) ]] </para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang(move_g['balance'])]] [[ company.currency_id.symbol ]]</para>
<para style="terp_default_Right_9">[[ formatLang(move_g['balance'], currency_obj = company.currency_id) ]]</para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang(move_g['quantity']) ]]</para>

View File

@ -224,7 +224,7 @@
<para style="terp_Default_Bold_Right_9_U">[[ formatLang (sum_credit(objects,data['form']['date1'],data['form']['date2'])) ]] </para>
</td>
<td>
<para style="terp_Default_Bold_Right_9_U">[[ formatLang (sum_balance(objects,data['form']['date1'],data['form']['date2'])) ]] [[ company.currency_id.symbol ]]</para>
<para style="terp_Default_Bold_Right_9_U">[[ formatLang (sum_balance(objects,data['form']['date1'],data['form']['date2']), currency_obj = company.currency_id) ]]</para>
</td>
</tr>
</blockTable>
@ -245,7 +245,7 @@
<para style="terp_Default_Bold_Right_9_U">[[ formatLang (account_sum_credit(account,data['form']['date1'],data['form']['date2'])) ]] </para>
</td>
<td>
<para style="terp_Default_Bold_Right_9_U">[[ formatLang (account_sum_balance(account,data['form']['date1'],data['form']['date2']))]] [[ company.currency_id.symbol ]]</para>
<para style="terp_Default_Bold_Right_9_U">[[ formatLang (account_sum_balance(account,data['form']['date1'],data['form']['date2']), currency_obj = company.currency_id) ]]</para>
</td>
</tr>
</blockTable>
@ -266,7 +266,7 @@
<para style="terp_Default_Bold_Right_9_U">[[ formatLang( move_g['credit']) ]] </para>
</td>
<td>
<para style="terp_Default_Bold_Right_9_U">[[ formatLang( move_g['balance']) ]] [[ company.currency_id.symbol ]]</para>
<para style="terp_Default_Bold_Right_9_U">[[ formatLang( move_g['balance'], currency_obj = company.currency_id) ]]</para>
</td>
</tr>
</blockTable>
@ -290,7 +290,7 @@
<para style="terp_default_9_italic_Rignt">[[ formatLang( move_a['credit']) ]] </para>
</td>
<td>
<para style="terp_default_9_italic_Rignt">[[ formatLang( move_a['balance']) ]] [[ company.currency_id.symbol]]</para>
<para style="terp_default_9_italic_Rignt">[[ formatLang( move_a['balance'], currency_obj = company.currency_id)]]</para>
</td>
</tr>
</blockTable>

View File

@ -164,7 +164,7 @@
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_credit(objects,data['form']['date1'],data['form']['date2']))]]</para>
</td>
<td>
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_balance(objects,data['form']['date1'],data['form']['date2']))]] [[ company.currency_id.symbol]]</para>
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_balance(objects,data['form']['date1'],data['form']['date2']), currency_obj = company.currency_id)]]</para>
</td>
<td>
<para style="terp_tblheader_Details_Right">[[ formatLang(sum_quantity(objects,data['form']['date1'],data['form']['date2'])) ]]</para>
@ -188,7 +188,7 @@
<para style="terp_tblheader_Details_Right">[[formatLang(move_g['credit'])]]</para>
</td>
<td>
<para style="terp_tblheader_Details_Right">[[ formatLang(move_g['balance'])]] [[ company.currency_id.symbol]]</para>
<para style="terp_tblheader_Details_Right">[[ formatLang(move_g['balance'], currency_obj = company.currency_id)]]</para>
</td>
<td>
<para style="terp_tblheader_Details_Right">[[formatLang(move_g['quantity']) ]]</para>
@ -212,7 +212,7 @@
<para style="terp_default_Right_9">[[ formatLang(move_a['credit']) ]] </para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang(move_a['balance']) ]] [[ company.currency_id.symbol]]</para>
<para style="terp_default_Right_9">[[ formatLang(move_a['balance'], currency_obj = company.currency_id)]]</para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang(move_a['quantity']) ]]</para>

View File

@ -5,7 +5,7 @@
<field name="name">analytic.entries.report.tree</field>
<field name="model">analytic.entries.report</field>
<field name="arch" type="xml">
<tree string="Analytic Entries Analysis">
<tree string="Analytic Entries Analysis" create="false">
<field name="date" invisible="1"/>
<field name="year" invisible="1"/>
<field name="day" invisible="1"/>

View File

@ -5,7 +5,7 @@
<field name="name">account.entries.report.tree</field>
<field name="model">account.entries.report</field>
<field name="arch" type="xml">
<tree colors="blue:move_state == 'draft';black:move_state == 'posted'" string="Entries Analysis">
<tree colors="blue:move_state == 'draft';black:move_state == 'posted'" create="false" string="Entries Analysis">
<field name="date" invisible="1"/>
<field name="date_created" invisible="1"/>
<field name="date_maturity" invisible="1"/>

View File

@ -5,7 +5,7 @@
<field name="name">account.invoice.report.tree</field>
<field name="model">account.invoice.report</field>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';gray:state in ('cancel','paid');black:state in ('proforma','proforma2')" string="Invoices Analysis">
<tree colors="blue:state == 'draft';gray:state in ('cancel','paid');black:state in ('proforma','proforma2')" create="false" string="Invoices Analysis">
<field name="date" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="year" invisible="1"/>

View File

@ -29,34 +29,11 @@ class Overdue(report_sxw.rml_parse):
super(Overdue, self).__init__(cr, uid, name, context=context)
self.localcontext.update( {
'time': time,
'adr_get': self._adr_get,
'getLines': self._lines_get,
'tel_get': self._tel_get,
'message': self._message,
})
self.context = context
def _adr_get(self, partner, type):
res = []
res_partner = pooler.get_pool(self.cr.dbname).get('res.partner')
addresses = res_partner.address_get(self.cr, self.uid, [partner.id], [type])
adr_id = addresses and addresses[type] or False
result = {
'name': False,
'street': False,
'street2': False,
'city': False,
'zip': False,
'state_id':False,
'country_id': False,
}
if adr_id:
result = res_partner.read(self.cr, self.uid, [adr_id], context=self.context.copy())
result[0]['country_id'] = result[0]['country_id'] and result[0]['country_id'][1] or False
result[0]['state_id'] = result[0]['state_id'] and result[0]['state_id'][1] or False
return result
res.append(result)
return res
def _tel_get(self,partner):
if not partner:

View File

@ -126,15 +126,10 @@
<blockTable colWidths="286.0,224.0" style="Tableau2">
<tr>
<td>
<para style="terp_default_9">[[ repeatIn(adr_get(o, 'invoice'),'addr') ]]</para>
</td>
<td>
<para style="terp_default_9">[[ o.title.name or '' ]] [[ o.name ]]</para>
<para style="terp_default_9">[[ addr['street'] ]]</para>
<para style="terp_default_9">[[ addr['street2'] or removeParentNode('para') ]]</para>
<para style="terp_default_9">[[ addr['zip'] ]] [[ addr['city'] ]]</para>
<para style="terp_default_9">[[ addr['state_id'] or removeParentNode('para')]]</para>
<para style="terp_default_9">[[ addr['country_id'] ]]</para>
<para style="terp_default_9">[[ display_address(o.partner_id)] ]]</para>
<para style="terp_default_9">
<font color="white"> </font>
</para>
@ -216,7 +211,7 @@
<para style="terp_default_Right_9">[[ (line['account_id']['type'] == 'receivable' and formatLang(line['credit']) or 0) or (line['account_id']['type'] == 'payable' and formatLang(line['debit'] * -1) or 0) ]]</para>
</td>
<td>
<para style="terp_default_Right_9">[[ time.strftime('%Y-%m-%d') &gt; formatLang((line['date_maturity'])) and formatLang(line['debit'] - line['credit']) ]] [[ company.currency_id.symbol ]]</para>
<para style="terp_default_Right_9">[[ time.strftime('%Y-%m-%d') &gt; formatLang((line['date_maturity'])) and formatLang(line['debit'] - line['credit'], currency_obj = company.currency_id) ]]</para>
</td>
<td>
<para style="terp_default_Centre_9">[[ line['blocked'] and 'X' or '' ]]</para>
@ -241,7 +236,7 @@
<para style="terp_default_Right_9">[[ formatLang((reduce(lambda x, y: x + ((y['account_id']['type'] == 'receivable' and y['credit'] or 0) or (y['account_id']['type'] == 'payable' and y['debit'] * -1 or 0)), getLines(o), 0))) ]] </para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang((reduce(lambda x, y: x + (y['debit'] - y['credit']), filter(lambda x: x['date_maturity'] &lt; time.strftime('%Y-%m-%d'), getLines(o)), 0))) ]] [[ company.currency_id.symbol ]]</para>
<para style="terp_default_Right_9">[[ formatLang((reduce(lambda x, y: x + (y['debit'] - y['credit']), filter(lambda x: x['date_maturity'] &lt; time.strftime('%Y-%m-%d'), getLines(o)), 0)), currency_obj=company.currency_id) ]]</para>
</td>
<td>
<para style="terp_default_9">
@ -261,7 +256,7 @@
<para style="terp_default_Bold_9">Balance :</para>
</td>
<td>
<para style="terp_tblheader_Details_Right">[[ formatLang((reduce(lambda x, y: x +(y['debit'] - y['credit']), getLines(o), 0))) ]] [[ company.currency_id.symbol ]]</para>
<para style="terp_tblheader_Details_Right">[[ formatLang((reduce(lambda x, y: x +(y['debit'] - y['credit']), getLines(o), 0)), currency_obj = company.currency_id) ]]</para>
</td>
<td>
<para style="terp_default_8">

View File

@ -5,7 +5,7 @@
<field name="name">account.treasury.report.tree</field>
<field name="model">account.treasury.report</field>
<field name="arch" type="xml">
<tree string="Treasury Analysis">
<tree string="Treasury Analysis" create="false">
<field name="date" invisible="1"/>
<field name="fiscalyear_id"/>
<field name="period_id"/>

View File

@ -7,15 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2011-02-01 18:11+0000\n"
"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) "
"<openerp@novotrade.hu>\n"
"PO-Revision-Date: 2012-10-09 13:58+0000\n"
"Last-Translator: krnkris <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-28 06:22+0000\n"
"X-Generator: Launchpad (build 15864)\n"
"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n"
"X-Generator: Launchpad (build 16112)\n"
#. module: account_analytic_default
#: help:account.analytic.default,partner_id:0
@ -134,12 +133,14 @@ msgstr "Gyűjtőkód alapértelmezések"
#. module: account_analytic_default
#: sql_constraint:stock.picking:0
msgid "Reference must be unique per Company!"
msgstr ""
msgstr "Minden vállalkozáshoz egyedi hivatkozás kell!"
#. module: account_analytic_default
#: view:account.analytic.default:0
msgid "Analytical defaults whose end date is greater than today or None"
msgstr ""
"A mai napnál későbbi lejáratú vagy nem létező dátumú elemzések "
"alapértelmezései"
#. module: account_analytic_default
#: help:account.analytic.default,product_id:0

View File

@ -106,27 +106,27 @@
</group>
<notebook colspan="4">
<page string="General">
<group>
<group>
<field name="purchase_value"/>
<field name="salvage_value"/>
<field name="value_residual"/>
<group>
<group>
<field name="purchase_value" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="salvage_value" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="value_residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="partner_id"/>
</group>
<group>
<field name="method"/>
<field name="method_progress_factor" attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
</group>
<group>
<field name="method"/>
<field name="method_progress_factor" attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
<label for="method_time"/>
<div>
<field name="method_time" on_change="onchange_method_time(method_time)" class="oe_inline"/>
<button name="%(action_asset_modify)d" states="open" string="Change Duration" type="action" icon="terp-stock_effects-object-colorize" class="oe_inline" colspan="1"/>
<field name="method_time" on_change="onchange_method_time(method_time)" class="oe_inline"/>
<button name="%(action_asset_modify)d" states="open" string="Change Duration" type="action" icon="terp-stock_effects-object-colorize" class="oe_inline" colspan="1"/>
</div>
<field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
<field name="method_number" attrs="{'invisible':[('method_time','=','end')], 'required':[('method_time','=','number')]}"/>
<field name="method_period"/>
<field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','=','number')]}"/>
</group>
</group>
<field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
<field name="method_number" attrs="{'invisible':[('method_time','=','end')], 'required':[('method_time','=','number')]}"/>
<field name="method_period"/>
<field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','=','number')]}"/>
</group>
</group>
</page>
<page string="Depreciation Board">
<field name="depreciation_line_ids" mode="tree">

View File

@ -6,7 +6,7 @@
<field name="name">asset.asset.report.tree</field>
<field name="model">asset.asset.report</field>
<field name="arch" type="xml">
<tree string="Assets">
<tree string="Assets" create="false">
<field name="name" invisible="1"/>
<field name="asset_id" invisible="1"/>
<field name="asset_category_id" invisible="1"/>

View File

@ -7,15 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2011-01-30 16:38+0000\n"
"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) "
"<openerp@novotrade.hu>\n"
"PO-Revision-Date: 2012-10-09 13:49+0000\n"
"Last-Translator: krnkris <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-28 06:27+0000\n"
"X-Generator: Launchpad (build 15864)\n"
"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n"
"X-Generator: Launchpad (build 16112)\n"
#. module: account_budget
#: field:crossovered.budget,creating_user_id:0
@ -105,6 +104,7 @@ msgid "Percentage"
msgstr "Százalék"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:crossovered.budget.report:0
msgid "to"
msgstr "-"
@ -138,21 +138,24 @@ msgstr ""
#: code:addons/account_budget/account_budget.py:119
#, python-format
msgid "The Budget '%s' has no accounts!"
msgstr ""
msgstr "A '%s' költségvetésnek nincs számlaszáma!"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "%"
msgstr "%"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Description"
msgstr "Leírás"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:crossovered.budget.report:0
msgid "Currency"
msgstr "Pénznem"
@ -187,6 +190,7 @@ msgid "Planned Amount"
msgstr "Tervezett összeg"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Perc(%)"
@ -199,6 +203,7 @@ msgid "Done"
msgstr "Kész"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Practical Amt"
@ -259,7 +264,7 @@ msgstr "Üzleti terv"
#. module: account_budget
#: view:crossovered.budget:0
msgid "To Approve Budgets"
msgstr ""
msgstr "Költségvetés jóváhagyása"
#. module: account_budget
#: code:addons/account_budget/account_budget.py:119
@ -333,6 +338,7 @@ msgid "Account Budget crossvered summary report"
msgstr "Üzleti terv összesítő kimutatás"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Theoretical Amt"
@ -359,9 +365,10 @@ msgstr "Nyomtatás"
#: view:crossovered.budget:0
#: field:crossovered.budget.lines,theoritical_amount:0
msgid "Theoretical Amount"
msgstr ""
msgstr "Elméleti mennyiség"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: field:crossovered.budget.lines,analytic_account_id:0
#: model:ir.model,name:account_budget.model_account_analytic_account
msgid "Analytic Account"
@ -373,6 +380,7 @@ msgid "Budget :"
msgstr "Üzleti terv :"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Planned Amt"
@ -413,6 +421,7 @@ msgid "Error! You can not create recursive analytic accounts."
msgstr "Hiba! Nem hozhat létre rekurzív gyűjtőkódokat."
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Analysis from"
@ -421,7 +430,7 @@ msgstr "Elemzési időszak"
#. module: account_budget
#: view:crossovered.budget:0
msgid "Draft Budgets"
msgstr ""
msgstr "Előzetes, tervezett költségvetés"
#, python-format
#~ msgid "The General Budget '%s' has no Accounts!"

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-02-08 00:35+0000\n"
"PO-Revision-Date: 2011-01-19 15:50+0000\n"
"PO-Revision-Date: 2012-10-09 16:05+0000\n"
"Last-Translator: Brice Muangkhot ສຸພາ ເມືອງໂຄຕ <bmuangkhot@gmail.com>\n"
"Language-Team: Lao <lo@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-28 06:27+0000\n"
"X-Generator: Launchpad (build 15864)\n"
"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n"
"X-Generator: Launchpad (build 16112)\n"
#. module: account_budget
#: field:crossovered.budget,creating_user_id:0
@ -31,7 +31,7 @@ msgstr "ຮັບຮູ້ເເລັວ"
#: model:ir.actions.act_window,name:account_budget.open_budget_post_form
#: model:ir.ui.menu,name:account_budget.menu_budget_post_form
msgid "Budgetary Positions"
msgstr "ຈຸດງົບປະມານ"
msgstr "ຕໍາແຫນ່ງງົບປະມານ"
#. module: account_budget
#: report:account.budget:0
@ -105,6 +105,7 @@ msgid "Percentage"
msgstr "ເປີເຊັນ"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:crossovered.budget.report:0
msgid "to"
msgstr "ຫາ"
@ -126,6 +127,14 @@ msgid ""
"revenue per analytic account and monitor its evolution based on the actuals "
"realised during that period."
msgstr ""
"ງົບປະມານແມ່ນຄາດຄະເນລາຍໄດ້ຂອງບໍລິສັດຂອງທ່ານແລະຄ່າໃຊ້ຈ່າຍຄາດວ່າຈະສໍາລັບໄລຍະເວລາ"
"ໃນອະນາຄົດໄດ້. ມີງົບປະມານ, "
"ບໍລິສັດສາມາດລະມັດລະວັງເບິ່ງວິທີການເງິນຫຼາຍປານເຂົາເຈົ້າໄດ້ຖືກກິນໃນໄລຍະເວລາໃນໄລ"
"ຍະໃດຫນຶ່ງ, ແລະສະວິທີທີ່ດີທີ່ສຸດເພື່ອແບ່ງມັນໃນບັນດາປະເພດຂອງລາຍການຕ່າງໆ. "
"ໂດຍການຮັກສາຕິດຕາມບ່ອນທີ່ເງິນຂອງທ່ານດີ, ທ່ານອາດຈະມີຫນ້ອຍອາດຈະ overspend, "
"ແລະອື່ນໆທີ່ອາດຈະຕອບສະຫນອງໄດ້ຕາມເປົ້າຫມາຍທາງດ້ານການເງິນຂອງທ່ານ. "
"ຄາດງົບປະມານໂດຍ detailing ລາຍຮັບຄາດວ່າຈະຕໍ່ບັນຊີແຍກທາດແລະຕິດຕາມກວດກາຂອງຕົນ "
"evolution ອີງຕາມຕົວຈິງໃນ realized ໃນໄລຍະນັ້ນ."
#. module: account_budget
#: code:addons/account_budget/account_budget.py:119
@ -134,18 +143,21 @@ msgid "The Budget '%s' has no accounts!"
msgstr ""
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "%"
msgstr ""
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Description"
msgstr "ຄຳອະທິບາຍ"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:crossovered.budget.report:0
msgid "Currency"
msgstr "ເງິນຕາ"
@ -170,7 +182,7 @@ msgstr "ເພື່ອຮັບຮູ້"
#. module: account_budget
#: view:crossovered.budget:0
msgid "Reset to Draft"
msgstr ""
msgstr "ປັບກັບຮ່າງ"
#. module: account_budget
#: view:account.budget.post:0
@ -180,6 +192,7 @@ msgid "Planned Amount"
msgstr "ຈໍານວນຄາດໝາຍມ້ຽນ"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Perc(%)"
@ -192,6 +205,7 @@ msgid "Done"
msgstr "ຈົບເເລັວ"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Practical Amt"
@ -203,7 +217,7 @@ msgstr ""
#: view:crossovered.budget:0
#: field:crossovered.budget.lines,practical_amount:0
msgid "Practical Amount"
msgstr ""
msgstr "ຈໍານວນເງິນປະຕິບັດໄດ້"
#. module: account_budget
#: field:crossovered.budget,date_to:0
@ -292,7 +306,7 @@ msgstr ""
#. module: account_budget
#: selection:crossovered.budget,state:0
msgid "Cancelled"
msgstr ""
msgstr "ຍົກເລີກ"
#. module: account_budget
#: view:crossovered.budget:0
@ -326,6 +340,7 @@ msgid "Account Budget crossvered summary report"
msgstr ""
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Theoretical Amt"
@ -355,6 +370,7 @@ msgid "Theoretical Amount"
msgstr ""
#. module: account_budget
#: report:account.analytic.account.budget:0
#: field:crossovered.budget.lines,analytic_account_id:0
#: model:ir.model,name:account_budget.model_account_analytic_account
msgid "Analytic Account"
@ -366,6 +382,7 @@ msgid "Budget :"
msgstr "ງົບປະມານ"
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Planned Amt"
@ -406,6 +423,7 @@ msgid "Error! You can not create recursive analytic accounts."
msgstr ""
#. module: account_budget
#: report:account.analytic.account.budget:0
#: report:account.budget:0
#: report:crossovered.budget.report:0
msgid "Analysis from"

View File

@ -32,7 +32,6 @@ class report_print_check(report_sxw.rml_parse):
'time': time,
'get_lines': self.get_lines,
'fill_stars' : self.fill_stars,
'get_zip_line': self.get_zip_line,
})
def fill_stars(self, amount):
amount = amount.replace('Dollars','')
@ -41,25 +40,6 @@ class report_print_check(report_sxw.rml_parse):
return ' '.join([amount,'*'*stars])
else: return amount
def get_zip_line(self, address):
'''
Get the address line
'''
ret = ''
if address:
if address.city:
ret += address.city
if address.state_id:
if address.state_id.name:
if ret:
ret += ', '
ret += address.state_id.name
if address.zip:
if ret:
ret += ' '
ret += address.zip
return ret
def get_lines(self, voucher_lines):
result = []

View File

@ -281,10 +281,7 @@
</td>
<td>
<para style="P15">[[ voucher.partner_id.name ]]</para>
<para style="P15">[[ voucher.partner_id.street or removeParentNode('para') ]]</para>
<para style="P15">[[ voucher.partner_id.street2 or removeParentNode('para') ]]</para>
<para style="P15">[[ get_zip_line(voucher.partner_id) ]] </para>
<para style="P15">[[ voucher.partner_id.country_id.name]]</para>
<para style="P15">[[ display_address(voucher.partner_id) or removeParentNode('para') ]]</para>
</td>
</tr>
</blockTable>

View File

@ -237,10 +237,7 @@
</td>
<td>
<para style="P15">[[ voucher.partner_id.name ]]</para>
<para style="P15">[[ voucher.partner_id.street or removeParentNode('para') ]]</para>
<para style="P15">[[ voucher.partner_id.street2 or removeParentNode('para') ]]</para>
<para style="P15">[[ get_zip_line(voucher.partner_id) ]] </para>
<para style="P15">[[ voucher.partner_id.country_id.name]]</para>
<para style="P15">[[ display_address(voucher.partner_id) or removeParentNode('para') ]]</para>
</td>
</tr>
</blockTable>

View File

@ -153,9 +153,7 @@
</td>
<td>
<para style="P9">[[ voucher.partner_id.name ]] </para>
<para style="P15">[[ voucher.partner_id.street2 or removeParentNode('para') ]]</para>
<para style="P15">[[ get_zip_line(voucher.partner_id) ]] </para>
<para style="P15">[[ voucher.partner_id.country_id.name]]</para>
<para style="P15">[[ display_address(voucher.partner_id) or removeParentNode('para') ]]</para>
</td>
<td>
<para/>

View File

@ -30,7 +30,6 @@ class report_rappel(report_sxw.rml_parse):
self.localcontext.update({
'time': time,
'ids_to_objects': self._ids_to_objects,
'adr_get': self._adr_get,
'getLines': self._lines_get,
'get_text': self._get_text
})
@ -43,11 +42,6 @@ class report_rappel(report_sxw.rml_parse):
all_lines.append(line)
return all_lines
def _adr_get(self, stat_line, type):
res_partner = pooler.get_pool(self.cr.dbname).get('res.partner')
adr = res_partner.address_get(self.cr, self.uid, [stat_line.partner_id.id], [type])[type]
return adr and res_partner.read(self.cr, self.uid, [adr]) or [{}]
def _lines_get(self, stat_by_partner_line):
pool = pooler.get_pool(self.cr.dbname)
moveline_obj = pool.get('account.move.line')

View File

@ -129,15 +129,10 @@
<blockTable colWidths="298.0,234.0" style="Tableau2">
<tr>
<td>
<para style="terp_default_9">[[ repeatIn(adr_get(o,'invoice'),'a' )]]</para>
</td>
<td>
<para style="terp_default_9">[[ o.partner_id.name or '' ]]</para>
<para style="terp_default_9">[[ a['street'] or '']]</para>
<para style="terp_default_9">[[ a['street2'] or removeParentNode('para') ]]</para>
<para style="terp_default_9">[[ a['zip'] or '' ]] [[ a['city'] or '' ]]</para>
<para style="terp_default_9">[[( a['state_id'] and a['state_id'][1]) or removeParentNode('para') ]]</para>
<para style="terp_default_9">[[( a['country_id'] and a['country_id'][1]) or '']]</para>
<para style="terp_default_9">[[ display_address(o.partner_id) or '']]</para>
<para style="terp_default_9">
<font color="white"> </font>
</para>

View File

@ -256,10 +256,10 @@
<para style="terp_default_Centre_9">[[line.date=='False' and '-' or formatLang(line.date,date=True) ]]</para>
</td>
<td>
<para style="terp_default_Right_9">[[formatLang(line.amount) or '-' ]] [[get_company_currency_symbol()]] </para>
<para style="terp_default_Right_9">[[formatLang(line.amount, currency_obj= user.company_id.currency_id) or '-' ]] </para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang(line.amount_currency) ]] [[ line.currency.symbol]] </para>
<para style="terp_default_Right_9">[[ formatLang(line.amount_currency, currency_obj= line.currency)]]</para>
</td>
</tr>
</blockTable>
@ -275,10 +275,10 @@
<para style="terp_default_Bold_9">Total:</para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang(get_amount_total(o)) or '' ]] [[get_company_currency_symbol()]] </para>
<para style="terp_default_Right_9">[[ formatLang(get_amount_total(o), currency_obj= user.company_id.currency_id) or '' ]] </para>
</td>
<td>
<para style="terp_default_Right_9">[[ formatLang(get_amount_total_in_currency(o)) or '' ]] [[get_company_currency_symbol()]] </para>
<para style="terp_default_Right_9">[[ formatLang(get_amount_total_in_currency(o), currency_obj= user.company_id.currency_id) or '' ]] </para>
</td>
</tr>
</blockTable>

View File

@ -32,7 +32,6 @@ class payment_order(report_sxw.rml_parse):
'time': time,
'get_invoice_name': self._get_invoice_name,
'get_company_currency': self._get_company_currency,
'get_company_currency_symbol': self._get_company_currency_symbol,
'get_amount_total_in_currency': self._get_amount_total_in_currency,
'get_amount_total': self._get_amount_total,
'get_account_name': self._get_account_name,
@ -72,12 +71,8 @@ class payment_order(report_sxw.rml_parse):
user = pool.get('res.users').browse(self.cr, self.uid, self.uid)
return user.company_id and user.company_id.currency_id and user.company_id.currency_id.symbol or False
def _get_company_currency_symbol(self):
pool = pooler.get_pool(self.cr.dbname)
user = pool.get('res.users').browse(self.cr, self.uid, self.uid)
return user.company_id and user.company_id.currency_id and user.company_id.currency_id.symbol or False
def _get_account_name(self, bank_id):
def _get_account_name(self,bank_id):
if bank_id:
pool = pooler.get_pool(self.cr.dbname)
value_name = pool.get('res.partner.bank').name_get(self.cr, self.uid, [bank_id])

View File

@ -1070,7 +1070,8 @@ class account_voucher(osv.osv):
if line.amount == line.amount_unreconciled:
if not line.move_line_id.amount_residual:
raise osv.except_osv(_('Wrong bank statement line'),_("You have to delete the bank statement line which the payment was reconciled to manually. Please check the payment of the partner %s by the amount of %s.")%(line.voucher_id.partner_id.name, line.voucher_id.amount))
currency_rate_difference = line.move_line_id.amount_residual - amount
sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
currency_rate_difference = sign * (line.move_line_id.amount_residual - amount)
else:
currency_rate_difference = 0.0
move_line = {

View File

@ -219,7 +219,7 @@
<para style="terp_default_9_30cm">[[ p['pname'] ]]</para>
</td>
<td>
<para style="terp_default_Right_9">[[ p['amount'] ]] [[ voucher.currency_id.symbol ]]</para>
<para style="terp_default_Right_9">[[ formatLang(p['amount'], currency_obj=voucher.currency_id) ]]</para>
</td>
</tr>
<tr>
@ -323,7 +323,7 @@
</para>
</td>
<td>
<para style="terp_default_Right_9_Bold">[[ voucher.amount ]] [[ voucher.currency_id.symbol ]]</para>
<para style="terp_default_Right_9_Bold">[[ formatLang(voucher.amount, currency_obj=voucher.currency_id) ]]</para>
</td>
</tr>
</blockTable>

View File

@ -6,7 +6,7 @@
<field name="name">sale.receipt.report.tree</field>
<field name="model">sale.receipt.report</field>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';gray:state in ('cancel','paid');black:state in ('proforma','proforma2')" string="Sales Receipt Analysis">
<tree colors="blue:state == 'draft';gray:state in ('cancel','paid');black:state in ('proforma','proforma2')" create="false" string="Sales Receipt Analysis">
<field name="date" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="year" invisible="1"/>

View File

@ -136,11 +136,8 @@
<group>
<group>
<field name="partner_id" required="1" invisible="context.get('line_type', False)" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" context="{'invoice_currency':currency_id, 'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}" string="Supplier"/>
<label for="amount" string="Amount"/>
<div>
<field name="amount" invisible="context.get('line_type', False)" on_change="onchange_amount(amount, payment_rate, partner_id, journal_id, currency_id, type, date, payment_rate_currency_id, company_id, context)" class="oe_inline"/>
<field name="currency_id" class="oe_inline"/>
</div>
<field name="currency_id" invisible="1"/>
<field name="amount" invisible="context.get('line_type', False)" on_change="onchange_amount(amount, payment_rate, partner_id, journal_id, currency_id, type, date, payment_rate_currency_id, company_id, context)" class="oe_inline" widget='monetary' options='{"currency_field": "currency_id"}'/>
<field name="journal_id"
domain="[('type','in',['bank', 'cash'])]"
invisible="context.get('line_type', False)"
@ -203,7 +200,7 @@
</group>
<group col="2">
<separator string="Payment Options" colspan="2"/>
<field name="writeoff_amount"/>
<field name="writeoff_amount" widget='monetary' options='{"currency_field": "currency_id"}'/>
<field name="payment_option" required="1"/>
<field name="writeoff_acc_id"
attrs="{'invisible':[('payment_option','!=','with_writeoff')], 'required':[('payment_option','=','with_writeoff')]}"
@ -294,13 +291,12 @@
<group>
<field name="state" invisible="1"/>
<field name="partner_id" required="1" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" string="Customer" context="{'search_default_customer': 1}"/>
<label for="amount" string="Paid Amount"/>
<div>
<field name="amount" class="oe_inline"
invisible="context.get('line_type', False)"
on_change="onchange_amount(amount, payment_rate, partner_id, journal_id, currency_id, type, date, payment_rate_currency_id, company_id, context)"/>
<field name="currency_id" class="oe_inline"/>
</div>
<field name="currency_id" invisible="1"/>
<field name="amount" class="oe_inline"
string="Paid Amount"
widget='monetary' options='{"currency_field": "currency_id"}'
invisible="context.get('line_type', False)"
on_change="onchange_amount(amount, payment_rate, partner_id, journal_id, currency_id, type, date, payment_rate_currency_id, company_id, context)"/>
<field name="journal_id"
domain="[('type','in',['bank', 'cash'])]"
invisible="context.get('line_type', False)"
@ -323,7 +319,7 @@
</group>
<notebook invisible="1">
<page string="Payment Information" groups="base.group_user">
<label for="line_cr_ids"/>
<label for="line_cr_ids"/>
<field name="line_cr_ids" context="{'journal_id':journal_id, 'type':type, 'partner_id':partner_id}" on_change="onchange_line_ids(line_dr_ids, line_cr_ids, amount, currency_id, type, context)">
<tree string="Invoices and outstanding transactions" editable="bottom" colors="gray:amount==0">
<field name="move_line_id" context="{'journal_id':parent.journal_id, 'partner_id':parent.partner_id}"
@ -367,7 +363,7 @@
<field name="paid_amount_in_company_currency" colspan="4" invisible="1"/>
</group>
<group>
<field name="writeoff_amount"/>
<field name="writeoff_amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="payment_option" required="1"/>
<field name="writeoff_acc_id"
attrs="{'invisible':[('payment_option','!=','with_writeoff')], 'required':[('payment_option','=','with_writeoff')]}"
@ -406,14 +402,12 @@
<group>
<group>
<field name="partner_id" domain="[('customer','=',True)]" required="1" invisible="context.get('line_type', False)" on_change="onchange_partner_id(partner_id, journal_id, amount, currency_id, type, date, context)" string="Customer" context="{'search_default_customer': 1}"/>
<label for="amount" string="Paid Amount"/>
<div>
<field name="amount" class="oe_inline"
invisible="context.get('line_type', False)"
on_change="onchange_amount(amount, payment_rate, partner_id, journal_id, currency_id, type, date, payment_rate_currency_id, company_id, context)"/>
<field name="currency_id" class="oe_inline"/>
</div>
<field name="currency_id" invisible="1"/>
<field name="amount" class="oe_inline"
string="Paid Amount"
widget="monetary" options="{'currency_field': 'currency_id'}"
invisible="context.get('line_type', False)"
on_change="onchange_amount(amount, payment_rate, partner_id, journal_id, currency_id, type, date, payment_rate_currency_id, company_id, context)"/>
<field name="journal_id"
domain="[('type','in',['bank', 'cash'])]"
invisible="context.get('line_type', False)"
@ -436,7 +430,7 @@
</group>
<notebook>
<page string="Payment Information" groups="base.group_user">
<label for="line_cr_ids"/>
<label for="line_cr_ids"/>
<field name="line_cr_ids" context="{'journal_id':journal_id, 'type':type, 'partner_id':partner_id}" on_change="onchange_line_ids(line_dr_ids, line_cr_ids, amount, currency_id, type, context)">
<tree string="Invoices and outstanding transactions" editable="bottom" colors="gray:amount==0">
<field name="move_line_id" context="{'journal_id':parent.journal_id, 'partner_id':parent.partner_id}"
@ -480,7 +474,7 @@
<field name="paid_amount_in_company_currency" colspan="4" invisible="1"/>
</group>
<group>
<field name="writeoff_amount"/>
<field name="writeoff_amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="payment_option" required="1"/>
<field name="writeoff_acc_id"
attrs="{'invisible':[('payment_option','!=','with_writeoff')], 'required':[('payment_option','=','with_writeoff')]}"

View File

@ -73,7 +73,7 @@
<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="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"/>
</group>
<group>

View File

@ -145,7 +145,7 @@
<field name="name">audittrail.log.tree</field>
<field name="model">audittrail.log</field>
<field name="arch" type="xml">
<tree string="AuditTrail Logs">
<tree string="AuditTrail Logs" create="false">
<field name="timestamp"/>
<field name="name"/>
<field name="object_id"/>

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<openerp>
<data>
<data noupdate="1">
<record id="provider_openerp" model="auth.oauth.provider">
<field name="name">OpenERP Accounts</field>

View File

@ -4,28 +4,25 @@ import simplejson
import werkzeug.urls
import werkzeug.utils
import openerp.modules.registry
import openerp.addons.web.controllers.main
import openerp.addons.web.common.http as openerpweb
from openerp import SUPERUSER_ID
import openerp
_logger = logging.getLogger(__name__)
class OAuthController(openerpweb.Controller):
class OAuthController(openerp.addons.web.http.Controller):
_cp_path = '/auth_oauth'
@openerpweb.jsonrequest
@openerp.addons.web.http.jsonrequest
def list_providers(self, req, dbname):
try:
registry = openerp.modules.registry.RegistryManager.get(dbname)
with registry.cursor() as cr:
providers = registry.get('auth.oauth.provider')
l = providers.read(cr, SUPERUSER_ID, providers.search(cr, SUPERUSER_ID, [('enabled','=',True)]))
l = providers.read(cr, openerp.SUPERUSER_ID, providers.search(cr, openerp.SUPERUSER_ID, [('enabled','=',True)]))
except Exception:
l = []
return l
@openerpweb.httprequest
@openerp.addons.web.http.httprequest
def signin(self, req, **kw):
state = simplejson.loads(kw['state'])
dbname = state['d']
@ -34,7 +31,7 @@ class OAuthController(openerpweb.Controller):
with registry.cursor() as cr:
try:
u = registry.get('res.users')
credentials = u.auth_oauth(cr, SUPERUSER_ID, provider, kw)
credentials = u.auth_oauth(cr, openerp.SUPERUSER_ID, provider, kw)
cr.commit()
return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials)
except AttributeError:
@ -43,7 +40,7 @@ class OAuthController(openerpweb.Controller):
url = "/#action=login&oauth_error=1"
except Exception,e:
# signup error
_logger.exception('oops')
_logger.exception("OAuth2: %s" % str(e))
url = "/#action=login&oauth_error=2"
return openerp.addons.web.controllers.main.set_cookie_and_redirect(req, url)

View File

@ -15,13 +15,17 @@ class res_users(osv.Model):
_inherit = 'res.users'
_columns = {
'oauth_provider_id': fields.many2one('auth.oauth.provider','OAuth Provider'),
'oauth_provider_id': fields.many2one('auth.oauth.provider', 'OAuth Provider'),
'oauth_uid': fields.char('OAuth User ID', help="Oauth Provider user_id"),
'oauth_access_token': fields.char('OAuth Token', readonly=True),
'oauth_access_token': fields.char('OAuth Access Token', readonly=True),
}
_sql_constraints = [
('uniq_users_oauth_provider_oauth_uid', 'unique(oauth_provider_id, oauth_uid)', 'OAuth UID must be unique per provider'),
]
def auth_oauth_rpc(self, cr, uid, endpoint, access_token, context=None):
params = urllib.urlencode({ 'access_token': access_token })
params = urllib.urlencode({'access_token': access_token})
if urlparse.urlparse(endpoint)[4]:
url = endpoint + '&' + params
else:
@ -41,22 +45,26 @@ class res_users(osv.Model):
validation = self.auth_oauth_rpc(cr, uid, p.validation_endpoint, access_token)
if validation.get("error"):
raise openerp.exceptions.AccessDenied
raise Exception(validation['error'])
if p.data_endpoint:
data = self.auth_oauth_rpc(cr, uid, p.data_endpoint, access_token)
validation.update(data)
# required
oauth_uid = validation['user_id']
if not oauth_uid:
raise openerp.exceptions.AccessDenied
raise openerp.exceptions.AccessDenied()
email = validation.get('email', 'provider_%d_user_%d' % (p.id, oauth_uid))
# optional
name = validation.get('name', email)
res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid)])
res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid), ('oauth_provider_id', '=', provider)])
if res:
self.write(cr, uid, res[0], { 'oauth_access_token': access_token })
assert len(res) == 1
self.write(cr, uid, res[0], {'oauth_access_token': access_token})
else:
# New user
# New user if signup module available
if not hasattr(self, '_signup_create_user'):
raise openerp.exceptions.AccessDenied()
new_user = {
'name': name,
'login': email,
@ -66,7 +74,9 @@ class res_users(osv.Model):
'oauth_access_token': access_token,
'active': True,
}
self.auth_signup_create(cr, uid, new_user)
# TODO pass signup token to allow attach new user to right partner
self._signup_create_user(cr, uid, new_user)
credentials = (cr.dbname, email, access_token)
return credentials
@ -74,7 +84,7 @@ class res_users(osv.Model):
try:
return super(res_users, self).check_credentials(cr, uid, password)
except openerp.exceptions.AccessDenied:
res = self.search(cr, SUPERUSER_ID, [('id','=',uid),('oauth_access_token','=',password)])
res = self.search(cr, SUPERUSER_ID, [('id', '=', uid), ('oauth_access_token', '=', password)])
if not res:
raise

View File

@ -23,24 +23,21 @@ import logging
import os
import tempfile
import urllib
from openerp import SUPERUSER_ID
import werkzeug.urls
import werkzeug.exceptions
from openerp.modules.registry import RegistryManager
from openerp.addons.web.controllers.main import login_and_redirect, set_cookie_and_redirect
try:
import openerp.addons.web.common.http as openerpweb
except ImportError:
import web.common.http as openerpweb # noqa
from openid import oidutil
from openid.store import filestore
from openid.consumer import consumer
from openid.cryptutil import randomString
from openid.extensions import ax, sreg
import openerp
from openerp import SUPERUSER_ID
from openerp.modules.registry import RegistryManager
from openerp.addons.web.controllers.main import login_and_redirect, set_cookie_and_redirect
from .. import utils
_logger = logging.getLogger(__name__)
@ -70,7 +67,7 @@ class GoogleAppsAwareConsumer(consumer.GenericConsumer):
return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to)
class OpenIDController(openerpweb.Controller):
class OpenIDController(openerp.addons.web.http.Controller):
_cp_path = '/auth_openid/login'
_store = filestore.FileOpenIDStore(_storedir)
@ -118,7 +115,7 @@ class OpenIDController(openerpweb.Controller):
def _get_realm(self, req):
return req.httprequest.host_url
@openerpweb.httprequest
@openerp.addons.web.http.httprequest
def verify_direct(self, req, db, url):
result = self._verify(req, db, url)
if 'error' in result:
@ -127,7 +124,7 @@ class OpenIDController(openerpweb.Controller):
return werkzeug.utils.redirect(result['value'])
return result['value']
@openerpweb.jsonrequest
@openerp.addons.web.http.jsonrequest
def verify(self, req, db, url):
return self._verify(req, db, url)
@ -157,7 +154,7 @@ class OpenIDController(openerpweb.Controller):
form_html = request.htmlMarkup(realm, redirect_to)
return {'action': 'post', 'value': form_html, 'session_id': req.session_id}
@openerpweb.httprequest
@openerp.addons.web.http.httprequest
def process(self, req, **kw):
session = getattr(req.session, 'openid_session', None)
if not session:
@ -225,7 +222,7 @@ class OpenIDController(openerpweb.Controller):
return set_cookie_and_redirect(req, '/#action=login&loginerror=1')
@openerpweb.jsonrequest
@openerp.addons.web.http.jsonrequest
def status(self, req):
session = getattr(req.session, 'openid_session', {})
return {'status': session.get('status'), 'message': session.get('message')}

View File

@ -1 +1,23 @@
import auth_reset_password
# -*- 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 controllers
import res_users

View File

@ -1,3 +1,24 @@
# -*- 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/>
#
##############################################################################
{
'name': 'Reset Password',
'description': """
@ -9,9 +30,11 @@ Allow users to reset their password from the login page.
'category': 'Authentication',
'website': 'http://www.openerp.com',
'installable': True,
'depends': ['auth_anonymous', 'email_template'],
'data': ['auth_reset_password.xml'],
'depends': ['auth_signup', 'email_template'],
'data': [
'auth_reset_password.xml',
'res_users_view.xml',
],
'js': ['static/src/js/reset_password.js'],
'css': ['static/src/css/reset_password.css'],
'qweb': ['static/src/xml/reset_password.xml'],
}

View File

@ -1,130 +0,0 @@
import base64
import hashlib
import simplejson
import time
import urlparse
from openerp.tools import config
from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
TWENTY_FOUR_HOURS = 24 * 60 * 60
def message_sign(data, secret):
src = simplejson.dumps([data, secret], indent=None, separators=(',', ':'), sort_keys=True)
sign = hashlib.sha1(src).hexdigest()
msg = simplejson.dumps([data, sign], indent=None, separators=(',', ':'), sort_keys=True)
# pad message to avoid '='
pad = (3 - len(msg) % 3) % 3
msg = msg + " " * pad
msg = base64.urlsafe_b64encode(msg)
return msg, sign
def message_check(msg, secret):
msg = base64.urlsafe_b64decode(msg)
l = simplejson.loads(msg)
msg_data = l[0]
msg_sign = l[1]
tmp, sign = message_sign(msg_data, secret)
if msg_sign == sign:
return msg_data
class res_users(osv.osv):
_inherit = 'res.users'
def _auth_reset_password_secret(self, cr, uid, context=None):
uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
res = {
'dbname': cr.dbname,
'uuid': uuid,
'admin_passwd': config['admin_passwd']
}
return res
def _auth_reset_password_host(self, cr, uid, context=None):
return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', '')
def _auth_reset_password_link(self, cr, uid, ids, context=None):
assert len(ids) == 1
host = self._auth_reset_password_host(cr, uid, context)
secret = self._auth_reset_password_secret(cr, uid, context)
msg_src = {
'time': time.time(),
'uid': ids[0],
}
msg, sign = message_sign(msg_src, secret)
link = urlparse.urljoin(host, '/login?db=%s&login=anonymous&key=anonymous#action=reset_password&token=%s' % (cr.dbname, msg))
return link
def _auth_reset_password_check_token(self, cr, uid, token, context=None):
secret = self._auth_reset_password_secret(cr, uid, context)
data = message_check(token, secret)
if data and (time.time() - data['time'] < TWENTY_FOUR_HOURS):
return data
return None
def _auth_reset_password_send_email(self, cr, uid, email_to, tpl_name, res_id, context=None):
model, tpl_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_reset_password', tpl_name)
assert model == 'email.template'
msg_id = self.pool.get(model).send_mail(cr, uid, tpl_id, res_id, force_send=False, context=context)
MailMessage = self.pool.get('mail.message')
MailMessage.write(cr, uid, [msg_id], {'email_to': email_to}, context=context)
MailMessage.send(cr, uid, [msg_id], context=context)
def send_reset_password_request(self, cr, uid, email, context=None):
# TODO reseting a password knowing only an email is not good enough (email can be shared between multiple logins).
ids = self.pool.get('res.users').search(cr, SUPERUSER_ID, [('user_email', '=', email)], context=context)
if ids:
self._auth_reset_password_send_email(cr, SUPERUSER_ID, email, 'reset_password_email', ids[0], context=context)
return True
#else:
# _m, company_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company')
# self._auth_reset_password_send_email(cr, uid, email, 'email_no_user', company_id, context=context)
return False
class auth_reset_password(osv.TransientModel):
_name = 'auth.reset_password'
_rec_name = 'password'
_columns = {
'password': fields.char('Password', size=64),
'password_confirmation': fields.char('Confirm Password', size=64),
'token': fields.char('Token', size=128),
'state': fields.selection([(x, x) for x in 'draft done missmatch error'.split()], required=True),
}
_defaults = {
'state': 'draft',
}
def create(self, cr, uid, values, context=None):
# NOTE here, invalid values raises exceptions to avoid storing
# sensitive data into the database (which then are available to anyone)
pw = values.get('password')
if not pw or pw != values.get('password_confirmation'):
raise osv.except_osv('Error', 'Passwords missmatch')
Users = self.pool.get('res.users')
data = Users._auth_reset_password_check_token(cr, uid, values.get('token', ''))
if data:
Users.write(cr, SUPERUSER_ID, data['uid'], {'password': pw}, context=context)
else:
raise osv.except_osv('Error', 'Invalid token')
# Dont store password
values = {'state': 'done'}
return super(auth_reset_password, self).create(cr, uid, values, context)
def change(self, cr, uid, ids, context=None):
return True
def onchange_pw(self, cr, uid, ids, password, password_confirmation, context=None):
if password != password_confirmation:
return {'value': {'state': 'missmatch'}}
return {'value': {'state': 'draft'}}
def onchange_token(self, cr, uid, ids, token, context=None):
Users = self.pool.get('res.users')
if not Users._auth_reset_password_check_token(cr, uid, token, context=context):
return {'value': {'state': 'error'}}
return {}

View File

@ -7,56 +7,15 @@
<field name="name">Reset Password</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field>
<field name="email_to" eval="False"><!--(set by reset_password module)--></field>
<field name="email_to">${object.email}</field>
<field name="subject">Password reset</field>
<field name="body_html"><![CDATA[
<p>A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}</p>
<p>A password reset was requested for the OpenERP account linked to this email.</p>
<p>You may change your password following this <a href="${object._auth_reset_password_link()}">link</a>,
or by copy-pasting the following URL in your browser: ${object._auth_reset_password_link()}</p>
<p>You may change your password following <a href="${object.signup_url}">this link</a>.</p>
<p>Note: If you did not ask for a password reset, you can safely ignore this email.</p>]]></field>
</record>
<!-- TODO get own css -->
<record id="reset_password_wizard_form_view" model="ir.ui.view">
<field name="name">auth.reset_password.form</field>
<field name="model">auth.reset_password</field>
<field name="arch" type="xml">
<form string="Reset Password" version="7.0">
<field name="state" invisible="1"/>
<field name="token" on_change="onchange_token(token)" invisible="1"/>
<group colspan="4" states="draft,missmatch">
<field name="password" required='1' on_change="onchange_pw(password,password_confirmation)"/>
<field name="password_confirmation" required='1' on_change="onchange_pw(password,password_confirmation)"/>
<group colspan="4" states="missmatch">
<div>Passwords missmatch</div>
</group>
<group colspan="2" col="1">
<button string="Change Password" name="change" icon="gtk-dialog-authentication" attrs="{'readonly': [('state', '=', 'missmatch')]}"/>
</group>
</group>
<group colspan="4" states="error" col="1">
<div>Invalid or expired token</div>
<button special="cancel" string="Close"/>
</group>
<group colspan="4" states="done" col="1">
<div>Password changed. We sent you an email confirming the password change.</div>
<button special="cancel" string="Close"/>
</group>
</form>
</field>
</record>
<record id="action_reset" model="ir.actions.act_window">
<field name="name">Reset Password</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">auth.reset_password</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,24 @@
# -*- 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 main
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,52 @@
# -*- 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 logging
import werkzeug
import openerp
from openerp import SUPERUSER_ID
from openerp.modules.registry import RegistryManager
_logger = logging.getLogger(__name__)
class Controller(openerp.addons.web.http.Controller):
_cp_path = '/auth_reset_password'
@openerp.addons.web.http.httprequest
def reset_password(self, req, dbname, login):
""" retrieve user, and perform reset password """
url = '/'
registry = RegistryManager.get(dbname)
with registry.cursor() as cr:
try:
res_users = registry.get('res.users')
res_users.reset_password(cr, SUPERUSER_ID, login)
cr.commit()
message = 'An email has been sent with credentials to reset your password'
except Exception as e:
# signup error
_logger.exception('error when resetting password')
message = e.message
url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(message)
return werkzeug.utils.redirect(url)
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,59 @@
# -*- 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/>
#
##############################################################################
from openerp.osv import osv, fields
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
from datetime import datetime, timedelta
def now(**kwargs):
dt = datetime.now() + timedelta(**kwargs)
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
class res_users(osv.osv):
_inherit = 'res.users'
def reset_password(self, cr, uid, login, context=None):
""" retrieve the user corresponding to login (login or email),
and reset their password
"""
user_ids = self.search(cr, uid, [('login', '=', login)], context=context)
if not user_ids:
user_ids = self.search(cr, uid, [('email', '=', login)], context=context)
if len(user_ids) != 1:
raise Exception('Reset password: invalid username or email')
return self.action_reset_password(cr, uid, user_ids, context=context)
def action_reset_password(self, cr, uid, ids, context=None):
""" create signup token for each user, and send their signup url by email """
# prepare reset password signup
res_partner = self.pool.get('res.partner')
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)]
res_partner.signup_prepare(cr, uid, partner_ids, expiration=now(days=+1), context=context)
# send email to users with their signup url
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_reset_password', 'reset_password_email')
assert template._name == 'email.template'
for user in self.browse(cr, uid, ids, context):
self.pool.get('email.template').send_mail(cr, uid, template.id, user.id, context=context)
return True

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="res_users_form_view" model="ir.ui.view">
<field name="name">user.form.reset_password</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet/*[1]" position="before">
<div class="oe_right oe_button_box">
<button string="Reset Password" type="object" name="action_reset_password"/>
</div>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -1,12 +0,0 @@
.openerp .oe_login .oe_login_pane {
height: 152px;
}
.openerp .oe_login .oe_login_pane ul.oe_login_switch a {
color: #eeeeee;
margin: 0 8px;
}
.openerp .oe_login .oe_login_pane ul.oe_login_switch a:hover {
text-decoration: underline;
}

View File

@ -1,72 +1,30 @@
openerp.auth_reset_password = function(instance) {
var _t = instance.web._t;
instance.web.Login.include({
start: function() {
var $e = this.$el;
$e.find('a.oe_login_switch').click(function() {
$e.find('ul.oe_login_switch').toggle();
var $m = $e.find('form input[name=is_reset_pw]');
$m.attr('checked', !$m.is(':checked'));
});
this.$('a.oe_reset_password').click(this.do_reset_password);
return this._super();
},
on_submit: function(ev) {
if(ev) {
do_reset_password: function(ev) {
if (ev) {
ev.preventDefault();
}
var $e = this.$el;
var db = $e.find("form [name=db]").val();
var db = this.$("form [name=db]").val();
var login = this.$("form input[name=login]").val();
if (!db) {
this.do_warn(_t("Login"), _t("No database selected !"));
this.do_warn("Login", "No database selected !");
return false;
} else if (!login) {
this.do_warn("Login", "Please enter a username or email address.")
return false;
}
var $m = $e.find('form input[name=is_reset_pw]');
if ($m.is(':checked')) {
var email = $e.find('form input[name=email]').val();
return this.do_reset_password(db, email);
} else {
return this._super(ev);
}
},
do_reset_password: function(db, email) {
var self = this;
instance.session.session_authenticate(db, 'anonymous', 'anonymous', true).pipe(function () {
var func = new instance.web.Model("res.users").get_func("send_reset_password_request");
return func(email).then(function(res) {
// show message
self.do_notify(_t('Reset Password'), _.str.sprintf(_t('We have sent an email to %s with further instructions'), email), true);
}, function(error, event) {
// no traceback please
event.preventDefault();
});
}).fail(function(error, event) {
// cannot log as anonymous or reset_password not installed
self.do_warn(_t('Reset Password'), _.str.sprintf(_t('Reset Password functionnality is not available for database %s'), db), true);
});
var params = {
dbname : db,
login: login,
};
var url = "/auth_reset_password/reset_password?" + $.param(params);
window.location = url;
}
});
instance.reset_password = {};
instance.reset_password.ResetPassword = instance.web.Widget.extend({
init: function(parent, params) {
this._super(parent);
this.token = (params && params.token) || false;
},
start: function() {
this.do_action({
name: 'Reset Password',
type: 'ir.actions.act_window',
context: {default_token: this.token},
res_model: 'auth.reset_password',
target: 'new',
views: [[false, 'form']]
});
}
});
instance.web.client_actions.add("reset_password", "instance.reset_password.ResetPassword");
};

View File

@ -1,26 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- vim:fdl=1:
-->
<!-- vim:fdl=1: -->
<templates id="template" xml:space="preserve">
<t t-extend="Login">
<t t-jquery="form ul:first">
// addClass does not work :(
this.attr('class', (this.attr('class') || '') + ' oe_login_switch');
</t>
<t t-jquery="form ul:first li:last" t-operation="after">
<li>
<a class="oe_login_switch" href="#">Forgot your password?</a>
</li>
</t>
<t t-jquery="form ul:first" t-operation="after">
<ul class="oe_login_switch" style="display:none;">
<li style="display:none;"><input type="checkbox" name="is_reset_pw"/></li>
<li>Email</li>
<li><input type="email" name="email"/></li>
<li><button name="submit">Reset Password</button></li>
<li><a class="oe_login_switch" href="#">&lt; Back</a></li>
</ul>
<li><a class="oe_reset_password" href="#">Reset password</a></li>
</t>
</t>
</templates>

View File

@ -34,7 +34,9 @@ Allow users to sign up.
'data': [
'auth_signup_data.xml',
'res_config.xml',
'res_users_view.xml',
],
'js': ['static/src/js/auth_signup.js'],
'css' : ['static/src/css/base.css'],
'qweb': ['static/src/xml/auth_signup.xml'],
}

View File

@ -1,35 +1,62 @@
# -*- 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 logging
import werkzeug.urls
import werkzeug
import openerp
from openerp.modules.registry import RegistryManager
from openerp.addons.web.controllers.main import login_and_redirect
import openerp.addons.web.common.http as openerpweb
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
class OpenIDController(openerpweb.Controller):
class Controller(openerp.addons.web.http.Controller):
_cp_path = '/auth_signup'
@openerpweb.httprequest
def signup(self, req, dbname, name, login, password):
@openerp.addons.web.http.jsonrequest
def retrieve(self, req, dbname, token):
""" retrieve the user info (name, login or email) corresponding to a signup token """
registry = RegistryManager.get(dbname)
user_info = None
with registry.cursor() as cr:
res_partner = registry.get('res.partner')
user_info = res_partner.signup_retrieve_info(cr, openerp.SUPERUSER_ID, token)
return user_info
@openerp.addons.web.http.httprequest
def signup(self, req, dbname, token, name, login, password, state=''):
""" sign up a user (new or existing), and log it in """
url = '/'
registry = RegistryManager.get(dbname)
with registry.cursor() as cr:
try:
Users = registry.get('res.users')
credentials = Users.auth_signup(cr, SUPERUSER_ID, name, login, password)
res_users = registry.get('res.users')
values = {'name': name, 'login': login, 'password': password}
credentials = res_users.signup(cr, openerp.SUPERUSER_ID, values, token)
cr.commit()
return login_and_redirect(req, *credentials)
except AttributeError:
# auth_signup is not installed
_logger.exception('attribute error when signup')
url = "/#action=auth_signup&error=NA" # Not Available
except Exception:
return login_and_redirect(req, *credentials, redirect_url='/#%s'%state)
except Exception as e:
# signup error
_logger.exception('error when signup')
url = "/#action=auth_signup&error=UE" # Unexcpected Error
url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(e.message)
return werkzeug.utils.redirect(url)
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -20,22 +20,27 @@
##############################################################################
from openerp.osv import osv, fields
from openerp.tools.safe_eval import safe_eval
class base_config_settings(osv.TransientModel):
_inherit = 'base.config.settings'
_columns = {
'auth_signup_uninvited': fields.boolean('Allow public users to sign up', help="If unchecked only invited users may sign up"),
'auth_signup_uninvited': fields.boolean('Allow external users to sign up', help="If unchecked, only invited users may sign up"),
'auth_signup_template_user_id': fields.many2one('res.users', 'Template user for new users created through signup'),
}
def get_default_auth_signup_template_user_id(self, cr, uid, fields, context=None):
icp = self.pool.get('ir.config_parameter')
# we use safe_eval on the result, since the value of the parameter is a nonempty string
return {
'auth_signup_template_user_id': icp.get_param(cr, uid, 'auth_signup.template_user_id', 0) or False
'auth_signup_uninvited': safe_eval(icp.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')),
'auth_signup_template_user_id': safe_eval(icp.get_param(cr, uid, 'auth_signup.template_user_id', 'False')),
}
def set_auth_signup_template_user_id(self, cr, uid, ids, context=None):
config = self.browse(cr, uid, ids[0], context=context)
icp = self.pool.get('ir.config_parameter')
icp.set_param(cr, uid, 'auth_signup.template_user_id', config.auth_signup_template_user_id.id)
# we store the repr of the values, since the value of the parameter is a required string
icp.set_param(cr, uid, 'auth_signup.allow_uninvited', repr(config.auth_signup_uninvited))
icp.set_param(cr, uid, 'auth_signup.template_user_id', repr(config.auth_signup_template_user_id.id))

View File

@ -14,7 +14,9 @@
</div>
<div attrs="{'invisible':[('auth_signup_uninvited','=',False)]}">
<label for="auth_signup_template_user_id"/>
<field name="auth_signup_template_user_id" class="oe_inline" domain="['|',('active','=',0),('active','=',1)]"/>
<field name="auth_signup_template_user_id" class="oe_inline"
attrs="{'required': [('auth_signup_uninvited', '=', True)]}"
domain="['|',('active','=',0),('active','=',1)]"/>
</div>
</xpath>
</field>

View File

@ -1,47 +1,201 @@
# -*- 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 random
import time
import urllib
import urlparse
import openerp
from openerp.osv import osv
from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.safe_eval import safe_eval
def random_token():
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
return ''.join(random.choice(chars) for i in xrange(20))
def now():
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
class res_partner(osv.Model):
_inherit = 'res.partner'
def _get_signup_valid(self, cr, uid, ids, name, arg, context=None):
dt = now()
res = {}
for partner in self.browse(cr, uid, ids, context):
res[partner.id] = bool(partner.signup_token) and \
(not partner.signup_expiration or dt <= partner.signup_expiration)
return res
def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
""" determine a signup url for a given partner """
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
# if required, make sure that every partner without user has a valid signup token
if context and context.get('signup_valid'):
unsigned_ids = [p.id for p in self.browse(cr, uid, ids, context) if not p.user_ids]
self.signup_prepare(cr, uid, unsigned_ids, context=context)
res = dict.fromkeys(ids, False)
for partner in self.browse(cr, uid, ids, context):
if partner.signup_token:
params = (urllib.quote(cr.dbname), urllib.quote(partner.signup_token))
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&token=%s" % params)
elif partner.user_ids:
user = partner.user_ids[0]
params = (urllib.quote(cr.dbname), urllib.quote(user.login))
res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&login=%s" % params)
return res
_columns = {
'signup_token': fields.char(size=24, string='Signup Token'),
'signup_expiration': fields.datetime(string='Signup Expiration'),
'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
}
def action_signup_prepare(self, cr, uid, ids, context=None):
return self.signup_prepare(cr, uid, ids, context=context)
def signup_prepare(self, cr, uid, ids, expiration=False, context=None):
""" generate a new token for the partners with the given validity, if necessary
:param expiration: the expiration datetime of the token (string, optional)
"""
for partner in self.browse(cr, uid, ids, context):
if expiration or not partner.signup_valid:
token = random_token()
while self._signup_retrieve_partner(cr, uid, token, context=context):
token = random_token()
partner.write({'signup_token': token, 'signup_expiration': expiration})
return True
def _signup_retrieve_partner(self, cr, uid, token,
check_validity=False, raise_exception=False, context=None):
""" find the partner corresponding to a token, and possibly check its validity
:param token: the token to resolve
:param check_validity: if True, also check validity
:param raise_exception: if True, raise exception instead of returning False
:return: partner (browse record) or False (if raise_exception is False)
"""
partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context)
if not partner_ids:
if raise_exception:
raise Exception("Signup token '%s' is not valid" % token)
return False
partner = self.browse(cr, uid, partner_ids[0], context)
if check_validity and not partner.signup_valid:
if raise_exception:
raise Exception("Signup token '%s' is no longer valid" % token)
return False
return partner
def signup_retrieve_info(self, cr, uid, token, context=None):
""" retrieve the user info about the token
:return: a dictionary with the user information:
- 'db': the name of the database
- 'token': the token, if token is valid
- 'name': the name of the partner, if token is valid
- 'login': the user login, if the user already exists
- 'email': the partner email, if the user does not exist
"""
partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None)
res = {'db': cr.dbname}
if partner.signup_valid:
res['token'] = token
res['name'] = partner.name
if partner.user_ids:
res['login'] = partner.user_ids[0].login
else:
res['email'] = partner.email or ''
return res
class res_users(osv.Model):
_inherit = 'res.users'
def auth_signup_create(self, cr, uid, new_user, context=None):
# new_user:
# login
# email
# name (optional)
# partner_id (optional)
# groups (optional)
# sign (for partner_id and groups)
#
user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0)
if user_template_id:
self.pool.get('res.users').copy(cr, SUPERUSER_ID, user_template_id, new_user, context=context)
else:
self.pool.get('res.users').create(cr, SUPERUSER_ID, new_user, context=context)
def _get_state(self, cr, uid, ids, name, arg, context=None):
return dict((user.id, 'new' if not user.login_date else 'reset' if user.signup_token else 'active')
for user in self.browse(cr, uid, ids, context))
def auth_signup(self, cr, uid, name, login, password, context=None):
r = (cr.dbname, login, password)
res = self.search(cr, uid, [("login", "=", login)])
if res:
# Existing user
user_id = res[0]
try:
self.check(cr.dbname, user_id, password)
# Same password
except openerp.exceptions.AccessDenied:
# Different password
raise
else:
# New user
new_user = {
'name': name,
'login': login,
'user_email': login,
'password': password,
'active': True,
}
self.auth_signup_create(cr, uid, new_user)
return r
_columns = {
'state': fields.function(_get_state, string='State', type='selection',
selection=[('new', 'New'), ('active', 'Active'), ('reset', 'Resetting Password')]),
}
#
def signup(self, cr, uid, values, token=None, context=None):
""" signup a user, to either:
- create a new user (no token), or
- create a user for a partner (with token, but no user for partner), or
- change the password of a user (with token, and existing user).
:param values: a dictionary with field values
:param token: signup token (optional)
:return: (dbname, login, password) for the signed up user
"""
assert values.get('login') and values.get('password')
result = (cr.dbname, values['login'], values['password'])
if token:
# signup with a token: find the corresponding partner id
res_partner = self.pool.get('res.partner')
partner = res_partner._signup_retrieve_partner(cr, uid, token,
check_validity=True, raise_exception=True, context=None)
# invalidate signup token
partner.write({'signup_token': False, 'signup_expiration': False})
if partner.user_ids:
# user exists, modify its password
partner.user_ids[0].write({'password': values['password']})
else:
# user does not exist: sign up invited user
self._signup_create_user(cr, uid, {
'name': partner.name,
'login': values['login'],
'password': values['password'],
'email': values['login'],
'partner_id': partner.id,
}, context=context)
return result
# sign up an external user
assert values.get('name'), 'Signup: no name given for new user'
self._signup_create_user(cr, uid, {
'name': values['name'],
'login': values['login'],
'password': values['password'],
'email': values['login'],
}, context=context)
return result
def _signup_create_user(self, cr, uid, values, context=None):
""" create a new user from the template user """
ir_config_parameter = self.pool.get('ir.config_parameter')
template_user_id = safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False'))
assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user'
# check that uninvited users may sign up
if 'partner_id' not in values:
if not safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
raise Exception('Signup is not allowed for uninvited users')
# create a copy of the template user (attached to a specific partner_id if given)
values['active'] = True
return self.copy(cr, uid, template_user_id, values, context=context)

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="res_users_form_view" model="ir.ui.view">
<field name="name">user.form.state</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="before">
<header>
<field name="state" widget="statusbar"/>
</header>
</xpath>
</field>
</record>
</data>
</openerp>

View File

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

View File

@ -0,0 +1,10 @@
@charset "utf-8";
.openerp .oe_login .oe_signup_show {
display: none;
}
.openerp .oe_login_signup .oe_signup_show {
display: block !important;
}
.openerp .oe_login_signup .oe_signup_hide {
display: none;
}

View File

@ -0,0 +1,13 @@
@charset "utf-8"
.openerp
// Regular login form
.oe_login
.oe_signup_show
display: none
// Signup form
.oe_login_signup
.oe_signup_show
display: block !important
.oe_signup_hide
display: none

View File

@ -5,56 +5,100 @@ openerp.auth_signup = function(instance) {
instance.web.Login.include({
start: function() {
var self = this;
this.$('a.oe_signup').click(function() {
var dbname = self.$("form [name=db]").val();
self.do_action({
type: 'ir.actions.client',
tag: 'auth_signup.signup',
params: {'dbname': dbname},
target: 'new',
name: 'Sign up'
});
return true;
var d = this._super();
// to switch between the signup and regular login form
this.$('a.oe_signup_signup').click(function() {
self.$el.addClass("oe_login_signup");
});
this.$('a.oe_signup_back').click(function() {
self.$el.removeClass("oe_login_signup");
delete self.params.token;
});
return this._super();
},
});
// if there is an error message in params, show it then forget it
if (self.params.error_message) {
this.show_error(self.params.error_message);
delete self.params.error_message;
}
instance.auth_signup.Signup = instance.web.Widget.extend({
template: 'auth_signup.signup',
init: function(parent, params) {
this.params = params;
return this._super();
// in case of a signup, retrieve the user information from the token
if (self.params.db && self.params.token) {
d = self.rpc("/auth_signup/retrieve", {dbname: self.params.db, token: self.params.token})
.done(self.on_token_loaded)
.fail(self.on_token_failed);
}
return d;
},
start: function() {
var self = this;
this.$('input[name=password_confirmation]').keyup(function() {
var v = $(this).val();
var $b = self.$('button');
if (_.isEmpty(v) || self.$('input[name=password]').val() === v) {
$b.removeAttr('disabled');
on_token_loaded: function(result) {
// select the right the database
this.selected_db = result.db;
this.on_db_loaded({db_list: [result.db]});
if (result.token) {
// switch to signup mode, set user name and login
this.$el.addClass("oe_login_signup");
this.$("form input[name=name]").val(result.name).attr("readonly", "readonly");
if (result.login) {
this.$("form input[name=login]").val(result.login).attr("readonly", "readonly");
} else {
$b.attr('disabled', 'disabled');
this.$("form input[name=login]").val(result.email);
}
});
this.$('form').submit(function(ev) {
if(ev) {
ev.preventDefault();
} else {
// remain in login mode, set login if present
delete this.params.token;
this.$("form input[name=login]").val(result.login || "");
}
},
on_token_failed: function(result, ev) {
if (ev) {
ev.preventDefault();
}
this.show_error("Invalid signup token");
delete this.params.db;
delete this.params.token;
},
on_submit: function(ev) {
if (ev) {
ev.preventDefault();
}
if (this.$el.hasClass("oe_login_signup")) {
// signup user (or reset password)
var db = this.$("form [name=db]").val();
var name = this.$("form input[name=name]").val();
var login = this.$("form input[name=login]").val();
var password = this.$("form input[name=password]").val();
var confirm_password = this.$("form input[name=confirm_password]").val();
if (!db) {
this.do_warn("Login", "No database selected !");
return false;
} else if (!name) {
this.do_warn("Login", "Please enter a name.")
return false;
} else if (!login) {
this.do_warn("Login", "Please enter a username.")
return false;
} else if (!password || !confirm_password) {
this.do_warn("Login", "Please enter a password and confirm it.")
return false;
} else if (password !== confirm_password) {
this.do_warn("Login", "Passwords do not match; please retype them.")
return false;
}
var params = {
dbname : self.params.dbname,
name: self.$('input[name=name]').val(),
login: self.$('input[name=email]').val(),
password: self.$('input[name=password]').val(),
dbname : db,
token: this.params.token || "",
name: name,
login: login,
password: password,
state: $.param(this.params)
};
var url = "/auth_signup/signup?" + $.param(params);
window.location = url;
});
return this._super();
}
} else {
// regular login
this._super(ev);
}
},
});
instance.web.client_actions.add("auth_signup.signup", "instance.auth_signup.Signup");
};

View File

@ -1,28 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- vim:fdl=1:
-->
<!-- vim:fdl=1: -->
<templates id="template" xml:space="preserve">
<t t-extend="Login">
<t t-jquery="form ul:first li:last" t-operation="after">
<li>
<a class="oe_signup" href="#">Sign Up</a>
</li>
<t t-extend="Login">
<t t-jquery="form ul:first li:contains('Username')" t-operation="before">
<li class="oe_signup_show">Name</li>
<li class="oe_signup_show"><input name="name" type="text"/></li>
</t>
<t t-jquery="form ul:first li:contains('Username')" t-operation="replace">
<li class="oe_signup_hide">Username</li>
<li class="oe_signup_show">Username (Email)</li>
</t>
<t t-jquery="form ul:first li:has(input[name=password])" t-operation="after">
<li class="oe_signup_show">Confirm Password</li>
<li class="oe_signup_show"><input name="confirm_password" type="password"/></li>
</t>
<t t-jquery="form ul:first li:has(button[name=submit])" t-operation="replace">
<li class="oe_signup_hide"><button name="submit">Log in</button></li>
<li class="oe_signup_show"><button name="submit">Sign in</button></li>
</t>
<t t-jquery="form ul:first li:last" t-operation="after">
<li><a class="oe_signup_hide oe_signup_signup" href="#">Sign Up</a></li>
<li><a class="oe_signup_show oe_signup_back" href="#">Back to Login</a></li>
</t>
</t>
</t>
<t t-name="auth_signup.signup">
<div>
<form>
Name = <input type="text" name="name"/><br/>
Email = <input type="email" name="email"/><br/>
Password = <input type="password" name="password"/><br/>
Confirmation = <input type="password" name="password_confirmation"/><br/>
<button type="submit" disabled="disabled">Signup</button>
</form>
</div>
</t>
</templates>

View File

@ -280,7 +280,7 @@
<field name="arch" type="xml">
<search string="Search Meetings">
<field name="name" string="Meeting" filter_domain="[('name','ilike',self)]"/>
<filter string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="My Meetings" help="My Meetings" domain="[('user_id','=',uid)]"/>
<field name="user_id"/>

View File

@ -26,13 +26,14 @@ Re-implement openerp's file import system:
'author': 'OpenERP SA',
'depends': ['base'],
'installable': True,
'auto_install': False, # set to true and allow uninstall?
'auto_install': True,
'css': [
'static/lib/select2/select2.css',
'static/src/css/import.css',
],
'js': [
'static/lib/select2/select2.js',
'static/lib/javascript-state-machine/state-machine.js',
'static/src/js/import.js',
],
'qweb': ['static/src/xml/import.xml'],

View File

@ -1,15 +1,12 @@
# -*- coding: utf-8 -*-
import simplejson
try:
import openerp.addons.web.common.http as openerpweb
except ImportError:
import web.common.http as openerpweb
import openerp
class ImportController(openerpweb.Controller):
class ImportController(openerp.addons.web.http.Controller):
_cp_path = '/base_import'
@openerpweb.httprequest
@openerp.addons.web.http.httprequest
def set_file(self, req, file, import_id, jsonp='callback'):
import_id = int(import_id)

View File

@ -83,6 +83,10 @@ class ir_import(orm.TransientModel):
}]
fields_got = self.pool[model].fields_get(cr, uid, context=context)
for name, field in fields_got.iteritems():
# an empty string means the field is deprecated, @deprecated must
# be absent or False to mean not-deprecated
if field.get('deprecated', False) is not False:
continue
if field.get('readonly'):
states = field.get('states')
if not states:
@ -97,7 +101,7 @@ class ir_import(orm.TransientModel):
'id': name,
'name': name,
'string': field['string'],
# Y U NO ALWAYS HAVE REQUIRED
# Y U NO ALWAYS HAS REQUIRED
'required': bool(field.get('required')),
'fields': [],
}
@ -124,8 +128,8 @@ class ir_import(orm.TransientModel):
"""
csv_iterator = csv.reader(
StringIO(record.file),
quotechar=options['quoting'],
delimiter=options['separator'])
quotechar=str(options['quoting']),
delimiter=str(options['separator']))
csv_nonempty = itertools.ifilter(None, csv_iterator)
# TODO: guess encoding with chardet? Or https://github.com/aadsm/jschardet
encoding = options.get('encoding', 'utf-8')
@ -307,22 +311,14 @@ class ir_import(orm.TransientModel):
except ValueError, e:
return [{
'type': 'error',
'message': str(e),
'message': unicode(e),
'record': False,
}]
try:
_logger.info('importing %d rows...', len(data))
(code, record, message, _wat) = self.pool[record.res_model].import_data(
cr, uid, import_fields, data, context=context)
_logger.info('done')
except Exception, e:
_logger.exception("Import failed")
# TODO: remove when exceptions stop being an "expected"
# behavior of import_data on some (most) invalid
# input.
code, record, message = -1, None, str(e)
_logger.info('importing %d rows...', len(data))
import_result = self.pool[record.res_model].load(
cr, uid, import_fields, data, context=context)
_logger.info('done')
# If transaction aborted, RELEASE SAVEPOINT is going to raise
# an InternalError (ROLLBACK should work, maybe). Ignore that.
@ -339,14 +335,4 @@ class ir_import(orm.TransientModel):
except psycopg2.InternalError:
pass
if code != -1:
return []
# TODO: add key for error location?
# TODO: error not within normal preview, how to display? Re-preview
# with higher ``count``?
return [{
'type': 'error',
'message': message,
'record': record or False
}]
return import_result['messages']

View File

@ -0,0 +1,20 @@
Copyright (c) 2012 Jake Gordon and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,327 @@
Javascript Finite State Machine (v2.1.0)
========================================
This standalone javascript micro-framework provides a finite state machine for your pleasure.
* You can find the [code here](https://github.com/jakesgordon/javascript-state-machine)
* You can find a [description here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)
* You can find a [working demo here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/)
Download
========
You can download [state-machine.js](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.js),
or the [minified version](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.min.js)
Alternatively:
git clone git@github.com:jakesgordon/javascript-state-machine
* All code is in state-machine.js
* Minified version provided in state-machine.min.js
* No 3rd party library is required
* Demo can be found in /index.html
* QUnit tests can be found in /test/index.html
Usage
=====
Include `state-machine.min.js` in your application.
In its simplest form, create a standalone state machine using:
var fsm = StateMachine.create({
initial: 'green',
events: [
{ name: 'warn', from: 'green', to: 'yellow' },
{ name: 'panic', from: 'yellow', to: 'red' },
{ name: 'calm', from: 'red', to: 'yellow' },
{ name: 'clear', from: 'yellow', to: 'green' }
]});
... will create an object with a method for each event:
* fsm.warn() - transition from 'green' to 'yellow'
* fsm.panic() - transition from 'yellow' to 'red'
* fsm.calm() - transition from 'red' to 'yellow'
* fsm.clear() - transition from 'yellow' to 'green'
along with the following members:
* fsm.current - contains the current state
* fsm.is(s) - return true if state `s` is the current state
* fsm.can(e) - return true if event `e` can be fired in the current state
* fsm.cannot(e) - return true if event `e` cannot be fired in the current state
Multiple 'from' and 'to' states for a single event
==================================================
If an event is allowed **from** multiple states, and always transitions to the same
state, then simply provide an array of states in the `from` attribute of an event. However,
if an event is allowed from multiple states, but should transition **to** a different
state depending on the current state, then provide multiple event entries with
the same name:
var fsm = StateMachine.create({
initial: 'hungry',
events: [
{ name: 'eat', from: 'hungry', to: 'satisfied' },
{ name: 'eat', from: 'satisfied', to: 'full' },
{ name: 'eat', from: 'full', to: 'sick' },
{ name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' },
]});
This example will create an object with 2 event methods:
* fsm.eat()
* fsm.rest()
The `rest` event will always transition to the `hungry` state, while the `eat` event
will transition to a state that is dependent on the current state.
>> NOTE: The `rest` event could use a wildcard '*' for the 'from' state if it should be
allowed from any current state.
>> NOTE: The `rest` event in the above example can also be specified as multiple events with
the same name if you prefer the verbose approach.
Callbacks
=========
4 callbacks are available if your state machine has methods using the following naming conventions:
* onbefore**event** - fired before the event
* onleave**state** - fired when leaving the old state
* onenter**state** - fired when entering the new state
* onafter**event** - fired after the event
You can affect the event in 3 ways:
* return `false` from an `onbeforeevent` handler to cancel the event.
* return `false` from an `onleavestate` handler to cancel the event.
* return `ASYNC` from an `onleavestate` handler to perform an asynchronous state transition (see next section)
For convenience, the 2 most useful callbacks can be shortened:
* on**event** - convenience shorthand for onafter**event**
* on**state** - convenience shorthand for onenter**state**
In addition, a generic `onchangestate()` callback can be used to call a single function for _all_ state changes:
All callbacks will be passed the same arguments:
* **event** name
* **from** state
* **to** state
* _(followed by any arguments you passed into the original event method)_
Callbacks can be specified when the state machine is first created:
var fsm = StateMachine.create({
initial: 'green',
events: [
{ name: 'warn', from: 'green', to: 'yellow' },
{ name: 'panic', from: 'yellow', to: 'red' },
{ name: 'calm', from: 'red', to: 'yellow' },
{ name: 'clear', from: 'yellow', to: 'green' }
],
callbacks: {
onpanic: function(event, from, to, msg) { alert('panic! ' + msg); },
onclear: function(event, from, to, msg) { alert('thanks to ' + msg); },
ongreen: function(event, from, to) { document.body.className = 'green'; },
onyellow: function(event, from, to) { document.body.className = 'yellow'; },
onred: function(event, from, to) { document.body.className = 'red'; },
}
});
fsm.panic('killer bees');
fsm.clear('sedatives in the honey pots');
...
Additionally, they can be added and removed from the state machine at any time:
fsm.ongreen = null;
fsm.onyellow = null;
fsm.onred = null;
fsm.onchangestate = function(event, from, to) { document.body.className = to; };
Asynchronous State Transitions
==============================
Sometimes, you need to execute some asynchronous code during a state transition and ensure the
new state is not entered until your code has completed.
A good example of this is when you transition out of a `menu` state, perhaps you want to gradually
fade the menu away, or slide it off the screen and don't want to transition to your `game` state
until after that animation has been performed.
You can now return `StateMachine.ASYNC` from your `onleavestate` handler and the state machine
will be _'put on hold'_ until you are ready to trigger the transition using the new `transition()`
method.
For example, using jQuery effects:
var fsm = StateMachine.create({
initial: 'menu',
events: [
{ name: 'play', from: 'menu', to: 'game' },
{ name: 'quit', from: 'game', to: 'menu' }
],
callbacks: {
onentermenu: function() { $('#menu').show(); },
onentergame: function() { $('#game').show(); },
onleavemenu: function() {
$('#menu').fadeOut('fast', function() {
fsm.transition();
});
return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in fadeOut callback above)
},
onleavegame: function() {
$('#game').slideDown('slow', function() {
fsm.transition();
};
return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in slideDown callback above)
}
}
});
State Machine Classes
=====================
You can also turn all instances of a _class_ into an FSM by applying
the state machine functionality to the prototype, including your callbacks
in your prototype, and providing a `startup` event for use when constructing
instances:
MyFSM = function() { // my constructor function
this.startup();
};
MyFSM.prototype = {
onpanic: function(event, from, to) { alert('panic'); },
onclear: function(event, from, to) { alert('all is clear'); },
// my other prototype methods
};
StateMachine.create({
target: MyFSM.prototype,
events: [
{ name: 'startup', from: 'none', to: 'green' },
{ name: 'warn', from: 'green', to: 'yellow' },
{ name: 'panic', from: 'yellow', to: 'red' },
{ name: 'calm', from: 'red', to: 'yellow' },
{ name: 'clear', from: 'yellow', to: 'green' }
]});
This should be easy to adjust to fit your appropriate mechanism for object construction.
Initialization Options
======================
How the state machine should initialize can depend on your application requirements, so
the library provides a number of simple options.
By default, if you dont specify any initial state, the state machine will be in the `'none'`
state and you would need to provide an event to take it out of this state:
var fsm = StateMachine.create({
events: [
{ name: 'startup', from: 'none', to: 'green' },
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "none"
fsm.startup();
alert(fsm.current); // "green"
If you specify the name of your initial event (as in all the earlier examples), then an
implicit `startup` event will be created for you and fired when the state machine is constructed.
var fsm = StateMachine.create({
initial: 'green',
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "green"
If your object already has a `startup` method you can use a different name for the initial event
var fsm = StateMachine.create({
initial: { state: 'green', event: 'init' },
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "green"
Finally, if you want to wait to call the initial state transition event until a later date you
can `defer` it:
var fsm = StateMachine.create({
initial: { state: 'green', event: 'init', defer: true },
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.current); // "none"
fsm.init();
alert(fsm.current); // "green"
Of course, we have now come full circle, this last example is pretty much functionally the
same as the first example in this section where you simply define your own startup event.
So you have a number of choices available to you when initializing your state machine.
Handling Failures
======================
By default, if you try to call an event method that is not allowed in the current state, the
state machine will throw an exception. If you prefer to handle the problem yourself, you can
define a custom `error` handler:
var fsm = StateMachine.create({
initial: 'green',
error: function(eventName, from, to, args, errorCode, errorMessage) {
return 'event ' + eventName + ' was naughty :- ' + errorMessage;
},
events: [
{ name: 'panic', from: 'green', to: 'red' },
{ name: 'calm', from: 'red', to: 'green' },
]});
alert(fsm.calm()); // "event calm was naughty :- event not allowed in current state green"
Release Notes
=============
See [RELEASE NOTES](https://github.com/jakesgordon/javascript-state-machine/blob/master/RELEASE_NOTES.md) file.
License
=======
See [LICENSE](https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE) file.
Contact
=======
If you have any ideas, feedback, requests or bug reports, you can reach me at
[jake@codeincomplete.com](mailto:jake@codeincomplete.com), or via
my website: [Code inComplete](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)

View File

@ -0,0 +1,32 @@
Version 2.1.0 (January 7th 2012)
--------------------------------
* Wrapped in self executing function to be more easily used with loaders like `require.js` or `curl.js` (issue #15)
* Allow event to be cancelled by returning `false` from `onleavestate` handler (issue #13) - WARNING: this breaks backward compatibility for async transitions (you now need to return `StateMachine.ASYNC` instead of `false`)
* Added explicit return values for event methods (issue #12)
* Added support for wildcard events that can be fired 'from' any state (issue #11)
* Added support for no-op events that transition 'to' the same state (issue #5)
* extended custom error callback to handle any exceptions caused by caller provided callbacks
* added custom error callback to override exception when an illegal state transition is attempted (thanks to cboone)
* fixed typos (thanks to cboone)
* fixed issue #4 - ensure before/after event hooks are called even if the event doesn't result in a state change
Version 2.0.0 (August 19th 2011)
--------------------------------
* adding support for asynchronous state transitions (see README) - with lots of qunit tests (see test/async.js).
* consistent arguments for ALL callbacks, first 3 args are ALWAYS event name, from state and to state, followed by whatever arguments the user passed to the original event method.
* added a generic `onchangestate(event,from,to)` callback to detect all state changes with a single function.
* allow callbacks to be declared at creation time (instead of having to attach them afterwards)
* renamed 'hooks' => 'callbacks'
* [read more...](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/)
Version 1.2.0 (June 21st 2011)
------------------------------
* allows the same event to transition to different states, depending on the current state (see 'Multiple...' section in README.md)
* [read more...](http://codeincomplete.com/posts/2011/6/21/javascript_state_machine_v1_2_0/)
Version 1.0.0 (June 1st 2011)
-----------------------------
* initial version
* [read more...](http://codeincomplete.com/posts/2011/6/1/javascript_state_machine/)

View File

@ -0,0 +1,8 @@
desc "create minified version of state-machine.js"
task :minify do
require File.expand_path(File.join(File.dirname(__FILE__), 'minifier/minifier'))
Minifier.enabled = true
Minifier.minify('state-machine.js')
end

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Javascript Finite State Machine</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="demo/demo.css" media="screen, print" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="demo" class='green'>
<h1> Finite State Machine </h1>
<div id="controls">
<button id="clear" onclick="Demo.clear();">clear</button>
<button id="calm" onclick="Demo.calm();">calm</button>
<button id="warn" onclick="Demo.warn();">warn</button>
<button id="panic" onclick="Demo.panic();">panic!</button>
</div>
<div id="diagram">
</div>
<div id="notes">
<i>dashed lines are asynchronous state transitions (3 seconds)</i>
</div>
<textarea id="output">
</textarea>
</div>
<script src="state-machine.js"></script>
<script src="demo/demo.js"></script>
</body>
</html>

View File

@ -0,0 +1,155 @@
(function (window) {
StateMachine = {
//---------------------------------------------------------------------------
VERSION: "2.1.0",
//---------------------------------------------------------------------------
Result: {
SUCCEEDED: 1, // the event transitioned successfully from one state to another
NOTRANSITION: 2, // the event was successfull but no state transition was necessary
CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback
ASYNC: 4, // the event is asynchronous and the caller is in control of when the transition occurs
},
Error: {
INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state
PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending
INVALID_CALLBACK: 300, // caller provided callback function threw an exception
},
WILDCARD: '*',
ASYNC: 'async',
//---------------------------------------------------------------------------
create: function(cfg, target) {
var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false }
var fsm = target || cfg.target || {};
var events = cfg.events || [];
var callbacks = cfg.callbacks || {};
var map = {};
var add = function(e) {
var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified
map[e.name] = map[e.name] || {};
for (var n = 0 ; n < from.length ; n++)
map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
};
if (initial) {
initial.event = initial.event || 'startup';
add({ name: initial.event, from: 'none', to: initial.state });
}
for(var n = 0 ; n < events.length ; n++)
add(events[n]);
for(var name in map) {
if (map.hasOwnProperty(name))
fsm[name] = StateMachine.buildEvent(name, map[name]);
}
for(var name in callbacks) {
if (callbacks.hasOwnProperty(name))
fsm[name] = callbacks[name]
}
fsm.current = 'none';
fsm.is = function(state) { return this.current == state; };
fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); }
fsm.cannot = function(event) { return !this.can(event); };
fsm.error = cfg.error || function(name, from, to, args, error, msg) { throw msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3)
if (initial && !initial.defer)
fsm[initial.event]();
return fsm;
},
//===========================================================================
doCallback: function(fsm, func, name, from, to, args) {
if (func) {
try {
return func.apply(fsm, [name, from, to].concat(args));
}
catch(e) {
return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function");
}
}
},
beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },
buildEvent: function(name, map) {
return function() {
var from = this.current;
var to = map[from] || map[StateMachine.WILDCARD] || from;
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
if (this.transition)
return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete");
if (this.cannot(name))
return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);
if (false === StateMachine.beforeEvent(this, name, from, to, args))
return StateMachine.CANCELLED;
if (from === to) {
StateMachine.afterEvent(this, name, from, to, args);
return StateMachine.NOTRANSITION;
}
// prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState)
var fsm = this;
this.transition = function() {
fsm.transition = null; // this method should only ever be called once
fsm.current = to;
StateMachine.enterState( fsm, name, from, to, args);
StateMachine.changeState(fsm, name, from, to, args);
StateMachine.afterEvent( fsm, name, from, to, args);
};
var leave = StateMachine.leaveState(this, name, from, to, args);
if (false === leave) {
this.transition = null;
return StateMachine.CANCELLED;
}
else if ("async" === leave) {
return StateMachine.ASYNC;
}
else {
if (this.transition)
this.transition(); // in case user manually called transition() but forgot to return ASYNC
return StateMachine.SUCCEEDED;
}
};
}
}; // StateMachine
//===========================================================================
if ("function" === typeof define) {
define("statemachine", [], function() { return StateMachine; });
}
else {
window.StateMachine = StateMachine;
}
}(this));

View File

@ -10,7 +10,8 @@
.oe_import .oe_import_grid,
.oe_import .oe_import_error_report,
.oe_import .oe_import_with_file,
.oe_import .oe_import_noheaders {
.oe_import .oe_import_noheaders,
.oe_import .oe_import_report_more {
display: none;
}
@ -19,7 +20,8 @@
}
.oe_import.oe_import_error .oe_import_error_report,
.oe_import.oe_import_with_file .oe_import_with_file,
.oe_import.oe_import_noheaders .oe_import_noheaders {
.oe_import.oe_import_noheaders .oe_import_noheaders,
.oe_import .oe_import_report_showmore .oe_import_report_more {
display: block;
}
@ -29,6 +31,9 @@
.oe_import .oe_import_error_report ul .oe_import_report_warning {
background-color: #FEFFD9;
}
.oe_import .oe_import_error_report ul .oe_import_report_info {
background-color: #d3ffd3;
}
.oe_import .oe_import_noheaders {
color: #888;
@ -47,7 +52,7 @@
}
.oe_import .oe_import_options label {
display: inline-block;
width: 10em;
width: 8em;
text-align: right;
}

View File

@ -47,31 +47,35 @@ openerp.base_import = function (instance) {
this._super.apply(this, arguments);
if(add_button) {
this.$buttons.on('click', '.oe_list_button_import', function() {
new instance.web.DataImport(self, self.dataset).open();
self.do_action({
type: 'ir.actions.client',
tag: 'import',
params: {
model: self.dataset.model
}
}, void 0, void 0, function () {
self.reload();
});
return false;
});
}
}
});
instance.web.DataImport = instance.web.Dialog.extend({
instance.web.client_actions.add(
'import', 'instance.web.DataImport');
instance.web.DataImport = instance.web.Widget.extend({
template: 'ImportView',
dialog_title: _lt("Import Data"),
opts: [
{name: 'encoding', label: _lt("Encoding:"), value: 'utf-8'},
{name: 'separator', label: _lt("Separator:"), value: ','},
{name: 'quoting', label: _lt("Quoting:"), value: '"'}
],
events: {
'change .oe_import_grid input': 'import_dryrun',
'change input.oe_import_file': 'file_update',
'change input.oe_import_has_header, .oe_import_options input': 'settings_updated',
'click a.oe_import_csv': function (e) {
e.preventDefault();
},
'click a.oe_import_export': function (e) {
e.preventDefault();
},
// 'change .oe_import_grid input': 'import_dryrun',
'change .oe_import_file': 'loaded_file',
'click .oe_import_file_reload': 'loaded_file',
'change input.oe_import_has_header, .oe_import_options input': 'settings_changed',
'click a.oe_import_toggle': function (e) {
e.preventDefault();
var $el = $(e.target);
@ -79,30 +83,84 @@ openerp.base_import = function (instance) {
? $el.next()
: $el.parent().next())
.toggle();
},
'click .oe_import_report a.oe_import_report_count': function (e) {
e.preventDefault();
$(e.target).parent().toggleClass('oe_import_report_showmore');
},
'click .oe_import_moreinfo_action a': function (e) {
e.preventDefault();
// #data will parse the attribute on its own, we don't like
// that sort of things
var action = JSON.parse($(e.target).attr('data-action'));
// FIXME: when JS-side clean_action
action.views = _(action.views).map(function (view) {
var id = view[0], type = view[1];
return [
id,
type !== 'tree' ? type
: action.view_type === 'form' ? 'list'
: 'tree'
];
});
this.do_action(_.extend(action, {
target: 'new',
flags: {
search_view: true,
display_title: true,
pager: true,
list: {selectable: false}
}
}));
},
// buttons
'click .oe_import_validate': 'validate',
'click .oe_import_import': 'import',
'click .oe_import_cancel': function (e) {
e.preventDefault();
this.exit();
}
},
init: function (parent, dataset) {
init: function (parent, params) {
var self = this;
this._super(parent, {
buttons: [
{text: _t("Import File"), click: function () {
self.do_import();
}, 'class': 'oe_import_dialog_button'}
]
});
this.res_model = parent.model;
this._super.apply(this, arguments);
this.res_model = params.model;
// import object id
this.id = null;
this.Import = new instance.web.Model('base_import.import');
},
start: function () {
var self = this;
return this.Import.call('create', [{
'res_model': this.res_model
}]).then(function (id) {
self.id = id;
self.$('input[name=import_id]').val(id);
});
this.setup_encoding_picker();
return $.when(
this._super(),
this.Import.call('create', [{
'res_model': this.res_model
}]).then(function (id) {
self.id = id;
self.$('input[name=import_id]').val(id);
})
)
},
setup_encoding_picker: function () {
this.$('input.oe_import_encoding').select2({
width: '160px',
query: function (q) {
var make = function (term) { return {id: term, text: term}; };
var suggestions = _.map(
('utf-8 utf-16 windows-1252 latin1 latin2 big5 ' +
'gb18030 shift_jis windows-1251 koir8_r').split(/\s+/),
make);
if (q.term) {
suggestions.unshift(make(q.term));
}
q.callback({results: suggestions});
},
initSelection: function (e, c) {
return c({id: 'utf-8', text: 'utf-8'});
}
}).select2('val', 'utf-8');
},
import_options: function () {
@ -118,34 +176,52 @@ openerp.base_import = function (instance) {
},
//- File & settings change section
file_update: function (e) {
onfile_loaded: function () {
this.$('.oe_import_button').prop('disabled', true);
if (!this.$('input.oe_import_file').val()) { return; }
this.$el.removeClass('oe_import_preview oe_import_error');
jsonp(this.$el, {
url: '/base_import/set_file'
}, this.proxy('settings_updated'));
}, this.proxy('settings_changed'));
},
settings_updated: function () {
onpreviewing: function () {
var self = this;
this.$('.oe_import_button').prop('disabled', true);
this.$el.addClass('oe_import_with_file');
// TODO: test that write // succeeded?
this.Import.call(
'parse_preview', [this.id, this.import_options()])
.then(this.proxy('preview'));
},
preview: function (result) {
this.$el.removeClass('oe_import_preview_error oe_import_error');
this.$el.toggleClass(
'oe_import_noheaders',
!this.$('input.oe_import_has_header').prop('checked'));
if (result.error) {
this.$el.addClass('oe_import_error');
this.$('.oe_import_error_report').html(
this.Import.call(
'parse_preview', [this.id, this.import_options()])
.then(function (result) {
var signal = result.error ? 'preview_failed' : 'preview_succeeded';
self[signal](result);
});
},
onpreview_error: function (event, from, to, result) {
this.$('.oe_import_options').show();
this.$el.addClass('oe_import_preview_error oe_import_error');
this.$('.oe_import_error_report').html(
QWeb.render('ImportView.preview.error', result));
return;
}
},
onpreview_success: function (event, from, to, result) {
this.$('.oe_import_import').removeClass('oe_highlight');
this.$('.oe_import_validate').addClass('oe_highlight');
this.$('.oe_import_button').prop('disabled', false);
this.$el.addClass('oe_import_preview');
this.$('table').html(QWeb.render('ImportView.preview', result));
if (result.headers.length === 1) {
this.$('.oe_import_options').show();
this.onresults(null, null, null, [{
type: 'warning',
message: _t("A single column was found in the file, this often means the file separator is incorrect")
}]);
}
var $fields = this.$('.oe_import_fields input');
this.render_fields_matches(result, $fields);
var data = this.generate_fields_completion(result);
@ -180,7 +256,6 @@ openerp.base_import = function (instance) {
width: 'resolve',
dropdownCssClass: 'oe_import_selector'
});
this.import_dryrun();
},
generate_fields_completion: function (root) {
var basic = [];
@ -252,40 +327,116 @@ openerp.base_import = function (instance) {
//- import itself
call_import: function (options) {
var self = this;
var fields = this.$('.oe_import_fields input.oe_import_match_field').map(function (index, el) {
return $(el).select2('val') || false;
}).get();
return this.Import.call(
'do', [this.id, fields, this.import_options()], options);
},
import_dryrun: function () {
// this.call_import({ dryrun: true })
// .then(this.proxy('render_import_errors'));
onvalidate: function () {
return this.call_import({ dryrun: true })
.then(this.proxy('validated'));
},
do_import: function () {
onimport: function () {
var self = this;
this.call_import({ dryrun: false }).then(function (errors) {
if (_.isEmpty(errors)) {
if (self.getParent().reload_content) {
self.getParent().reload_content();
}
self.close();
return this.call_import({ dryrun: false }).then(function (message) {
if (!_.any(message, function (message) {
return message.type === 'error' })) {
self['import_succeeded']();
return;
}
self.render_import_errors(errors);
self['import_failed'](message);
});
},
render_import_errors: function (errors) {
if (_.isEmpty(errors)) {
this.$el.removeClass('oe_import_error');
return;
onimported: function () {
this.exit();
},
exit: function () {
this.do_action({
type: 'ir.actions.client',
tag: 'history_back'
});
},
onresults: function (event, from, to, message) {
var no_messages = _.isEmpty(message);
this.$('.oe_import_import').toggleClass('oe_highlight', no_messages);
this.$('.oe_import_validate').toggleClass('oe_highlight', !no_messages);
if (no_messages) {
message.push({
type: 'info',
message: _t("Everything seems valid.")
});
}
// import failed (or maybe just warnings, if we ever get
// warnings?)
// row indexes come back 0-indexed, spreadsheets
// display 1-indexed.
var offset = 1;
// offset more if header
if (this.import_options().headers) { offset += 1; }
this.$el.addClass('oe_import_error');
this.$('.oe_import_error_report').html(
QWeb.render('ImportView.error', {errors: errors}));
QWeb.render('ImportView.error', {
errors: _(message).groupBy('message'),
at: function (rows) {
var from = rows.from + offset;
var to = rows.to + offset;
if (from === to) {
return _.str.sprintf(_t("at row %d"), from);
}
return _.str.sprintf(_t("between rows %d and %d"),
from, to);
},
more: function (n) {
return _.str.sprintf(_t("(%d more)"), n);
},
info: function (msg) {
if (typeof msg === 'string') {
return _.str.sprintf(
'<div class="oe_import_moreinfo oe_import_moreinfo_message">%s</div>',
_.str.escapeHTML(msg));
}
if (msg instanceof Array) {
return _.str.sprintf(
'<div class="oe_import_moreinfo oe_import_moreinfo_choices">%s <ul>%s</ul></div>',
_.str.escapeHTML(_t("Here are the possible values:")),
_(msg).map(function (msg) {
return '<li>'
+ _.str.escapeHTML(msg)
+ '</li>';
}).join(''));
}
// Final should be object, action descriptor
return [
'<div class="oe_import_moreinfo oe_import_moreinfo_action">',
_.str.sprintf('<a href="#" data-action="%s">',
_.str.escapeHTML(JSON.stringify(msg))),
_.str.escapeHTML(
_t("Get all possible values")),
'</a>',
'</div>'
].join('')
},
})).get(0).scrollIntoView();
},
});
// FSM-ize DataImport
StateMachine.create({
target: instance.web.DataImport.prototype,
events: [
{ name: 'loaded_file',
from: ['none', 'file_loaded', 'preview_error', 'preview_success', 'results'],
to: 'file_loaded' },
{ name: 'settings_changed',
from: ['file_loaded', 'preview_error', 'preview_success', 'results'],
to: 'previewing' },
{ name: 'preview_failed', from: 'previewing', to: 'preview_error' },
{ name: 'preview_succeeded', from: 'previewing', to: 'preview_success' },
{ name: 'validate', from: 'preview_success', to: 'validating' },
{ name: 'validate', from: 'results', to: 'validating' },
{ name: 'validated', from: 'validating', to: 'results' },
{ name: 'import', from: ['preview_success', 'results'], to: 'importing' },
{ name: 'import_succeeded', from: 'importing', to: 'imported'},
{ name: 'import_failed', from: 'importing', to: 'results' }
]
})
};

View File

@ -2,17 +2,45 @@
<t t-name="ImportView">
<t t-set="_id" t-value="_.uniqueId('export')"/>
<form action="" method="post" enctype="multipart/form-data" class="oe_import">
<header>
<button type="button" disabled="disabled"
class="oe_button oe_import_button oe_import_validate oe_highlight"
>Validate</button>
<button type="button" disabled="disabled"
class="oe_button oe_import_button oe_import_import"
>Import</button>
<span class="oe_fade">or</span>
<a class="oe_import_cancel" href="#">Cancel</a>
</header>
<input type="hidden" name="session_id"
t-att-value="widget.session.session_id"/>
<input type="hidden" name="import_id"/>
<h2>Upload your file</h2>
<p>Select the <a href="#" class="oe_import_csv">.CSV</a>
<p>Select the <a
href="http://en.wikipedia.org/wiki/Comma-separated_values"
class="oe_import_csv" target="_blank">.CSV</a>
file to import. If you need a sample importable file, you
can use <a href="#" class="oe_import_export">the export
tool</a> to generate one.</p>
can use the export tool to generate one.</p>
<label t-attf-for="file_#{_id}" autofocus="autofocus">CSV File:</label>
<input type="file" id-attf-id="file_#{_id}"
name="file" class="oe_import_file"/>
<button type="button" class="oe_import_file_reload">
<img src="/web/static/src/img/icons/gtk-refresh.png"/>
</button>
<div class="oe_import_with_file">
<a href="#" class="oe_import_toggle">
File Format Options…</a>
<div class="oe_import_toggled oe_import_options">
<p t-foreach="widget.opts" t-as="option">
<!-- no @name, avoid submission when file_update called -->
<label t-attf-for="#{option.name}_#{_id}">
<t t-esc="option.label"/></label>
<input t-attf-id="#{option.name}_#{_id}"
t-attf-class="oe_import_#{option.name}"
t-att-value="option.value"/>
</p>
</div>
</div>
<div class="oe_import_with_file">
<h2>Map your data to OpenERP</h2>
@ -27,18 +55,6 @@
<div class="oe_import_error_report"></div>
<table class="oe_import_grid" width="100%"/>
<a href="#" class="oe_import_toggle">
File Format Options…</a>
<div class="oe_import_toggled oe_import_options">
<p t-foreach="widget.opts" t-as="option">
<!-- no @name, avoid submission when file_update called -->
<label t-attf-for="#{option.name}_#{_id}">
<t t-esc="option.label"/></label>
<input t-attf-id="#{option.name}_#{_id}"
t-attf-class="oe_import_#{option.name}"
t-att-value="option.value"/>
</p>
</div>
<h2>Frequently Asked Questions</h2>
<dl>
@ -81,17 +97,37 @@
</tr>
</t>
<t t-name="ImportView.preview.error">
<p>Import preview failed due to: <t t-esc="error"/></p>
<p>Import preview failed due to: <t t-esc="error"/>. The issue is
usually an incorrect file encoding.</p>
<p>Here is the start of the file we could not import:</p>
<pre><t t-esc="preview"/></pre>
</t>
<ul t-name="ImportView.error">
<li t-foreach="errors" t-as="error" t-attf-class="oe_import_report_#{error.type}">
<!-- can also have error.record, but may be *huge* if
e.g. has image fields -->
<t t-esc="error.message"/>
<li t-foreach="errors" t-as="error"
t-attf-class="oe_import_report oe_import_report_#{error_value[0].type}">
<t t-call="ImportView.error.each">
<t t-set="error" t-value="error_value[0]"/>
</t>
<a href="#" class="oe_import_report_count" t-if="error_value.length gt 1">
<t t-esc="more(error_value.length - 1)"/>
</a>
<ul class="oe_import_report_more" t-if="error_value.length gt 1">
<li t-foreach="error_value.length - 1" t-as="index">
<t t-call="ImportView.error.each">
<t t-set="error" t-value="error_value[index + 1]"/>
</t>
</li>
</ul>
</li>
</ul>
<t t-name="ImportView.error.each">
<span class="oe_import_report_message">
<t t-esc="error.message"/>
</span>
<t t-if="error.rows" t-esc="at(error.rows)"/>
<t t-if="error.moreinfo" t-raw="info(error.moreinfo)"/>
</t>
<t t-extend="ListView.buttons">
<t t-jquery="span.oe_alternative">
this.attr('t-if', 'widget.options.import_enabled');

View File

@ -1,19 +1,14 @@
# -*- coding: utf-8 -*-
from xml.etree import ElementTree
try:
import openerp.addons.web.common.http as openerpweb
from openerp.addons.web.common import nonliterals
from openerp.addons.web.controllers.main import load_actions_from_ir_values
except ImportError:
import web.common.http as openerpweb # noqa
from web.common import nonliterals # noqa
from web.controllers.main import load_actions_from_ir_values # noqa
import openerp
from openerp.addons.web import nonliterals
from openerp.addons.web.controllers.main import load_actions_from_ir_values
class Board(openerpweb.Controller):
class Board(openerp.addons.web.http.Controller):
_cp_path = '/board'
@openerpweb.jsonrequest
@openerp.addons.web.http.jsonrequest
def add_to_dashboard(self, req, menu_id, action_id, context_to_save, domain, view_mode, name=''):
# FIXME move this method to board.board model
to_eval = nonliterals.CompoundContext(context_to_save)

View File

@ -25,9 +25,9 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
}).bind('sortstop', self.do_save_dashboard);
var old_title = this.__parentedParent.get('title');
this.__parentedParent.on_record_loaded.add_last(function(){
self.__parentedParent.set({ 'title' : old_title});
});
this.__parentedParent.on('load_record', self, function(){
self.__parentedParent.set({ 'title': old_title});
})
// Events
this.$el.find('.oe_dashboard_link_reset').click(this.on_reset);
this.$el.find('.oe_dashboard_link_change_layout').click(this.on_change_layout);
@ -173,7 +173,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
},
on_load_action: function(result, index, action_attrs) {
var self = this,
action = result.result,
action = result,
view_mode = action_attrs.view_mode;
if (action_attrs.context && action_attrs.context['dashboard_merge_domains_contexts'] === false) {

View File

@ -26,6 +26,7 @@ from osv import fields, osv
import time
import tools
from tools.translate import _
from tools import html2plaintext
from base.res.res_partner import format_address
@ -812,9 +813,11 @@ class crm_lead(base_stage, format_address, osv.osv):
This override updates the document according to the email.
"""
if custom_values is None: custom_values = {}
desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
custom_values.update({
'name': msg.get('subject') or _("No Subject"),
'description': msg.get('body'),
'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,

View File

@ -160,17 +160,17 @@
<field name="street2"/>
<div class="address_format">
<field name="city" placeholder="City" style="width: 40%%"/>
<field name="state_id" options='{"no_open": true}' placeholder="State" style="width: 24%%"/>
<field name="state_id" options='{"no_open": True}' placeholder="State" style="width: 24%%"/>
<field name="zip" placeholder="ZIP" style="width: 34%%"/>
</div>
<field name="country_id" placeholder="Country" options='{"no_open": true}'/>
<field name="country_id" placeholder="Country" options='{"no_open": True}'/>
</div>
</group>
<group>
<label for="contact_name" />
<div>
<field name="contact_name" class="oe_inline"/>,
<field name="title" placeholder="Title" domain="[('domain', '=', 'contact')]" class="oe_inline" options='{"no_open": true}'/>
<field name="title" placeholder="Title" domain="[('domain', '=', 'contact')]" class="oe_inline" options='{"no_open": True}'/>
</div>
<field name="email_from" widget="email"/>
<field name="function" />
@ -356,10 +356,10 @@
<field name="name" string="Lead / Customer" filter_domain="['|','|',('partner_name','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/>
<field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike',self)]" />
<field name="create_date"/>
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter icon="terp-mail-message-new" string="Unread Messages" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter icon="terp-check" string="New" name="new" help="New Leads" domain="[('state','=','draft')]"/>
<filter icon="terp-camera_test" string="Open" name="open" domain="[('state','=','open')]"/>
<filter icon="terp-camera_test" string="In Progress" name="open" domain="[('state','=','open')]"/>
<separator/>
<filter string="Unassigned Leads" icon="terp-personal-" domain="[('user_id','=', False)]" help="Unassigned Leads" />
<separator/>
@ -477,10 +477,10 @@
<field name="street2"/>
<div class="address_format">
<field name="city" placeholder="City" style="width: 40%%"/>
<field name="state_id" options='{"no_open": true}' placeholder="State" style="width: 24%%"/>
<field name="state_id" options='{"no_open": True}' placeholder="State" style="width: 24%%"/>
<field name="zip" placeholder="ZIP" style="width: 34%%"/>
</div>
<field name="country_id" placeholder="Country" options='{"no_open": true}'/>
<field name="country_id" placeholder="Country" options='{"no_open": True}'/>
</div>
</group>
@ -488,7 +488,7 @@
<label for="contact_name" />
<div>
<field name="contact_name" class="oe_inline"/>
<field name="title" placeholder="Title" domain="[('domain', '=', 'contact')]" options='{"no_open": true}' class="oe_inline"/>
<field name="title" placeholder="Title" domain="[('domain', '=', 'contact')]" options='{"no_open": True}' class="oe_inline"/>
</div>
<field name="function" />
<field name="mobile"/>
@ -569,10 +569,10 @@
<field name="name" string="Opportunity / Customer"
filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/>
<field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike', self)]" />
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter icon="terp-mail-message-new" string="Unread Messages" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter icon="terp-check" string="New" help="New Opportunities" name="new" domain="[('state','=','draft')]"/>
<filter icon="terp-camera_test" string="Open" help="Open Opportunities" name="open" domain="[('state','=','open')]"/>
<filter icon="terp-camera_test" string="In Progress" help="Open Opportunities" name="open" domain="[('state','=','open')]"/>
<separator/>
<filter string="Unassigned Opportunities" icon="terp-personal-" domain="[('user_id','=', False)]" help="Unassigned Opportunities" />
<separator/>
@ -580,9 +580,8 @@
domain="['|', ('section_id.user_id','=',uid), ('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Opportunities that are assigned to either me or one of the sale teams I manage" />
<field name="user_id"/>
<field name="country_id"/>
<field name="partner_id"/>
<field name="section_id" context="{'invisible_section': False, 'default_section_id': self}"/>
<field name="partner_id"/>
<group expand="0" string="Group By..." colspan="16">
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Team" help="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}"/>

View File

@ -8,7 +8,7 @@
<field name="name">crm.lead.report.tree</field>
<field name="model">crm.lead.report</field>
<field name="arch" type="xml">
<tree string="Leads Analysis">
<tree string="Leads Analysis" create="false">
<field name="creation_year" invisible="1"/>
<field name="creation_month" invisible="1"/>
<field name="creation_day" invisible="1"/>
@ -130,7 +130,7 @@
<field name="name">crm.lead.report.tree</field>
<field name="model">crm.lead.report</field>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';black:state in ('open','pending','done');gray:state == 'cancel' " string="Opportunities Analysis">
<tree colors="blue:state == 'draft';black:state in ('open','pending','done');gray:state == 'cancel' " create="false" string="Opportunities Analysis">
<field name="creation_year" invisible="1"/>
<field name="creation_month" invisible="1"/>
<field name="creation_day" invisible="1"/>

View File

@ -8,7 +8,7 @@
<field name="name">crm.phonecall.report.tree</field>
<field name="model">crm.phonecall.report</field>
<field name="arch" type="xml">
<tree string="Phone calls">
<tree string="Phone calls" create="false">
<field name="name" invisible="1"/>
<field name="month" invisible="1"/>
<field name="section_id" invisible="1"/>

View File

@ -32,6 +32,13 @@ class crm_configuration(osv.osv_memory):
'group_fund_raising': fields.boolean("Manage Fund Raising",
implied_group='crm.group_fund_raising',
help="""Allows you to trace and manage your activities for fund raising."""),
'module_crm_claim':fields.boolean("Manage Customer Claims",
help="""Allows you to track your customers/suppliers claims and grievances.
This installs the module crm_claim."""),
'module_crm_helpdesk':fields.boolean("Manage Helpdesk and Support",
help="""Allows you to communicate with Customer, process Customer query, and provide better help and support. This installs the module crm_helpdesk."""),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -7,6 +7,22 @@
<field name="model">sale.config.settings</field>
<field name="inherit_id" ref="base_setup.view_sale_config_settings"/>
<field name="arch" type="xml">
<div name="config_sale" position="before">
<separator string="After-Sale Services"/>
<group>
<label for="id" string="Features"/>
<div>
<div>
<field name="module_crm_claim" class="oe_inline"/>
<label for="module_crm_claim"/>
</div>
<div>
<field name="module_crm_helpdesk" class="oe_inline"/>
<label for="module_crm_helpdesk"/>
</div>
</div>
</group>
</div>
<group name="On Mail Client" version="7.0" position="after">
<group>
<label for="id" string="On Mail Server"/>

View File

@ -15,31 +15,12 @@
</field>
</record>
<record id="view_partners_tree_crm2" model="ir.ui.view">
<field name="name">view.res.partner.tree.crm.inherited2</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field eval="18" name="priority"/>
<field name="arch" type="xml">
<field name="phone" position="after">
<field name="section_id" completion="1" invisible="context.get('invisible_section', True)"/>
</field>
</field>
</record>
<record id="view_partners_form_crm3" model="ir.ui.view">
<field name="name">view.res.partner.search.crm.inherited3</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field eval="18" name="priority"/>
<field name="arch" type="xml">
<field name="category_id" position="after">
<field name="section_id" completion="1"/>
<field name="section_id" completion="1" widget="selection" context="{'invisible_section': False}"/>
</field>
<xpath expr="//field[@name='user_id']" position="after">
<field name="country_id"/>
</xpath>
<xpath expr="//group[@string='Group By...']" position="after">
<group string="Display">
<filter string="Show Sales Team" context="{'invisible_section': False}"/>

View File

@ -25,8 +25,10 @@
'version': '1.0',
'category': 'Customer Relationship Management',
'description': """
This modules allows you to track your customers/suppliers claims and grievances.
Manage Customer Claims.
================================================================================
This application allows you to track your customers/suppliers claims and grievances.
It is fully integrated with the email gateway so that you can create
automatically new claims based on incoming emails.

View File

@ -26,6 +26,7 @@ from osv import fields, osv
import time
import tools
from tools.translate import _
from tools import html2plaintext
CRM_CLAIM_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
@ -192,9 +193,10 @@ class crm_claim(base_stage, osv.osv):
This override updates the document according to the email.
"""
if custom_values is None: custom_values = {}
desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
custom_values.update({
'name': msg.get('subject') or _("No Subject"),
'description': msg.get('body'),
'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
})

View File

@ -8,7 +8,7 @@
<field name="name">crm.claim.report.tree</field>
<field name="model">crm.claim.report</field>
<field name="arch" type="xml">
<tree string="Claims">
<tree string="Claims" create="false">
<field name="name" invisible="1"/>
<field name="month" invisible="1"/>
<field name="section_id" invisible="1"/>

View File

@ -25,6 +25,7 @@ from crm import crm
from osv import fields, osv
import tools
from tools.translate import _
from tools import html2plaintext
CRM_HELPDESK_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
@ -104,9 +105,10 @@ class crm_helpdesk(base_state, base_stage, osv.osv):
This override updates the document according to the email.
"""
if custom_values is None: custom_values = {}
desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
custom_values.update({
'name': msg.get('subject') or _("No Subject"),
'description': msg.get('body'),
'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,

View File

@ -8,7 +8,7 @@
<field name="name">crm.helpdesk.report.tree</field>
<field name="model">crm.helpdesk.report</field>
<field name="arch" type="xml">
<tree string="Helpdesk">
<tree string="Helpdesk" create="false">
<field name="name" invisible="1"/>
<field name="month" invisible="1"/>
<field name="section_id" invisible="1"/>

View File

@ -56,7 +56,7 @@
<filter string="Assigned Partner" icon="terp-personal" domain="[]" context="{'group_by':'partner_assigned_id'}"/>
</filter>
<field name="user_id" position="after">
<field name="partner_id" position="after">
<field name="partner_assigned_id"/>
</field>
</field>

View File

@ -65,7 +65,7 @@
<field name="name">crm.lead.assign.report.tree</field>
<field name="model">crm.lead.report.assign</field>
<field name="arch" type="xml">
<tree string="Opportunities Assignment Analysis">
<tree string="Opportunities Assignment Analysis" create="false">
<field name="year" invisible="1"/>
<field name="month" invisible="1"/>
<field name="date_assign" invisible="1"/>

View File

@ -41,7 +41,7 @@
<field name="name">crm.partner.assign.report.tree</field>
<field name="model">crm.partner.report.assign</field>
<field name="arch" type="xml">
<tree string="Opportunities Assignment Analysis">
<tree string="Opportunities Assignment Analysis" create="false">
<field name="partner_id" invisible="1"/>
<field name="country_id" invisible="1"/>
<field name="grade_id" invisible="1"/>

View File

@ -36,8 +36,7 @@ DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config['roo
class document_file(osv.osv):
_inherit = 'ir.attachment'
_rec_name = 'datas_fname'
_rec_name = 'name'
def _attach_parent_id(self, cr, uid, ids=None, context=None):
"""Migrate ir.attachments to the document module.
@ -149,29 +148,12 @@ class document_file(osv.osv):
_sql_constraints = [
# filename_uniq is not possible in pure SQL
]
def _check_duplication(self, cr, uid, vals, ids=None, op='create'):
name = vals.get('name', False)
parent_id = vals.get('parent_id', False)
res_model = vals.get('res_model', False)
res_id = vals.get('res_id', 0)
if op == 'write':
for file in self.browse(cr, uid, ids): # FIXME fields_only
if not name:
name = file.name
if not parent_id:
parent_id = file.parent_id and file.parent_id.id or False
if not res_model:
res_model = file.res_model and file.res_model or False
if not res_id:
res_id = file.res_id and file.res_id or 0
res = self.search(cr, uid, [('id', '<>', file.id), ('name', '=', name), ('parent_id', '=', parent_id), ('res_model', '=', res_model), ('res_id', '=', res_id)])
if len(res):
return False
if op == 'create':
res = self.search(cr, uid, [('name', '=', name), ('parent_id', '=', parent_id), ('res_id', '=', res_id), ('res_model', '=', res_model)])
if len(res):
return False
return True
def onchange_file(self, cr, uid, ids, datas_fname=False, context=None):
res = {'value':{}}
if datas_fname:
res['value'].update({'name': datas_fname})
return res
def check(self, cr, uid, ids, mode, context=None, values=None):
"""Check access wrt. res_model, relax the rule of ir.attachment parent
@ -219,8 +201,6 @@ class document_file(osv.osv):
res = self.search(cr, uid, [('id', 'in', ids)])
if not len(res):
return False
if not self._check_duplication(cr, uid, vals, ids, 'write'):
raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
# if nodes call this write(), they must skip the code below
from_node = context and context.get('__from_node', False)
@ -286,22 +266,8 @@ class document_file(osv.osv):
else:
if vals.get('file_size'):
del vals['file_size']
result = self._check_duplication(cr, uid, vals)
if not result:
domain = [
('res_id', '=', vals['res_id']),
('res_model', '=', vals['res_model']),
('datas_fname', '=', vals['datas_fname']),
]
attach_ids = self.search(cr, uid, domain, context=context)
super(document_file, self).write(cr, uid, attach_ids,
{'datas' : vals['datas']},
context=context)
result = attach_ids[0]
else:
#raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
result = super(document_file, self).create(cr, uid, vals, context)
return result
return super(document_file, self).create(cr, uid, vals, context)
def __get_partner_id(self, cr, uid, res_model, res_id, context=None):
""" A helper to retrieve the associated partner from any res_model+id

View File

@ -228,6 +228,9 @@
<field name="priority" eval="1"/>
<field name="inherit_id" ref="base.view_attachment_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='datas_fname']" position="replace">
<field name="datas_fname" invisible="1" on_change="onchange_file(datas_fname)"/>
</xpath>
<field name="url" position="after">
<field name="user_id"/>
</field>
@ -338,15 +341,6 @@
<field name="value" eval="'ir.actions.act_window,%d'%action_document_file_directory_form"/>
</record>
<record model="ir.ui.view" id="view_attachment_form_inherit">
<field name="name">ir.attachment.view.inherit</field>
<field name="model">ir.attachment</field>
<field name="inherit_id" ref="base.view_attachment_form"/>
<field name="arch" type="xml">
<field name="datas_fname" position = "replace" >
</field>
</field>
</record>
<record model="ir.ui.view" id="view_process_node_form_inherit1">
<field name="name">process.node.form</field>

View File

@ -55,7 +55,7 @@
</div>
<field name="content" placeholder="e.g. Once upon a time..." class="oe_edit_only"/>
<div class="oe_document_page">
<field name="display_content" widget="html" class="oe_view_only" options='{"safe": true}'/>
<field name="display_content" widget="html" class="oe_view_only" options='{"safe": True}'/>
</div>
</form>
</field>

View File

@ -8,7 +8,7 @@
<field name="model">wizard.document.page.history.show_diff</field>
<field name="arch" type="xml">
<form string="Difference" version="7.0">
<field name="diff" widget="html" options='{"safe": true}'/>
<field name="diff" widget="html" options='{"safe": True}'/>
<footer>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>

View File

@ -4,14 +4,8 @@ import textwrap
import simplejson
import werkzeug.wrappers
try:
# embedded
import openerp.addons.web.common.http as openerpweb
import openerp.addons.web.controllers.main as webmain
except ImportError:
# standalone
import web.common.http as openerpweb
import web.controllers.main as webmain
import openerp.addons.web.http as openerpweb
import openerp.addons.web.controllers.main as webmain
class EDI(openerpweb.Controller):
# http://hostname:8069/edi/view?db=XXXX&token=XXXXXXXXXXX

View File

@ -319,7 +319,7 @@ class email_template(osv.osv):
ext = "." + format
if not report_name.endswith(ext):
report_name += ext
attachments.append(report_name, result)
attachments.append((report_name, result))
# Add template attachments
for attach in template.attachment_ids:

View File

@ -20,7 +20,7 @@
<field name="email_cc" readonly="1" attrs="{'invisible':[('email_cc','=',False)]}"/>
<field name="reply_to" readonly="1" attrs="{'invisible':[('reply_to','=',False)]}"/>
<field name="subject" readonly="1"/>
<field name="body_html" widget="html" readonly="1" options='{"safe": true}'/>
<field name="body_html" widget="html" readonly="1" options='{"safe": True}'/>
</group>
</form>
</field>

View File

@ -39,7 +39,7 @@
attrs="{'invisible':[('use_template','=',False)]}"
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
</xpath>
<xpath expr="//button[@class='oe_mail_compose_message_attachment']" position="before">
<xpath expr="//button[@name='dummy']" position="before">
<button icon="/email_template/static/src/img/email_template.png"
type="object" name="toggle_template" string=""
help="Use a message template"

View File

@ -267,25 +267,18 @@ class event_event(osv.osv):
return {'value': dic}
def on_change_address_id(self, cr, uid, ids, address_id, context=None):
values = {
'street' : False,
'street2' : False,
'city' : False,
'zip' : False,
'country_id' : False,
'state_id' : False,
}
if isinstance(address_id, (long, int)):
address = self.pool.get('res.partner').browse(cr, uid, address_id, context=context)
values.update({
'street' : address.street,
'street2' : address.street2,
'city' : address.city,
'country_id' : address.country_id and address.country_id.id,
'state_id' : address.state_id and address.state_id.id,
'zip' : address.zip,
})
values = {}
if not address_id:
return values
address = self.pool.get('res.partner').browse(cr, uid, address_id, context=context)
values.update({
'street' : address.street,
'street2' : address.street2,
'city' : address.city,
'country_id' : address.country_id and address.country_id.id or False,
'state_id' : address.state_id and address.state_id.id or False,
'zip' : address.zip,
})
return {'value' : values}

View File

@ -126,10 +126,10 @@
<field name="street2"/>
<div class="address_format">
<field name="city" placeholder="City" style="width: 40%%"/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": true}'/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": True}'/>
<field name="zip" placeholder="ZIP" style="width: 20%%"/>
</div>
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": true}'/>
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}'/>
</div>
</group>
<group>
@ -339,7 +339,7 @@
<field name="arch" type="xml">
<search string="Events">
<field name="name" string="Events"/>
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter icon="terp-check" string="Unconfirmed" name="draft" domain="[('state','=','draft')]" help="Events in New state"/>
<filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/>
@ -529,7 +529,7 @@
<field name="arch" type="xml">
<search string="Event Registration">
<field name="name" string="Participant" filter_domain="['|','|','|',('name','ilike',self),('partner_id','ilike',self),('email','ilike',self),('origin','ilike',self)]"/>
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter icon="terp-check" string="New" name="draft" domain="[('state','=','draft')]" help="Registrations in unconfirmed state"/>
<filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','open')]" help="Confirmed registrations"/>

View File

@ -23,16 +23,16 @@ from osv import fields, osv
import tools
class report_event_registration(osv.osv):
_name = "report.event.registration"
_description = "Events Analysis"
_auto = False
_columns = {
'event_date': fields.char('Event Start Date', size=64, readonly=True),
'year': fields.char('Year', size=4, readonly=True),
'month': fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'),
('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'),
('10','October'), ('11','November'), ('12','December')], 'Month',readonly=True),
'month': fields.selection([
('01','January'), ('02','February'), ('03','March'), ('04','April'),
('05','May'), ('06','June'), ('07','July'), ('08','August'),
('09','September'), ('10','October'), ('11','November'), ('12','December')], 'Month',readonly=True),
'event_id': fields.many2one('event.event', 'Event', required=True),
'draft_state': fields.integer(' # No of Draft Registrations', size=20),
'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20),
@ -48,17 +48,18 @@ class report_event_registration(osv.osv):
'company_id': fields.many2one('res.company', 'Company', readonly=True),
}
_order = 'event_date desc'
def init(self, cr):
"""
initialize the sql view for the event registration
cr -- the cursor
Initialize the sql view for the event registration
"""
tools.drop_view_if_exists(cr, 'report_event_registration')
cr.execute("""
CREATE OR REPLACE view report_event_registration AS (
SELECT
event_id,
r.id,
# TOFIX this request won't select events that have no registration
cr.execute(""" CREATE VIEW report_event_registration AS (
SELECT
e.id::char || '/' || coalesce(r.id::char,'') AS id,
e.id AS event_id,
e.user_id AS user_id,
r.user_id AS user_id_registration,
r.name AS name_registration,
@ -72,23 +73,21 @@ class report_event_registration(osv.osv):
CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state,
e.type AS event_type,
e.register_max AS register_max,
e.state AS event_state,
r.state AS registration_state
FROM
e.state AS event_state,
r.state AS registration_state
FROM
event_event e
LEFT JOIN event_registration r ON (e.id=r.event_id)
LEFT JOIN
event_registration r ON (e.id=r.event_id)
GROUP BY
GROUP BY
event_id,
user_id_registration,
e.id,
r.id,
registration_state,
r.nb_register,
event_type, e.id, e.date_begin, e.main_speaker_id,
e.register_max,event_id, e.user_id,e.company_id,
event_type,
e.id,
e.date_begin,
e.user_id,
event_state,
e.company_id,
@ -97,9 +96,8 @@ class report_event_registration(osv.osv):
month,
e.register_max,
name_registration
)
""")
)
""")
report_event_registration()

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